feat: scaffold a Next.js app when installing in an empty directory#173
Conversation
Running `workos install` in an empty dir was a dead end: detection found no framework and the state machine errored at the hasIntegration guard. Now a new compound `scaffold` state runs before `preparing`. It checks whether the dir is scaffoldable (empty or only create-next-app-safe files), confirms once interactively (auto in headless / --scaffold), runs a pinned create-next-app@16 deterministically via child_process, then converges into the existing detect -> credentials -> agent pipeline that wires AuthKit unchanged. - New src/lib/scaffold module: isScaffoldableEmptyDir, resolvePackageManager, buildCreateNextAppArgs, runCreateNextApp. The agent Bash allowlist is untouched (the scaffold spawns directly, never through the LLM). - scaffold:* event family plus adapter wiring (CLI confirm + next-steps hint, headless NDJSON with scaffolded:true, dashboard auto-proceed). - --scaffold and --pm flags; package manager resolved from npm_config_user_agent (npm fallback). - scaffolded recorded as a session telemetry tag in run-with-core. SAFE_EMPTY_FILES is a verified subset of create-next-app v16 validFiles (README.md/.vscode excluded since upstream does not allow them). Review cycle: 1 (PASS).
The empty-dir error was a bare "Could not detect framework integration", which gave no hint about the empty-dir-vs-existing-project fork. Replace it with an actionable message that names the directory and points to both paths: run in an empty directory to scaffold, or run from a project root / pass --install-dir for an existing project.
Add a Features bullet, the --scaffold and --pm options to the Installer Options list, a note explaining the empty-dir scaffold behavior (and that a README.md / package.json opts the directory out), and a greenfield example.
Greptile SummaryThis PR adds greenfield scaffolding support: running
Confidence Score: 5/5Safe to merge — the scaffold path is well-gated, all three adapters handle the new event family, and the two previously flagged issues (per-PM runner and stderr cap) are already fixed. The core scaffold module is correct and thoroughly tested. The XState compound state follows existing machine patterns precisely — guards read from event output before context assignment, the onError fallback silently passes through to the existing install path, and the headless/interactive split is cleanly separated. No functional regressions were found in the changed files. No files require special attention. The only observations are a scaffold:skipped event with no consumers and timing-based test delays that could be fragile under CI load — neither affects production behavior. Important Files Changed
|
v1 scaffolding only supports Next.js, so passing e.g. `--integration react` in an empty directory silently produced a Next.js app. Emit a one-shot `scaffold:notice` before prompting/auto-scaffolding when a non-Next integration was requested, surfaced by all three adapters (CLI warn, headless NDJSON, dashboard status). True multi-framework scaffolding remains a tracked follow-up (noted in scaffold.ts).
--integration was read in only two places: it seeded context.integration at startup, which the detection actor then unconditionally overwrote. So it never affected which installer ran — vestigial surface that predated the XState detection step. Remove the flag plus its option/type/buildOptions plumbing and README docs. This also drops the scaffold:notice machinery added earlier in this branch, which existed solely to warn that --integration couldn't steer the Next.js-only scaffold. With the flag gone, there's nothing to warn about. Detection is unchanged and still selects the integration. The install command is non-strict, so a leftover --integration is silently ignored rather than erroring — behavior is unchanged (the flag was already a no-op). True multi-framework scaffolding remains a tracked follow-up.
Addresses Greptile review on PR #173. - runCreateNextApp hardcoded `npx`, which ENOENTs on a bun-only machine (no npm/npx on PATH) even when --pm bun was passed. Route through each package manager's own runner: npm -> `npx --yes`, pnpm -> `pnpm dlx`, yarn -> `yarn dlx`, bun -> `bunx`. (`yarn dlx` assumes Yarn >= 2; Yarn 1 users can fall back to --pm npm.) - The thrown error appended the full unbounded create-next-app stderr; cap it at 2000 chars so a long dependency-resolution trace stays actionable. Tests: runner-per-PM matrix + stderr cap.
Addresses the two P2s from the greptile --agent review on PR #173. - stderr was accumulated unbounded and only sliced when building the error message; cap it at collection time so a pathological create-next-app failure can't buffer hundreds of KB. The message-time slice stays for the single-large-chunk case. - When create-next-app is killed by a signal, the close handler received code=null and `code ?? 1` masked it as "exited with code 1". Capture the signal and report "was killed by signal <SIG>". The review's critical finding (empty-CWD scaffolding "fails silently" on an undefined installDir) is a false positive: installDir is typed `string` and buildOptions always resolves it to process.cwd() via resolveInstallDir, so it is never undefined. No change. Tests: kill-signal rejection path.
What & why
Running
workos installin an empty directory was a dead end: detection found no framework and the state machine errored at thehasIntegrationguard. This adds a delegate-don't-template scaffold path — on an empty dir the CLI scaffolds a blank Next.js app withcreate-next-app, then the existing install pipeline detects and wires AuthKit, unchanged.How
scaffoldXState state beforepreparing(checking → prompting → running → done), mirroring the existinggitCheck/branchChecksub-machines. Both transitions that fedpreparingnow route throughscaffoldfirst.src/lib/scaffoldmodule:isScaffoldableEmptyDir,resolvePackageManager,buildCreateNextAppArgs,runCreateNextApp. Pinnedcreate-next-app@16(major float),--yesdefaults, spawned viachild_process. The agent Bash allowlist is untouched — the scaffold never goes through the LLM.scaffold:*event family + wiring across all three adapters: CLI confirm (default yes) + a "next steps" hint, headless NDJSON withscaffolded: true, dashboard auto-proceed.--scaffoldand--pmflags; package manager resolved fromnpm_config_user_agent(npm fallback).scaffoldedrecorded as a session telemetry tag.Empty-dir gate
Scaffolds only when every entry in the dir is in
SAFE_EMPTY_FILES(a verified subset of create-next-app v16'svalidFiles): empty, or only VCS/editor/cruft metadata (.git,.gitignore,LICENSE,.idea, …). Any project file — includingREADME.mdorpackage.json— opts the directory out and it's treated as an existing project. Interactive = single confirm; headless /--scaffold= auto.Also in this PR
--integrationflag. It was read in only two places and was unconditionally overwritten by detection before it could affect anything — vestigial surface that predated the XState detection step. Detection still selects the integration; removal is non-breaking (the install command is non-strict, so a leftover--integrationis ignored). True multi-framework scaffolding is the future home for a flag like this.Known follow-ups (not in this PR)
README.mddoesn't scaffold (by the gate above). If that's an important entry point, it needs a relocate-then-scaffold follow-up.create-*tool), at which point a framework-selection flag becomes meaningful again.create-next-apprun,pnpm dlxPM resolution, headlessscaffolded: true).Validation
pnpm typecheck✓ ·pnpm lint✓ (0/0) ·pnpm test✓ (2079) ·pnpm build✓.