/ src / lib / server / runtime / devserver-launch.ts
devserver-launch.ts
 1  import fs from 'fs'
 2  import path from 'path'
 3  
 4  type FrameworkKind = 'next' | 'npm' | 'unknown'
 5  
 6  interface PackageJsonLike {
 7    scripts?: Record<string, unknown>
 8    dependencies?: Record<string, unknown>
 9    devDependencies?: Record<string, unknown>
10  }
11  
12  export interface DevServerLaunchResolution {
13    inputDir: string
14    launchDir: string
15    packageRoot: string | null
16    framework: FrameworkKind
17  }
18  
19  const NEXT_CONFIG_FILES = [
20    'next.config.js',
21    'next.config.mjs',
22    'next.config',
23  ]
24  
25  function readPackageJson(dir: string): PackageJsonLike | null {
26    const pkgPath = path.join(dir, 'package.json')
27    if (!fs.existsSync(pkgPath)) return null
28    try {
29      const parsed: unknown = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
30      return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
31        ? parsed as PackageJsonLike
32        : null
33    } catch {
34      return null
35    }
36  }
37  
38  function hasNextDependency(pkg: PackageJsonLike): boolean {
39    const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }
40    return typeof deps.next === 'string' && deps.next.trim().length > 0
41  }
42  
43  function hasNextScript(pkg: PackageJsonLike): boolean {
44    const scripts = Object.values(pkg.scripts || {})
45    return scripts.some((value) => typeof value === 'string' && /\bnext\b/.test(value))
46  }
47  
48  function hasNextConfig(dir: string): boolean {
49    return NEXT_CONFIG_FILES.some((file) => fs.existsSync(path.join(dir, file)))
50  }
51  
52  function classifyPackageRoot(dir: string, pkg: PackageJsonLike): FrameworkKind {
53    return hasNextDependency(pkg) || hasNextScript(pkg) || hasNextConfig(dir)
54      ? 'next'
55      : 'npm'
56  }
57  
58  export function resolveDevServerLaunchDir(startDir: string): DevServerLaunchResolution {
59    const inputDir = path.resolve(startDir)
60    let current = inputDir
61  
62    while (true) {
63      const pkg = readPackageJson(current)
64      if (pkg) {
65        const framework = classifyPackageRoot(current, pkg)
66        return {
67          inputDir,
68          launchDir: current,
69          packageRoot: current,
70          framework,
71        }
72      }
73  
74      const parent = path.dirname(current)
75      if (parent === current) {
76        return {
77          inputDir,
78          launchDir: inputDir,
79          packageRoot: null,
80          framework: 'unknown',
81        }
82      }
83      current = parent
84    }
85  }