diff --git a/cli/commands/generate/command.ts b/cli/commands/generate/command.ts index 9bc732044f..93065df421 100644 --- a/cli/commands/generate/command.ts +++ b/cli/commands/generate/command.ts @@ -1,6 +1,6 @@ import { getConfig } from "veryfront/config"; import { cliLogger } from "#cli/utils"; -import { createError, toError } from "veryfront/errors"; +import { ALREADY_EXISTS, createError, toError } from "veryfront/errors"; import { parseExtensionManifest } from "veryfront/extensions"; import { exists, join, readTextFile } from "veryfront/fs"; import { generateIntegration } from "./integration-generator.ts"; @@ -123,12 +123,10 @@ export async function generateCommand( }); if (!result.success) { - throw toError( - createError({ - type: "config", - message: result.message, - }), - ); + throw ALREADY_EXISTS.create({ + detail: result.message, + context: { paths: result.files.map((file) => file.path) }, + }); } for (const file of result.files) cliLogger.info(`Created ${file.path}`); diff --git a/cli/commands/generate/handler.test.ts b/cli/commands/generate/handler.test.ts index a7a2f70a18..8cf8742523 100644 --- a/cli/commands/generate/handler.test.ts +++ b/cli/commands/generate/handler.test.ts @@ -3,7 +3,8 @@ import "#veryfront/schemas/_test-setup.ts"; * Tests for generate command handler */ -import { assertEquals } from "#veryfront/testing/assert.ts"; +import { assertEquals, assertRejects } from "#veryfront/testing/assert.ts"; +import { VeryfrontError } from "veryfront/errors"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { handleGenerateCommand, parseGenerateArgs } from "./handler.ts"; import type { ParsedArgs } from "#cli/shared/types"; @@ -107,3 +108,13 @@ describe("commands/generate/handler", () => { }); }); }); + +describe("commands/generate/handler usage errors", () => { + it("rejects missing arguments as a registered usage error", async () => { + const error = await assertRejects(() => handleGenerateCommand({ _: [] })); + + assertEquals(error instanceof VeryfrontError, true); + assertEquals((error as VeryfrontError).slug, "invalid-argument"); + assertEquals((error as VeryfrontError).exitCode, 2); + }); +}); diff --git a/cli/commands/generate/handler.ts b/cli/commands/generate/handler.ts index 86b2be9ec2..b5e85d79ee 100644 --- a/cli/commands/generate/handler.ts +++ b/cli/commands/generate/handler.ts @@ -2,6 +2,7 @@ * Generate command handler */ +import { INVALID_ARGUMENT } from "veryfront/errors"; import { defineSchema, lazySchema } from "veryfront/schemas"; import { generateCommand } from "./index.ts"; import { showHeader } from "#cli/utils"; @@ -30,11 +31,11 @@ export async function handleGenerateCommand(args: ParsedArgs): Promise { showHeader(); const result = parseGenerateArgs(args); if (!result.success) { - throw new Error( - `Invalid arguments. Usage: veryfront generate \n\nValid types: ${ + throw INVALID_ARGUMENT.create({ + detail: `Invalid arguments. Usage: veryfront generate \n\nValid types: ${ VALID_TYPES.join(", ") }`, - ); + }); } const { type, name } = result.data; @@ -45,11 +46,11 @@ export async function handleGenerateCommand(args: ParsedArgs): Promise { } if (!type || !name) { - throw new Error( - `Invalid arguments. Usage: veryfront generate \n\nValid types: ${ + throw INVALID_ARGUMENT.create({ + detail: `Invalid arguments. Usage: veryfront generate \n\nValid types: ${ VALID_TYPES.join(", ") }`, - ); + }); } await generateCommand(cwd(), type, name); diff --git a/cli/commands/init/init-command.ts b/cli/commands/init/init-command.ts index 7a0b036ed7..89c80ccd5d 100644 --- a/cli/commands/init/init-command.ts +++ b/cli/commands/init/init-command.ts @@ -6,7 +6,7 @@ import { cliLogger as logger, isVerbose } from "#cli/utils"; import { brand, dim } from "#cli/ui"; import { createTransientSpinner } from "../../ui/progress.ts"; -import { createError, toError } from "veryfront/errors"; +import { INVALID_ARGUMENT } from "veryfront/errors"; import type { InitOptions, InitRuntime, InitTemplate } from "./types.ts"; import { cwd } from "veryfront/platform"; import { getDlxCommand, getInstallCommand, getRunCommand } from "../../utils/package-manager.ts"; @@ -135,7 +135,7 @@ export async function initCommand( if (name) { const nameError = validateProjectName(name); if (nameError) { - throw toError(createError({ type: "config", message: nameError })); + throw INVALID_ARGUMENT.create({ detail: nameError }); } } diff --git a/cli/commands/init/init.integration.test.ts b/cli/commands/init/init.integration.test.ts index 8f7bb00b5b..6fce590640 100644 --- a/cli/commands/init/init.integration.test.ts +++ b/cli/commands/init/init.integration.test.ts @@ -687,16 +687,14 @@ describe("init command integration", () => { "--skip-install", "--skip-env-prompt", ]); - // Non-zero exit; the project directory must not exist. - assertEquals(result.code !== 0, true); + // Usage exit code; the project directory must not exist. + assertEquals(result.code, 2); assertEquals(await exists(projectDir), false); - // The error message should surface the validator. - assertEquals( - ((result.stdout ?? "") + (result.stderr ?? "")).includes( - "Invalid runtime value", - ), - true, - ); + // A classified usage error that surfaces the validator, not unknown-error. + const output = (result.stdout ?? "") + (result.stderr ?? ""); + assertEquals(output.includes("[invalid-argument]"), true); + assertEquals(output.includes("Invalid runtime value"), true); + assertEquals(output.includes("unknown-error"), false); }); }); @@ -722,8 +720,10 @@ describe("init command integration", () => { const result = await runInitCommand([dirName, "-t", "minimal", "--skip-install"]); const output = (result.stdout ?? "") + (result.stderr ?? ""); - assertEquals(result.code === 0, false); + assertEquals(result.code, 1); + assertEquals(output.includes("[already-exists]"), true); assertEquals(output.includes("already contains README.md"), true); + assertEquals(output.includes("unknown-error"), false); assertEquals(output.includes("Stack trace"), false); assertEquals(await Deno.readTextFile(join(dirPath, "README.md")), "mine\n"); } finally { diff --git a/cli/commands/init/runtime.test.ts b/cli/commands/init/runtime.test.ts index f67a2a26da..368e1944b8 100644 --- a/cli/commands/init/runtime.test.ts +++ b/cli/commands/init/runtime.test.ts @@ -3,6 +3,7 @@ import "#veryfront/schemas/_test-setup.ts"; import { assertEquals, assertThrows } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; +import { VeryfrontError } from "veryfront/errors"; import { parseRuntime } from "./runtime.ts"; describe("parseRuntime", () => { @@ -46,3 +47,13 @@ describe("parseRuntime", () => { } }); }); + +describe("parseRuntime error classification", () => { + it("throws a registered usage error, not an unclassified one", () => { + const error = assertThrows(() => parseRuntime("rust")); + + assertEquals(error instanceof VeryfrontError, true); + assertEquals((error as VeryfrontError).slug, "invalid-argument"); + assertEquals((error as VeryfrontError).exitCode, 2); + }); +}); diff --git a/cli/commands/init/runtime.ts b/cli/commands/init/runtime.ts index f420b5c786..259ad024b9 100644 --- a/cli/commands/init/runtime.ts +++ b/cli/commands/init/runtime.ts @@ -1,3 +1,4 @@ +import { INVALID_ARGUMENT } from "veryfront/errors"; import type { InitRuntime } from "./types.ts"; const VALID_RUNTIMES: readonly InitRuntime[] = ["node", "bun", "deno"]; @@ -14,8 +15,9 @@ export function parseRuntime(value: unknown): InitRuntime { ) { return value as InitRuntime; } - throw new Error( - `Invalid runtime value: ${JSON.stringify(value)}. ` + + throw INVALID_ARGUMENT.create({ + detail: `Invalid runtime value: ${JSON.stringify(value)}. ` + `Must be one of: ${VALID_RUNTIMES.join(", ")}.`, - ); + context: { value, allowed: VALID_RUNTIMES }, + }); } diff --git a/cli/mcp/tools/catalog-tools.test.ts b/cli/mcp/tools/catalog-tools.test.ts index ed115fa998..b650c2b987 100644 --- a/cli/mcp/tools/catalog-tools.test.ts +++ b/cli/mcp/tools/catalog-tools.test.ts @@ -218,22 +218,6 @@ describe("mcp/tools/catalog-tools", () => { }); }); - it("keeps the existing-directory failure response", async () => { - const parentDir = await Deno.makeTempDir(); - createdDirs.push(parentDir); - const projectDir = join(parentDir, "example-app"); - await Deno.mkdir(projectDir); - - const result = await vfCreateProject.execute({ - name: "Example App", - template: "minimal", - directory: parentDir, - }); - - assertEquals(result.success, false); - assertEquals(result.message, `Directory already exists: ${projectDir}`); - }); - it("reports project-name validation failures", async () => { const result = await vfCreateProject.execute({ name: "invalid/name", diff --git a/cli/mcp/tools/catalog-tools.ts b/cli/mcp/tools/catalog-tools.ts index 6a6b09739c..4d99d08a7d 100644 --- a/cli/mcp/tools/catalog-tools.ts +++ b/cli/mcp/tools/catalog-tools.ts @@ -13,7 +13,7 @@ import { INTEGRATION_CATEGORIES } from "../../commands/init/catalog.ts"; import { createProject as createSharedProject } from "../../shared/project-creation.ts"; import { validateProjectName } from "../../shared/project-name.ts"; import type { MCPTool } from "../tools.ts"; -import { directoryExists, formatError, toSlug } from "./helpers.ts"; +import { formatError, toSlug } from "./helpers.ts"; import type { InitTemplate } from "../../commands/init/types.ts"; import type { IntegrationName } from "../../../templates/types.ts"; @@ -391,16 +391,16 @@ export const vfCreateProject: MCPTool = "cli.mcp.tool.vf_create_project", async () => { try { - const { name, parentDir, projectDir } = resolveCreateProjectPaths(input); + const { name, parentDir } = resolveCreateProjectPaths(input); const nameError = validateProjectName(name); if (nameError) { return { success: false, message: `Failed to create project: ${nameError}` }; } - if (await directoryExists(projectDir)) { - return { success: false, message: `Directory already exists: ${projectDir}` }; - } - + // Whether the target can be written to is `createProject`'s call, + // so this tool refuses exactly what `veryfront init` refuses: a file + // the scaffold would overwrite, named in the message - not a + // directory that merely exists. const creation = await createSharedProject({ name, parentDir, diff --git a/cli/router.test.ts b/cli/router.test.ts index 87b049837d..8eff8bba02 100644 --- a/cli/router.test.ts +++ b/cli/router.test.ts @@ -599,7 +599,7 @@ describe("cli/router helpers", () => { assertEquals(parsed.command, "serve"); assertEquals(parsed.error.code, "USAGE_ERROR"); assertEquals(parsed.error.slug, "invalid-arguments"); - assertEquals(parsed.error.registrySlug, "unknown-error"); + assertEquals(parsed.error.registrySlug, "invalid-argument"); } finally { restoreAll(); } diff --git a/cli/shared/args.test.ts b/cli/shared/args.test.ts index eed6456ad5..89a35ddaa7 100644 --- a/cli/shared/args.test.ts +++ b/cli/shared/args.test.ts @@ -1,5 +1,6 @@ import "#veryfront/schemas/_test-setup.ts"; -import { assertEquals } from "#veryfront/testing/assert.ts"; +import { assertEquals, assertThrows } from "#veryfront/testing/assert.ts"; +import { VeryfrontError } from "veryfront/errors"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { register, tryResolve } from "veryfront/extensions/contracts"; import type { SchemaValidator } from "veryfront/extensions/schema"; @@ -13,6 +14,7 @@ import { extractArg, extractArgs, GLOBAL_BOOLEAN_FLAGS, + parseArgsOrThrow, parseCliArgs, } from "./args.ts"; import { COMMANDS } from "../help/command-definitions.ts"; @@ -370,3 +372,20 @@ describe("cli/shared/args", () => { }); }); }); + +describe("parseArgsOrThrow", () => { + it("throws a registered usage error naming the command and the problem", () => { + const failing = () => ({ + success: false as const, + error: Object.assign(new Error("expected number, received NaN"), { issues: [] }), + }); + + const error = assertThrows(() => parseArgsOrThrow(failing, "dev", { _: [] })); + + assertEquals(error instanceof VeryfrontError, true); + const vfError = error as VeryfrontError; + assertEquals(vfError.slug, "invalid-argument"); + assertEquals(vfError.exitCode, 2); + assertEquals(vfError.detail, "Invalid dev arguments: expected number, received NaN"); + }); +}); diff --git a/cli/shared/args.ts b/cli/shared/args.ts index 528e457bbf..f5bf6be1fc 100644 --- a/cli/shared/args.ts +++ b/cli/shared/args.ts @@ -6,6 +6,7 @@ * @module cli/shared/args */ +import { INVALID_ARGUMENT } from "veryfront/errors"; import type { Schema } from "veryfront/extensions/schema"; import { COMMANDS } from "../help/command-definitions.ts"; import { suggestCommand } from "./suggest.ts"; @@ -203,9 +204,10 @@ export function parseArgsOrThrow( ): T { const result = parser(args); if (!result.success) { - throw new Error( - `Invalid ${commandName} arguments: ${result.error.message}`, - ); + throw INVALID_ARGUMENT.create({ + detail: `Invalid ${commandName} arguments: ${result.error.message}`, + context: { command: commandName, issues: result.error.issues }, + }); } return result.data; } diff --git a/cli/shared/project-creation.test.ts b/cli/shared/project-creation.test.ts index b3ecdef6de..3a83305306 100644 --- a/cli/shared/project-creation.test.ts +++ b/cli/shared/project-creation.test.ts @@ -9,7 +9,7 @@ import { } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { exists, makeTempDir, remove } from "#veryfront/testing/deno-compat.ts"; -import { join } from "veryfront/platform/path"; +import { dirname, join } from "veryfront/platform/path"; import { formatCLIError, VeryfrontError } from "veryfront/errors"; import { STARTER_TEMPLATE_NAMES } from "../../templates/types.ts"; import { @@ -892,6 +892,284 @@ describe("createProject when a path cannot be written through", () => { } }); + it("refuses a linked .gitignore before merging it", async () => { + const parentDir = await makeTempDir({ prefix: "veryfront-create-gitignore-link-" }); + const projectDir = join(parentDir, "contract-project"); + const outside = join(parentDir, "outside-gitignore"); + + try { + await Deno.mkdir(projectDir); + await Deno.writeTextFile(outside, "keep-me\n"); + await Deno.symlink(outside, join(projectDir, ".gitignore")); + + await assertRejects( + () => createProject(baseRequest(parentDir)), + Error, + 'Directory "contract-project" already contains .gitignore as a file or a link', + ); + + assertEquals(await Deno.readTextFile(outside), "keep-me\n"); + assertEquals(await exists(join(projectDir, "README.md")), false); + } finally { + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); + + it("refuses a .gitignore directory before writing scaffold files", async () => { + const parentDir = await makeTempDir({ prefix: "veryfront-create-gitignore-dir-" }); + const projectDir = join(parentDir, "contract-project"); + + try { + await Deno.mkdir(join(projectDir, ".gitignore"), { recursive: true }); + + await assertRejects( + () => createProject(baseRequest(parentDir)), + Error, + 'Directory "contract-project" already contains .gitignore as a file or a link', + ); + + assertEquals(await exists(join(projectDir, "README.md")), false); + } finally { + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); + + it("refuses a non-file .gitignore before writing scaffold files", async () => { + if (Deno.build.os === "windows") return; + + const parentDir = await makeTempDir({ prefix: "veryfront-create-gitignore-fifo-" }); + const projectDir = join(parentDir, "contract-project"); + + try { + await Deno.mkdir(projectDir, { recursive: true }); + const command = new Deno.Command("mkfifo", { + args: [join(projectDir, ".gitignore")], + }); + const output = await command.output(); + if (!output.success) return; + + await assertRejects( + () => createProject(baseRequest(parentDir)), + Error, + 'Directory "contract-project" already contains .gitignore as a file or a link', + ); + + assertEquals(await exists(join(projectDir, "README.md")), false); + } finally { + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); + + it("replaces a hard-linked .gitignore without modifying the other link", async () => { + if (Deno.build.os === "windows") return; + + const parentDir = await makeTempDir({ prefix: "veryfront-create-gitignore-hardlink-" }); + const projectDir = join(parentDir, "contract-project"); + const outside = join(parentDir, "outside-gitignore"); + + try { + await Deno.mkdir(projectDir); + await Deno.writeTextFile(outside, "keep-me\n"); + try { + await Deno.link(outside, join(projectDir, ".gitignore")); + } catch { + return; + } + + await createProject(baseRequest(parentDir)); + + assertEquals(await Deno.readTextFile(outside), "keep-me\n"); + assertStringIncludes(await Deno.readTextFile(join(projectDir, ".gitignore")), "keep-me"); + assertEquals(await exists(join(projectDir, "README.md")), true); + } finally { + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); + + it("refuses an unreplacable .gitignore before writing scaffold files", async () => { + if (Deno.build.os === "windows") return; + + const parentDir = await makeTempDir({ prefix: "veryfront-create-gitignore-readonly-" }); + const projectDir = join(parentDir, "contract-project"); + + try { + await Deno.mkdir(projectDir); + await Deno.writeTextFile(join(projectDir, ".gitignore"), "keep-me\n"); + await Deno.chmod(projectDir, 0o500); + + await assertRejects( + () => createProject(baseRequest(parentDir)), + Error, + ); + + assertEquals(await Deno.readTextFile(join(projectDir, ".gitignore")), "keep-me\n"); + assertEquals(await exists(join(projectDir, "README.md")), false); + } finally { + await Deno.chmod(projectDir, 0o700).catch(() => {}); + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); + + it("refuses an unreadable .gitignore before writing scaffold files", async () => { + if (Deno.build.os === "windows") return; + + const parentDir = await makeTempDir({ prefix: "veryfront-create-gitignore-unreadable-" }); + const projectDir = join(parentDir, "contract-project"); + const gitignorePath = join(projectDir, ".gitignore"); + + try { + await Deno.mkdir(projectDir); + await Deno.writeTextFile(gitignorePath, "keep-me\n"); + const before = await Deno.lstat(gitignorePath); + await Deno.chmod(gitignorePath, 0o000); + + await assertRejects( + () => createProject(baseRequest(parentDir)), + Error, + ); + + const after = await Deno.lstat(gitignorePath); + assertEquals(after.ino, before.ino); + await Deno.chmod(gitignorePath, 0o600); + assertEquals(await Deno.readTextFile(gitignorePath), "keep-me\n"); + assertEquals(await exists(join(projectDir, "README.md")), false); + } finally { + await Deno.chmod(gitignorePath, 0o600).catch(() => {}); + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); + + it("refuses installer lockfiles before dependency installation can replace them", async () => { + const parentDir = await makeTempDir({ prefix: "veryfront-create-lockfile-conflict-" }); + const projectDir = join(parentDir, "contract-project"); + const lockfile = join(projectDir, "package-lock.json"); + + try { + await Deno.mkdir(projectDir); + await Deno.writeTextFile(lockfile, "keep-me\n"); + + await assertRejects( + () => + createProject({ + ...baseRequest(parentDir), + installDependencies: true, + }), + Error, + 'Directory "contract-project" already contains package-lock.json. Use --force to overwrite.', + ); + + assertEquals(await Deno.readTextFile(lockfile), "keep-me\n"); + assertEquals(await exists(join(projectDir, "README.md")), false); + } finally { + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); + + it("refuses npm's hidden lockfile before dependency installation can replace it", async () => { + const parentDir = await makeTempDir({ prefix: "veryfront-create-hidden-lockfile-" }); + const projectDir = join(parentDir, "contract-project"); + const lockfile = join(projectDir, "node_modules", ".package-lock.json"); + + try { + await Deno.mkdir(join(projectDir, "node_modules"), { recursive: true }); + await Deno.writeTextFile(lockfile, "keep-me\n"); + + await assertRejects( + () => + createProject({ + ...baseRequest(parentDir), + installDependencies: true, + }), + Error, + 'Directory "contract-project" already contains node_modules/.package-lock.json, node_modules. Use --force to overwrite.', + ); + + assertEquals(await Deno.readTextFile(lockfile), "keep-me\n"); + assertEquals(await exists(join(projectDir, "README.md")), false); + } finally { + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); + + it("refuses npm shrinkwrap before dependency installation can replace it", async () => { + const parentDir = await makeTempDir({ prefix: "veryfront-create-shrinkwrap-" }); + const projectDir = join(parentDir, "contract-project"); + const lockfile = join(projectDir, "npm-shrinkwrap.json"); + + try { + await Deno.mkdir(projectDir); + await Deno.writeTextFile(lockfile, "keep-me\n"); + + await assertRejects( + () => + createProject({ + ...baseRequest(parentDir), + installDependencies: true, + }), + Error, + 'Directory "contract-project" already contains npm-shrinkwrap.json. Use --force to overwrite.', + ); + + assertEquals(await Deno.readTextFile(lockfile), "keep-me\n"); + assertEquals(await exists(join(projectDir, "README.md")), false); + } finally { + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); + + it("refuses existing node_modules before dependency installation can prune it", async () => { + const parentDir = await makeTempDir({ prefix: "veryfront-create-node-modules-" }); + const projectDir = join(parentDir, "contract-project"); + const userFile = join(projectDir, "node_modules", "user-owned", "data.txt"); + + try { + await Deno.mkdir(dirname(userFile), { recursive: true }); + await Deno.writeTextFile(userFile, "keep-me\n"); + + await assertRejects( + () => + createProject({ + ...baseRequest(parentDir), + installDependencies: true, + }), + Error, + 'Directory "contract-project" already contains node_modules. Use --force to overwrite.', + ); + + assertEquals(await Deno.readTextFile(userFile), "keep-me\n"); + assertEquals(await exists(join(projectDir, "README.md")), false); + } finally { + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); + + it("refuses existing node_modules before Bun dependency installation can prune it", async () => { + const parentDir = await makeTempDir({ prefix: "veryfront-create-bun-node-modules-" }); + const projectDir = join(parentDir, "contract-project"); + const userFile = join(projectDir, "node_modules", "user-owned", "data.txt"); + + try { + await Deno.mkdir(dirname(userFile), { recursive: true }); + await Deno.writeTextFile(userFile, "keep-me\n"); + + await assertRejects( + () => + createProject({ + ...baseRequest(parentDir), + runtime: "bun", + installDependencies: true, + }), + Error, + 'Directory "contract-project" already contains node_modules. Use --force to overwrite.', + ); + + assertEquals(await Deno.readTextFile(userFile), "keep-me\n"); + assertEquals(await exists(join(projectDir, "README.md")), false); + } finally { + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); + it("still reports a real file at a scaffold path as an overwritable conflict", async () => { const parentDir = await makeTempDir({ prefix: "veryfront-create-leaf-file-" }); const projectDir = join(parentDir, "contract-project"); @@ -912,6 +1190,28 @@ describe("createProject when a path cannot be written through", () => { } }); + it("refuses a linked project root instead of scaffolding through it", async () => { + const parentDir = await makeTempDir({ prefix: "veryfront-create-linked-root-" }); + const outside = await makeTempDir({ prefix: "veryfront-create-outside-" }); + + try { + await Deno.symlink(outside, join(parentDir, "contract-project")); + + await assertRejects( + () => createProject({ ...baseRequest(parentDir), conflictPolicy: "overwrite" }), + Error, + 'Directory "contract-project" is a link the scaffold cannot write through', + ); + + // Nothing reached the link target, which is outside the parent entirely. + assertEquals(await exists(join(outside, "README.md")), false); + assertEquals(await exists(join(outside, "package.json")), false); + } finally { + await remove(parentDir, { recursive: true }).catch(() => {}); + await remove(outside, { recursive: true }).catch(() => {}); + } + }); + it("scaffolds normally when the directories it needs are absent or already directories", async () => { const parentDir = await makeTempDir({ prefix: "veryfront-create-blocked-clear-" }); const projectDir = join(parentDir, "contract-project"); @@ -930,3 +1230,40 @@ describe("createProject when a path cannot be written through", () => { } }); }); + +describe("createProject error classification", () => { + it("rejects a bad project name as a usage error", async () => { + const parentDir = await makeTempDir({ prefix: "veryfront-create-name-class-" }); + + try { + const error = await assertRejects(() => + createProject({ ...baseRequest(parentDir), name: "nested/name" }) + ); + + assertInstanceOf(error, VeryfrontError); + assertEquals(error.slug, "invalid-argument"); + assertEquals(error.exitCode, 2); + } finally { + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); + + it("rejects files it would overwrite as already-exists", async () => { + const parentDir = await makeTempDir({ prefix: "veryfront-create-exists-class-" }); + + try { + await Deno.writeTextFile(join(parentDir, "README.md"), "mine\n"); + + const error = await assertRejects(() => + createProject({ ...baseRequest(parentDir), name: undefined }) + ); + + assertInstanceOf(error, VeryfrontError); + assertEquals(error.slug, "already-exists"); + assertEquals(error.exitCode, 1); + assertEquals(error.detail?.includes("--force"), true); + } finally { + await remove(parentDir, { recursive: true }).catch(() => {}); + } + }); +}); diff --git a/cli/shared/project-creation.ts b/cli/shared/project-creation.ts index a8295fcb34..85ccf22a6e 100644 --- a/cli/shared/project-creation.ts +++ b/cli/shared/project-creation.ts @@ -1,7 +1,15 @@ -import { createError, TEMPLATE_NOT_FOUND, toError } from "veryfront/errors"; +import { + ALREADY_EXISTS, + createError, + INVALID_ARGUMENT, + TEMPLATE_NOT_FOUND, + toError, +} from "veryfront/errors"; +import { isNotFoundError } from "veryfront/fs"; import { cliLogger as logger } from "#cli/utils"; import { createFileSystem } from "veryfront/platform"; import { join } from "veryfront/platform/path"; +import { LOCKFILE_CLIENTS, NPM_FAMILY_CLIENTS } from "veryfront/utils/package-client"; import { ensureDir } from "#std/fs.ts"; import { buildDenoConfig, createDenoConfig } from "../commands/init/deno-config-generator.ts"; import { @@ -190,7 +198,10 @@ function validateIntegrationsOrThrow(integrations: IntegrationName[]): void { for (const error of validation.errors) logger.error(error); - throw createConfigError("Invalid integrations specified"); + throw INVALID_ARGUMENT.create({ + detail: "Invalid integrations specified", + context: { integrations }, + }); } function dedupeEnvVars(envVars: EnvVarConfig[]): EnvVarConfig[] { @@ -373,15 +384,26 @@ async function writeEnvFiles( async function writeGitignore(projectDir: string): Promise { const fs = createFileSystem(); + if (!fs.rename) { + throw createConfigError("Filesystem does not support atomic .gitignore replacement."); + } const gitignorePath = join(projectDir, ".gitignore"); + const temporaryPath = join(projectDir, `.gitignore.veryfront-${crypto.randomUUID()}.tmp`); let existingGitignore: string | undefined; try { existingGitignore = await fs.readTextFile(gitignorePath); - } catch { + } catch (error) { + if (!isNotFoundError(error)) throw error; existingGitignore = undefined; } - await fs.writeTextFile(gitignorePath, generateGitignoreContent(existingGitignore)); + await fs.writeTextFile(temporaryPath, generateGitignoreContent(existingGitignore)); + try { + await fs.rename(temporaryPath, gitignorePath); + } catch (error) { + await fs.remove(temporaryPath).catch(() => {}); + throw error; + } logger.debug("Updated file: .gitignore"); } @@ -495,6 +517,45 @@ function scaffoldWritePaths(assembly: ScaffoldAssembly, request: CreateProjectRe return paths; } +function installerWritePaths(request: CreateProjectRequest): string[] { + if (!request.installDependencies) return []; + + const packageManager = packageManagerPreference(request.runtime); + const lockfiles = LOCKFILE_CLIENTS + .filter(([, client]) => client === packageManager) + .map(([path]) => path); + if (packageManager === "npm") { + lockfiles.push("npm-shrinkwrap.json", "node_modules/.package-lock.json"); + } + return lockfiles; +} + +function installerConflictPaths(request: CreateProjectRequest): string[] { + const paths = installerWritePaths(request); + if ( + request.installDependencies && + NPM_FAMILY_CLIENTS.includes(packageManagerPreference(request.runtime)) + ) { + paths.push("node_modules"); + } + return paths; +} + +function conflictWritePaths( + assembly: ScaffoldAssembly, + request: CreateProjectRequest, +): string[] { + return [...scaffoldWritePaths(assembly, request), ...installerConflictPaths(request)]; +} + +function protectedMergePaths(): string[] { + return [".gitignore"]; +} + +function protectedLeafPaths(request: CreateProjectRequest): string[] { + return [...protectedMergePaths(), ...installerWritePaths(request)]; +} + async function findExistingPaths(dir: string, paths: string[]): Promise { const fs = createFileSystem(); const existing: string[] = []; @@ -504,6 +565,21 @@ async function findExistingPaths(dir: string, paths: string[]): Promise { + const fs = createFileSystem(); + if (!fs.lstat) return false; + try { + return (await fs.lstat(path)).isSymlink === true; + } catch { + return false; // Nothing there, so nothing to write through. + } +} + /** * Paths the scaffold cannot write through, checked before anything is written. * @@ -519,7 +595,11 @@ async function findExistingPaths(dir: string, paths: string[]): Promise { +async function findUnwritablePaths( + dir: string, + paths: string[], + protectedLeafPaths: string[] = [], +): Promise { const fs = createFileSystem(); // `lstat` is what makes a link visible: `stat` follows it and reports the // target. It is optional only for virtual filesystems that have no links of @@ -528,7 +608,7 @@ async function findUnwritablePaths(dir: string, paths: string[]): Promise(); - for (const path of paths) { + for (const path of [...paths, ...protectedLeafPaths]) { const segments = path.split("/"); for (let depth = 1; depth <= segments.length; depth++) { const prefix = segments.slice(0, depth).join("/"); @@ -543,6 +623,10 @@ async function findUnwritablePaths(dir: string, paths: string[]): Promise` (and `npm create veryfront -- --template`) was given a name that is not in the starter catalog. The detail carries the list of valid names so a wrong guess is self-correcting. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/config.ts#L120) | -| `TIMEOUT_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L52) | +| `TIMEOUT_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L67) | | `TOKEN_STORAGE_ERROR` | Token storage adapter failures (replaces TokenStorageError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/server.ts#L102) | | `TOOL_ID_CONFLICT` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/agent.ts#L51) | | `TRIGGER_CONFIG_INVALID` | Trigger ID format and input serialization validation failures | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/config.ts#L106) | @@ -261,31 +262,28 @@ These import paths group focused functionality under this module. Each is a sepa ### `veryfront/errors/general` ```ts -import { - AUTHENTICATION_REQUIRED, - FILE_NOT_FOUND, - GENERAL_REGISTRY, -} from "veryfront/errors/general"; +import { ALREADY_EXISTS, AUTHENTICATION_REQUIRED, FILE_NOT_FOUND } from "veryfront/errors/general"; ``` #### Components -| Name | Description | Source | -| ------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | -| `AUTHENTICATION_REQUIRED` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L11) | -| `FILE_NOT_FOUND` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L27) | -| `GENERAL_REGISTRY` | Registry fragment for GENERAL errors (slug → definition). | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L121) | -| `INITIALIZATION_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L60) | -| `INPUT_VALIDATION_FAILED` | HTTP request input validation failures (replaces ValidationError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L86) | -| `INVALID_ARGUMENT` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L43) | -| `NESTED_CWD_SCOPE` | A scope that owns the process working directory was opened inside another one. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L103) | -| `NOT_SUPPORTED` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L68) | -| `PERMISSION_DENIED` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L19) | -| `PROJECT_SOURCE_EMPTY` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L94) | -| `RESOURCE_NOT_FOUND` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L35) | -| `SECURITY_VIOLATION` | Path traversal / secure-fs violations (replaces SecurityError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L77) | -| `TIMEOUT_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L52) | -| `UNKNOWN_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L3) | +| Name | Description | Source | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| `ALREADY_EXISTS` | Writing would replace something that is already there. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L58) | +| `AUTHENTICATION_REQUIRED` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L11) | +| `FILE_NOT_FOUND` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L27) | +| `GENERAL_REGISTRY` | Registry fragment for GENERAL errors (slug → definition). | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L136) | +| `INITIALIZATION_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L75) | +| `INPUT_VALIDATION_FAILED` | HTTP request input validation failures (replaces ValidationError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L101) | +| `INVALID_ARGUMENT` | A value the caller supplied is not acceptable: a CLI flag, a positional argument, a config field, or a function argument. Exit code 2 is the CLI's "invalid usage" code, so a script can tell a typo from a failed run. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L48) | +| `NESTED_CWD_SCOPE` | A scope that owns the process working directory was opened inside another one. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L118) | +| `NOT_SUPPORTED` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L83) | +| `PERMISSION_DENIED` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L19) | +| `PROJECT_SOURCE_EMPTY` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L109) | +| `RESOURCE_NOT_FOUND` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L35) | +| `SECURITY_VIOLATION` | Path traversal / secure-fs violations (replaces SecurityError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L92) | +| `TIMEOUT_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L67) | +| `UNKNOWN_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L3) | ### `veryfront/errors/module` diff --git a/docs/api-reference/veryfront/index.client.md b/docs/api-reference/veryfront/index.client.md index 568d2b86d4..de0590f6b2 100644 --- a/docs/api-reference/veryfront/index.client.md +++ b/docs/api-reference/veryfront/index.client.md @@ -31,10 +31,10 @@ export function GET() { ### Components -| Name | Description | Source | -| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -| `CommonSchemas` | Lazy-getter object that preserves the `CommonSchemas.email` call shape. Each access returns the cached `Schema` (memoized inside `defineSchema`), so chained calls like `CommonSchemas.email.parse(x)` work as before. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/schemas/common.ts#L91) | -| `INPUT_VALIDATION_FAILED` | HTTP request input validation failures (replaces ValidationError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L86) | +| Name | Description | Source | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| `CommonSchemas` | Lazy-getter object that preserves the `CommonSchemas.email` call shape. Each access returns the cached `Schema` (memoized inside `defineSchema`), so chained calls like `CommonSchemas.email.parse(x)` work as before. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/schemas/common.ts#L91) | +| `INPUT_VALIDATION_FAILED` | HTTP request input validation failures (replaces ValidationError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L101) | ### Functions diff --git a/docs/api-reference/veryfront/index.md b/docs/api-reference/veryfront/index.md index f009691c3f..cdbe4c1aca 100644 --- a/docs/api-reference/veryfront/index.md +++ b/docs/api-reference/veryfront/index.md @@ -49,10 +49,10 @@ export function getServerData(ctx: DataContext) { ### Components -| Name | Description | Source | -| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -| `CommonSchemas` | Lazy-getter object that preserves the `CommonSchemas.email` call shape. Each access returns the cached `Schema` (memoized inside `defineSchema`), so chained calls like `CommonSchemas.email.parse(x)` work as before. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/schemas/common.ts#L91) | -| `INPUT_VALIDATION_FAILED` | HTTP request input validation failures (replaces ValidationError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L86) | +| Name | Description | Source | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| `CommonSchemas` | Lazy-getter object that preserves the `CommonSchemas.email` call shape. Each access returns the cached `Schema` (memoized inside `defineSchema`), so chained calls like `CommonSchemas.email.parse(x)` work as before. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/schemas/common.ts#L91) | +| `INPUT_VALIDATION_FAILED` | HTTP request input validation failures (replaces ValidationError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L101) | ### Functions diff --git a/docs/api-reference/veryfront/scaffold.md b/docs/api-reference/veryfront/scaffold.md index a486432414..a024ada980 100644 --- a/docs/api-reference/veryfront/scaffold.md +++ b/docs/api-reference/veryfront/scaffold.md @@ -38,20 +38,20 @@ for (const file of files) { | Name | Description | Source | | --------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -| `SCAFFOLD_TEMPLATE_ALIASES` | Slugs other product surfaces use for a template this CLI names differently. | [source](https://github.com/veryfront/veryfront-code/blob/main/cli/shared/project-creation.ts#L659) | +| `SCAFFOLD_TEMPLATE_ALIASES` | Slugs other product surfaces use for a template this CLI names differently. | [source](https://github.com/veryfront/veryfront-code/blob/main/cli/shared/project-creation.ts#L758) | ### Functions | Name | Description | Source | | ------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -| `listScaffoldTemplates` | Every template slug a caller may ask for, canonical names and aliases. | [source](https://github.com/veryfront/veryfront-code/blob/main/cli/shared/project-creation.ts#L674) | -| `materializeScaffold` | Produce the complete contents of a new project without touching a disk. | [source](https://github.com/veryfront/veryfront-code/blob/main/cli/shared/project-creation.ts#L713) | -| `resolveScaffoldTemplate` | Canonical starter template for a slug, or `null` when nothing matches. | [source](https://github.com/veryfront/veryfront-code/blob/main/cli/shared/project-creation.ts#L666) | +| `listScaffoldTemplates` | Every template slug a caller may ask for, canonical names and aliases. | [source](https://github.com/veryfront/veryfront-code/blob/main/cli/shared/project-creation.ts#L773) | +| `materializeScaffold` | Produce the complete contents of a new project without touching a disk. | [source](https://github.com/veryfront/veryfront-code/blob/main/cli/shared/project-creation.ts#L812) | +| `resolveScaffoldTemplate` | Canonical starter template for a slug, or `null` when nothing matches. | [source](https://github.com/veryfront/veryfront-code/blob/main/cli/shared/project-creation.ts#L765) | ### Types | Name | Description | Source | | ---------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -| `MaterializedScaffold` | A new project: every file it starts with, plus anything worth telling the author. | [source](https://github.com/veryfront/veryfront-code/blob/main/cli/shared/project-creation.ts#L695) | -| `MaterializeScaffoldRequest` | What to build: which starter, under what name, for which runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/cli/shared/project-creation.ts#L679) | +| `MaterializedScaffold` | A new project: every file it starts with, plus anything worth telling the author. | [source](https://github.com/veryfront/veryfront-code/blob/main/cli/shared/project-creation.ts#L794) | +| `MaterializeScaffoldRequest` | What to build: which starter, under what name, for which runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/cli/shared/project-creation.ts#L778) | | `TemplateFile` | | [source](https://github.com/veryfront/veryfront-code/blob/main/templates/types.ts#L17) | diff --git a/docs/api-reference/veryfront/security.md b/docs/api-reference/veryfront/security.md index 7aac4214da..825da23772 100644 --- a/docs/api-reference/veryfront/security.md +++ b/docs/api-reference/veryfront/security.md @@ -41,9 +41,9 @@ applySecurityHeaders(response.headers, false, generateNonce(), null); | `DEFAULT_CORS_HEADERS` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/cors/constants.ts#L9) | | `DEFAULT_CORS_METHODS` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/http/cors/constants.ts#L1) | | `DEFAULT_LIMITS` | Framework-owned request limits. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/input-validation/types.ts#L21) | -| `INPUT_VALIDATION_FAILED` | HTTP request input validation failures (replaces ValidationError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L86) | +| `INPUT_VALIDATION_FAILED` | HTTP request input validation failures (replaces ValidationError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L101) | | `PathValidationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/path-validation/types.ts#L39) | -| `SECURITY_VIOLATION` | Path traversal / secure-fs violations (replaces SecurityError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L77) | +| `SECURITY_VIOLATION` | Path traversal / secure-fs violations (replaces SecurityError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L92) | | `SERVER_PERMISSIONS` | SERVER - CLI server (dev, production, proxy, MCP, split-mode). Also used by build and test tasks that need equivalent access. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/deno-permissions.ts#L14) | | `ValidationPresets` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/path-validation/presets.ts#L45) | | `WORKFLOW_RUN_PERMISSIONS` | WORKFLOW_RUN - `ProcessRunExecutor` (RESTRICTED). Runs user-authored code - no `--allow-run`, `--allow-ffi`, or `--allow-sys`. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/security/deno-permissions.ts#L37) | diff --git a/docs/guides/errors.md b/docs/guides/errors.md index d32542efde..1ee9488ed3 100644 --- a/docs/guides/errors.md +++ b/docs/guides/errors.md @@ -853,12 +853,20 @@ Requested resource not found. ### invalid-argument -Invalid function argument. +Invalid argument. - **HTTP status:** 400 - **CLI exit code:** 2 - **What to do:** Check argument types and values +### already-exists + +Target already exists. + +- **HTTP status:** 409 +- **CLI exit code:** 1 +- **What to do:** Choose a different name, or remove the existing target first + ### timeout-error Operation timed out. diff --git a/src/errors/catalog/general-errors.test.ts b/src/errors/catalog/general-errors.test.ts index e17faa28f7..17f0a9f027 100644 --- a/src/errors/catalog/general-errors.test.ts +++ b/src/errors/catalog/general-errors.test.ts @@ -12,6 +12,7 @@ describe("errors/catalog/general-errors", () => { "file-not-found", "resource-not-found", "invalid-argument", + "already-exists", "timeout-error", ]; @@ -35,8 +36,8 @@ describe("errors/catalog/general-errors", () => { } }); - it("should have 6 entries", () => { - assertEquals(Object.keys(GENERAL_ERROR_CATALOG).length, 6); + it("should have 7 entries", () => { + assertEquals(Object.keys(GENERAL_ERROR_CATALOG).length, 7); }); it("unknown-error should suggest running veryfront doctor", () => { diff --git a/src/errors/catalog/general-errors.ts b/src/errors/catalog/general-errors.ts index c5987e2f90..c189efa290 100644 --- a/src/errors/catalog/general-errors.ts +++ b/src/errors/catalog/general-errors.ts @@ -58,6 +58,17 @@ export const GENERAL_ERROR_CATALOG: PartialErrorCatalog = Object.freeze({ ], ), + "already-exists": createSimpleError( + "already-exists", + "Target already exists", + "The command would overwrite a file or directory that is already there.", + [ + "Choose a different name", + "Remove the existing file or directory first", + "Commands that can overwrite, such as 'veryfront init', accept --force", + ], + ), + "timeout-error": createSimpleError( "timeout-error", "Operation timed out", diff --git a/src/errors/error-registry.test.ts b/src/errors/error-registry.test.ts index 6b8eae9282..7c3f2f635a 100644 --- a/src/errors/error-registry.test.ts +++ b/src/errors/error-registry.test.ts @@ -29,9 +29,9 @@ describe("error-registry", () => { assertEquals(slugs.length, uniqueSlugs.size, "Duplicate slugs detected"); }); - it("should have 121 registered errors", () => { + it("should have 122 registered errors", () => { const slugs = getAllSlugs(); - assertEquals(slugs.length, 121); + assertEquals(slugs.length, 122); }); it("registers every local integration boundary error", () => { @@ -347,7 +347,7 @@ describe("error-registry", () => { DEV: 5, DEPLOY: 16, AGENT: 9, - GENERAL: 13, + GENERAL: 14, }; for ( @@ -478,3 +478,19 @@ describe("error-registry", () => { }); }); }); + +describe("CLI usage errors", () => { + it("maps a bad argument to invalid-argument with the usage exit code", () => { + const error = getErrorBySlug("invalid-argument"); + assertEquals(error.title, "Invalid argument"); + assertEquals(error.exitCode, 2); + }); + + it("registers already-exists for targets that would be overwritten", () => { + const error = getErrorBySlug("already-exists"); + assertEquals(error.category, "GENERAL"); + assertEquals(error.status, 409); + assertEquals(error.exitCode, 1); + assertEquals(error.suggestion?.includes("different name"), true); + }); +}); diff --git a/src/errors/error-registry/general.ts b/src/errors/error-registry/general.ts index aaba25a956..270a1508f9 100644 --- a/src/errors/error-registry/general.ts +++ b/src/errors/error-registry/general.ts @@ -40,15 +40,30 @@ export const RESOURCE_NOT_FOUND = defineError({ suggestion: "Verify the referenced resource ID or name exists", }); +/** + * A value the caller supplied is not acceptable: a CLI flag, a positional + * argument, a config field, or a function argument. Exit code 2 is the CLI's + * "invalid usage" code, so a script can tell a typo from a failed run. + */ export const INVALID_ARGUMENT = defineError({ slug: "invalid-argument", category: "GENERAL", status: 400, - title: "Invalid function argument", + title: "Invalid argument", suggestion: "Check argument types and values", exitCode: 2, }); +/** Writing would replace something that is already there. */ +export const ALREADY_EXISTS = defineError({ + slug: "already-exists", + category: "GENERAL", + status: 409, + title: "Target already exists", + suggestion: "Choose a different name, or remove the existing target first", + exitCode: 1, +}); + export const TIMEOUT_ERROR = defineError({ slug: "timeout-error", category: "GENERAL", @@ -125,6 +140,7 @@ export const GENERAL_REGISTRY = { "file-not-found": FILE_NOT_FOUND, "resource-not-found": RESOURCE_NOT_FOUND, "invalid-argument": INVALID_ARGUMENT, + "already-exists": ALREADY_EXISTS, "timeout-error": TIMEOUT_ERROR, "initialization-error": INITIALIZATION_ERROR, "not-supported": NOT_SUPPORTED, diff --git a/src/errors/index.ts b/src/errors/index.ts index 0cacd65bd7..f043fd899b 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -34,6 +34,7 @@ export { AGENT_INTENT_ERROR, AGENT_NOT_FOUND, AGENT_TIMEOUT, + ALREADY_EXISTS, API_CLIENT_ERROR, API_ERROR, API_ROUTE_ERROR, diff --git a/src/server/handlers/dev/dashboard/api.test.ts b/src/server/handlers/dev/dashboard/api.test.ts index 4d4f4c0cb6..b88a7caf37 100644 --- a/src/server/handlers/dev/dashboard/api.test.ts +++ b/src/server/handlers/dev/dashboard/api.test.ts @@ -214,7 +214,7 @@ describe("Dashboard API - GET endpoints", () => { assertEquals("errors" in body, true); assertEquals("categories" in body, true); assertEquals("count" in body, true); - assertEquals(body.count, 66); + assertEquals(body.count, 67); assertEquals(body.categories, { config: 7, build: 9, @@ -225,7 +225,7 @@ describe("Dashboard API - GET endpoints", () => { dev: 5, rsc: 6, deployment: 4, - general: 6, + general: 7, }); const errorsByCode = new Map( diff --git a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts index 79a8e53e2e..f2dc470e5f 100644 --- a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts +++ b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts @@ -7,7 +7,7 @@ */ export const CLIENT_BOOT_BUNDLE: string = - 'var ct=Object.defineProperty;var lt=(e,t,n)=>t in e?ct(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>lt(e,typeof t!="symbol"?t+"":t,n);var Ar=Array.prototype.at,Tr=Array.prototype.filter,dt=Array.prototype.join,Cr=Array.prototype.map,wr=Array.prototype.pop,ut=Array.prototype.push,Dr=Array.prototype.sort,Ae=Reflect.apply;function z(e,t){return Ae(dt,e,[t])}function M(e,t){Ae(ut,e,[t])}var Ir=Set.prototype.has,Or=String.prototype.charCodeAt,Mr=String.prototype.includes,Pr=String.prototype.indexOf,Hr=String.prototype.lastIndexOf,Lr=String.prototype.slice,Ur=String.prototype.split,$r=String.prototype.startsWith;var S=Object.getOwnPropertyDescriptor,kr=S(URL.prototype,"origin").get,vr=S(URL.prototype,"pathname").get,Fr=S(URL.prototype,"protocol").get,Br=S(URL.prototype,"hostname").get,Vr=S(URL.prototype,"port").get,zr=S(URL.prototype,"search").get,jr=S(URL.prototype,"hash").get;var ft="3.2.3",gt=Object.entries;function pt(e){let t=[];if(e?.external?.length&&M(t,`external=${z(e.external,",")}`),M(t,`target=${e?.target??"es2022"}`),e?.deps){let n=[],r=gt(e.deps);for(let o=0;ot||n?.(r,...o)}debug(t,...n){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...n)}info(t,...n){this.log(1,console.log,`[${this.prefix}] ${t}`,...n)}warn(t,...n){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...n)}error(t,...n){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...n)}};function Dt(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var W=Dt(),u=new C("RSC",W),xo=new C("PREFETCH",W),So=new C("HYDRATE",W),bo=new C("VERYFRONT",W);var Nt="veryfront-hydration-data";function ue(e){try{let t=[...e.querySelectorAll(`[id="${Nt}"]`)];if(t.length!==1)return null;let n=e.body;if(!n)return null;let r=t[0];return n.firstElementChild!==r&&r.parentElement!==n||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function w(e=document){try{let t=ue(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return u.debug("hydration data parse failed",t),null}}function K(e,t){if(!t?.startsWith("on:"))return!1;try{let n=ue(e);if(!n)return!1;let r=JSON.parse(n.textContent||"{}");return r.dependencyPinningCacheKey=t,n.textContent=JSON.stringify(r),!0}catch(n){return u.debug("hydration dependency snapshot seed failed",n),!1}}function Y(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function It(e,t){if(!t)return e;let n=e.includes("?")?"&":"?";return`${e}${n}v=${encodeURIComponent(t)}`}function X(e,t){if(!t?.startsWith("on:"))return e;let n=e.indexOf("#"),r=n===-1?"":e.slice(n),o=n===-1?e:e.slice(0,n),i=o.indexOf("?"),s=i===-1?o:o.slice(0,i),a=new URLSearchParams(i===-1?"":o.slice(i+1));a.set("pins",t);let l=a.toString();return`${s}${l?`?${l}`:""}${r}`}function Ot(e,t){return It(`${Ie}${ce(e)}.js`,t)}function Mt(e,t,n){let r=t?`&v=${encodeURIComponent(t)}`:"";return X(`${H}module?rel=${encodeURIComponent(e)}${r}`,n)}function U(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[G]:t}:{}}function Pt(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Ht=/\\.(tsx|ts|jsx|mdx|js)$/;function Lt(e){let t=Pt(e),n=[e,t];return Ht.test(t)||n.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(n))}function Ut(e,t){if(!e)return null;for(let n of Lt(t)){let r=e[n];if(r)return r}return null}function J(e){if(e.strategy==="fs"){let n=e.absPath??e.rel;return n?X(Ot(n,e.version),e.dependencyPinningCacheKey):null}let t=Ut(e.releaseAssetModules,e.rel);return t||Mt(e.rel,e.version,e.dependencyPinningCacheKey)}function q(e=document,t=P){let n=le(e);return{react:j("react",n)?"react":Ce(t),reactDomClient:j("react-dom/client",n)?"react-dom/client":we(t)}}function Oe(e=document){let t=le(e);return j("veryfront/router",t)?"veryfront/router":null}var Z={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Ho={debug:Z.gray,info:Z.green,warn:Z.yellow,error:Z.red};var y="[REDACTED]",p=Reflect.apply,$t=Array.prototype.pop,kt=Array.prototype.push;var Uo=Array.prototype,$o=BigInt.prototype.toString,Ue=Map,vt=Map.prototype.delete,Ft=Map.prototype.get,Bt=Map.prototype.keys,Vt=Map.prototype.set;var _=Object.getOwnPropertyDescriptor,zt=Object.getPrototypeOf,ko=Object.hasOwn,vo=Object.prototype,jt=Set,Gt=decodeURIComponent,T=URL,Fo=Number.isFinite,Bo=Number.isInteger,fe=RegExp.prototype.exec,Wt=_(RegExp.prototype,"global").get,Kt=_(RegExp.prototype,"unicode").get,Yt=String.prototype.charCodeAt,Xt=String.prototype.includes,Jt=String.prototype.indexOf,Me=String.prototype.slice,$e=String.prototype.startsWith,ke=String.prototype.toLowerCase,qt=Set.prototype.add,Vo=Set.prototype.delete,Zt=Set.prototype.has,Qt=zt(new Ue().keys()).next,en=_(Map.prototype,"size").get,zo=_(T.prototype,"host").get,jo=_(T.prototype,"origin").get,tn=_(T.prototype,"password").get,Go=_(T.prototype,"pathname").get,Wo=_(T.prototype,"protocol").get,nn=_(T.prototype,"username").get,rn=/[^a-z0-9]/g,on=/([a-z0-9])([A-Z])/g,sn=/([A-Z])([A-Z][a-z])/g,an=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function R(e,t,n){let r=p(Wt,t,[]),o=p(Kt,t,[]),i=0,s=!1,a="";t.lastIndex=0;try{for(;;){let l=p(fe,t,[e]);if(l===null)break;let c=l[0],d=l.index;if(a+=A(e,i,d),a+=typeof n=="string"?n:n(l),i=d+c.length,s=!0,!r)break;c.length===0&&(t.lastIndex=cn(e,d,o))}}finally{t.lastIndex=0}return s?a+A(e,i):e}function ge(e){let t=p(ke,e,[]);return R(t,rn,"")}function D(e,t){return p(Yt,e,[t])}function cn(e,t,n){let r=t+1;if(!n||r>=e.length)return r;let o=D(e,t);if(o<55296||o>56319)return r;let i=D(e,r);return i>=56320&&i<=57343?t+2:r}function A(e,t,n){return n===void 0?p(Me,e,[t]):p(Me,e,[t,n])}function ln(e){let t=[],n=0;for(let r=0;r<=e.length;r++){let o=r===e.length?-1:D(e,r);o>=97&&o<=122||o>=48&&o<=57||(r>n&&(t[t.length]=A(e,n,r)),n=r+1)}return t}var Q=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],dn=512,un=128,$=new Ue;var fn=256;function gn(e){let t=e.length<=un;if(t){let o=p(Ft,$,[e]);if(o!==void 0)return o}let n=ge(e),r=n==="auth";for(let o=0;!r&&o=dn){let i=p(Bt,$,[]),s=p(Qt,i,[]).value;s!==void 0&&p(vt,$,[s])}p(Vt,$,[e,r])}return r}var Pe=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],ve=new jt;for(let e=0;e=65&&t<=90||t>=97&&t<=122}function Fe(e){return En(e)||e==="_"||e==="$"}function Rn(e){if(!e)return!1;let t=D(e,0);return Fe(e)||t>=48&&t<=57||e==="."||e==="-"}function Be(e,t){let n=t,r=e[n]===\'"\'||e[n]==="\'"?e[n++]:"";if(!Fe(e[n]))return!1;for(n++;Rn(e[n]);)n++;if(r){if(e[n]!==r)return!1;n++}for(;e[n]===" "||e[n]==="\t";)n++;return e[n]===":"||e[n]==="="}function Ve(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||hn(e)}function ze(e,t){let n=t;for(;n=e.length||Be(e,n)}function _n(e,t){let n=t,r=!0;if(p($e,e,[y,t])){let d=t+y.length;if(He(e,d))return{end:d,replacement:y};n=d,r=!1}let o=r&&(e[n]===\'"\'||e[n]==="\'"||e[n]==="`")?e[n]:"",i=!1,s=()=>o?`${o}${y}${i?o:""}`:y,a=[],l="",c=-1;for(let d=n;d0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:e.length,replacement:s()};if(p($t,a,[]),d++,a.length===0&&He(e,d))return{end:d,replacement:s()};continue}if(a.length>0||!Ve(f)){d++;continue}let E=d;if(d=ze(e,d),d>=e.length||Be(e,d))return{end:E,replacement:s()}}return{end:e.length,replacement:s()}}function Le(e,t,n,r){let o=0,i="";for(let s=p(fe,t,[e]);s;s=p(fe,t,[e])){let a=s[n];if(!xn(a))continue;let l=t.lastIndex,c=r===void 0?void 0:s[r],d=l+y.length;if((c==="?"||c==="&"||c===";")&&p($e,e,[y,l])&&e[d]==="#")continue;let f=_n(e,l);i+=A(e,o,s.index),i+=s[0],i+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:i+A(e,o)}function xn(e){if(e.length>fn)return!0;let t=R(e,sn,i=>`${i[1]} ${i[2]}`),n=R(t,on,i=>`${i[1]} ${i[2]}`),r=p(ke,n,[]),o=ln(r);for(let i=0;i{let r=n[1],o=n[2],i=p(Jt,o,[":"]);if(i===-1)return`${r}${y}@`;let s=A(o,0,i);return`${r}${s}:${y}@`});return t=R(t,yn,n=>{let r=n[1],o=n[2],i=n[3];return Sn(r,o,i)?n[0]:`${r}${o}:${y}@`}),t=R(t,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,n=>{let r=n[1],o=n[2],i=bn(o);return p(Zt,ve,[ge(i)])||gn(i)?`${r}${o}=${y}`:n[0]}),t=R(t,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${y}`),t=R(t,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,an,y),t=Le(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=Le(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var An=2048;var qo=64*1024,Tn=256,Cn="https://veryfront.com/docs/code/guides/errors#",je="...[truncated]",ye="unknown-error";function Ge(e,t){if(e.length<=t)return e;let n=Math.max(0,t-je.length);return`${wn(e,n)}${je}`}function wn(e,t){let n=e.slice(0,t),r=n.charCodeAt(n.length-1);return r>=55296&&r<=56319&&(n=n.slice(0,-1)),n}function Dn(e){let t="";for(let n=0;n=55296&&r<=56319){let o=e.charCodeAt(n+1);o>=56320&&o<=57343?(t+=e.slice(n,n+2),n++):t+="\\uFFFD";continue}t+=r>=56320&&r<=57343?"\\uFFFD":e.charAt(n)}return t}function N(e){return typeof e!="string"?y:Ge(pe(e),An)}function Nn(e){let t=typeof e=="string"?pe(e):ye,n=Ge(t||ye,Tn),r=Dn(n);return r==="."||r===".."?ye:r}function ee(e){let t=encodeURIComponent(Nn(e));return`${Cn}${t}`}var Ye=Reflect.apply,In=Object.freeze,On=Object.getOwnPropertyDescriptors,We=Number.isFinite,Xe=new WeakSet,Mn=WeakSet.prototype.add,Pn=WeakSet.prototype.has,Hn=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function h(e){let t={...e},n={...t,create(r){let o=r?.message,i=r?.detail,s=r?.cause,a=r?.instance,l=r?.context,c=r?.status??t.status;return new me(o||i||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:i,cause:s,instance:a,context:l})}};return In(n)}var me=class extends Error{constructor(n,r){super(n);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");Ye(Mn,Xe,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let n=Ke(this);return n?{type:ee(n.slug),title:N(n.title),status:n.status,detail:n.detail===void 0?void 0:N(n.detail),instance:n.instance===void 0?void 0:N(n.instance),category:n.category,suggestion:n.suggestion===void 0?void 0:N(n.suggestion),cause:typeof n.cause=="string"?N(n.cause):void 0}:{type:ee("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let n=Ke(this);return ee(n?.slug??"unknown-error")}};function Je(e){return typeof e=="object"&&e!==null&&Ye(Pn,Xe,[e])===!0}function Ke(e){return Je(e)?Ln(e):null}function Ln(e){try{if(!Je(e))return null;let t=On(e),n=oe=>{let O=t[oe];return O&&"value"in O?O.value:void 0},r=n("slug"),o=n("category"),i=n("status"),s=n("title"),a=n("message"),l=n("suggestion"),c=n("exitCode"),d=n("detail"),f=n("cause"),E=n("instance"),F=n("context"),x=n("stack");return typeof r!="string"||!Hn.has(o)||typeof i!="number"||!We(i)||typeof s!="string"||typeof a!="string"||l!==void 0&&typeof l!="string"||c!==void 0&&(typeof c!="number"||!We(c))||d!==void 0&&typeof d!="string"||E!==void 0&&typeof E!="string"||x!==void 0&&typeof x!="string"?null:{slug:r,category:o,status:i,title:s,message:a,suggestion:l,exitCode:c,detail:d,cause:f,instance:E,context:F,stack:x}}catch{return null}}var ri=h({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),oi=h({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),ii=h({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),si=h({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),ai=h({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),ci=h({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),li=h({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),di=h({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),ui=h({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),qe=h({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),fi=h({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),gi=h({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),pi=h({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Un=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function $n(){return Un.map(({source:e,flags:t,name:n})=>({pattern:new RegExp(e,t),name:n}))}function kn(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function k(e,t={}){let{allowInlineScripts:n=!1,strict:r=!1,warn:o=!0}=t;for(let{pattern:i,name:s}of $n())if(!(n&&s==="inline script")&&(i.lastIndex=0,!!i.test(e)&&(o&&console.warn(`[Security] Suspicious ${s} detected in server HTML`),r||!kn())))throw qe.create({detail:`Potentially unsafe HTML: ${s} detected`});return e}function v(e,t){let n=t==="root"?L:`rsc-slot-${t}`,r=e.getElementById(n);if(r)return r;let o=e.createElement("div");return o.id=n,e.body.appendChild(o),o}function vn(e,t){if(t.type!=="slot")return;let n=v(e,t.id);n.innerHTML=k(String(t.html??""))}function Ze(e,t){let n=t.split(`\n`),r=n.pop()??"";for(let o of n){let i=o.trim();if(!i)continue;let s;try{s=JSON.parse(i)}catch(l){u.debug("[client-dom] malformed NDJSON line",{line:i,error:l instanceof Error?l.message:String(l)});continue}if(!s||typeof s!="object")continue;let a=s;if(a.type==="slot"){vn(e,a);try{Vn(e,a.id||"root")}catch(l){u.debug("[client-dom] hydration optional failed",l)}}}return r}function Fn(e){return new Promise((t,n)=>{let r=()=>n(new DOMException("aborted","AbortError"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:!0})})}async function Qe(e,t=document,n){let r="body"in e?e:null,o=r?.body??e;if(!o)return;r&&K(t,r.headers.get(G));let i=o.getReader(),s=new TextDecoder,a="",l=!1;try{for(;;){if(n?.aborted)throw new DOMException("aborted","AbortError");let c=i.read(),{done:d,value:f}=n?await Promise.race([c,Fn(n)]):await c;if(d){l=!0;break}a+=s.decode(f,{stream:!0}),a=Ze(t,a)}a&&Ze(t,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||u.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await i.cancel()}catch(c){l||u.debug("[client-dom] reader.cancel failed",c)}try{i.releaseLock()}catch(c){u.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){u.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){u.debug("[client-dom] response.body.cancel failed",c)}}}function Bn(e,t){let n=v(e,t),r=[],o=i=>{let s=i;s.dataset?.clientRef&&r.push(s);for(let a of i.children)o(a)};return o(n),r}function Vn(e,t){let n=Bn(e,t);for(let r of n){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",u.debug("[client-dom] marked for hydration",o))}}var zn=new Set(["server","client","html","fragment"]);function et(e){if(!e)return[];try{let t=JSON.parse(e);return Gn(t)?t.nodes:[]}catch{return[]}}async function Ee(e,t,n){return await Promise.all(e.map(r=>jn(r,t,n)))}async function jn(e,t,n){if(e.type==="html")return e.text??e.html??"";let r=await Ee(e.children??[],t,n);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...r);if(e.type==="server")return t.createElement(e.component,e.props??{},...r);let o=await n(e.component);return o?t.createElement(o,e.props??{},...r):null}function Gn(e){return!he(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>tt(t,0))}function tt(e,t){return t>100||!he(e)||!zn.has(e.type)||e.type==="html"&&typeof e.html!="string"&&typeof e.text!="string"||e.type==="client"&&typeof e.component!="string"||e.type==="server"&&e.component!==void 0&&typeof e.component!="string"||e.props!==void 0&&!he(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(n=>tt(n,t+1))}function he(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Wn(e){if(!e)return{};let t={};for(let[n,r]of Object.entries(e))t[n]=Array.isArray(r)?r.join("/"):r;return t}async function te(e,t,n=document){try{let r=Oe(n);if(!r)return e;let i=(await import(r)).wrapForHydration;return typeof i!="function"?e:i(e,{params:Wn(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(r){return u.debug("router provider wrap failed",r),e}}var Kn="Unknown dependency snapshot",Yn="export default null; // Unknown dependency snapshot",Re="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function Xn(){return globalThis}async function Jn(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===Kn||t===Yn}catch{return!1}}async function I(e,t=()=>globalThis.location.reload()){if(!await Jn(e))return!1;let n=Xn();if(n[Re])return!0;n[Re]=!0;try{t()}catch{return delete n[Re],!1}return!0}async function ne(e,t=globalThis.fetch,n=()=>globalThis.location.reload()){try{let r=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(r.length!==1||!r[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await I(o,n)}catch{return!1}}var qn=100;function Zn(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=qn){let n=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;n&&globalThis.__VF_CLIENT_MOD_CACHE.delete(n)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function nt(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let n=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return n?{moduleUrl:n[1],exportName:n[2]||"default"}:(u.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function Qn(e){let t=e.dataset?.rscProps;if(!t)return{};try{let n=JSON.parse(t);return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}catch(n){return u.debug("hydrate: invalid client boundary props, using empty props",n),{}}}function er(e){return et(e.dataset?.rscChildren)}function tr(e){return"/_veryfront/rsc/manifest"}function nr(e){return U(e)}async function rr(e=document){try{let t=w(e),n=await fetch(tr(t),{headers:nr(t)});return n.ok?await n.json():(await I(n),null)}catch{return null}}async function rt(e,t,n,r={}){let o=or(e,t,n,r.releaseAssetModules),i=t.moduleUrl??t.rel;if(!i)return null;let s=`${i}#${e.hash??""}`;try{let a=globalThis.__VF_CLIENT_MOD_CACHE?.get(s);if(a)return a}catch(a){u.debug("hydrate: cache get failed",a)}if(!o)return null;try{let a=await(r.importModule??(l=>import(l)))(o);try{Zn(s,a)}catch(l){u.debug("hydrate: cache set failed",l)}return a}catch(a){return u.debug("hydrate: failed to import module",{moduleUrl:o,error:a}),await(r.recoverSnapshotFailure??ne)(o),null}}function or(e,t,n,r){if(t.moduleUrl)return X(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(i=>i.rel===t.rel)?.path;return J({strategy:n,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:r})}function ir(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),n=new Set(t);return t.filter(r=>{let o=r.parentElement;for(;o;){if(n.has(o))return!1;o=o.parentElement}return!0})}async function ot(e=document){let t=null;try{t=await rr(e)}catch(c){u.debug("hydrate: fetch manifest failed",c)}if(!t){u.debug("hydrate: no manifest");return}let n=ir(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!n.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){u.debug("hydrate: hmr hash read failed",c)}if(n.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed",c)}return}let r=w(e),o=Y(r),i=r?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){u.debug("hydrate: test mode flags failed",c)}let s=q(e,r?.reactVersion),[{default:a},{createRoot:l}]=await Promise.all([import(s.react),import(s.reactDomClient)]);for(let c of n){let d=c.dataset?.clientRef??"";if(!d||c.dataset?.hydrated==="true")continue;let f=nt(d);if(!f)continue;let E=await rt(t,f,o,{releaseAssetModules:i});if(!E)continue;let F=E[f.exportName]??E.default;if(typeof F=="function")try{let x=l(c),oe=Qn(c),O=er(c),it=await Ee(O,{Fragment:a.Fragment,createElement(B,ie,...V){return a.createElement(B,ie,...V)}},async B=>{let ie=t.modules.find(at=>at.id===B),V=t.components?.[B],Se=ie?.clientRef??(V?`${V}#default`:void 0);if(!Se)return null;let se=nt(Se);if(!se)return null;let ae=await rt(t,se,o,{releaseAssetModules:i});if(!ae)return null;let be=ae[se.exportName]??ae.default;return typeof be=="function"?be:null}),st=await te(a.createElement(F,oe,...it),r,e);x.render(st),c.dataset.hydrated="true"}catch(x){u.warn("hydrate: render failed",x)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed (post)",c)}}var _e="data-vf-react-head-owner";var sr=2*1024*1024,Li=sr*2;var Ui=64*1024,$i=1024*1024,ki=1024*1024;var vi=new TextEncoder;async function ar(){let e=w(document),t=q(document,e?.reactVersion),[n,r]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:n,ReactDOM:r}}var cr=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function xe(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||cr.has(e.tagName.toUpperCase())}function lr(e,t){return e.find(n=>n.tagName.toUpperCase()==="DIV"&&!!n.getAttribute("class")?.trim()&&!xe(n))??t}function dr(e,t){return e===t}function ur(e,t){let n=document.createElement("div");n.setAttribute("data-veryfront-hydration-root","page");let r=e.find(o=>!xe(o));r?.parentNode===t?t.insertBefore(n,r):t.appendChild(n);for(let o of e)!xe(o)&&o.parentNode===t&&n.appendChild(o);return n}function fr(e,t){for(let n of e){let r=[...n.hasAttribute(_e)?[n]:[],...n.querySelectorAll(`[${_e}]`)];for(let o of r)t.contains(o)||o.remove()}}function gr(e,t,n=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!n.getElementById("root")}function pr(e,t){return t?.pagePath?!1:!!e.getElementById(L)}function yr(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function mr(e){return e==="rsc-module"}function hr(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function Er(e,t,n){return J({strategy:t,rel:e,releaseAssetModules:n?.releaseAssetModules,dependencyPinningCacheKey:n?.dependencyPinningCacheKey})}async function Rr(e,t){try{let n=await fetch(H+"stream"+e,{headers:U(t)});if(!n.ok)return await I(n)?"snapshot-conflict":"failure";if(!n.body)return"failure";let r=new AbortController;return addEventListener("pagehide",()=>r.abort(),{once:!0}),await Qe(n,document,r.signal),"success"}catch(n){return u.debug("tryStream failed",n),"failure"}}async function re(){try{await ot(document)}catch(e){u.debug("hydration failed",e)}}async function _r(e,t,n){try{let{React:r,ReactDOM:o}=await ar(),i=Er(e,t,n);if(!i)return!1;u.debug("Loading component from:",i);let s;try{s=await import(i)}catch(E){throw await ne(i),E}let a=s.default;if(typeof a!="function")return u.debug("Page component is not a function"),!1;let l=Array.from(document.body.children),c=lr(l,document.body),d=dr(c,document.body)?ur(l,document.body):c;fr(l,d);let f=await te(r.createElement(a,{}),n);return mr(t)?o.createRoot(d).render(f):o.hydrateRoot(d,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),u.debug("Page component hydrated successfully"),!0}catch(r){return u.error("Page hydration failed",r),!1}}async function xr(e,t){try{let n=await fetch(H+"payload"+e,{headers:U(t)});if(!n.ok)return await I(n)?"snapshot-conflict":"failure";let r=await n.json();if(K(document,r?.dependencyPinningCacheKey),r?.slots){for(let[o,i]of Object.entries(r.slots))v(document,o).innerHTML=k(String(i||""));return"success"}return v(document,L).innerHTML=k(String(r?.html||"")),"success"}catch(n){return u.debug("payload fetch failed",n),"failure"}}async function Sr(){try{let e=w(document),t=hr(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(yr()){await re();return}let n=e?.pagePath,r=Y(e);if(n){if(gr(globalThis.window,e,document)){u.debug("Page renderer owns hydration");return}u.debug("Found page component in hydration data:",n),await _r(n,r,e)&&u.debug("Client component hydrated successfully");return}if(!pr(document,e))return;let o=await Rr(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await re();return}let i=await xr(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await re();return}await re()}catch(e){u.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{Sr()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{Sr as boot,Er as buildPageHydrationModuleUrl,hr as buildRSCTransportQuery,fr as retireAbandonedHeadOwnerMarkers,lr as selectHydrationRoot,pr as shouldAttemptRSCTransport,yr as shouldHydrateOnly,mr as shouldRenderPageComponent,gr as shouldUsePageRendererHydration,dr as shouldWrapPageHydrationRoot};\n'; + 'var ct=Object.defineProperty;var lt=(e,t,n)=>t in e?ct(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var h=(e,t,n)=>lt(e,typeof t!="symbol"?t+"":t,n);var Ar=Array.prototype.at,Tr=Array.prototype.filter,dt=Array.prototype.join,Cr=Array.prototype.map,wr=Array.prototype.pop,ut=Array.prototype.push,Dr=Array.prototype.sort,Ae=Reflect.apply;function z(e,t){return Ae(dt,e,[t])}function M(e,t){Ae(ut,e,[t])}var Ir=Set.prototype.has,Or=String.prototype.charCodeAt,Mr=String.prototype.includes,Pr=String.prototype.indexOf,Hr=String.prototype.lastIndexOf,Lr=String.prototype.slice,Ur=String.prototype.split,$r=String.prototype.startsWith;var S=Object.getOwnPropertyDescriptor,kr=S(URL.prototype,"origin").get,vr=S(URL.prototype,"pathname").get,Fr=S(URL.prototype,"protocol").get,Br=S(URL.prototype,"hostname").get,Vr=S(URL.prototype,"port").get,zr=S(URL.prototype,"search").get,jr=S(URL.prototype,"hash").get;var ft="3.2.3",gt=Object.entries;function pt(e){let t=[];if(e?.external?.length&&M(t,`external=${z(e.external,",")}`),M(t,`target=${e?.target??"es2022"}`),e?.deps){let n=[],r=gt(e.deps);for(let o=0;ot||n?.(r,...o)}debug(t,...n){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...n)}info(t,...n){this.log(1,console.log,`[${this.prefix}] ${t}`,...n)}warn(t,...n){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...n)}error(t,...n){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...n)}};function Dt(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var W=Dt(),u=new C("RSC",W),xo=new C("PREFETCH",W),So=new C("HYDRATE",W),bo=new C("VERYFRONT",W);var Nt="veryfront-hydration-data";function ue(e){try{let t=[...e.querySelectorAll(`[id="${Nt}"]`)];if(t.length!==1)return null;let n=e.body;if(!n)return null;let r=t[0];return n.firstElementChild!==r&&r.parentElement!==n||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function w(e=document){try{let t=ue(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return u.debug("hydration data parse failed",t),null}}function K(e,t){if(!t?.startsWith("on:"))return!1;try{let n=ue(e);if(!n)return!1;let r=JSON.parse(n.textContent||"{}");return r.dependencyPinningCacheKey=t,n.textContent=JSON.stringify(r),!0}catch(n){return u.debug("hydration dependency snapshot seed failed",n),!1}}function Y(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function It(e,t){if(!t)return e;let n=e.includes("?")?"&":"?";return`${e}${n}v=${encodeURIComponent(t)}`}function X(e,t){if(!t?.startsWith("on:"))return e;let n=e.indexOf("#"),r=n===-1?"":e.slice(n),o=n===-1?e:e.slice(0,n),s=o.indexOf("?"),i=s===-1?o:o.slice(0,s),a=new URLSearchParams(s===-1?"":o.slice(s+1));a.set("pins",t);let l=a.toString();return`${i}${l?`?${l}`:""}${r}`}function Ot(e,t){return It(`${Ie}${ce(e)}.js`,t)}function Mt(e,t,n){let r=t?`&v=${encodeURIComponent(t)}`:"";return X(`${H}module?rel=${encodeURIComponent(e)}${r}`,n)}function U(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[G]:t}:{}}function Pt(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Ht=/\\.(tsx|ts|jsx|mdx|js)$/;function Lt(e){let t=Pt(e),n=[e,t];return Ht.test(t)||n.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(n))}function Ut(e,t){if(!e)return null;for(let n of Lt(t)){let r=e[n];if(r)return r}return null}function J(e){if(e.strategy==="fs"){let n=e.absPath??e.rel;return n?X(Ot(n,e.version),e.dependencyPinningCacheKey):null}let t=Ut(e.releaseAssetModules,e.rel);return t||Mt(e.rel,e.version,e.dependencyPinningCacheKey)}function q(e=document,t=P){let n=le(e);return{react:j("react",n)?"react":Ce(t),reactDomClient:j("react-dom/client",n)?"react-dom/client":we(t)}}function Oe(e=document){let t=le(e);return j("veryfront/router",t)?"veryfront/router":null}var Z={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Ho={debug:Z.gray,info:Z.green,warn:Z.yellow,error:Z.red};var y="[REDACTED]",p=Reflect.apply,$t=Array.prototype.pop,kt=Array.prototype.push;var Uo=Array.prototype,$o=BigInt.prototype.toString,Ue=Map,vt=Map.prototype.delete,Ft=Map.prototype.get,Bt=Map.prototype.keys,Vt=Map.prototype.set;var _=Object.getOwnPropertyDescriptor,zt=Object.getPrototypeOf,ko=Object.hasOwn,vo=Object.prototype,jt=Set,Gt=decodeURIComponent,T=URL,Fo=Number.isFinite,Bo=Number.isInteger,fe=RegExp.prototype.exec,Wt=_(RegExp.prototype,"global").get,Kt=_(RegExp.prototype,"unicode").get,Yt=String.prototype.charCodeAt,Xt=String.prototype.includes,Jt=String.prototype.indexOf,Me=String.prototype.slice,$e=String.prototype.startsWith,ke=String.prototype.toLowerCase,qt=Set.prototype.add,Vo=Set.prototype.delete,Zt=Set.prototype.has,Qt=zt(new Ue().keys()).next,en=_(Map.prototype,"size").get,zo=_(T.prototype,"host").get,jo=_(T.prototype,"origin").get,tn=_(T.prototype,"password").get,Go=_(T.prototype,"pathname").get,Wo=_(T.prototype,"protocol").get,nn=_(T.prototype,"username").get,rn=/[^a-z0-9]/g,on=/([a-z0-9])([A-Z])/g,sn=/([A-Z])([A-Z][a-z])/g,an=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function R(e,t,n){let r=p(Wt,t,[]),o=p(Kt,t,[]),s=0,i=!1,a="";t.lastIndex=0;try{for(;;){let l=p(fe,t,[e]);if(l===null)break;let c=l[0],d=l.index;if(a+=A(e,s,d),a+=typeof n=="string"?n:n(l),s=d+c.length,i=!0,!r)break;c.length===0&&(t.lastIndex=cn(e,d,o))}}finally{t.lastIndex=0}return i?a+A(e,s):e}function ge(e){let t=p(ke,e,[]);return R(t,rn,"")}function D(e,t){return p(Yt,e,[t])}function cn(e,t,n){let r=t+1;if(!n||r>=e.length)return r;let o=D(e,t);if(o<55296||o>56319)return r;let s=D(e,r);return s>=56320&&s<=57343?t+2:r}function A(e,t,n){return n===void 0?p(Me,e,[t]):p(Me,e,[t,n])}function ln(e){let t=[],n=0;for(let r=0;r<=e.length;r++){let o=r===e.length?-1:D(e,r);o>=97&&o<=122||o>=48&&o<=57||(r>n&&(t[t.length]=A(e,n,r)),n=r+1)}return t}var Q=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],dn=512,un=128,$=new Ue;var fn=256;function gn(e){let t=e.length<=un;if(t){let o=p(Ft,$,[e]);if(o!==void 0)return o}let n=ge(e),r=n==="auth";for(let o=0;!r&&o=dn){let s=p(Bt,$,[]),i=p(Qt,s,[]).value;i!==void 0&&p(vt,$,[i])}p(Vt,$,[e,r])}return r}var Pe=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],ve=new jt;for(let e=0;e=65&&t<=90||t>=97&&t<=122}function Fe(e){return En(e)||e==="_"||e==="$"}function Rn(e){if(!e)return!1;let t=D(e,0);return Fe(e)||t>=48&&t<=57||e==="."||e==="-"}function Be(e,t){let n=t,r=e[n]===\'"\'||e[n]==="\'"?e[n++]:"";if(!Fe(e[n]))return!1;for(n++;Rn(e[n]);)n++;if(r){if(e[n]!==r)return!1;n++}for(;e[n]===" "||e[n]==="\t";)n++;return e[n]===":"||e[n]==="="}function Ve(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||hn(e)}function ze(e,t){let n=t;for(;n=e.length||Be(e,n)}function _n(e,t){let n=t,r=!0;if(p($e,e,[y,t])){let d=t+y.length;if(He(e,d))return{end:d,replacement:y};n=d,r=!1}let o=r&&(e[n]===\'"\'||e[n]==="\'"||e[n]==="`")?e[n]:"",s=!1,i=()=>o?`${o}${y}${s?o:""}`:y,a=[],l="",c=-1;for(let d=n;d0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:e.length,replacement:i()};if(p($t,a,[]),d++,a.length===0&&He(e,d))return{end:d,replacement:i()};continue}if(a.length>0||!Ve(f)){d++;continue}let E=d;if(d=ze(e,d),d>=e.length||Be(e,d))return{end:E,replacement:i()}}return{end:e.length,replacement:i()}}function Le(e,t,n,r){let o=0,s="";for(let i=p(fe,t,[e]);i;i=p(fe,t,[e])){let a=i[n];if(!xn(a))continue;let l=t.lastIndex,c=r===void 0?void 0:i[r],d=l+y.length;if((c==="?"||c==="&"||c===";")&&p($e,e,[y,l])&&e[d]==="#")continue;let f=_n(e,l);s+=A(e,o,i.index),s+=i[0],s+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:s+A(e,o)}function xn(e){if(e.length>fn)return!0;let t=R(e,sn,s=>`${s[1]} ${s[2]}`),n=R(t,on,s=>`${s[1]} ${s[2]}`),r=p(ke,n,[]),o=ln(r);for(let s=0;s{let r=n[1],o=n[2],s=p(Jt,o,[":"]);if(s===-1)return`${r}${y}@`;let i=A(o,0,s);return`${r}${i}:${y}@`});return t=R(t,yn,n=>{let r=n[1],o=n[2],s=n[3];return Sn(r,o,s)?n[0]:`${r}${o}:${y}@`}),t=R(t,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,n=>{let r=n[1],o=n[2],s=bn(o);return p(Zt,ve,[ge(s)])||gn(s)?`${r}${o}=${y}`:n[0]}),t=R(t,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${y}`),t=R(t,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,an,y),t=Le(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=Le(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var An=2048;var qo=64*1024,Tn=256,Cn="https://veryfront.com/docs/code/guides/errors#",je="...[truncated]",ye="unknown-error";function Ge(e,t){if(e.length<=t)return e;let n=Math.max(0,t-je.length);return`${wn(e,n)}${je}`}function wn(e,t){let n=e.slice(0,t),r=n.charCodeAt(n.length-1);return r>=55296&&r<=56319&&(n=n.slice(0,-1)),n}function Dn(e){let t="";for(let n=0;n=55296&&r<=56319){let o=e.charCodeAt(n+1);o>=56320&&o<=57343?(t+=e.slice(n,n+2),n++):t+="\\uFFFD";continue}t+=r>=56320&&r<=57343?"\\uFFFD":e.charAt(n)}return t}function N(e){return typeof e!="string"?y:Ge(pe(e),An)}function Nn(e){let t=typeof e=="string"?pe(e):ye,n=Ge(t||ye,Tn),r=Dn(n);return r==="."||r===".."?ye:r}function ee(e){let t=encodeURIComponent(Nn(e));return`${Cn}${t}`}var Ye=Reflect.apply,In=Object.freeze,On=Object.getOwnPropertyDescriptors,We=Number.isFinite,Xe=new WeakSet,Mn=WeakSet.prototype.add,Pn=WeakSet.prototype.has,Hn=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function m(e){let t={...e},n={...t,create(r){let o=r?.message,s=r?.detail,i=r?.cause,a=r?.instance,l=r?.context,c=r?.status??t.status;return new me(o||s||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:s,cause:i,instance:a,context:l})}};return In(n)}var me=class extends Error{constructor(n,r){super(n);h(this,"slug");h(this,"category");h(this,"status");h(this,"title");h(this,"suggestion");h(this,"exitCode");h(this,"detail");h(this,"cause");h(this,"instance");h(this,"context");Ye(Mn,Xe,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let n=Ke(this);return n?{type:ee(n.slug),title:N(n.title),status:n.status,detail:n.detail===void 0?void 0:N(n.detail),instance:n.instance===void 0?void 0:N(n.instance),category:n.category,suggestion:n.suggestion===void 0?void 0:N(n.suggestion),cause:typeof n.cause=="string"?N(n.cause):void 0}:{type:ee("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let n=Ke(this);return ee(n?.slug??"unknown-error")}};function Je(e){return typeof e=="object"&&e!==null&&Ye(Pn,Xe,[e])===!0}function Ke(e){return Je(e)?Ln(e):null}function Ln(e){try{if(!Je(e))return null;let t=On(e),n=oe=>{let O=t[oe];return O&&"value"in O?O.value:void 0},r=n("slug"),o=n("category"),s=n("status"),i=n("title"),a=n("message"),l=n("suggestion"),c=n("exitCode"),d=n("detail"),f=n("cause"),E=n("instance"),F=n("context"),x=n("stack");return typeof r!="string"||!Hn.has(o)||typeof s!="number"||!We(s)||typeof i!="string"||typeof a!="string"||l!==void 0&&typeof l!="string"||c!==void 0&&(typeof c!="number"||!We(c))||d!==void 0&&typeof d!="string"||E!==void 0&&typeof E!="string"||x!==void 0&&typeof x!="string"?null:{slug:r,category:o,status:s,title:i,message:a,suggestion:l,exitCode:c,detail:d,cause:f,instance:E,context:F,stack:x}}catch{return null}}var rs=m({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),os=m({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),ss=m({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),is=m({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),as=m({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),cs=m({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid argument",suggestion:"Check argument types and values",exitCode:2}),ls=m({slug:"already-exists",category:"GENERAL",status:409,title:"Target already exists",suggestion:"Choose a different name, or remove the existing target first",exitCode:1}),ds=m({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),us=m({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),fs=m({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),qe=m({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),gs=m({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),ps=m({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),ys=m({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Un=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function $n(){return Un.map(({source:e,flags:t,name:n})=>({pattern:new RegExp(e,t),name:n}))}function kn(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function k(e,t={}){let{allowInlineScripts:n=!1,strict:r=!1,warn:o=!0}=t;for(let{pattern:s,name:i}of $n())if(!(n&&i==="inline script")&&(s.lastIndex=0,!!s.test(e)&&(o&&console.warn(`[Security] Suspicious ${i} detected in server HTML`),r||!kn())))throw qe.create({detail:`Potentially unsafe HTML: ${i} detected`});return e}function v(e,t){let n=t==="root"?L:`rsc-slot-${t}`,r=e.getElementById(n);if(r)return r;let o=e.createElement("div");return o.id=n,e.body.appendChild(o),o}function vn(e,t){if(t.type!=="slot")return;let n=v(e,t.id);n.innerHTML=k(String(t.html??""))}function Ze(e,t){let n=t.split(`\n`),r=n.pop()??"";for(let o of n){let s=o.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch(l){u.debug("[client-dom] malformed NDJSON line",{line:s,error:l instanceof Error?l.message:String(l)});continue}if(!i||typeof i!="object")continue;let a=i;if(a.type==="slot"){vn(e,a);try{Vn(e,a.id||"root")}catch(l){u.debug("[client-dom] hydration optional failed",l)}}}return r}function Fn(e){return new Promise((t,n)=>{let r=()=>n(new DOMException("aborted","AbortError"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:!0})})}async function Qe(e,t=document,n){let r="body"in e?e:null,o=r?.body??e;if(!o)return;r&&K(t,r.headers.get(G));let s=o.getReader(),i=new TextDecoder,a="",l=!1;try{for(;;){if(n?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:d,value:f}=n?await Promise.race([c,Fn(n)]):await c;if(d){l=!0;break}a+=i.decode(f,{stream:!0}),a=Ze(t,a)}a&&Ze(t,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||u.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){l||u.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){u.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){u.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){u.debug("[client-dom] response.body.cancel failed",c)}}}function Bn(e,t){let n=v(e,t),r=[],o=s=>{let i=s;i.dataset?.clientRef&&r.push(i);for(let a of s.children)o(a)};return o(n),r}function Vn(e,t){let n=Bn(e,t);for(let r of n){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",u.debug("[client-dom] marked for hydration",o))}}var zn=new Set(["server","client","html","fragment"]);function et(e){if(!e)return[];try{let t=JSON.parse(e);return Gn(t)?t.nodes:[]}catch{return[]}}async function Ee(e,t,n){return await Promise.all(e.map(r=>jn(r,t,n)))}async function jn(e,t,n){if(e.type==="html")return e.text??e.html??"";let r=await Ee(e.children??[],t,n);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...r);if(e.type==="server")return t.createElement(e.component,e.props??{},...r);let o=await n(e.component);return o?t.createElement(o,e.props??{},...r):null}function Gn(e){return!he(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>tt(t,0))}function tt(e,t){return t>100||!he(e)||!zn.has(e.type)||e.type==="html"&&typeof e.html!="string"&&typeof e.text!="string"||e.type==="client"&&typeof e.component!="string"||e.type==="server"&&e.component!==void 0&&typeof e.component!="string"||e.props!==void 0&&!he(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(n=>tt(n,t+1))}function he(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Wn(e){if(!e)return{};let t={};for(let[n,r]of Object.entries(e))t[n]=Array.isArray(r)?r.join("/"):r;return t}async function te(e,t,n=document){try{let r=Oe(n);if(!r)return e;let s=(await import(r)).wrapForHydration;return typeof s!="function"?e:s(e,{params:Wn(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(r){return u.debug("router provider wrap failed",r),e}}var Kn="Unknown dependency snapshot",Yn="export default null; // Unknown dependency snapshot",Re="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function Xn(){return globalThis}async function Jn(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===Kn||t===Yn}catch{return!1}}async function I(e,t=()=>globalThis.location.reload()){if(!await Jn(e))return!1;let n=Xn();if(n[Re])return!0;n[Re]=!0;try{t()}catch{return delete n[Re],!1}return!0}async function ne(e,t=globalThis.fetch,n=()=>globalThis.location.reload()){try{let r=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(r.length!==1||!r[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await I(o,n)}catch{return!1}}var qn=100;function Zn(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=qn){let n=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;n&&globalThis.__VF_CLIENT_MOD_CACHE.delete(n)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function nt(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let n=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return n?{moduleUrl:n[1],exportName:n[2]||"default"}:(u.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function Qn(e){let t=e.dataset?.rscProps;if(!t)return{};try{let n=JSON.parse(t);return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}catch(n){return u.debug("hydrate: invalid client boundary props, using empty props",n),{}}}function er(e){return et(e.dataset?.rscChildren)}function tr(e){return"/_veryfront/rsc/manifest"}function nr(e){return U(e)}async function rr(e=document){try{let t=w(e),n=await fetch(tr(t),{headers:nr(t)});return n.ok?await n.json():(await I(n),null)}catch{return null}}async function rt(e,t,n,r={}){let o=or(e,t,n,r.releaseAssetModules),s=t.moduleUrl??t.rel;if(!s)return null;let i=`${s}#${e.hash??""}`;try{let a=globalThis.__VF_CLIENT_MOD_CACHE?.get(i);if(a)return a}catch(a){u.debug("hydrate: cache get failed",a)}if(!o)return null;try{let a=await(r.importModule??(l=>import(l)))(o);try{Zn(i,a)}catch(l){u.debug("hydrate: cache set failed",l)}return a}catch(a){return u.debug("hydrate: failed to import module",{moduleUrl:o,error:a}),await(r.recoverSnapshotFailure??ne)(o),null}}function or(e,t,n,r){if(t.moduleUrl)return X(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(s=>s.rel===t.rel)?.path;return J({strategy:n,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:r})}function sr(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),n=new Set(t);return t.filter(r=>{let o=r.parentElement;for(;o;){if(n.has(o))return!1;o=o.parentElement}return!0})}async function ot(e=document){let t=null;try{t=await rr(e)}catch(c){u.debug("hydrate: fetch manifest failed",c)}if(!t){u.debug("hydrate: no manifest");return}let n=sr(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!n.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){u.debug("hydrate: hmr hash read failed",c)}if(n.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed",c)}return}let r=w(e),o=Y(r),s=r?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){u.debug("hydrate: test mode flags failed",c)}let i=q(e,r?.reactVersion),[{default:a},{createRoot:l}]=await Promise.all([import(i.react),import(i.reactDomClient)]);for(let c of n){let d=c.dataset?.clientRef??"";if(!d||c.dataset?.hydrated==="true")continue;let f=nt(d);if(!f)continue;let E=await rt(t,f,o,{releaseAssetModules:s});if(!E)continue;let F=E[f.exportName]??E.default;if(typeof F=="function")try{let x=l(c),oe=Qn(c),O=er(c),st=await Ee(O,{Fragment:a.Fragment,createElement(B,se,...V){return a.createElement(B,se,...V)}},async B=>{let se=t.modules.find(at=>at.id===B),V=t.components?.[B],Se=se?.clientRef??(V?`${V}#default`:void 0);if(!Se)return null;let ie=nt(Se);if(!ie)return null;let ae=await rt(t,ie,o,{releaseAssetModules:s});if(!ae)return null;let be=ae[ie.exportName]??ae.default;return typeof be=="function"?be:null}),it=await te(a.createElement(F,oe,...st),r,e);x.render(it),c.dataset.hydrated="true"}catch(x){u.warn("hydrate: render failed",x)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed (post)",c)}}var _e="data-vf-react-head-owner";var ir=2*1024*1024,Us=ir*2;var $s=64*1024,ks=1024*1024,vs=1024*1024;var Fs=new TextEncoder;async function ar(){let e=w(document),t=q(document,e?.reactVersion),[n,r]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:n,ReactDOM:r}}var cr=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function xe(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||cr.has(e.tagName.toUpperCase())}function lr(e,t){return e.find(n=>n.tagName.toUpperCase()==="DIV"&&!!n.getAttribute("class")?.trim()&&!xe(n))??t}function dr(e,t){return e===t}function ur(e,t){let n=document.createElement("div");n.setAttribute("data-veryfront-hydration-root","page");let r=e.find(o=>!xe(o));r?.parentNode===t?t.insertBefore(n,r):t.appendChild(n);for(let o of e)!xe(o)&&o.parentNode===t&&n.appendChild(o);return n}function fr(e,t){for(let n of e){let r=[...n.hasAttribute(_e)?[n]:[],...n.querySelectorAll(`[${_e}]`)];for(let o of r)t.contains(o)||o.remove()}}function gr(e,t,n=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!n.getElementById("root")}function pr(e,t){return t?.pagePath?!1:!!e.getElementById(L)}function yr(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function mr(e){return e==="rsc-module"}function hr(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function Er(e,t,n){return J({strategy:t,rel:e,releaseAssetModules:n?.releaseAssetModules,dependencyPinningCacheKey:n?.dependencyPinningCacheKey})}async function Rr(e,t){try{let n=await fetch(H+"stream"+e,{headers:U(t)});if(!n.ok)return await I(n)?"snapshot-conflict":"failure";if(!n.body)return"failure";let r=new AbortController;return addEventListener("pagehide",()=>r.abort(),{once:!0}),await Qe(n,document,r.signal),"success"}catch(n){return u.debug("tryStream failed",n),"failure"}}async function re(){try{await ot(document)}catch(e){u.debug("hydration failed",e)}}async function _r(e,t,n){try{let{React:r,ReactDOM:o}=await ar(),s=Er(e,t,n);if(!s)return!1;u.debug("Loading component from:",s);let i;try{i=await import(s)}catch(E){throw await ne(s),E}let a=i.default;if(typeof a!="function")return u.debug("Page component is not a function"),!1;let l=Array.from(document.body.children),c=lr(l,document.body),d=dr(c,document.body)?ur(l,document.body):c;fr(l,d);let f=await te(r.createElement(a,{}),n);return mr(t)?o.createRoot(d).render(f):o.hydrateRoot(d,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),u.debug("Page component hydrated successfully"),!0}catch(r){return u.error("Page hydration failed",r),!1}}async function xr(e,t){try{let n=await fetch(H+"payload"+e,{headers:U(t)});if(!n.ok)return await I(n)?"snapshot-conflict":"failure";let r=await n.json();if(K(document,r?.dependencyPinningCacheKey),r?.slots){for(let[o,s]of Object.entries(r.slots))v(document,o).innerHTML=k(String(s||""));return"success"}return v(document,L).innerHTML=k(String(r?.html||"")),"success"}catch(n){return u.debug("payload fetch failed",n),"failure"}}async function Sr(){try{let e=w(document),t=hr(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(yr()){await re();return}let n=e?.pagePath,r=Y(e);if(n){if(gr(globalThis.window,e,document)){u.debug("Page renderer owns hydration");return}u.debug("Found page component in hydration data:",n),await _r(n,r,e)&&u.debug("Client component hydrated successfully");return}if(!pr(document,e))return;let o=await Rr(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await re();return}let s=await xr(t,e);if(s==="snapshot-conflict")return;if(s==="success"){await re();return}await re()}catch(e){u.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{Sr()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{Sr as boot,Er as buildPageHydrationModuleUrl,hr as buildRSCTransportQuery,fr as retireAbandonedHeadOwnerMarkers,lr as selectHydrationRoot,pr as shouldAttemptRSCTransport,yr as shouldHydrateOnly,mr as shouldRenderPageComponent,gr as shouldUsePageRendererHydration,dr as shouldWrapPageHydrationRoot};\n'; export const CLIENT_DOM_BUNDLE: string = - 'var yt=Object.defineProperty;var ht=(t,n,e)=>n in t?yt(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e;var m=(t,n,e)=>ht(t,typeof n!="symbol"?n+"":n,e);var O={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Me={debug:O.gray,info:O.green,warn:O.yellow,error:O.red};var p="[REDACTED]",g=Reflect.apply,xt=Array.prototype.pop,Rt=Array.prototype.push;var He=Array.prototype,je=BigInt.prototype.toString,v=Map,St=Map.prototype.delete,_t=Map.prototype.get,bt=Map.prototype.keys,At=Map.prototype.set;var x=Object.getOwnPropertyDescriptor,Tt=Object.getPrototypeOf,ze=Object.hasOwn,Ve=Object.prototype,It=Set,Ct=decodeURIComponent,S=URL,Fe=Number.isFinite,Ge=Number.isInteger,w=RegExp.prototype.exec,Ot=x(RegExp.prototype,"global").get,Nt=x(RegExp.prototype,"unicode").get,Dt=String.prototype.charCodeAt,Ut=String.prototype.includes,Lt=String.prototype.indexOf,V=String.prototype.slice,W=String.prototype.startsWith,Y=String.prototype.toLowerCase,$t=Set.prototype.add,Be=Set.prototype.delete,wt=Set.prototype.has,Pt=Tt(new v().keys()).next,Mt=x(Map.prototype,"size").get,ve=x(S.prototype,"host").get,We=x(S.prototype,"origin").get,kt=x(S.prototype,"password").get,Ye=x(S.prototype,"pathname").get,Ke=x(S.prototype,"protocol").get,Ht=x(S.prototype,"username").get,jt=/[^a-z0-9]/g,zt=/([a-z0-9])([A-Z])/g,Vt=/([A-Z])([A-Z][a-z])/g,Ft=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function h(t,n,e){let r=g(Ot,n,[]),o=g(Nt,n,[]),s=0,i=!1,a="";n.lastIndex=0;try{for(;;){let u=g(w,n,[t]);if(u===null)break;let c=u[0],l=u.index;if(a+=R(t,s,l),a+=typeof e=="string"?e:e(u),s=l+c.length,i=!0,!r)break;c.length===0&&(n.lastIndex=Gt(t,l,o))}}finally{n.lastIndex=0}return i?a+R(t,s):t}function P(t){let n=g(Y,t,[]);return h(n,jt,"")}function b(t,n){return g(Dt,t,[n])}function Gt(t,n,e){let r=n+1;if(!e||r>=t.length)return r;let o=b(t,n);if(o<55296||o>56319)return r;let s=b(t,r);return s>=56320&&s<=57343?n+2:r}function R(t,n,e){return e===void 0?g(V,t,[n]):g(V,t,[n,e])}function Bt(t){let n=[],e=0;for(let r=0;r<=t.length;r++){let o=r===t.length?-1:b(t,r);o>=97&&o<=122||o>=48&&o<=57||(r>e&&(n[n.length]=R(t,e,r)),e=r+1)}return n}var N=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],vt=512,Wt=128,C=new v;var Yt=256;function Kt(t){let n=t.length<=Wt;if(n){let o=g(_t,C,[t]);if(o!==void 0)return o}let e=P(t),r=e==="auth";for(let o=0;!r&&o=vt){let s=g(bt,C,[]),i=g(Pt,s,[]).value;i!==void 0&&g(St,C,[i])}g(At,C,[t,r])}return r}var F=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],K=new It;for(let t=0;t=65&&n<=90||n>=97&&n<=122}function X(t){return Qt(t)||t==="_"||t==="$"}function te(t){if(!t)return!1;let n=b(t,0);return X(t)||n>=48&&n<=57||t==="."||t==="-"}function J(t,n){let e=n,r=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!X(t[e]))return!1;for(e++;te(t[e]);)e++;if(r){if(t[e]!==r)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function q(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||Zt(t)}function Z(t,n){let e=n;for(;e=t.length||J(t,e)}function ee(t,n){let e=n,r=!0;if(g(W,t,[p,n])){let l=n+p.length;if(G(t,l))return{end:l,replacement:p};e=l,r=!1}let o=r&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",s=!1,i=()=>o?`${o}${p}${s?o:""}`:p,a=[],u="",c=-1;for(let l=e;l0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:t.length,replacement:i()};if(g(xt,a,[]),l++,a.length===0&&G(t,l))return{end:l,replacement:i()};continue}if(a.length>0||!q(f)){l++;continue}let I=l;if(l=Z(t,l),l>=t.length||J(t,l))return{end:I,replacement:i()}}return{end:t.length,replacement:i()}}function B(t,n,e,r){let o=0,s="";for(let i=g(w,n,[t]);i;i=g(w,n,[t])){let a=i[e];if(!ne(a))continue;let u=n.lastIndex,c=r===void 0?void 0:i[r],l=u+p.length;if((c==="?"||c==="&"||c===";")&&g(W,t,[p,u])&&t[l]==="#")continue;let f=ee(t,u);s+=R(t,o,i.index),s+=i[0],s+=f.replacement,o=f.end,n.lastIndex=f.end}return o===0?t:s+R(t,o)}function ne(t){if(t.length>Yt)return!0;let n=h(t,Vt,s=>`${s[1]} ${s[2]}`),e=h(n,zt,s=>`${s[1]} ${s[2]}`),r=g(Y,e,[]),o=Bt(r);for(let s=0;s{let r=e[1],o=e[2],s=g(Lt,o,[":"]);if(s===-1)return`${r}${p}@`;let i=R(o,0,s);return`${r}${i}:${p}@`});return n=h(n,Jt,e=>{let r=e[1],o=e[2],s=e[3];return re(r,o,s)?e[0]:`${r}${o}:${p}@`}),n=h(n,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,e=>{let r=e[1],o=e[2],s=oe(o);return g(wt,K,[P(s)])||Kt(s)?`${r}${o}=${p}`:e[0]}),n=h(n,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${e[2]}${p}`),n=h(n,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${p}`),n=h(n,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,e=>`${e[1]}${e[2]}${p}`),n=h(n,Ft,p),n=B(n,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),n=B(n,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),n}var se=2048;var Qe=64*1024,ie=256,ae="https://veryfront.com/docs/code/guides/errors#",Q="...[truncated]",k="unknown-error";function tt(t,n){if(t.length<=n)return t;let e=Math.max(0,n-Q.length);return`${ce(t,e)}${Q}`}function ce(t,n){let e=t.slice(0,n),r=e.charCodeAt(e.length-1);return r>=55296&&r<=56319&&(e=e.slice(0,-1)),e}function ue(t){let n="";for(let e=0;e=55296&&r<=56319){let o=t.charCodeAt(e+1);o>=56320&&o<=57343?(n+=t.slice(e,e+2),e++):n+="\\uFFFD";continue}n+=r>=56320&&r<=57343?"\\uFFFD":t.charAt(e)}return n}function A(t){return typeof t!="string"?p:tt(M(t),se)}function le(t){let n=typeof t=="string"?M(t):k,e=tt(n||k,ie),r=ue(e);return r==="."||r===".."?k:r}function D(t){let n=encodeURIComponent(le(t));return`${ae}${n}`}var rt=Reflect.apply,de=Object.freeze,ge=Object.getOwnPropertyDescriptors,et=Number.isFinite,ot=new WeakSet,fe=WeakSet.prototype.add,pe=WeakSet.prototype.has,me=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function E(t){let n={...t},e={...n,create(r){let o=r?.message,s=r?.detail,i=r?.cause,a=r?.instance,u=r?.context,c=r?.status??n.status;return new H(o||s||n.title,{slug:n.slug,category:n.category,status:c,title:n.title,suggestion:n.suggestion,exitCode:n.exitCode,detail:s,cause:i,instance:a,context:u})}};return de(e)}var H=class extends Error{constructor(e,r){super(e);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");rt(fe,ot,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let e=nt(this);return e?{type:D(e.slug),title:A(e.title),status:e.status,detail:e.detail===void 0?void 0:A(e.detail),instance:e.instance===void 0?void 0:A(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:A(e.suggestion),cause:typeof e.cause=="string"?A(e.cause):void 0}:{type:D("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=nt(this);return D(e?.slug??"unknown-error")}};function st(t){return typeof t=="object"&&t!==null&&rt(pe,ot,[t])===!0}function nt(t){return st(t)?Ee(t):null}function Ee(t){try{if(!st(t))return null;let n=ge(t),e=Et=>{let $=n[Et];return $&&"value"in $?$.value:void 0},r=e("slug"),o=e("category"),s=e("status"),i=e("title"),a=e("message"),u=e("suggestion"),c=e("exitCode"),l=e("detail"),f=e("cause"),I=e("instance"),mt=e("context"),L=e("stack");return typeof r!="string"||!me.has(o)||typeof s!="number"||!et(s)||typeof i!="string"||typeof a!="string"||u!==void 0&&typeof u!="string"||c!==void 0&&(typeof c!="number"||!et(c))||l!==void 0&&typeof l!="string"||I!==void 0&&typeof I!="string"||L!==void 0&&typeof L!="string"?null:{slug:r,category:o,status:s,title:i,message:a,suggestion:u,exitCode:c,detail:l,cause:f,instance:I,context:mt,stack:L}}catch{return null}}var sn=E({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),an=E({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),cn=E({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),un=E({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),ln=E({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),dn=E({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),gn=E({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),fn=E({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),pn=E({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),it=E({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),mn=E({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),En=E({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),yn=E({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var ye=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function he(){return ye.map(({source:t,flags:n,name:e})=>({pattern:new RegExp(t,n),name:e}))}function xe(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function at(t,n={}){let{allowInlineScripts:e=!1,strict:r=!1,warn:o=!0}=n;for(let{pattern:s,name:i}of he())if(!(e&&i==="inline script")&&(s.lastIndex=0,!!s.test(t)&&(o&&console.warn(`[Security] Suspicious ${i} detected in server HTML`),r||!xe())))throw it.create({detail:`Potentially unsafe HTML: ${i} detected`});return t}var T=class{constructor(n,e){m(this,"prefix",n);m(this,"level",e)}log(n,e,r,...o){this.level>n||e?.(r,...o)}debug(n,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${n}`,...e)}info(n,...e){this.log(1,console.log,`[${this.prefix}] ${n}`,...e)}warn(n,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${n}`,...e)}error(n,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${n}`,...e)}};function Re(){if(typeof window>"u")return 2;let t=globalThis;return t.__VERYFRONT_DEV__||t.__RSC_DEV__?t.__VERYFRONT_DEBUG__||t.__RSC_DEBUG__?0:1:2}var U=Re(),y=new T("RSC",U),Sn=new T("PREFETCH",U),_n=new T("HYDRATE",U),bn=new T("VERYFRONT",U);var In=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var Se=5e3,_e=1e4,Nn=16*1024*1024,be=5e3;var Ae=100;var Te=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),Dn=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Un=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:Se,api:3e4,ssr:_e,hmr:3e4,sandbox:be}),cache:Object.freeze({jit:Object.freeze({maxSize:Ae,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:Te})});var d="/_veryfront",j={RSC:`${d}/rsc/`,FS:`${d}/fs/`,MODULES:`${d}/modules/`,PAGES:`${d}/pages/`,DATA:`${d}/data/`,LIB:`${d}/lib/`,CHUNKS:`${d}/chunks/`,CLIENT:`${d}/client/`},ut={HMR_RUNTIME:`${d}/hmr-runtime.js`,HMR:`${d}/hmr.js`,ERROR_OVERLAY:`${d}/error-overlay.js`,DEV_LOADER:`${d}/dev-loader.js`,CLIENT_LOG:`${d}/log`,CLIENT_JS:`${d}/client.js`,ROUTER_JS:`${d}/router.js`,PREFETCH_JS:`${d}/prefetch.js`,MANIFEST_JSON:`${d}/manifest.json`,APP_JS:`${d}/app.js`,RSC_CLIENT:`${d}/rsc/client.js`,RSC_MANIFEST:`${d}/rsc/manifest`,RSC_STREAM:`${d}/rsc/stream`,RSC_PAYLOAD:`${d}/rsc/payload`,RSC_RENDER:`${d}/rsc/render`,RSC_PAGE:`${d}/rsc/page`,RSC_MODULE:`${d}/rsc/module`,RSC_DOM:`${d}/rsc/dom.js`,LIB_CHAT_REACT:`${d}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${d}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${d}/lib/chat/primitives.js`};var Ie={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},$n=Ie.CACHE;var wn={HMR_RUNTIME:ut.HMR_RUNTIME,ERROR_OVERLAY:ut.ERROR_OVERLAY};var Ce=j.RSC,Oe=j.FS;var lt="rsc-root",z="x-veryfront-dependency-pins";var jn=Array.prototype.at,zn=Array.prototype.filter,Vn=Array.prototype.join,Fn=Array.prototype.map,Gn=Array.prototype.pop,Bn=Array.prototype.push,vn=Array.prototype.sort;var Yn=Set.prototype.has,Kn=String.prototype.charCodeAt,Xn=String.prototype.includes,Jn=String.prototype.indexOf,qn=String.prototype.lastIndexOf,Zn=String.prototype.slice,Qn=String.prototype.split,tr=String.prototype.startsWith;var _=Object.getOwnPropertyDescriptor,er=_(URL.prototype,"origin").get,nr=_(URL.prototype,"pathname").get,rr=_(URL.prototype,"protocol").get,or=_(URL.prototype,"hostname").get,sr=_(URL.prototype,"port").get,ir=_(URL.prototype,"search").get,ar=_(URL.prototype,"hash").get;var Tr=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var De="veryfront-hydration-data";function dt(t){try{let n=[...t.querySelectorAll(`[id="${De}"]`)];if(n.length!==1)return null;let e=t.body;if(!e)return null;let r=n[0];return e.firstElementChild!==r&&r.parentElement!==e||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function gt(t,n){if(!n?.startsWith("on:"))return!1;try{let e=dt(t);if(!e)return!1;let r=JSON.parse(e.textContent||"{}");return r.dependencyPinningCacheKey=n,e.textContent=JSON.stringify(r),!0}catch(e){return y.debug("hydration dependency snapshot seed failed",e),!1}}function pt(t,n){let e=n==="root"?lt:`rsc-slot-${n}`,r=t.getElementById(e);if(r)return r;let o=t.createElement("div");return o.id=e,t.body.appendChild(o),o}function Ue(t,n){if(n.type!=="slot")return;let e=pt(t,n.id);e.innerHTML=at(String(n.html??""))}function ft(t,n){let e=n.split(`\n`),r=e.pop()??"";for(let o of e){let s=o.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch(u){y.debug("[client-dom] malformed NDJSON line",{line:s,error:u instanceof Error?u.message:String(u)});continue}if(!i||typeof i!="object")continue;let a=i;if(a.type==="slot"){Ue(t,a);try{we(t,a.id||"root")}catch(u){y.debug("[client-dom] hydration optional failed",u)}}}return r}function Le(t){return new Promise((n,e)=>{let r=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){r();return}t.addEventListener("abort",r,{once:!0})})}async function vr(t,n=document,e){let r="body"in t?t:null,o=r?.body??t;if(!o)return;r&>(n,r.headers.get(z));let s=o.getReader(),i=new TextDecoder,a="",u=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:l,value:f}=e?await Promise.race([c,Le(e)]):await c;if(l){u=!0;break}a+=i.decode(f,{stream:!0}),a=ft(n,a)}a&&ft(n,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||y.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){u||y.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){y.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){y.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){y.debug("[client-dom] response.body.cancel failed",c)}}}function $e(t,n){let e=pt(t,n),r=[],o=s=>{let i=s;i.dataset?.clientRef&&r.push(i);for(let a of s.children)o(a)};return o(e),r}function we(t,n){let e=$e(t,n);for(let r of e){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",y.debug("[client-dom] marked for hydration",o))}}export{vr as consumeNdjsonStream,pt as getContainer};\n'; + 'var yt=Object.defineProperty;var xt=(t,n,e)=>n in t?yt(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e;var E=(t,n,e)=>xt(t,typeof n!="symbol"?n+"":n,e);var O={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Me={debug:O.gray,info:O.green,warn:O.yellow,error:O.red};var p="[REDACTED]",g=Reflect.apply,ht=Array.prototype.pop,Rt=Array.prototype.push;var He=Array.prototype,je=BigInt.prototype.toString,v=Map,St=Map.prototype.delete,_t=Map.prototype.get,bt=Map.prototype.keys,At=Map.prototype.set;var h=Object.getOwnPropertyDescriptor,Tt=Object.getPrototypeOf,ze=Object.hasOwn,Ve=Object.prototype,Ct=Set,It=decodeURIComponent,S=URL,Fe=Number.isFinite,Ge=Number.isInteger,w=RegExp.prototype.exec,Ot=h(RegExp.prototype,"global").get,Nt=h(RegExp.prototype,"unicode").get,Dt=String.prototype.charCodeAt,Ut=String.prototype.includes,Lt=String.prototype.indexOf,V=String.prototype.slice,W=String.prototype.startsWith,Y=String.prototype.toLowerCase,$t=Set.prototype.add,Be=Set.prototype.delete,wt=Set.prototype.has,Pt=Tt(new v().keys()).next,Mt=h(Map.prototype,"size").get,ve=h(S.prototype,"host").get,We=h(S.prototype,"origin").get,kt=h(S.prototype,"password").get,Ye=h(S.prototype,"pathname").get,Ke=h(S.prototype,"protocol").get,Ht=h(S.prototype,"username").get,jt=/[^a-z0-9]/g,zt=/([a-z0-9])([A-Z])/g,Vt=/([A-Z])([A-Z][a-z])/g,Ft=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function x(t,n,e){let r=g(Ot,n,[]),o=g(Nt,n,[]),s=0,i=!1,a="";n.lastIndex=0;try{for(;;){let u=g(w,n,[t]);if(u===null)break;let c=u[0],l=u.index;if(a+=R(t,s,l),a+=typeof e=="string"?e:e(u),s=l+c.length,i=!0,!r)break;c.length===0&&(n.lastIndex=Gt(t,l,o))}}finally{n.lastIndex=0}return i?a+R(t,s):t}function P(t){let n=g(Y,t,[]);return x(n,jt,"")}function b(t,n){return g(Dt,t,[n])}function Gt(t,n,e){let r=n+1;if(!e||r>=t.length)return r;let o=b(t,n);if(o<55296||o>56319)return r;let s=b(t,r);return s>=56320&&s<=57343?n+2:r}function R(t,n,e){return e===void 0?g(V,t,[n]):g(V,t,[n,e])}function Bt(t){let n=[],e=0;for(let r=0;r<=t.length;r++){let o=r===t.length?-1:b(t,r);o>=97&&o<=122||o>=48&&o<=57||(r>e&&(n[n.length]=R(t,e,r)),e=r+1)}return n}var N=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],vt=512,Wt=128,I=new v;var Yt=256;function Kt(t){let n=t.length<=Wt;if(n){let o=g(_t,I,[t]);if(o!==void 0)return o}let e=P(t),r=e==="auth";for(let o=0;!r&&o=vt){let s=g(bt,I,[]),i=g(Pt,s,[]).value;i!==void 0&&g(St,I,[i])}g(At,I,[t,r])}return r}var F=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],K=new Ct;for(let t=0;t=65&&n<=90||n>=97&&n<=122}function X(t){return Qt(t)||t==="_"||t==="$"}function te(t){if(!t)return!1;let n=b(t,0);return X(t)||n>=48&&n<=57||t==="."||t==="-"}function J(t,n){let e=n,r=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!X(t[e]))return!1;for(e++;te(t[e]);)e++;if(r){if(t[e]!==r)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function q(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||Zt(t)}function Z(t,n){let e=n;for(;e=t.length||J(t,e)}function ee(t,n){let e=n,r=!0;if(g(W,t,[p,n])){let l=n+p.length;if(G(t,l))return{end:l,replacement:p};e=l,r=!1}let o=r&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",s=!1,i=()=>o?`${o}${p}${s?o:""}`:p,a=[],u="",c=-1;for(let l=e;l0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:t.length,replacement:i()};if(g(ht,a,[]),l++,a.length===0&&G(t,l))return{end:l,replacement:i()};continue}if(a.length>0||!q(f)){l++;continue}let C=l;if(l=Z(t,l),l>=t.length||J(t,l))return{end:C,replacement:i()}}return{end:t.length,replacement:i()}}function B(t,n,e,r){let o=0,s="";for(let i=g(w,n,[t]);i;i=g(w,n,[t])){let a=i[e];if(!ne(a))continue;let u=n.lastIndex,c=r===void 0?void 0:i[r],l=u+p.length;if((c==="?"||c==="&"||c===";")&&g(W,t,[p,u])&&t[l]==="#")continue;let f=ee(t,u);s+=R(t,o,i.index),s+=i[0],s+=f.replacement,o=f.end,n.lastIndex=f.end}return o===0?t:s+R(t,o)}function ne(t){if(t.length>Yt)return!0;let n=x(t,Vt,s=>`${s[1]} ${s[2]}`),e=x(n,zt,s=>`${s[1]} ${s[2]}`),r=g(Y,e,[]),o=Bt(r);for(let s=0;s{let r=e[1],o=e[2],s=g(Lt,o,[":"]);if(s===-1)return`${r}${p}@`;let i=R(o,0,s);return`${r}${i}:${p}@`});return n=x(n,Jt,e=>{let r=e[1],o=e[2],s=e[3];return re(r,o,s)?e[0]:`${r}${o}:${p}@`}),n=x(n,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,e=>{let r=e[1],o=e[2],s=oe(o);return g(wt,K,[P(s)])||Kt(s)?`${r}${o}=${p}`:e[0]}),n=x(n,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${p}`),n=x(n,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,Ft,p),n=B(n,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),n=B(n,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),n}var se=2048;var Qe=64*1024,ie=256,ae="https://veryfront.com/docs/code/guides/errors#",Q="...[truncated]",k="unknown-error";function tt(t,n){if(t.length<=n)return t;let e=Math.max(0,n-Q.length);return`${ce(t,e)}${Q}`}function ce(t,n){let e=t.slice(0,n),r=e.charCodeAt(e.length-1);return r>=55296&&r<=56319&&(e=e.slice(0,-1)),e}function ue(t){let n="";for(let e=0;e=55296&&r<=56319){let o=t.charCodeAt(e+1);o>=56320&&o<=57343?(n+=t.slice(e,e+2),e++):n+="\\uFFFD";continue}n+=r>=56320&&r<=57343?"\\uFFFD":t.charAt(e)}return n}function A(t){return typeof t!="string"?p:tt(M(t),se)}function le(t){let n=typeof t=="string"?M(t):k,e=tt(n||k,ie),r=ue(e);return r==="."||r===".."?k:r}function D(t){let n=encodeURIComponent(le(t));return`${ae}${n}`}var rt=Reflect.apply,de=Object.freeze,ge=Object.getOwnPropertyDescriptors,et=Number.isFinite,ot=new WeakSet,fe=WeakSet.prototype.add,pe=WeakSet.prototype.has,me=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function m(t){let n={...t},e={...n,create(r){let o=r?.message,s=r?.detail,i=r?.cause,a=r?.instance,u=r?.context,c=r?.status??n.status;return new H(o||s||n.title,{slug:n.slug,category:n.category,status:c,title:n.title,suggestion:n.suggestion,exitCode:n.exitCode,detail:s,cause:i,instance:a,context:u})}};return de(e)}var H=class extends Error{constructor(e,r){super(e);E(this,"slug");E(this,"category");E(this,"status");E(this,"title");E(this,"suggestion");E(this,"exitCode");E(this,"detail");E(this,"cause");E(this,"instance");E(this,"context");rt(fe,ot,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let e=nt(this);return e?{type:D(e.slug),title:A(e.title),status:e.status,detail:e.detail===void 0?void 0:A(e.detail),instance:e.instance===void 0?void 0:A(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:A(e.suggestion),cause:typeof e.cause=="string"?A(e.cause):void 0}:{type:D("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=nt(this);return D(e?.slug??"unknown-error")}};function st(t){return typeof t=="object"&&t!==null&&rt(pe,ot,[t])===!0}function nt(t){return st(t)?Ee(t):null}function Ee(t){try{if(!st(t))return null;let n=ge(t),e=Et=>{let $=n[Et];return $&&"value"in $?$.value:void 0},r=e("slug"),o=e("category"),s=e("status"),i=e("title"),a=e("message"),u=e("suggestion"),c=e("exitCode"),l=e("detail"),f=e("cause"),C=e("instance"),mt=e("context"),L=e("stack");return typeof r!="string"||!me.has(o)||typeof s!="number"||!et(s)||typeof i!="string"||typeof a!="string"||u!==void 0&&typeof u!="string"||c!==void 0&&(typeof c!="number"||!et(c))||l!==void 0&&typeof l!="string"||C!==void 0&&typeof C!="string"||L!==void 0&&typeof L!="string"?null:{slug:r,category:o,status:s,title:i,message:a,suggestion:u,exitCode:c,detail:l,cause:f,instance:C,context:mt,stack:L}}catch{return null}}var sn=m({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),an=m({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),cn=m({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),un=m({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),ln=m({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),dn=m({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid argument",suggestion:"Check argument types and values",exitCode:2}),gn=m({slug:"already-exists",category:"GENERAL",status:409,title:"Target already exists",suggestion:"Choose a different name, or remove the existing target first",exitCode:1}),fn=m({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),pn=m({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),mn=m({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),it=m({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),En=m({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),yn=m({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),xn=m({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var ye=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function xe(){return ye.map(({source:t,flags:n,name:e})=>({pattern:new RegExp(t,n),name:e}))}function he(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function at(t,n={}){let{allowInlineScripts:e=!1,strict:r=!1,warn:o=!0}=n;for(let{pattern:s,name:i}of xe())if(!(e&&i==="inline script")&&(s.lastIndex=0,!!s.test(t)&&(o&&console.warn(`[Security] Suspicious ${i} detected in server HTML`),r||!he())))throw it.create({detail:`Potentially unsafe HTML: ${i} detected`});return t}var T=class{constructor(n,e){E(this,"prefix",n);E(this,"level",e)}log(n,e,r,...o){this.level>n||e?.(r,...o)}debug(n,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${n}`,...e)}info(n,...e){this.log(1,console.log,`[${this.prefix}] ${n}`,...e)}warn(n,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${n}`,...e)}error(n,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${n}`,...e)}};function Re(){if(typeof window>"u")return 2;let t=globalThis;return t.__VERYFRONT_DEV__||t.__RSC_DEV__?t.__VERYFRONT_DEBUG__||t.__RSC_DEBUG__?0:1:2}var U=Re(),y=new T("RSC",U),_n=new T("PREFETCH",U),bn=new T("HYDRATE",U),An=new T("VERYFRONT",U);var In=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var Se=5e3,_e=1e4,Dn=16*1024*1024,be=5e3;var Ae=100;var Te=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),Un=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Ln=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:Se,api:3e4,ssr:_e,hmr:3e4,sandbox:be}),cache:Object.freeze({jit:Object.freeze({maxSize:Ae,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:Te})});var d="/_veryfront",j={RSC:`${d}/rsc/`,FS:`${d}/fs/`,MODULES:`${d}/modules/`,PAGES:`${d}/pages/`,DATA:`${d}/data/`,LIB:`${d}/lib/`,CHUNKS:`${d}/chunks/`,CLIENT:`${d}/client/`},ut={HMR_RUNTIME:`${d}/hmr-runtime.js`,HMR:`${d}/hmr.js`,ERROR_OVERLAY:`${d}/error-overlay.js`,DEV_LOADER:`${d}/dev-loader.js`,CLIENT_LOG:`${d}/log`,CLIENT_JS:`${d}/client.js`,ROUTER_JS:`${d}/router.js`,PREFETCH_JS:`${d}/prefetch.js`,MANIFEST_JSON:`${d}/manifest.json`,APP_JS:`${d}/app.js`,RSC_CLIENT:`${d}/rsc/client.js`,RSC_MANIFEST:`${d}/rsc/manifest`,RSC_STREAM:`${d}/rsc/stream`,RSC_PAYLOAD:`${d}/rsc/payload`,RSC_RENDER:`${d}/rsc/render`,RSC_PAGE:`${d}/rsc/page`,RSC_MODULE:`${d}/rsc/module`,RSC_DOM:`${d}/rsc/dom.js`,LIB_CHAT_REACT:`${d}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${d}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${d}/lib/chat/primitives.js`};var Ce={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},wn=Ce.CACHE;var Pn={HMR_RUNTIME:ut.HMR_RUNTIME,ERROR_OVERLAY:ut.ERROR_OVERLAY};var Ie=j.RSC,Oe=j.FS;var lt="rsc-root",z="x-veryfront-dependency-pins";var zn=Array.prototype.at,Vn=Array.prototype.filter,Fn=Array.prototype.join,Gn=Array.prototype.map,Bn=Array.prototype.pop,vn=Array.prototype.push,Wn=Array.prototype.sort;var Kn=Set.prototype.has,Xn=String.prototype.charCodeAt,Jn=String.prototype.includes,qn=String.prototype.indexOf,Zn=String.prototype.lastIndexOf,Qn=String.prototype.slice,tr=String.prototype.split,er=String.prototype.startsWith;var _=Object.getOwnPropertyDescriptor,nr=_(URL.prototype,"origin").get,rr=_(URL.prototype,"pathname").get,or=_(URL.prototype,"protocol").get,sr=_(URL.prototype,"hostname").get,ir=_(URL.prototype,"port").get,ar=_(URL.prototype,"search").get,cr=_(URL.prototype,"hash").get;var Cr=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var De="veryfront-hydration-data";function dt(t){try{let n=[...t.querySelectorAll(`[id="${De}"]`)];if(n.length!==1)return null;let e=t.body;if(!e)return null;let r=n[0];return e.firstElementChild!==r&&r.parentElement!==e||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function gt(t,n){if(!n?.startsWith("on:"))return!1;try{let e=dt(t);if(!e)return!1;let r=JSON.parse(e.textContent||"{}");return r.dependencyPinningCacheKey=n,e.textContent=JSON.stringify(r),!0}catch(e){return y.debug("hydration dependency snapshot seed failed",e),!1}}function pt(t,n){let e=n==="root"?lt:`rsc-slot-${n}`,r=t.getElementById(e);if(r)return r;let o=t.createElement("div");return o.id=e,t.body.appendChild(o),o}function Ue(t,n){if(n.type!=="slot")return;let e=pt(t,n.id);e.innerHTML=at(String(n.html??""))}function ft(t,n){let e=n.split(`\n`),r=e.pop()??"";for(let o of e){let s=o.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch(u){y.debug("[client-dom] malformed NDJSON line",{line:s,error:u instanceof Error?u.message:String(u)});continue}if(!i||typeof i!="object")continue;let a=i;if(a.type==="slot"){Ue(t,a);try{we(t,a.id||"root")}catch(u){y.debug("[client-dom] hydration optional failed",u)}}}return r}function Le(t){return new Promise((n,e)=>{let r=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){r();return}t.addEventListener("abort",r,{once:!0})})}async function Wr(t,n=document,e){let r="body"in t?t:null,o=r?.body??t;if(!o)return;r&>(n,r.headers.get(z));let s=o.getReader(),i=new TextDecoder,a="",u=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:l,value:f}=e?await Promise.race([c,Le(e)]):await c;if(l){u=!0;break}a+=i.decode(f,{stream:!0}),a=ft(n,a)}a&&ft(n,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||y.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){u||y.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){y.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){y.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){y.debug("[client-dom] response.body.cancel failed",c)}}}function $e(t,n){let e=pt(t,n),r=[],o=s=>{let i=s;i.dataset?.clientRef&&r.push(i);for(let a of s.children)o(a)};return o(e),r}function we(t,n){let e=$e(t,n);for(let r of e){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",y.debug("[client-dom] marked for hydration",o))}}export{Wr as consumeNdjsonStream,pt as getContainer};\n'; diff --git a/tests/integration/cli/commands/generate/generate-conflict.test.ts b/tests/integration/cli/commands/generate/generate-conflict.test.ts new file mode 100644 index 0000000000..c224478fd2 --- /dev/null +++ b/tests/integration/cli/commands/generate/generate-conflict.test.ts @@ -0,0 +1,24 @@ +import "#veryfront/schemas/_test-setup.ts"; +import { assertEquals, assertRejects } from "#veryfront/testing/assert.ts"; +import { describe, it } from "#veryfront/testing/bdd.ts"; +import { VeryfrontError } from "veryfront/errors"; +import { generateCommand } from "../../../../../cli/commands/generate/index.ts"; + +describe("generateCommand conflicts", () => { + it("refuses to overwrite an existing file with an already-exists error", async () => { + const projectDir = await Deno.makeTempDir({ prefix: "veryfront-generate-conflict-" }); + + try { + await generateCommand(projectDir, "tool", "calculator"); + + const error = await assertRejects(() => generateCommand(projectDir, "tool", "calculator")); + + assertEquals(error instanceof VeryfrontError, true); + assertEquals((error as VeryfrontError).slug, "already-exists"); + assertEquals((error as VeryfrontError).exitCode, 1); + assertEquals((error as VeryfrontError).detail?.includes("tools/calculator.ts"), true); + } finally { + await Deno.remove(projectDir, { recursive: true }).catch(() => {}); + } + }); +}); diff --git a/tests/integration/cli/mcp/tools/catalog-tools-project-creation.test.ts b/tests/integration/cli/mcp/tools/catalog-tools-project-creation.test.ts new file mode 100644 index 0000000000..69cd641399 --- /dev/null +++ b/tests/integration/cli/mcp/tools/catalog-tools-project-creation.test.ts @@ -0,0 +1,297 @@ +import "#veryfront/schemas/_test-setup.ts"; +import { assertEquals } from "#veryfront/testing/assert.ts"; +import { afterEach, describe, it } from "#veryfront/testing/bdd.ts"; +import { dirname, join } from "veryfront/platform/path"; +import { vfCreateProject } from "../../../../../cli/mcp/tools/catalog-tools.ts"; + +async function createFakeNpm(): Promise { + const binDir = await Deno.makeTempDir(); + const logPath = join(binDir, "npm.log"); + const isWindows = Deno.build.os === "windows"; + const npmPath = join(binDir, isWindows ? "npm.cmd" : "npm"); + const script = isWindows + ? [ + "@echo off", + `>>"${logPath}" echo %CD% %*`, + `>package-lock.json echo {"lockfileVersion":3,"packages":{}}`, + "exit /b 0", + "", + ].join("\r\n") + : `#!/usr/bin/env sh +printf '%s\n' "$PWD $*" >> "${logPath}" +printf '%s\n' '{"lockfileVersion":3,"packages":{}}' > package-lock.json +exit 0 +`; + + await Deno.writeTextFile(npmPath, script); + if (!isWindows) await Deno.chmod(npmPath, 0o755); + return binDir; +} + +async function withFakeNpm(action: () => Promise): Promise { + const binDir = await createFakeNpm(); + const pathDelimiter = Deno.build.os === "windows" ? ";" : ":"; + const originalDenoPath = Deno.env.get("PATH"); + const nextPath = `${binDir}${pathDelimiter}${originalDenoPath ?? ""}`; + + try { + Deno.env.set("PATH", nextPath); + await action(); + } finally { + if (originalDenoPath === undefined) Deno.env.delete("PATH"); + else Deno.env.set("PATH", originalDenoPath); + await Deno.remove(binDir, { recursive: true }).catch(() => {}); + } +} + +describe("vfCreateProject filesystem conflicts", () => { + const createdDirs: string[] = []; + + afterEach(async () => { + for (const dir of createdDirs.splice(0)) { + await Deno.remove(dir, { recursive: true }).catch(() => {}); + } + }); + + it("refuses a directory holding files the scaffold would overwrite", async () => { + const parentDir = await Deno.makeTempDir(); + createdDirs.push(parentDir); + const projectDir = join(parentDir, "example-app"); + await Deno.mkdir(projectDir); + await Deno.writeTextFile(join(projectDir, "README.md"), "mine\n"); + + const result = await vfCreateProject.execute({ + name: "Example App", + template: "minimal", + directory: parentDir, + }); + + assertEquals(result.success, false); + assertEquals(result.projectDir, undefined); + assertEquals(result.message.includes("already contains README.md"), true); + assertEquals(await Deno.readTextFile(join(projectDir, "README.md")), "mine\n"); + }); + + it("scaffolds into an existing empty directory", async () => { + const parentDir = await Deno.makeTempDir(); + createdDirs.push(parentDir); + const projectDir = join(parentDir, "example-app"); + await Deno.mkdir(projectDir); + + await withFakeNpm(async () => { + const result = await vfCreateProject.execute({ + name: "Example App", + template: "minimal", + directory: parentDir, + }); + + assertEquals(result.success, true); + assertEquals(result.projectDir, projectDir); + }); + }); + + it("refuses a linked project directory instead of scaffolding outside the parent", async () => { + const parentDir = await Deno.makeTempDir(); + createdDirs.push(parentDir); + const outside = await Deno.makeTempDir(); + createdDirs.push(outside); + await Deno.symlink(outside, join(parentDir, "example-app")); + + const result = await vfCreateProject.execute({ + name: "Example App", + template: "minimal", + directory: parentDir, + }); + + assertEquals(result.success, false); + assertEquals(result.projectDir, undefined); + assertEquals(result.message.includes("is a link the scaffold cannot write through"), true); + const written: string[] = []; + for await (const entry of Deno.readDir(outside)) written.push(entry.name); + assertEquals(written, []); + }); + + it("refuses a linked .gitignore instead of merging through it", async () => { + const parentDir = await Deno.makeTempDir(); + createdDirs.push(parentDir); + const projectDir = join(parentDir, "example-app"); + const outside = join(parentDir, "outside-gitignore"); + await Deno.mkdir(projectDir); + await Deno.writeTextFile(outside, "keep-me\n"); + await Deno.symlink(outside, join(projectDir, ".gitignore")); + + const result = await vfCreateProject.execute({ + name: "Example App", + template: "minimal", + directory: parentDir, + }); + + assertEquals(result.success, false); + assertEquals(result.projectDir, undefined); + assertEquals( + result.message.includes("already contains .gitignore as a file or a link"), + true, + ); + assertEquals(await Deno.readTextFile(outside), "keep-me\n"); + assertEquals(await Deno.readTextFile(join(projectDir, ".gitignore")), "keep-me\n"); + }); + + it("refuses a .gitignore directory before partially scaffolding", async () => { + const parentDir = await Deno.makeTempDir(); + createdDirs.push(parentDir); + const projectDir = join(parentDir, "example-app"); + await Deno.mkdir(join(projectDir, ".gitignore"), { recursive: true }); + + const result = await vfCreateProject.execute({ + name: "Example App", + template: "minimal", + directory: parentDir, + }); + + assertEquals(result.success, false); + assertEquals(result.projectDir, undefined); + assertEquals( + result.message.includes("already contains .gitignore as a file or a link"), + true, + ); + assertEquals( + await Deno.readTextFile(join(projectDir, ".gitignore", "README.md")).catch( + () => "absent", + ), + "absent", + ); + assertEquals( + await Deno.readTextFile(join(projectDir, "README.md")).catch(() => "absent"), + "absent", + ); + }); + + it("refuses a non-file .gitignore before partially scaffolding", async () => { + if (Deno.build.os === "windows") return; + + const parentDir = await Deno.makeTempDir(); + createdDirs.push(parentDir); + const projectDir = join(parentDir, "example-app"); + await Deno.mkdir(projectDir, { recursive: true }); + const output = await new Deno.Command("mkfifo", { + args: [join(projectDir, ".gitignore")], + }).output(); + if (!output.success) return; + + const result = await vfCreateProject.execute({ + name: "Example App", + template: "minimal", + directory: parentDir, + }); + + assertEquals(result.success, false); + assertEquals(result.projectDir, undefined); + assertEquals( + result.message.includes("already contains .gitignore as a file or a link"), + true, + ); + assertEquals( + await Deno.readTextFile(join(projectDir, "README.md")).catch(() => "absent"), + "absent", + ); + }); + + it("refuses a package lock before dependency installation can replace it", async () => { + const parentDir = await Deno.makeTempDir(); + createdDirs.push(parentDir); + const projectDir = join(parentDir, "example-app"); + const lockfile = join(projectDir, "package-lock.json"); + await Deno.mkdir(projectDir); + await Deno.writeTextFile(lockfile, "keep-me\n"); + + const result = await vfCreateProject.execute({ + name: "Example App", + template: "minimal", + directory: parentDir, + }); + + assertEquals(result.success, false); + assertEquals(result.projectDir, undefined); + assertEquals(result.message.includes("already contains package-lock.json"), true); + assertEquals(await Deno.readTextFile(lockfile), "keep-me\n"); + assertEquals( + await Deno.readTextFile(join(projectDir, "README.md")).catch(() => "absent"), + "absent", + ); + }); + + it("refuses npm hidden lockfile before dependency installation can replace it", async () => { + const parentDir = await Deno.makeTempDir(); + createdDirs.push(parentDir); + const projectDir = join(parentDir, "example-app"); + const lockfile = join(projectDir, "node_modules", ".package-lock.json"); + await Deno.mkdir(join(projectDir, "node_modules"), { recursive: true }); + await Deno.writeTextFile(lockfile, "keep-me\n"); + + const result = await vfCreateProject.execute({ + name: "Example App", + template: "minimal", + directory: parentDir, + }); + + assertEquals(result.success, false); + assertEquals(result.projectDir, undefined); + assertEquals( + result.message.includes("already contains node_modules/.package-lock.json"), + true, + ); + assertEquals(await Deno.readTextFile(lockfile), "keep-me\n"); + assertEquals( + await Deno.readTextFile(join(projectDir, "README.md")).catch(() => "absent"), + "absent", + ); + }); + + it("refuses npm shrinkwrap before dependency installation can replace it", async () => { + const parentDir = await Deno.makeTempDir(); + createdDirs.push(parentDir); + const projectDir = join(parentDir, "example-app"); + const lockfile = join(projectDir, "npm-shrinkwrap.json"); + await Deno.mkdir(projectDir); + await Deno.writeTextFile(lockfile, "keep-me\n"); + + const result = await vfCreateProject.execute({ + name: "Example App", + template: "minimal", + directory: parentDir, + }); + + assertEquals(result.success, false); + assertEquals(result.projectDir, undefined); + assertEquals(result.message.includes("already contains npm-shrinkwrap.json"), true); + assertEquals(await Deno.readTextFile(lockfile), "keep-me\n"); + assertEquals( + await Deno.readTextFile(join(projectDir, "README.md")).catch(() => "absent"), + "absent", + ); + }); + + it("refuses existing node_modules before dependency installation can prune it", async () => { + const parentDir = await Deno.makeTempDir(); + createdDirs.push(parentDir); + const projectDir = join(parentDir, "example-app"); + const userFile = join(projectDir, "node_modules", "user-owned", "data.txt"); + await Deno.mkdir(dirname(userFile), { recursive: true }); + await Deno.writeTextFile(userFile, "keep-me\n"); + + const result = await vfCreateProject.execute({ + name: "Example App", + template: "minimal", + directory: parentDir, + }); + + assertEquals(result.success, false); + assertEquals(result.projectDir, undefined); + assertEquals(result.message.includes("already contains node_modules"), true); + assertEquals(await Deno.readTextFile(userFile), "keep-me\n"); + assertEquals( + await Deno.readTextFile(join(projectDir, "README.md")).catch(() => "absent"), + "absent", + ); + }); +});