(cwd: string | undefined, output: string)
| 35 | * the user the exact one-line fix. Returns null when the environment looks fine (don't cry wolf). |
| 36 | */ |
| 37 | export function diagnoseDevEnv(cwd: string | undefined, output: string): string | null { |
| 38 | const notFound = /command not found|: not found|code 127|UNRESOLVED_IMPORT|Could not resolve|cannot find module/i.test( |
| 39 | output, |
| 40 | ); |
| 41 | if (!notFound) return null; |
| 42 | const root = cwd || process.cwd(); |
| 43 | const nm = path.join(root, 'node_modules'); |
| 44 | let detail: string | null = null; |
| 45 | try { |
| 46 | if (!fs.existsSync(nm)) { |
| 47 | detail = 'node_modules does not exist'; |
| 48 | } else { |
| 49 | const hasBin = fs.existsSync(path.join(nm, '.bin')); |
| 50 | const pkgs = fs.readdirSync(nm).filter((n) => !n.startsWith('.')); |
| 51 | // A real install has dozens-to-hundreds of top-level entries and a populated .bin. |
| 52 | if (!hasBin || pkgs.length <= 3) { |
| 53 | detail = `node_modules looks incomplete (${pkgs.length} top-level package(s), .bin ${hasBin ? 'present' : 'missing'})`; |
| 54 | } |
| 55 | } |
| 56 | } catch { |
| 57 | /* if we can't inspect, don't fabricate a diagnosis */ |
| 58 | } |
| 59 | if (!detail) return null; |
| 60 | return [ |
| 61 | `[ENV_DEPS_BROKEN] ${detail}.`, |
| 62 | `This is an ENVIRONMENT problem in the project folder, not a bug in the code — the dependency`, |
| 63 | `install is broken (usually a stale .npmrc, or an npm/pnpm lockfile mismatch). Re-running install`, |
| 64 | `from here will keep failing, so do NOT retry the dev server in a loop.`, |
| 65 | `Tell the user to run this once in their own terminal, then retry the task:`, |
| 66 | ` rm -rf node_modules package-lock.json pnpm-lock.yaml .npmrc && npm install`, |
| 67 | ].join('\n'); |
| 68 | } |
| 69 | |
| 70 | const StartArgs = z.object({ |
| 71 | name: z.string().min(1).max(64).describe('Short id for later reference. e.g. "frontend", "api", "watcher".'), |
no outgoing calls
no test coverage detected