From d3942423f56e8f193fd137b002a044a69ff46127 Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Sun, 17 May 2026 08:29:27 -0500 Subject: [PATCH 1/3] Repair pnpm webviewer installs before success --- .changeset/quiet-bikes-validate.md | 5 + packages/cli/src/core/executeInitPlan.ts | 126 +++++++++++++++++- packages/cli/tests/executor.test.ts | 163 +++++++++++++++++++++++ packages/cli/tests/test-layer.ts | 13 ++ 4 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 .changeset/quiet-bikes-validate.md diff --git a/.changeset/quiet-bikes-validate.md b/.changeset/quiet-bikes-validate.md new file mode 100644 index 00000000..bb17d00d --- /dev/null +++ b/.changeset/quiet-bikes-validate.md @@ -0,0 +1,5 @@ +--- +"@proofkit/cli": patch +--- + +Fix webviewer scaffold installs by validating Vite native deps and repairing missing Rolldown bindings. diff --git a/packages/cli/src/core/executeInitPlan.ts b/packages/cli/src/core/executeInitPlan.ts index 8a877119..d5d37c91 100644 --- a/packages/cli/src/core/executeInitPlan.ts +++ b/packages/cli/src/core/executeInitPlan.ts @@ -16,7 +16,13 @@ import { PromptService, SettingsService, } from "~/core/context.js"; -import { DirectoryConflictError, FileSystemError, isCliError, UserCancelledError } from "~/core/errors.js"; +import { + DirectoryConflictError, + ExternalCommandError, + FileSystemError, + isCliError, + UserCancelledError, +} from "~/core/errors.js"; import { applyPackageJsonMutations } from "~/core/planInit.js"; import type { InitPlan } from "~/core/types.js"; import { getIntentInstallCommand } from "~/helpers/intent.js"; @@ -33,6 +39,9 @@ import { sortPackageJson } from "~/utils/sortPackageJson.js"; const AGENT_METADATA_DIRS = new Set([".agents", ".claude", ".clawed", ".clinerules", ".cursor", ".windsurf"]); const IMPORT_ALIAS_WILDCARD_REGEX = /\*/g; const IMPORT_ALIAS_TRAILING_SLASH_REGEX = /\/?$/; +const ROLLDOWN_NATIVE_BINDING_REGEX = + /Cannot find native binding|@rolldown[/+]binding-[\w-]+|rolldown[\s\S]*native binding/i; +const PNPM_ROLLDOWN_REPAIR_COMMAND = "rm -rf node_modules pnpm-lock.yaml && pnpm install --force"; const chalk = new Chalk({ level: 1 }); const formatCommand = (command: string) => chalk.cyan(command); @@ -94,6 +103,36 @@ function getPackageScriptCommand(plan: InitPlan, scriptName: string) { return { command, args }; } +function getErrorDetails(error: unknown): string { + const parts: string[] = []; + const add = (value: unknown) => { + if (typeof value === "string" && value.trim()) { + parts.push(value.trim()); + } + }; + if (error instanceof Error) { + add(error.message); + } else if (typeof error === "string") { + add(error); + } else if (typeof error === "object" && error !== null) { + add("message" in error ? error.message : undefined); + } + const cause = typeof error === "object" && error !== null && "cause" in error ? error.cause : undefined; + if (typeof cause === "object" && cause !== null) { + add("message" in cause ? cause.message : undefined); + add("stdout" in cause ? cause.stdout : undefined); + add("stderr" in cause ? cause.stderr : undefined); + add("shortMessage" in cause ? cause.shortMessage : undefined); + } else { + add(cause); + } + return Array.from(new Set(parts)).join("\n"); +} + +function isRolldownNativeBindingError(error: unknown): boolean { + return ROLLDOWN_NATIVE_BINDING_REGEX.test(getErrorDetails(error)); +} + function getMeaningfulDirectoryEntries(entries: string[]) { return entries.filter((entry) => { if (AGENT_METADATA_DIRS.has(entry)) { @@ -405,6 +444,91 @@ export const executeInitPlan = (plan: InitPlan) => stdout: "pipe", stderr: "pipe", }); + + if (plan.request.appType === "webviewer" && plan.request.packageManager === "pnpm") { + consoleService.info("Validating Vite native dependencies..."); + const validateVite = processService.run("pnpm", ["exec", "vite", "--version"], { + cwd: plan.targetDir, + stdout: "pipe", + stderr: "pipe", + }); + const validationResult = yield* Effect.either(validateVite); + + if (validationResult._tag === "Left") { + if (!isRolldownNativeBindingError(validationResult.left)) { + return yield* Effect.fail(validationResult.left); + } + + const validationDetails = getErrorDetails(validationResult.left); + consoleService.warn( + [ + "Vite native dependency validation failed because Rolldown native bindings are missing.", + validationDetails ? `Validation output:\n${validationDetails}` : undefined, + `Repairing install: ${PNPM_ROLLDOWN_REPAIR_COMMAND}`, + ] + .filter(Boolean) + .join("\n"), + ); + + yield* fs.remove(path.join(plan.targetDir, "node_modules")); + yield* fs.remove(path.join(plan.targetDir, "pnpm-lock.yaml")); + const repairResult = yield* Effect.either( + processService.run("pnpm", ["install", "--force"], { + cwd: plan.targetDir, + stdout: "pipe", + stderr: "pipe", + }), + ); + + if (repairResult._tag === "Left") { + const repairDetails = getErrorDetails(repairResult.left); + return yield* Effect.fail( + new ExternalCommandError({ + message: [ + "Vite native dependency repair failed.", + "Repair command: pnpm install --force", + repairDetails ? `Repair output:\n${repairDetails}` : undefined, + `Manual recovery: ${PNPM_ROLLDOWN_REPAIR_COMMAND}`, + ] + .filter(Boolean) + .join("\n"), + command: "pnpm", + args: ["install", "--force"], + cwd: plan.targetDir, + cause: repairResult.left, + }), + ); + } + + const repairedValidation = yield* Effect.either( + processService.run("pnpm", ["exec", "vite", "--version"], { + cwd: plan.targetDir, + stdout: "pipe", + stderr: "pipe", + }), + ); + + if (repairedValidation._tag === "Left") { + const repairedValidationDetails = getErrorDetails(repairedValidation.left); + return yield* Effect.fail( + new ExternalCommandError({ + message: [ + "Vite native dependency validation still failed after repair.", + "Validation command: pnpm exec vite --version", + repairedValidationDetails ? `Validation output:\n${repairedValidationDetails}` : undefined, + `Manual recovery: ${PNPM_ROLLDOWN_REPAIR_COMMAND}`, + ] + .filter(Boolean) + .join("\n"), + command: "pnpm", + args: ["exec", "vite", "--version"], + cwd: plan.targetDir, + cause: repairedValidation.left, + }), + ); + } + } + } } if (plan.tasks.runUltraciteInit) { diff --git a/packages/cli/tests/executor.test.ts b/packages/cli/tests/executor.test.ts index b0ca09bc..42cc78f9 100644 --- a/packages/cli/tests/executor.test.ts +++ b/packages/cli/tests/executor.test.ts @@ -63,6 +63,7 @@ describe("executeInitPlan command paths", () => { expect(tracker.commands).toEqual([ "pnpm install", + "pnpm exec vite --version", [ "pnpx ultracite init --quiet --linter oxlint --pm pnpm --frameworks react --editors universal cursor", "--agents universal claude codex --hooks cursor windsurf codebuddy claude --integrations husky lint-staged", @@ -87,6 +88,168 @@ describe("executeInitPlan command paths", () => { expect(pnpmWorkspaceFile).toContain("blockExoticSubdeps: true"); }); + it("validates Vite native dependencies after webviewer pnpm install", async () => { + const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "proofkit-vite-validate-ok-")); + const tracker = { + commands: [] as string[], + gitInits: 0, + codegens: 0, + filemakerBootstraps: 0, + }; + + const plan = planInit( + makeInitRequest({ + appType: "webviewer", + dataSource: "none", + packageManager: "pnpm", + noInstall: false, + noGit: true, + cwd, + }), + { + templateDir: getSharedTemplateDir("vite-wv"), + packageManagerVersion: "11.0.0", + }, + ); + + await Effect.runPromise(executeInitPlan(plan).pipe(makeTestLayer({ cwd, packageManager: "pnpm", tracker }))); + + expect(tracker.commands).toContain("pnpm install"); + expect(tracker.commands).toContain("pnpm exec vite --version"); + expect(tracker.commands).not.toContain("pnpm install --force"); + }); + + it("repairs pnpm webviewer install when Rolldown native binding is missing", async () => { + const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "proofkit-vite-validate-repair-")); + const projectDir = path.join(cwd, "demo-app"); + const console = { + error: [] as string[], + info: [] as string[], + note: [] as Array<{ message: string; title?: string }>, + success: [] as string[], + warn: [] as string[], + }; + const tracker = { + commands: [] as string[], + gitInits: 0, + codegens: 0, + filemakerBootstraps: 0, + }; + + const plan = planInit( + makeInitRequest({ + appType: "webviewer", + dataSource: "none", + packageManager: "pnpm", + noInstall: false, + noGit: true, + cwd, + }), + { + templateDir: getSharedTemplateDir("vite-wv"), + packageManagerVersion: "11.0.0", + }, + ); + + await Effect.runPromise( + executeInitPlan(plan).pipe( + makeTestLayer({ + console, + cwd, + packageManager: "pnpm", + processRuns: { + "pnpm exec vite --version": [ + new ExternalCommandError({ + message: "Cannot find native binding. Missing @rolldown/binding-darwin-arm64", + command: "pnpm", + args: ["exec", "vite", "--version"], + cwd: projectDir, + }), + { stdout: "vite/8.0.13", stderr: "" }, + ], + }, + tracker, + }), + ), + ); + + expect(tracker.commands).toContain("pnpm install --force"); + expect(tracker.commands.filter((command) => command === "pnpm exec vite --version")).toHaveLength(2); + expect(console.warn.join("\n")).toContain("Rolldown native bindings are missing"); + expect(console.warn.join("\n")).toContain("rm -rf node_modules pnpm-lock.yaml && pnpm install --force"); + }); + + it("returns actionable error when Vite validation still fails after repair", async () => { + const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "proofkit-vite-validate-repair-fail-")); + const projectDir = path.join(cwd, "demo-app"); + const console = { + error: [] as string[], + info: [] as string[], + note: [] as Array<{ message: string; title?: string }>, + success: [] as string[], + warn: [] as string[], + }; + const tracker = { + commands: [] as string[], + gitInits: 0, + codegens: 0, + filemakerBootstraps: 0, + }; + + const plan = planInit( + makeInitRequest({ + appType: "webviewer", + dataSource: "none", + packageManager: "pnpm", + noInstall: false, + noGit: true, + cwd, + }), + { + templateDir: getSharedTemplateDir("vite-wv"), + packageManagerVersion: "11.0.0", + }, + ); + + const failure = await getFailure( + executeInitPlan(plan).pipe( + makeTestLayer({ + console, + cwd, + packageManager: "pnpm", + processRuns: { + "pnpm exec vite --version": [ + new ExternalCommandError({ + message: "Cannot find native binding. Missing @rolldown/binding-darwin-arm64", + command: "pnpm", + args: ["exec", "vite", "--version"], + cwd: projectDir, + }), + new ExternalCommandError({ + message: "Cannot find native binding. Missing @rolldown/binding-darwin-arm64", + command: "pnpm", + args: ["exec", "vite", "--version"], + cwd: projectDir, + }), + ], + "pnpm install --force": [{ stdout: "reinstalled", stderr: "" }], + }, + tracker, + }), + ), + ); + + expect(failure).toMatchObject({ + _tag: "ExternalCommandError", + message: expect.stringContaining("Vite native dependency validation still failed after repair"), + }); + expect(failure).toMatchObject({ + message: expect.stringContaining("Manual recovery: rm -rf node_modules pnpm-lock.yaml && pnpm install --force"), + }); + expect(tracker.commands).toContain("pnpm install --force"); + expect(console.warn.join("\n")).toContain("Validation output"); + }); + it("runs Ultracite with browser framework presets", async () => { const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "proofkit-ultracite-browser-")); const tracker = { diff --git a/packages/cli/tests/test-layer.ts b/packages/cli/tests/test-layer.ts index e3d7d42b..a19b5c9e 100644 --- a/packages/cli/tests/test-layer.ts +++ b/packages/cli/tests/test-layer.ts @@ -88,6 +88,7 @@ export function makeTestLayer(options: { deployDemoFile?: unknown; packageManagerGetVersion?: Partial>; }; + processRuns?: Record>; }) { const tracker = options.tracker; const promptScript = { @@ -348,6 +349,18 @@ export function makeTestLayer(options: { run: (command: string, args: string[]) => { const processCommand = [command, ...args].join(" "); tracker?.commands.push(processCommand); + const scriptedResult = options.processRuns?.[processCommand]?.shift(); + if (scriptedResult) { + if ( + typeof scriptedResult === "object" && + scriptedResult !== null && + "stdout" in scriptedResult && + "stderr" in scriptedResult + ) { + return Effect.succeed(scriptedResult as { stdout: string; stderr: string }); + } + return Effect.fail(scriptedResult as ExternalCommandError); + } const processRunFailure = options.failures?.processRun; if (options.failProcessCommand === processCommand) { if (!processRunFailure) { From 8b54a24e8f80f7f22d4a8d0570d36e59b49b888f Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Sun, 17 May 2026 08:48:50 -0500 Subject: [PATCH 2/3] Update proofkit docs and config --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f91c46ab..34c75c4d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,6 +109,8 @@ jobs: - name: Build run: pnpm ci:build + env: + PROOFKIT_MANIFEST_REVALIDATE_SECRET: ci-build-placeholder cli-smoke: name: CLI External Integration Smoke Tests @@ -182,6 +184,8 @@ jobs: - name: Build run: pnpm ci:build + env: + PROOFKIT_MANIFEST_REVALIDATE_SECRET: ci-build-placeholder - name: Publish preview packages run: | From 375b748db2915d2d45c4f5222dc826f20fdcbb13 Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Sun, 17 May 2026 11:34:31 -0500 Subject: [PATCH 3/3] Fix proofkit CI workflow --- packages/cli/src/core/executeInitPlan.ts | 8 ++++---- packages/cli/tests/executor.test.ts | 6 ++++-- packages/cli/tests/test-layer.ts | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/core/executeInitPlan.ts b/packages/cli/src/core/executeInitPlan.ts index d5d37c91..7dfdadb5 100644 --- a/packages/cli/src/core/executeInitPlan.ts +++ b/packages/cli/src/core/executeInitPlan.ts @@ -41,7 +41,7 @@ const IMPORT_ALIAS_WILDCARD_REGEX = /\*/g; const IMPORT_ALIAS_TRAILING_SLASH_REGEX = /\/?$/; const ROLLDOWN_NATIVE_BINDING_REGEX = /Cannot find native binding|@rolldown[/+]binding-[\w-]+|rolldown[\s\S]*native binding/i; -const PNPM_ROLLDOWN_REPAIR_COMMAND = "rm -rf node_modules pnpm-lock.yaml && pnpm install --force"; +const PNPM_ROLLDOWN_REPAIR_INSTRUCTION = "Delete node_modules and pnpm-lock.yaml, then run: pnpm install --force"; const chalk = new Chalk({ level: 1 }); const formatCommand = (command: string) => chalk.cyan(command); @@ -464,7 +464,7 @@ export const executeInitPlan = (plan: InitPlan) => [ "Vite native dependency validation failed because Rolldown native bindings are missing.", validationDetails ? `Validation output:\n${validationDetails}` : undefined, - `Repairing install: ${PNPM_ROLLDOWN_REPAIR_COMMAND}`, + `Repairing install: ${PNPM_ROLLDOWN_REPAIR_INSTRUCTION}`, ] .filter(Boolean) .join("\n"), @@ -488,7 +488,7 @@ export const executeInitPlan = (plan: InitPlan) => "Vite native dependency repair failed.", "Repair command: pnpm install --force", repairDetails ? `Repair output:\n${repairDetails}` : undefined, - `Manual recovery: ${PNPM_ROLLDOWN_REPAIR_COMMAND}`, + `Manual recovery: ${PNPM_ROLLDOWN_REPAIR_INSTRUCTION}`, ] .filter(Boolean) .join("\n"), @@ -516,7 +516,7 @@ export const executeInitPlan = (plan: InitPlan) => "Vite native dependency validation still failed after repair.", "Validation command: pnpm exec vite --version", repairedValidationDetails ? `Validation output:\n${repairedValidationDetails}` : undefined, - `Manual recovery: ${PNPM_ROLLDOWN_REPAIR_COMMAND}`, + `Manual recovery: ${PNPM_ROLLDOWN_REPAIR_INSTRUCTION}`, ] .filter(Boolean) .join("\n"), diff --git a/packages/cli/tests/executor.test.ts b/packages/cli/tests/executor.test.ts index 42cc78f9..a9e2158f 100644 --- a/packages/cli/tests/executor.test.ts +++ b/packages/cli/tests/executor.test.ts @@ -176,7 +176,7 @@ describe("executeInitPlan command paths", () => { expect(tracker.commands).toContain("pnpm install --force"); expect(tracker.commands.filter((command) => command === "pnpm exec vite --version")).toHaveLength(2); expect(console.warn.join("\n")).toContain("Rolldown native bindings are missing"); - expect(console.warn.join("\n")).toContain("rm -rf node_modules pnpm-lock.yaml && pnpm install --force"); + expect(console.warn.join("\n")).toContain("Delete node_modules and pnpm-lock.yaml, then run: pnpm install --force"); }); it("returns actionable error when Vite validation still fails after repair", async () => { @@ -244,7 +244,9 @@ describe("executeInitPlan command paths", () => { message: expect.stringContaining("Vite native dependency validation still failed after repair"), }); expect(failure).toMatchObject({ - message: expect.stringContaining("Manual recovery: rm -rf node_modules pnpm-lock.yaml && pnpm install --force"), + message: expect.stringContaining( + "Manual recovery: Delete node_modules and pnpm-lock.yaml, then run: pnpm install --force", + ), }); expect(tracker.commands).toContain("pnpm install --force"); expect(console.warn.join("\n")).toContain("Validation output"); diff --git a/packages/cli/tests/test-layer.ts b/packages/cli/tests/test-layer.ts index a19b5c9e..6e800b07 100644 --- a/packages/cli/tests/test-layer.ts +++ b/packages/cli/tests/test-layer.ts @@ -350,7 +350,7 @@ export function makeTestLayer(options: { const processCommand = [command, ...args].join(" "); tracker?.commands.push(processCommand); const scriptedResult = options.processRuns?.[processCommand]?.shift(); - if (scriptedResult) { + if (scriptedResult !== undefined) { if ( typeof scriptedResult === "object" && scriptedResult !== null &&