create-warlock 5.30.0 → 5.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +43 -2
  2. package/README.md +89 -15
  3. package/esm/commands/create-new-app/index.mjs +20 -17
  4. package/esm/commands/create-new-app/index.mjs.map +1 -1
  5. package/esm/commands/create-new-app/types.d.mts +14 -8
  6. package/esm/commands/create-warlock-app/index.mjs +2 -1
  7. package/esm/commands/create-warlock-app/index.mjs.map +1 -1
  8. package/esm/features/agent-kit-targets.mjs +1 -1
  9. package/esm/features/agent-kit-targets.mjs.map +1 -1
  10. package/esm/features/database-drivers.mjs +1 -1
  11. package/esm/features/database-drivers.mjs.map +1 -1
  12. package/esm/features/features-map.mjs +12 -12
  13. package/esm/features/features-map.mjs.map +1 -1
  14. package/esm/flags/assert-package-manager.mjs +1 -1
  15. package/esm/flags/assert-package-manager.mjs.map +1 -1
  16. package/esm/flags/definitions.mjs +6 -1
  17. package/esm/flags/definitions.mjs.map +1 -1
  18. package/esm/flags/seed-from-flags.mjs +2 -1
  19. package/esm/flags/seed-from-flags.mjs.map +1 -1
  20. package/esm/helpers/app.mjs +63 -6
  21. package/esm/helpers/app.mjs.map +1 -1
  22. package/esm/helpers/exec.mjs +1 -1
  23. package/esm/helpers/exec.mjs.map +1 -1
  24. package/esm/helpers/insert-import.mjs +1 -1
  25. package/esm/helpers/insert-import.mjs.map +1 -1
  26. package/esm/helpers/package-manager.mjs.map +1 -1
  27. package/esm/helpers/paths.mjs.map +1 -1
  28. package/esm/helpers/pnpm-build-approvals.mjs.map +1 -1
  29. package/esm/helpers/project-builder-helpers.mjs.map +1 -1
  30. package/esm/helpers/warlock-versions.mjs +1 -1
  31. package/esm/helpers/warlock-versions.mjs.map +1 -1
  32. package/esm/index.d.mts +3 -5
  33. package/esm/index.mjs +6 -4
  34. package/esm/index.mjs.map +1 -1
  35. package/esm/prompts/ask-agent-targets.mjs.map +1 -1
  36. package/esm/prompts/ask-ai-features.mjs.map +1 -1
  37. package/esm/prompts/ask-features.mjs +53 -0
  38. package/esm/prompts/ask-features.mjs.map +1 -0
  39. package/esm/prompts/ask-lint-toolchain.mjs +21 -0
  40. package/esm/prompts/ask-lint-toolchain.mjs.map +1 -0
  41. package/esm/prompts/fit-to-terminal.mjs.map +1 -1
  42. package/esm/summary.mjs +12 -2
  43. package/esm/summary.mjs.map +1 -1
  44. package/esm/ui/banner.mjs +115 -47
  45. package/esm/ui/banner.mjs.map +1 -1
  46. package/esm/ui/report.mjs.map +1 -1
  47. package/esm/ui/spinners.mjs.map +1 -1
  48. package/esm/ui/wrap-text.mjs +55 -0
  49. package/esm/ui/wrap-text.mjs.map +1 -0
  50. package/llms-full.txt +110 -25
  51. package/package.json +3 -3
  52. package/skills/create-a-warlock-project/SKILL.md +110 -25
  53. package/templates/toolchains/oxc/.oxfmtrc.json +15 -0
  54. package/templates/toolchains/oxc/.oxlintrc.json +24 -0
  55. package/templates/toolchains/oxc/.vscode/extensions.json +3 -0
  56. package/templates/toolchains/oxc/.vscode/settings.json +35 -0
  57. package/templates/warlock/.prettierrc.json +3 -1
  58. package/templates/warlock/AGENTS.md +22 -2
  59. package/templates/warlock/README.md +18 -3
  60. package/templates/warlock/package.json +8 -6
  61. package/templates/warlock/skills/api-design/SKILL.md +2 -2
  62. package/templates/warlock/skills/code-standards/SKILL.md +18 -4
  63. package/templates/warlock/skills/security-baseline/SKILL.md +12 -1
  64. package/templates/warlock/skills/testing-strategy/SKILL.md +17 -0
  65. package/templates/warlock/src/app/auth/controllers/login.controller.ts +1 -1
  66. package/templates/warlock/src/app/auth/routes.ts +25 -5
  67. package/templates/warlock/src/app/auth/utils/types.ts +6 -1
  68. package/templates/warlock/src/app/contact/controllers/contact.controller.ts +1 -1
  69. package/templates/warlock/src/app/contact/routes.ts +1 -1
  70. package/templates/warlock/src/app/locale/controllers/locale.controller.ts +1 -1
  71. package/templates/warlock/src/app/locale/routes.ts +1 -1
  72. package/templates/warlock/src/app/posts/controllers/create-new-post.controller.ts +1 -1
  73. package/templates/warlock/src/app/posts/controllers/update-post.controller.ts +1 -1
  74. package/templates/warlock/src/app/posts/models/post/post.model.ts +1 -1
  75. package/templates/warlock/src/app/posts/schema/create-post.schema.ts +1 -1
  76. package/templates/warlock/src/app/posts/schema/update-post.schema.ts +1 -1
  77. package/templates/warlock/src/app/users/controllers/create-new-user.controller.ts +1 -1
  78. package/templates/warlock/src/app/users/controllers/list-users.controller.ts +10 -2
  79. package/templates/warlock/src/app/users/models/user/user.model.ts +1 -1
  80. package/templates/warlock/src/app/users/schema/create-user.schema.ts +1 -1
  81. package/templates/warlock/src/app/users/schema/list-users.schema.ts +15 -0
  82. package/templates/warlock/src/config/storage.ts +1 -1
  83. package/templates/warlock/src/config/tests.ts +8 -0
  84. package/templates/warlock/src/web/404.css +2 -6
  85. package/templates/warlock/src/web/assets.d.ts +2 -0
  86. package/templates/warlock/src/web/home/components/contact-form-controls.tsx +1 -1
  87. package/templates/warlock/src/web/home/components/hero-section.tsx +1 -0
  88. package/templates/warlock/tsconfig.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"agent-kit-targets.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/features/agent-kit-targets.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir, tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nconst requireFromHere = createRequire(import.meta.url);\n\n/**\n * `agent-kit` target selection for the scaffolder's `--agents` flag.\n *\n * The valid target list is NOT hardcoded here as a literal source of truth —\n * it is derived, in order of preference, from:\n *\n * 1. The `@mongez/agent-kit` package installed alongside `create-warlock`\n * itself, when its own metadata exposes a target list.\n * 2. A runtime fetch of https://mongez.js.org/agent-kit/llms.txt, cached\n * locally so repeated runs do not refetch.\n *\n * The one exception is `claude`, the default target: it MUST work fully\n * offline, so it is a tiny built-in fallback rather than something that ever\n * depends on a successful network call.\n */\n\n/** The only target guaranteed valid with no network access and no cache. */\nexport const BUILTIN_AGENT_KIT_TARGETS: readonly string[] = [\"claude\"];\n\n/** The default `--agents` value when the flag is not passed. */\nexport const DEFAULT_AGENT_KIT_TARGET = \"claude\";\n\nconst AGENT_KIT_LLMS_URL = \"https://mongez.js.org/agent-kit/llms.txt\";\n\n/**\n * How long the whole remote lookup may take.\n *\n * An unbounded `fetch` does not fail on a stalled network — it WAITS, and a\n * caller that only handles rejection never gets to run its fallback. The\n * scaffolder's wizard asks this question six answers in, so \"hangs forever\"\n * is strictly worse than \"gives up and offers the built-ins\".\n *\n * Each request carries an abort signal so the socket is actually torn down,\n * and {@link getValidAgentKitTargets} additionally races the whole operation,\n * because an injected `fetchImpl` is under no obligation to honour a signal.\n */\nexport const AGENT_KIT_FETCH_TIMEOUT_MS = 5_000;\n\n/** Local cache of the last successfully fetched/derived target list. */\nfunction cacheFilePath(): string {\n const base = process.env.XDG_CACHE_HOME ?? path.join(homedir(), \".cache\");\n return path.join(base, \"create-warlock\", \"agent-kit-targets.json\");\n}\n\nfunction readCache(): string[] | undefined {\n try {\n const raw = readFileSync(cacheFilePath(), \"utf8\");\n const parsed = JSON.parse(raw) as { targets?: unknown };\n if (Array.isArray(parsed.targets)) {\n return parsed.targets.filter(\n (value): value is string => typeof value === \"string\",\n );\n }\n return undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeCache(targets: string[]): void {\n try {\n const filePath = cacheFilePath();\n mkdirSync(path.dirname(filePath), { recursive: true });\n writeFileSync(\n filePath,\n JSON.stringify({ targets, fetchedAt: new Date().toISOString() }),\n \"utf8\",\n );\n } catch {\n // The cache is a best-effort convenience — a read-only filesystem must\n // never turn a successful fetch into a failed run.\n }\n}\n\n/**\n * Best-effort read of the installed `@mongez/agent-kit` package's own\n * declared target list, when it publishes one. Returns `undefined` (never\n * throws) when the package is not installed or exposes no such metadata —\n * the caller falls back to the network source.\n */\nfunction readInstalledPackageTargets(): string[] | undefined {\n for (const base of [process.cwd(), tmpdir()]) {\n try {\n const packageJsonPath = requireFromHere.resolve(\n \"@mongez/agent-kit/package.json\",\n { paths: [base] },\n );\n const pkg = JSON.parse(readFileSync(packageJsonPath, \"utf8\")) as {\n agentKit?: { targets?: unknown };\n };\n if (Array.isArray(pkg.agentKit?.targets)) {\n return pkg.agentKit.targets.filter(\n (value): value is string => typeof value === \"string\",\n );\n }\n } catch {\n // Not installed, or no metadata — try the next base / fall through.\n }\n }\n return undefined;\n}\n\n/**\n * Pull the agent-integrations doc's raw `--target <slug>` occurrences out of\n * `agent-kit`'s own llms.txt index (following the link it documents for\n * itself), so the concrete slug list is parsed from the source rather than\n * retyped here.\n */\nexport async function fetchRemoteAgentKitTargets(\n fetchImpl: typeof fetch = fetch,\n timeoutMs: number = AGENT_KIT_FETCH_TIMEOUT_MS,\n): Promise<string[]> {\n const indexResponse = await fetchImpl(AGENT_KIT_LLMS_URL, {\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!indexResponse.ok) {\n throw new Error(\n `agent-kit llms.txt request failed with HTTP ${indexResponse.status}`,\n );\n }\n const index = await indexResponse.text();\n\n const rawLinkMatch = index.match(\n /\\[Agent integrations\\]\\((https:\\/\\/[^)]+agent-integrations[^)]*)\\)/,\n );\n\n const docUrl = rawLinkMatch?.[1]\n ?.replace(\"github.com\", \"raw.githubusercontent.com\")\n .replace(\"/blob/\", \"/\");\n\n const targets = new Set<string>();\n\n if (docUrl) {\n const docResponse = await fetchImpl(docUrl, {\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (docResponse.ok) {\n const doc = await docResponse.text();\n for (const match of doc.matchAll(/--target[= ]([a-z][a-z0-9-]*)/gi)) {\n const target = match[1];\n\n if (target) targets.add(target.toLowerCase());\n }\n }\n }\n\n if (targets.size === 0) {\n throw new Error(\n \"Could not parse any agent-kit target names out of the fetched docs\",\n );\n }\n\n return [...targets];\n}\n\n/**\n * Resolve the full list of valid `agent-kit` targets, trying the installed\n * package first, then the cache-backed network fetch.\n *\n * Throws when neither source succeeds — the caller only reaches this for an\n * explicitly requested NON-default target, where a silent fallback to a\n * stale or guessed list would be worse than a clear failure.\n */\nexport async function getValidAgentKitTargets(\n fetchImpl: typeof fetch = fetch,\n timeoutMs: number = AGENT_KIT_FETCH_TIMEOUT_MS,\n): Promise<string[]> {\n const installed = readInstalledPackageTargets();\n if (installed && installed.length > 0) {\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...installed]);\n }\n\n try {\n const fetched = await withTimeout(\n fetchRemoteAgentKitTargets(fetchImpl, timeoutMs),\n timeoutMs,\n );\n writeCache(fetched);\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...fetched]);\n } catch (fetchError) {\n const cached = readCache();\n if (cached && cached.length > 0) {\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...cached]);\n }\n throw new Error(\n `Could not determine the valid agent-kit targets (fetch failed and no cache exists): ${\n (fetchError as Error).message\n }`,\n );\n }\n}\n\n/**\n * Reject if `work` has not settled within `timeoutMs`.\n *\n * The abort signals on each request cover the real network; this covers the\n * case they cannot — a `fetchImpl` that ignores its signal, or a promise that\n * simply never settles. Without it \"bounded\" would be a property of the\n * happy path only.\n */\nexport async function withTimeout<T>(\n work: Promise<T>,\n timeoutMs: number,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n try {\n return await Promise.race([\n work,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new Error(\n `agent-kit target lookup timed out after ${timeoutMs}ms`,\n ),\n ),\n timeoutMs,\n );\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nfunction dedupe(values: string[]): string[] {\n return [...new Set(values)];\n}\n\n/**\n * Resolve and validate the `--agents` selection.\n *\n * An unset or empty selection resolves to `[DEFAULT_AGENT_KIT_TARGET]`\n * without touching the disk or the network. A selection made entirely of\n * built-in targets (today, just `claude`) is likewise resolved offline. Only\n * an explicit NON-default target triggers {@link getValidAgentKitTargets},\n * and an invalid one throws, naming every valid target it found.\n */\nexport async function resolveAgentTargets(\n requested: string[] | undefined,\n fetchImpl: typeof fetch = fetch,\n): Promise<string[]> {\n const targets =\n requested && requested.length > 0 ? requested : [DEFAULT_AGENT_KIT_TARGET];\n\n const needsValidation = targets.some(\n target => !BUILTIN_AGENT_KIT_TARGETS.includes(target),\n );\n\n if (!needsValidation) {\n return targets;\n }\n\n const valid = await getValidAgentKitTargets(fetchImpl);\n const invalid = targets.filter(target => !valid.includes(target));\n\n if (invalid.length > 0) {\n throw new Error(\n `Unknown agent-kit target(s): ${invalid.join(\", \")} — valid targets: ${valid\n .slice()\n .sort()\n .join(\", \")}`,\n );\n }\n\n return targets;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"agent-kit-targets.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/features/agent-kit-targets.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir, tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nconst requireFromHere = createRequire(import.meta.url);\n\n/**\n * `agent-kit` target selection for the scaffolder's `--agents` flag.\n *\n * The valid target list is NOT hardcoded here as a literal source of truth —\n * it is derived, in order of preference, from:\n *\n * 1. The `@mongez/agent-kit` package installed alongside `create-warlock`\n * itself, when its own metadata exposes a target list.\n * 2. A runtime fetch of https://mongez.js.org/agent-kit/llms.txt, cached\n * locally so repeated runs do not refetch.\n *\n * The one exception is `claude`, the default target: it MUST work fully\n * offline, so it is a tiny built-in fallback rather than something that ever\n * depends on a successful network call.\n */\n\n/** The only target guaranteed valid with no network access and no cache. */\nexport const BUILTIN_AGENT_KIT_TARGETS: readonly string[] = [\"claude\"];\n\n/** The default `--agents` value when the flag is not passed. */\nexport const DEFAULT_AGENT_KIT_TARGET = \"claude\";\n\nconst AGENT_KIT_LLMS_URL = \"https://mongez.js.org/agent-kit/llms.txt\";\n\n/**\n * How long the whole remote lookup may take.\n *\n * An unbounded `fetch` does not fail on a stalled network — it WAITS, and a\n * caller that only handles rejection never gets to run its fallback. The\n * scaffolder's wizard asks this question six answers in, so \"hangs forever\"\n * is strictly worse than \"gives up and offers the built-ins\".\n *\n * Each request carries an abort signal so the socket is actually torn down,\n * and {@link getValidAgentKitTargets} additionally races the whole operation,\n * because an injected `fetchImpl` is under no obligation to honour a signal.\n */\nexport const AGENT_KIT_FETCH_TIMEOUT_MS = 5_000;\n\n/** Local cache of the last successfully fetched/derived target list. */\nfunction cacheFilePath(): string {\n const base = process.env.XDG_CACHE_HOME ?? path.join(homedir(), \".cache\");\n return path.join(base, \"create-warlock\", \"agent-kit-targets.json\");\n}\n\nfunction readCache(): string[] | undefined {\n try {\n const raw = readFileSync(cacheFilePath(), \"utf8\");\n const parsed = JSON.parse(raw) as { targets?: unknown };\n if (Array.isArray(parsed.targets)) {\n return parsed.targets.filter((value): value is string => typeof value === \"string\");\n }\n return undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeCache(targets: string[]): void {\n try {\n const filePath = cacheFilePath();\n mkdirSync(path.dirname(filePath), { recursive: true });\n writeFileSync(\n filePath,\n JSON.stringify({ targets, fetchedAt: new Date().toISOString() }),\n \"utf8\",\n );\n } catch {\n // The cache is a best-effort convenience — a read-only filesystem must\n // never turn a successful fetch into a failed run.\n }\n}\n\n/**\n * Best-effort read of the installed `@mongez/agent-kit` package's own\n * declared target list, when it publishes one. Returns `undefined` (never\n * throws) when the package is not installed or exposes no such metadata —\n * the caller falls back to the network source.\n */\nfunction readInstalledPackageTargets(): string[] | undefined {\n for (const base of [process.cwd(), tmpdir()]) {\n try {\n const packageJsonPath = requireFromHere.resolve(\"@mongez/agent-kit/package.json\", {\n paths: [base],\n });\n const pkg = JSON.parse(readFileSync(packageJsonPath, \"utf8\")) as {\n agentKit?: { targets?: unknown };\n };\n if (Array.isArray(pkg.agentKit?.targets)) {\n return pkg.agentKit.targets.filter((value): value is string => typeof value === \"string\");\n }\n } catch {\n // Not installed, or no metadata — try the next base / fall through.\n }\n }\n return undefined;\n}\n\n/**\n * Pull the agent-integrations doc's raw `--target <slug>` occurrences out of\n * `agent-kit`'s own llms.txt index (following the link it documents for\n * itself), so the concrete slug list is parsed from the source rather than\n * retyped here.\n */\nexport async function fetchRemoteAgentKitTargets(\n fetchImpl: typeof fetch = fetch,\n timeoutMs: number = AGENT_KIT_FETCH_TIMEOUT_MS,\n): Promise<string[]> {\n const indexResponse = await fetchImpl(AGENT_KIT_LLMS_URL, {\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!indexResponse.ok) {\n throw new Error(`agent-kit llms.txt request failed with HTTP ${indexResponse.status}`);\n }\n const index = await indexResponse.text();\n\n const rawLinkMatch = index.match(\n /\\[Agent integrations\\]\\((https:\\/\\/[^)]+agent-integrations[^)]*)\\)/,\n );\n\n const docUrl = rawLinkMatch?.[1]\n ?.replace(\"github.com\", \"raw.githubusercontent.com\")\n .replace(\"/blob/\", \"/\");\n\n const targets = new Set<string>();\n\n if (docUrl) {\n const docResponse = await fetchImpl(docUrl, {\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (docResponse.ok) {\n const doc = await docResponse.text();\n for (const match of doc.matchAll(/--target[= ]([a-z][a-z0-9-]*)/gi)) {\n const target = match[1];\n\n if (target) targets.add(target.toLowerCase());\n }\n }\n }\n\n if (targets.size === 0) {\n throw new Error(\"Could not parse any agent-kit target names out of the fetched docs\");\n }\n\n return [...targets];\n}\n\n/**\n * Resolve the full list of valid `agent-kit` targets, trying the installed\n * package first, then the cache-backed network fetch.\n *\n * Throws when neither source succeeds — the caller only reaches this for an\n * explicitly requested NON-default target, where a silent fallback to a\n * stale or guessed list would be worse than a clear failure.\n */\nexport async function getValidAgentKitTargets(\n fetchImpl: typeof fetch = fetch,\n timeoutMs: number = AGENT_KIT_FETCH_TIMEOUT_MS,\n): Promise<string[]> {\n const installed = readInstalledPackageTargets();\n if (installed && installed.length > 0) {\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...installed]);\n }\n\n try {\n const fetched = await withTimeout(fetchRemoteAgentKitTargets(fetchImpl, timeoutMs), timeoutMs);\n writeCache(fetched);\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...fetched]);\n } catch (fetchError) {\n const cached = readCache();\n if (cached && cached.length > 0) {\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...cached]);\n }\n throw new Error(\n `Could not determine the valid agent-kit targets (fetch failed and no cache exists): ${\n (fetchError as Error).message\n }`,\n );\n }\n}\n\n/**\n * Reject if `work` has not settled within `timeoutMs`.\n *\n * The abort signals on each request cover the real network; this covers the\n * case they cannot — a `fetchImpl` that ignores its signal, or a promise that\n * simply never settles. Without it \"bounded\" would be a property of the\n * happy path only.\n */\nexport async function withTimeout<T>(work: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n try {\n return await Promise.race([\n work,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`agent-kit target lookup timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nfunction dedupe(values: string[]): string[] {\n return [...new Set(values)];\n}\n\n/**\n * Resolve and validate the `--agents` selection.\n *\n * An unset or empty selection resolves to `[DEFAULT_AGENT_KIT_TARGET]`\n * without touching the disk or the network. A selection made entirely of\n * built-in targets (today, just `claude`) is likewise resolved offline. Only\n * an explicit NON-default target triggers {@link getValidAgentKitTargets},\n * and an invalid one throws, naming every valid target it found.\n */\nexport async function resolveAgentTargets(\n requested: string[] | undefined,\n fetchImpl: typeof fetch = fetch,\n): Promise<string[]> {\n const targets = requested && requested.length > 0 ? requested : [DEFAULT_AGENT_KIT_TARGET];\n\n const needsValidation = targets.some((target) => !BUILTIN_AGENT_KIT_TARGETS.includes(target));\n\n if (!needsValidation) {\n return targets;\n }\n\n const valid = await getValidAgentKitTargets(fetchImpl);\n const invalid = targets.filter((target) => !valid.includes(target));\n\n if (invalid.length > 0) {\n throw new Error(\n `Unknown agent-kit target(s): ${invalid.join(\", \")} — valid targets: ${valid\n .slice()\n .sort()\n .join(\", \")}`,\n );\n }\n\n return targets;\n}\n"],"mappings":"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"}
@@ -74,5 +74,5 @@ function getDatabaseLabel(value) {
74
74
  }
75
75
 
76
76
  //#endregion
77
- export { NO_DATABASE, getDatabaseDriver, getDatabaseDriverOptions, getDatabaseLabel, isNoDatabase };
77
+ export { NO_DATABASE, databaseDrivers, getDatabaseDriver, getDatabaseDriverOptions, getDatabaseLabel, isNoDatabase };
78
78
  //# sourceMappingURL=database-drivers.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"database-drivers.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/features/database-drivers.ts"],"sourcesContent":["/**\n * Database driver options and configuration\n */\nexport type DatabaseDriver = {\n value: string;\n label: string;\n package: string;\n packageVersion: string;\n defaultPort: number;\n disabled?: boolean;\n hint?: string;\n};\n\n/**\n * Sentinel `--db` value / select option that means \"no database at all\".\n *\n * Picking it wires no driver, pulls no driver package, and deletes the\n * template's `src/config/database.ts` — the framework's database connector is\n * config-gated on that file, so with it gone the app boots with no database.\n */\nexport const NO_DATABASE = \"none\";\n\nexport const databaseDrivers: DatabaseDriver[] = [\n {\n value: \"mongodb\",\n label: \"MongoDB\",\n package: \"mongodb\",\n packageVersion: \"^7.0.0\",\n defaultPort: 27017,\n },\n {\n value: \"postgres\",\n label: \"PostgreSQL\",\n package: \"pg\",\n packageVersion: \"^8.11.0\",\n defaultPort: 5432,\n },\n {\n value: \"mysql\",\n label: \"MySQL (Coming Soon)\",\n package: \"mysql2\",\n packageVersion: \"^3.5.0\",\n defaultPort: 3306,\n disabled: true,\n hint: \"MySQL support coming in a future release\",\n },\n];\n\n/**\n * Get database driver options for the select prompt.\n *\n * The real drivers come first, followed by a \"None\" opt-out so the user can\n * scaffold an app with no database.\n */\nexport function getDatabaseDriverOptions() {\n return [\n ...databaseDrivers.map(driver => ({\n value: driver.value,\n label: driver.label,\n hint: driver.hint,\n disabled: driver.disabled,\n })),\n {\n value: NO_DATABASE,\n label: \"None\",\n hint: \"No database — skips the driver and removes src/config/database.ts\",\n disabled: undefined,\n },\n ];\n}\n\n/**\n * Get database driver config by value\n */\nexport function getDatabaseDriver(value: string): DatabaseDriver | undefined {\n return databaseDrivers.find(driver => driver.value === value);\n}\n\n/**\n * Whether a `--db` value / selection means \"no database\".\n */\nexport function isNoDatabase(value: string | undefined): boolean {\n return value === NO_DATABASE;\n}\n\n/**\n * Human-readable database label for the success screen: `\"None\"` when no\n * database was chosen, otherwise the driver's own label (falling back to the\n * raw value for an unrecognized driver).\n */\nexport function getDatabaseLabel(value: string): string {\n if (isNoDatabase(value)) return \"None\";\n\n return getDatabaseDriver(value)?.label ?? value;\n}\n\n/**\n * Get database driver dependency\n */\nexport function getDatabaseDependency(\n driverValue: string,\n): Record<string, string> {\n const driver = getDatabaseDriver(driverValue);\n if (!driver) return {};\n\n return {\n [driver.package]: driver.packageVersion,\n };\n}\n"],"mappings":";;;;;;;;AAoBA,MAAa,cAAc;AAE3B,MAAa,kBAAoC;CAC/C;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;CACf;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;CACf;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;EACb,UAAU;EACV,MAAM;CACR;AACF;;;;;;;AAQA,SAAgB,2BAA2B;CACzC,OAAO,CACL,GAAG,gBAAgB,KAAI,YAAW;EAChC,OAAO,OAAO;EACd,OAAO,OAAO;EACd,MAAM,OAAO;EACb,UAAU,OAAO;CACnB,EAAE,GACF;EACE,OAAO;EACP,OAAO;EACP,MAAM;EACN,UAAU;CACZ,CACF;AACF;;;;AAKA,SAAgB,kBAAkB,OAA2C;CAC3E,OAAO,gBAAgB,MAAK,WAAU,OAAO,UAAU,KAAK;AAC9D;;;;AAKA,SAAgB,aAAa,OAAoC;CAC/D,OAAO,UAAU;AACnB;;;;;;AAOA,SAAgB,iBAAiB,OAAuB;CACtD,IAAI,aAAa,KAAK,GAAG,OAAO;CAEhC,OAAO,kBAAkB,KAAK,CAAC,EAAE,SAAS;AAC5C"}
1
+ {"version":3,"file":"database-drivers.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/features/database-drivers.ts"],"sourcesContent":["/**\n * Database driver options and configuration\n */\nexport type DatabaseDriver = {\n value: string;\n label: string;\n package: string;\n packageVersion: string;\n defaultPort: number;\n disabled?: boolean;\n hint?: string;\n};\n\n/**\n * Sentinel `--db` value / select option that means \"no database at all\".\n *\n * Picking it wires no driver, pulls no driver package, and deletes the\n * template's `src/config/database.ts` — the framework's database connector is\n * config-gated on that file, so with it gone the app boots with no database.\n */\nexport const NO_DATABASE = \"none\";\n\nexport const databaseDrivers: DatabaseDriver[] = [\n {\n value: \"mongodb\",\n label: \"MongoDB\",\n package: \"mongodb\",\n packageVersion: \"^7.0.0\",\n defaultPort: 27017,\n },\n {\n value: \"postgres\",\n label: \"PostgreSQL\",\n package: \"pg\",\n packageVersion: \"^8.11.0\",\n defaultPort: 5432,\n },\n {\n value: \"mysql\",\n label: \"MySQL (Coming Soon)\",\n package: \"mysql2\",\n packageVersion: \"^3.5.0\",\n defaultPort: 3306,\n disabled: true,\n hint: \"MySQL support coming in a future release\",\n },\n];\n\n/**\n * Get database driver options for the select prompt.\n *\n * The real drivers come first, followed by a \"None\" opt-out so the user can\n * scaffold an app with no database.\n */\nexport function getDatabaseDriverOptions() {\n return [\n ...databaseDrivers.map((driver) => ({\n value: driver.value,\n label: driver.label,\n hint: driver.hint,\n disabled: driver.disabled,\n })),\n {\n value: NO_DATABASE,\n label: \"None\",\n hint: \"No database — skips the driver and removes src/config/database.ts\",\n disabled: undefined,\n },\n ];\n}\n\n/**\n * Get database driver config by value\n */\nexport function getDatabaseDriver(value: string): DatabaseDriver | undefined {\n return databaseDrivers.find((driver) => driver.value === value);\n}\n\n/**\n * Whether a `--db` value / selection means \"no database\".\n */\nexport function isNoDatabase(value: string | undefined): boolean {\n return value === NO_DATABASE;\n}\n\n/**\n * Human-readable database label for the success screen: `\"None\"` when no\n * database was chosen, otherwise the driver's own label (falling back to the\n * raw value for an unrecognized driver).\n */\nexport function getDatabaseLabel(value: string): string {\n if (isNoDatabase(value)) return \"None\";\n\n return getDatabaseDriver(value)?.label ?? value;\n}\n\n/**\n * Get database driver dependency\n */\nexport function getDatabaseDependency(driverValue: string): Record<string, string> {\n const driver = getDatabaseDriver(driverValue);\n if (!driver) return {};\n\n return {\n [driver.package]: driver.packageVersion,\n };\n}\n"],"mappings":";;;;;;;;AAoBA,MAAa,cAAc;AAE3B,MAAa,kBAAoC;CAC/C;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;CACf;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;CACf;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;EACb,UAAU;EACV,MAAM;CACR;AACF;;;;;;;AAQA,SAAgB,2BAA2B;CACzC,OAAO,CACL,GAAG,gBAAgB,KAAK,YAAY;EAClC,OAAO,OAAO;EACd,OAAO,OAAO;EACd,MAAM,OAAO;EACb,UAAU,OAAO;CACnB,EAAE,GACF;EACE,OAAO;EACP,OAAO;EACP,MAAM;EACN,UAAU;CACZ,CACF;AACF;;;;AAKA,SAAgB,kBAAkB,OAA2C;CAC3E,OAAO,gBAAgB,MAAM,WAAW,OAAO,UAAU,KAAK;AAChE;;;;AAKA,SAAgB,aAAa,OAAoC;CAC/D,OAAO,UAAU;AACnB;;;;;;AAOA,SAAgB,iBAAiB,OAAuB;CACtD,IAAI,aAAa,KAAK,GAAG,OAAO;CAEhC,OAAO,kBAAkB,KAAK,CAAC,EAAE,SAAS;AAC5C"}
@@ -198,17 +198,6 @@ const aiPackages = [
198
198
  }
199
199
  ];
200
200
  /**
201
- * Feature options for the multiselect prompt, ordered by group with the group
202
- * surfaced in the hint (keeps a flat list scannable without group widgets).
203
- */
204
- function getFeatureOptions() {
205
- return features.map((feature) => ({
206
- value: feature.key,
207
- label: feature.label,
208
- hint: `${feature.group} — ${feature.hint}`
209
- }));
210
- }
211
- /**
212
201
  * Keys pre-checked in the feature multiselect.
213
202
  */
214
203
  function getDefaultFeatureKeys() {
@@ -247,7 +236,18 @@ function getAllFeatureKeys() {
247
236
  ...aiPackages.map((pkg) => pkg.key)
248
237
  ];
249
238
  }
239
+ /** Feature sections in manifest order, with short lists for the grouped picker. */
240
+ function getFeatureGroups() {
241
+ return [...new Set(features.map((feature) => feature.group))].map((label) => ({
242
+ label,
243
+ options: features.filter((feature) => feature.group === label).map((feature) => ({
244
+ value: feature.key,
245
+ label: feature.label,
246
+ hint: feature.hint
247
+ }))
248
+ }));
249
+ }
250
250
 
251
251
  //#endregion
252
- export { getAiPackageOptions, getAiProviderOptions, getAllFeatureKeys, getDefaultFeatureKeys, getFeatureOptions };
252
+ export { aiPackages, aiProviders, features, getAiPackageOptions, getAiProviderOptions, getAllFeatureKeys, getDefaultFeatureKeys, getFeatureGroups };
253
253
  //# sourceMappingURL=features-map.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"features-map.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/features/features-map.ts"],"sourcesContent":["/**\n * Presentation manifest for the scaffolder's feature prompts.\n *\n * This file is **display metadata only** — keys, labels, hints, grouping, and\n * default selections. It deliberately holds NO dependency names or versions:\n * the single source of truth for what each feature installs is the `add`\n * command's feature map in `@warlock.js/core`\n * (`src/generations/add-command.action.ts`). The scaffolder collects the\n * selections here and delegates the actual install to `warlock add`, so the\n * two never drift on versions again.\n *\n * Every `key` below MUST exist in core’s `allowedFeatures`. That guard now\n * EXISTS: `builder/scripts/check-feature-parity.ts`, run by every publishing\n * script in `builder/package.json`, so a release aborts before `pkgist` starts.\n * It could not live in this repo: this package deliberately does not depend on\n * `@warlock.js/core`, and our own CI checks out only this repo, so core’s keys\n * are not present here to compare against. `builder` sits above both and\n * imports each by relative path, adding no dependency edge in either direction.\n *\n * A subset assertion ALONE is not enough and has already let a\n * bug ship: `--features=web,tailwind` was rejected with \"Unknown feature(s):\n * tailwind\" because `tailwind` and `shadcn` existed in core and were missing\n * from THIS file — a direction a scaffolder ⊆ core check cannot see. The guard\n * has to run both ways:\n *\n * 1. scaffolder ⊆ core — catches a typo or a key core renamed.\n * 2. core ⊆ scaffolder + an explicit omission list — catches a feature core\n * gained that never became scaffoldable.\n *\n * The two lists are not equal and should not be forced to be. As of this\n * writing core holds 28 keys and this file offers 24; the other four are\n * deliberate and are the entire allow-list direction (2) needs:\n * `mongodb`, `postgres`, `mysql` (the database driver has its own select) and\n * `ai` (pulled automatically by every `ai-*` key's `requires`). Anything beyond\n * those four is a gap, not a decision, and should fail the build.\n */\n\nexport type FeatureGroup =\n | \"Auth & Access\"\n | \"Rendering & Mail\"\n | \"Media\"\n | \"Storage & Cache\"\n | \"Jobs & Messaging\"\n | \"Realtime\"\n | \"API Docs\"\n | \"Tooling\";\n\nexport type FeatureOption = {\n /** Must match a key in core's `add` feature map. */\n key: string;\n label: string;\n hint: string;\n group: FeatureGroup;\n defaultSelected?: boolean;\n};\n\n/**\n * Optional features offered in the general multiselect step. The database\n * driver (its own select) and AI providers + capability packages (their own\n * step) are intentionally NOT here — they have dedicated prompts.\n */\nexport const features: FeatureOption[] = [\n // Auth & Access\n {\n key: \"access\",\n label: \"Access (authorization)\",\n hint: \"RBAC permission checks, ABAC policies, and roles — pairs with auth\",\n group: \"Auth & Access\",\n },\n {\n key: \"auth-google\",\n label: \"Google sign-in\",\n hint: \"Google OAuth sign-in for @warlock.js/auth\",\n group: \"Auth & Access\",\n },\n {\n key: \"auth-passkeys\",\n label: \"Passkey sign-in\",\n hint: \"WebAuthn passkey login for @warlock.js/auth\",\n group: \"Auth & Access\",\n },\n\n // Rendering & Mail\n {\n key: \"web\",\n label: \"Web (SSR React pages)\",\n hint: \"Server-rendered React pages on the same server as your API — scaffolds src/web and registers the connector\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"tailwind\",\n label: \"Tailwind CSS\",\n hint: \"Tailwind v4 for the SSR page layer — creates src/web/app.css (the CSS-first config) and wires it through Vite (pulls web)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"shadcn\",\n label: \"shadcn/ui prerequisites\",\n hint: \"components.json, cn(), design tokens, and the web/* tsconfig path so `npx shadcn add <component>` works first time (pulls tailwind)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"sitemap\",\n label: \"Sitemap\",\n hint: \"Runtime sitemap.xml generation from the page registry — requires app.publicUrl or PUBLIC_APP_URL (pulls web)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"devtools\",\n label: \"Devtools (/__warlock)\",\n hint: \"Development-only dashboard: request timeline, queries + EXPLAIN, N+1 warnings, dev mailbox, logs, cache, routes — installed as a dev dependency, never runs in production\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"react\",\n label: \"React (rendering & mails)\",\n hint: \"React + ReactDOM for non-interactive rendering and HTML/email generation\",\n group: \"Rendering & Mail\",\n defaultSelected: true,\n },\n {\n key: \"react-email\",\n label: \"React Email\",\n hint: \"Build email templates with React + Tailwind (pulls react + mail)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"mail\",\n label: \"Mail (Nodemailer)\",\n hint: \"Send emails via SMTP\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"ses\",\n label: \"Amazon SES\",\n hint: \"Send emails via the AWS SES API\",\n group: \"Rendering & Mail\",\n },\n\n // Media\n {\n key: \"image\",\n label: \"Image processing (Sharp)\",\n hint: \"Resize, convert, and optimize images\",\n group: \"Media\",\n },\n\n // Storage & Cache\n {\n key: \"s3\",\n label: \"S3 storage\",\n hint: \"AWS S3 for cloud file storage\",\n group: \"Storage & Cache\",\n },\n {\n key: \"redis\",\n label: \"Redis cache\",\n hint: \"Redis driver for the cache layer\",\n group: \"Storage & Cache\",\n },\n\n // Jobs & Messaging\n {\n key: \"scheduler\",\n label: \"Scheduler\",\n hint: \"Background tasks and cron jobs\",\n group: \"Jobs & Messaging\",\n },\n {\n key: \"queue\",\n label: \"Queue (BullMQ)\",\n hint: \"Durable Redis-backed background jobs with retries and workers\",\n group: \"Jobs & Messaging\",\n },\n {\n key: \"bull-board\",\n label: \"Bull Board (queue dashboard)\",\n hint: \"Web dashboard for inspecting queues and jobs — enabled outside production only, and refuses to mount unguarded (pulls queue)\",\n group: \"Jobs & Messaging\",\n },\n {\n key: \"herald\",\n label: \"Herald (RabbitMQ)\",\n hint: \"Message broker for event-driven architecture\",\n group: \"Jobs & Messaging\",\n },\n {\n key: \"notifications\",\n label: \"Notifications\",\n hint: \"Multi-channel notifications — mail + in-app database, preferences, idempotency\",\n group: \"Jobs & Messaging\",\n },\n\n // Realtime\n {\n key: \"socket\",\n label: \"Socket.IO\",\n hint: \"Realtime websocket server\",\n group: \"Realtime\",\n },\n\n // API Docs (swagger / postman / openapi) — not shipped yet; they will arrive\n // together in the unified @warlock.js/api-docs package. Re-add the selection\n // once that package exists. (Removed so the wizard can't offer unbuilt features.)\n\n // Tooling\n {\n key: \"test\",\n label: \"Testing (Vitest)\",\n hint: \"Vitest + coverage + per-worker DB/cache test setup\",\n group: \"Tooling\",\n },\n];\n\nexport type AiProviderOption = {\n /** Must match a provider/package key in core's `add` feature map. */\n key: string;\n label: string;\n hint: string;\n};\n\n/**\n * AI providers offered in the dedicated AI step. Selecting any of these pulls\n * the core `@warlock.js/ai` package automatically via the provider's `requires`\n * in core's feature map — the scaffolder never lists `ai` as a standalone pick.\n *\n * Keys are `ai-`prefixed to mirror core's renamed provider feature keys\n * (`ai-openai` → `@warlock.js/ai-openai`, etc.).\n */\nexport const aiProviders: AiProviderOption[] = [\n { key: \"ai-openai\", label: \"OpenAI\", hint: \"GPT models via the OpenAI API\" },\n {\n key: \"ai-google\",\n label: \"Google (Gemini)\",\n hint: \"Gemini models via Google AI\",\n },\n {\n key: \"ai-anthropic\",\n label: \"Anthropic (Claude)\",\n hint: \"Claude models via the Anthropic API\",\n },\n {\n key: \"ai-bedrock\",\n label: \"AWS Bedrock\",\n hint: \"Foundation models via Amazon Bedrock\",\n },\n { key: \"ai-ollama\", label: \"Ollama\", hint: \"Local models via Ollama\" },\n];\n\n/**\n * AI capability packages offered alongside the providers in the dedicated AI\n * step. These are the satellite packages that sit on top of the core\n * `@warlock.js/ai` toolkit (each pulls the core `ai` package via its `requires`\n * in core's feature map, same as the providers).\n */\nexport const aiPackages: AiProviderOption[] = [\n {\n key: \"ai-tools\",\n label: \"AI Tools\",\n hint: \"Ready-made agent tools (web search, fetch, HTTP, calculator, date-time) + MCP client/server\",\n },\n {\n key: \"ai-panoptic\",\n label: \"AI Observability (Panoptic)\",\n hint: \"Tracing + exporters + a zero-setup local dashboard\",\n },\n {\n key: \"ai-workspace\",\n label: \"AI Workspace\",\n hint: \"Policy-jailed filesystem + shell for coding agents\",\n },\n];\n\n/**\n * Feature options for the multiselect prompt, ordered by group with the group\n * surfaced in the hint (keeps a flat list scannable without group widgets).\n */\nexport function getFeatureOptions() {\n return features.map(feature => ({\n value: feature.key,\n label: feature.label,\n hint: `${feature.group} — ${feature.hint}`,\n }));\n}\n\n/**\n * Keys pre-checked in the feature multiselect.\n */\nexport function getDefaultFeatureKeys(): string[] {\n return features\n .filter(feature => feature.defaultSelected)\n .map(feature => feature.key);\n}\n\n/**\n * AI provider options for the dedicated AI multiselect step.\n */\nexport function getAiProviderOptions() {\n return aiProviders.map(provider => ({\n value: provider.key,\n label: provider.label,\n hint: provider.hint,\n }));\n}\n\n/**\n * AI capability package options for the dedicated AI multiselect step. Offered\n * together with the providers (see `getAiProviderOptions`).\n */\nexport function getAiPackageOptions() {\n return aiPackages.map(pkg => ({\n value: pkg.key,\n label: pkg.label,\n hint: pkg.hint,\n }));\n}\n\n/**\n * Every selectable key the scaffolder knows about (features + AI providers +\n * AI capability packages). Used to validate `--features` / `--ai` flags in\n * non-interactive mode.\n */\nexport function getAllFeatureKeys(): string[] {\n return [\n ...features.map(feature => feature.key),\n ...aiProviders.map(provider => provider.key),\n ...aiPackages.map(pkg => pkg.key),\n ];\n}\n"],"mappings":";;;;;;AA6DA,MAAa,WAA4B;CAEvC;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;EACP,iBAAiB;CACnB;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAOA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;AACF;;;;;;;;;AAiBA,MAAa,cAAkC;CAC7C;EAAE,KAAK;EAAa,OAAO;EAAU,MAAM;CAAgC;CAC3E;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EAAE,KAAK;EAAa,OAAO;EAAU,MAAM;CAA0B;AACvE;;;;;;;AAQA,MAAa,aAAiC;CAC5C;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;AACF;;;;;AAMA,SAAgB,oBAAoB;CAClC,OAAO,SAAS,KAAI,aAAY;EAC9B,OAAO,QAAQ;EACf,OAAO,QAAQ;EACf,MAAM,GAAG,QAAQ,MAAM,KAAK,QAAQ;CACtC,EAAE;AACJ;;;;AAKA,SAAgB,wBAAkC;CAChD,OAAO,SACJ,QAAO,YAAW,QAAQ,eAAe,CAAC,CAC1C,KAAI,YAAW,QAAQ,GAAG;AAC/B;;;;AAKA,SAAgB,uBAAuB;CACrC,OAAO,YAAY,KAAI,cAAa;EAClC,OAAO,SAAS;EAChB,OAAO,SAAS;EAChB,MAAM,SAAS;CACjB,EAAE;AACJ;;;;;AAMA,SAAgB,sBAAsB;CACpC,OAAO,WAAW,KAAI,SAAQ;EAC5B,OAAO,IAAI;EACX,OAAO,IAAI;EACX,MAAM,IAAI;CACZ,EAAE;AACJ;;;;;;AAOA,SAAgB,oBAA8B;CAC5C,OAAO;EACL,GAAG,SAAS,KAAI,YAAW,QAAQ,GAAG;EACtC,GAAG,YAAY,KAAI,aAAY,SAAS,GAAG;EAC3C,GAAG,WAAW,KAAI,QAAO,IAAI,GAAG;CAClC;AACF"}
1
+ {"version":3,"file":"features-map.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/features/features-map.ts"],"sourcesContent":["/**\n * Presentation manifest for the scaffolder's feature prompts.\n *\n * This file is **display metadata only** — keys, labels, hints, grouping, and\n * default selections. It deliberately holds NO dependency names or versions:\n * the single source of truth for what each feature installs is the `add`\n * command's feature map in `@warlock.js/core`\n * (`src/generations/add-command.action.ts`). The scaffolder collects the\n * selections here and delegates the actual install to `warlock add`, so the\n * two never drift on versions again.\n *\n * Every `key` below MUST exist in core’s `allowedFeatures`. That guard now\n * EXISTS: `builder/scripts/check-feature-parity.ts`, run by every publishing\n * script in `builder/package.json`, so a release aborts before `pkgist` starts.\n * It could not live in this repo: this package deliberately does not depend on\n * `@warlock.js/core`, and our own CI checks out only this repo, so core’s keys\n * are not present here to compare against. `builder` sits above both and\n * imports each by relative path, adding no dependency edge in either direction.\n *\n * A subset assertion ALONE is not enough and has already let a\n * bug ship: `--features=web,tailwind` was rejected with \"Unknown feature(s):\n * tailwind\" because `tailwind` and `shadcn` existed in core and were missing\n * from THIS file — a direction a scaffolder ⊆ core check cannot see. The guard\n * has to run both ways:\n *\n * 1. scaffolder ⊆ core — catches a typo or a key core renamed.\n * 2. core ⊆ scaffolder + an explicit omission list — catches a feature core\n * gained that never became scaffoldable.\n *\n * The two lists are not equal and should not be forced to be. As of this\n * writing core holds 28 keys and this file offers 24; the other four are\n * deliberate and are the entire allow-list direction (2) needs:\n * `mongodb`, `postgres`, `mysql` (the database driver has its own select) and\n * `ai` (pulled automatically by every `ai-*` key's `requires`). Anything beyond\n * those four is a gap, not a decision, and should fail the build.\n */\n\nexport type FeatureGroup =\n | \"Auth & Access\"\n | \"Rendering & Mail\"\n | \"Media\"\n | \"Storage & Cache\"\n | \"Jobs & Messaging\"\n | \"Realtime\"\n | \"API Docs\"\n | \"Tooling\";\n\nexport type FeatureOption = {\n /** Must match a key in core's `add` feature map. */\n key: string;\n label: string;\n hint: string;\n group: FeatureGroup;\n defaultSelected?: boolean;\n};\n\n/**\n * Optional features offered in the general multiselect step. The database\n * driver (its own select) and AI providers + capability packages (their own\n * step) are intentionally NOT here — they have dedicated prompts.\n */\nexport const features: FeatureOption[] = [\n // Auth & Access\n {\n key: \"access\",\n label: \"Access (authorization)\",\n hint: \"RBAC permission checks, ABAC policies, and roles — pairs with auth\",\n group: \"Auth & Access\",\n },\n {\n key: \"auth-google\",\n label: \"Google sign-in\",\n hint: \"Google OAuth sign-in for @warlock.js/auth\",\n group: \"Auth & Access\",\n },\n {\n key: \"auth-passkeys\",\n label: \"Passkey sign-in\",\n hint: \"WebAuthn passkey login for @warlock.js/auth\",\n group: \"Auth & Access\",\n },\n\n // Rendering & Mail\n {\n key: \"web\",\n label: \"Web (SSR React pages)\",\n hint: \"Server-rendered React pages on the same server as your API — scaffolds src/web and registers the connector\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"tailwind\",\n label: \"Tailwind CSS\",\n hint: \"Tailwind v4 for the SSR page layer — creates src/web/app.css (the CSS-first config) and wires it through Vite (pulls web)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"shadcn\",\n label: \"shadcn/ui prerequisites\",\n hint: \"components.json, cn(), design tokens, and the web/* tsconfig path so `npx shadcn add <component>` works first time (pulls tailwind)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"sitemap\",\n label: \"Sitemap\",\n hint: \"Runtime sitemap.xml generation from the page registry — requires app.publicUrl or PUBLIC_APP_URL (pulls web)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"devtools\",\n label: \"Devtools (/__warlock)\",\n hint: \"Development-only dashboard: request timeline, queries + EXPLAIN, N+1 warnings, dev mailbox, logs, cache, routes — installed as a dev dependency, never runs in production\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"react\",\n label: \"React (rendering & mails)\",\n hint: \"React + ReactDOM for non-interactive rendering and HTML/email generation\",\n group: \"Rendering & Mail\",\n defaultSelected: true,\n },\n {\n key: \"react-email\",\n label: \"React Email\",\n hint: \"Build email templates with React + Tailwind (pulls react + mail)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"mail\",\n label: \"Mail (Nodemailer)\",\n hint: \"Send emails via SMTP\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"ses\",\n label: \"Amazon SES\",\n hint: \"Send emails via the AWS SES API\",\n group: \"Rendering & Mail\",\n },\n\n // Media\n {\n key: \"image\",\n label: \"Image processing (Sharp)\",\n hint: \"Resize, convert, and optimize images\",\n group: \"Media\",\n },\n\n // Storage & Cache\n {\n key: \"s3\",\n label: \"S3 storage\",\n hint: \"AWS S3 for cloud file storage\",\n group: \"Storage & Cache\",\n },\n {\n key: \"redis\",\n label: \"Redis cache\",\n hint: \"Redis driver for the cache layer\",\n group: \"Storage & Cache\",\n },\n\n // Jobs & Messaging\n {\n key: \"scheduler\",\n label: \"Scheduler\",\n hint: \"Background tasks and cron jobs\",\n group: \"Jobs & Messaging\",\n },\n {\n key: \"queue\",\n label: \"Queue (BullMQ)\",\n hint: \"Durable Redis-backed background jobs with retries and workers\",\n group: \"Jobs & Messaging\",\n },\n {\n key: \"bull-board\",\n label: \"Bull Board (queue dashboard)\",\n hint: \"Web dashboard for inspecting queues and jobs — enabled outside production only, and refuses to mount unguarded (pulls queue)\",\n group: \"Jobs & Messaging\",\n },\n {\n key: \"herald\",\n label: \"Herald (RabbitMQ)\",\n hint: \"Message broker for event-driven architecture\",\n group: \"Jobs & Messaging\",\n },\n {\n key: \"notifications\",\n label: \"Notifications\",\n hint: \"Multi-channel notifications — mail + in-app database, preferences, idempotency\",\n group: \"Jobs & Messaging\",\n },\n\n // Realtime\n {\n key: \"socket\",\n label: \"Socket.IO\",\n hint: \"Realtime websocket server\",\n group: \"Realtime\",\n },\n\n // API Docs (swagger / postman / openapi) — not shipped yet; they will arrive\n // together in the unified @warlock.js/api-docs package. Re-add the selection\n // once that package exists. (Removed so the wizard can't offer unbuilt features.)\n\n // Tooling\n {\n key: \"test\",\n label: \"Testing (Vitest)\",\n hint: \"Vitest + coverage + per-worker DB/cache test setup\",\n group: \"Tooling\",\n },\n];\n\nexport type AiProviderOption = {\n /** Must match a provider/package key in core's `add` feature map. */\n key: string;\n label: string;\n hint: string;\n};\n\n/**\n * AI providers offered in the dedicated AI step. Selecting any of these pulls\n * the core `@warlock.js/ai` package automatically via the provider's `requires`\n * in core's feature map — the scaffolder never lists `ai` as a standalone pick.\n *\n * Keys are `ai-`prefixed to mirror core's renamed provider feature keys\n * (`ai-openai` → `@warlock.js/ai-openai`, etc.).\n */\nexport const aiProviders: AiProviderOption[] = [\n { key: \"ai-openai\", label: \"OpenAI\", hint: \"GPT models via the OpenAI API\" },\n {\n key: \"ai-google\",\n label: \"Google (Gemini)\",\n hint: \"Gemini models via Google AI\",\n },\n {\n key: \"ai-anthropic\",\n label: \"Anthropic (Claude)\",\n hint: \"Claude models via the Anthropic API\",\n },\n {\n key: \"ai-bedrock\",\n label: \"AWS Bedrock\",\n hint: \"Foundation models via Amazon Bedrock\",\n },\n { key: \"ai-ollama\", label: \"Ollama\", hint: \"Local models via Ollama\" },\n];\n\n/**\n * AI capability packages offered alongside the providers in the dedicated AI\n * step. These are the satellite packages that sit on top of the core\n * `@warlock.js/ai` toolkit (each pulls the core `ai` package via its `requires`\n * in core's feature map, same as the providers).\n */\nexport const aiPackages: AiProviderOption[] = [\n {\n key: \"ai-tools\",\n label: \"AI Tools\",\n hint: \"Ready-made agent tools (web search, fetch, HTTP, calculator, date-time) + MCP client/server\",\n },\n {\n key: \"ai-panoptic\",\n label: \"AI Observability (Panoptic)\",\n hint: \"Tracing + exporters + a zero-setup local dashboard\",\n },\n {\n key: \"ai-workspace\",\n label: \"AI Workspace\",\n hint: \"Policy-jailed filesystem + shell for coding agents\",\n },\n];\n\n/**\n * Feature options for the multiselect prompt, ordered by group with the group\n * surfaced in the hint (keeps a flat list scannable without group widgets).\n */\nexport function getFeatureOptions() {\n return features.map((feature) => ({\n value: feature.key,\n label: feature.label,\n hint: `${feature.group} — ${feature.hint}`,\n }));\n}\n\n/**\n * Keys pre-checked in the feature multiselect.\n */\nexport function getDefaultFeatureKeys(): string[] {\n return features.filter((feature) => feature.defaultSelected).map((feature) => feature.key);\n}\n\n/**\n * AI provider options for the dedicated AI multiselect step.\n */\nexport function getAiProviderOptions() {\n return aiProviders.map((provider) => ({\n value: provider.key,\n label: provider.label,\n hint: provider.hint,\n }));\n}\n\n/**\n * AI capability package options for the dedicated AI multiselect step. Offered\n * together with the providers (see `getAiProviderOptions`).\n */\nexport function getAiPackageOptions() {\n return aiPackages.map((pkg) => ({\n value: pkg.key,\n label: pkg.label,\n hint: pkg.hint,\n }));\n}\n\n/**\n * Every selectable key the scaffolder knows about (features + AI providers +\n * AI capability packages). Used to validate `--features` / `--ai` flags in\n * non-interactive mode.\n */\nexport function getAllFeatureKeys(): string[] {\n return [\n ...features.map((feature) => feature.key),\n ...aiProviders.map((provider) => provider.key),\n ...aiPackages.map((pkg) => pkg.key),\n ];\n}\n\n/** Feature sections in manifest order, with short lists for the grouped picker. */\nexport function getFeatureGroups() {\n const labels = [...new Set(features.map((feature) => feature.group))];\n return labels.map((label) => ({\n label,\n options: features\n .filter((feature) => feature.group === label)\n .map((feature) => ({\n value: feature.key,\n label: feature.label,\n hint: feature.hint,\n })),\n }));\n}\n"],"mappings":";;;;;;AA6DA,MAAa,WAA4B;CAEvC;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;EACP,iBAAiB;CACnB;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAOA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;AACF;;;;;;;;;AAiBA,MAAa,cAAkC;CAC7C;EAAE,KAAK;EAAa,OAAO;EAAU,MAAM;CAAgC;CAC3E;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EAAE,KAAK;EAAa,OAAO;EAAU,MAAM;CAA0B;AACvE;;;;;;;AAQA,MAAa,aAAiC;CAC5C;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;AACF;;;;AAiBA,SAAgB,wBAAkC;CAChD,OAAO,SAAS,QAAQ,YAAY,QAAQ,eAAe,CAAC,CAAC,KAAK,YAAY,QAAQ,GAAG;AAC3F;;;;AAKA,SAAgB,uBAAuB;CACrC,OAAO,YAAY,KAAK,cAAc;EACpC,OAAO,SAAS;EAChB,OAAO,SAAS;EAChB,MAAM,SAAS;CACjB,EAAE;AACJ;;;;;AAMA,SAAgB,sBAAsB;CACpC,OAAO,WAAW,KAAK,SAAS;EAC9B,OAAO,IAAI;EACX,OAAO,IAAI;EACX,MAAM,IAAI;CACZ,EAAE;AACJ;;;;;;AAOA,SAAgB,oBAA8B;CAC5C,OAAO;EACL,GAAG,SAAS,KAAK,YAAY,QAAQ,GAAG;EACxC,GAAG,YAAY,KAAK,aAAa,SAAS,GAAG;EAC7C,GAAG,WAAW,KAAK,QAAQ,IAAI,GAAG;CACpC;AACF;;AAGA,SAAgB,mBAAmB;CAEjC,OAAO,CADS,GAAG,IAAI,IAAI,SAAS,KAAK,YAAY,QAAQ,KAAK,CAAC,CACvD,CAAC,CAAC,KAAK,WAAW;EAC5B;EACA,SAAS,SACN,QAAQ,YAAY,QAAQ,UAAU,KAAK,CAAC,CAC5C,KAAK,aAAa;GACjB,OAAO,QAAQ;GACf,OAAO,QAAQ;GACf,MAAM,QAAQ;EAChB,EAAE;CACN,EAAE;AACJ"}
@@ -40,5 +40,5 @@ function assertPackageManagerAvailable(requested) {
40
40
  }
41
41
 
42
42
  //#endregion
43
- export { assertPackageManagerAvailable };
43
+ export { assertPackageManagerAllowed, assertPackageManagerAvailable };
44
44
  //# sourceMappingURL=assert-package-manager.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"assert-package-manager.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/assert-package-manager.ts"],"sourcesContent":["import { cancel } from \"@clack/prompts\";\nimport {\n ALLOWED_PACKAGE_MANAGERS,\n getSystemPackageManagers,\n isValidPackageManager,\n} from \"../helpers/package-manager\";\n\n/**\n * Is `--pm` spelled like a package manager we support? Availability validation\n * calls this first, so an unknown executable is rejected before installation.\n */\nexport function assertPackageManagerAllowed(\n requested: string | undefined,\n): void {\n if (!requested) return;\n\n if (!isValidPackageManager(requested)) {\n cancel(\n `Unknown package manager \"${requested}\" — expected one of: ${ALLOWED_PACKAGE_MANAGERS.join(\", \")}`,\n );\n process.exit(1);\n }\n}\n\n/**\n * Is `--pm` spelled right AND actually on this machine? Every CLI path uses\n * this stronger check before it selects a manager or begins scaffolding. The\n * scaffold always installs dependencies; accepting a known-unavailable\n * executable would leave a known partial project behind.\n *\n * `bun` is the case that makes this concrete. It is in\n * {@link ALLOWED_PACKAGE_MANAGERS}, so the spelling check passes.\n *\n * MUST be called after package-manager detection has completed, since\n * detection is what {@link getSystemPackageManagers} reports.\n */\nexport function assertPackageManagerAvailable(\n requested: string | undefined,\n): void {\n if (requested === undefined) return;\n\n if (!requested.trim()) {\n cancel(\n \"The --pm flag requires a package manager value (npm, yarn, pnpm, or bun).\",\n );\n process.exit(1);\n }\n\n assertPackageManagerAllowed(requested);\n\n const available = getSystemPackageManagers();\n\n if (!available.includes(requested)) {\n cancel(\n `Package manager \"${requested}\" was not found on this machine — detected: ${available.join(\", \")}. Install it, or pass one of those to --pm.`,\n );\n process.exit(1);\n }\n}\n"],"mappings":";;;;;;;;AAWA,SAAgB,4BACd,WACM;CACN,IAAI,CAAC,WAAW;CAEhB,IAAI,CAAC,sBAAsB,SAAS,GAAG;EACrC,OACE,4BAA4B,UAAU,uBAAuB,yBAAyB,KAAK,IAAI,GACjG;EACA,QAAQ,KAAK,CAAC;CAChB;AACF;;;;;;;;;;;;;AAcA,SAAgB,8BACd,WACM;CACN,IAAI,cAAc,QAAW;CAE7B,IAAI,CAAC,UAAU,KAAK,GAAG;EACrB,OACE,2EACF;EACA,QAAQ,KAAK,CAAC;CAChB;CAEA,4BAA4B,SAAS;CAErC,MAAM,YAAY,yBAAyB;CAE3C,IAAI,CAAC,UAAU,SAAS,SAAS,GAAG;EAClC,OACE,oBAAoB,UAAU,8CAA8C,UAAU,KAAK,IAAI,EAAE,4CACnG;EACA,QAAQ,KAAK,CAAC;CAChB;AACF"}
1
+ {"version":3,"file":"assert-package-manager.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/assert-package-manager.ts"],"sourcesContent":["import { cancel } from \"@clack/prompts\";\nimport {\n ALLOWED_PACKAGE_MANAGERS,\n getSystemPackageManagers,\n isValidPackageManager,\n} from \"../helpers/package-manager\";\n\n/**\n * Is `--pm` spelled like a package manager we support? Availability validation\n * calls this first, so an unknown executable is rejected before installation.\n */\nexport function assertPackageManagerAllowed(requested: string | undefined): void {\n if (!requested) return;\n\n if (!isValidPackageManager(requested)) {\n cancel(\n `Unknown package manager \"${requested}\" — expected one of: ${ALLOWED_PACKAGE_MANAGERS.join(\", \")}`,\n );\n process.exit(1);\n }\n}\n\n/**\n * Is `--pm` spelled right AND actually on this machine? Every CLI path uses\n * this stronger check before it selects a manager or begins scaffolding. The\n * scaffold always installs dependencies; accepting a known-unavailable\n * executable would leave a known partial project behind.\n *\n * `bun` is the case that makes this concrete. It is in\n * {@link ALLOWED_PACKAGE_MANAGERS}, so the spelling check passes.\n *\n * MUST be called after package-manager detection has completed, since\n * detection is what {@link getSystemPackageManagers} reports.\n */\nexport function assertPackageManagerAvailable(requested: string | undefined): void {\n if (requested === undefined) return;\n\n if (!requested.trim()) {\n cancel(\"The --pm flag requires a package manager value (npm, yarn, pnpm, or bun).\");\n process.exit(1);\n }\n\n assertPackageManagerAllowed(requested);\n\n const available = getSystemPackageManagers();\n\n if (!available.includes(requested)) {\n cancel(\n `Package manager \"${requested}\" was not found on this machine — detected: ${available.join(\", \")}. Install it, or pass one of those to --pm.`,\n );\n process.exit(1);\n }\n}\n"],"mappings":";;;;;;;;AAWA,SAAgB,4BAA4B,WAAqC;CAC/E,IAAI,CAAC,WAAW;CAEhB,IAAI,CAAC,sBAAsB,SAAS,GAAG;EACrC,OACE,4BAA4B,UAAU,uBAAuB,yBAAyB,KAAK,IAAI,GACjG;EACA,QAAQ,KAAK,CAAC;CAChB;AACF;;;;;;;;;;;;;AAcA,SAAgB,8BAA8B,WAAqC;CACjF,IAAI,cAAc,QAAW;CAE7B,IAAI,CAAC,UAAU,KAAK,GAAG;EACrB,OAAO,2EAA2E;EAClF,QAAQ,KAAK,CAAC;CAChB;CAEA,4BAA4B,SAAS;CAErC,MAAM,YAAY,yBAAyB;CAE3C,IAAI,CAAC,UAAU,SAAS,SAAS,GAAG;EAClC,OACE,oBAAoB,UAAU,8CAA8C,UAAU,KAAK,IAAI,EAAE,4CACnG;EACA,QAAQ,KAAK,CAAC;CAChB;AACF"}
@@ -33,6 +33,11 @@ const FLAG_DEFINITIONS = [
33
33
  description: "Package manager to use (npm, yarn, pnpm, bun)",
34
34
  defaultValue: "inferred from the invoking agent (npm_config_user_agent) or the system"
35
35
  },
36
+ {
37
+ usage: "--lint=<oxc|eslint>",
38
+ description: "Linter and formatter: Oxlint/Oxfmt or ESLint/Prettier",
39
+ defaultValue: "oxc"
40
+ },
36
41
  {
37
42
  usage: "--agents=<list>",
38
43
  description: "Comma-separated agent-kit targets to derive docs/skills for",
@@ -88,5 +93,5 @@ ${FLAG_DEFINITIONS.map(({ usage, description, defaultValue }) => {
88
93
  }
89
94
 
90
95
  //#endregion
91
- export { buildHelpText };
96
+ export { FLAG_DEFINITIONS, buildHelpText };
92
97
  //# sourceMappingURL=definitions.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/definitions.ts"],"sourcesContent":["import {\n getAiPackageOptions,\n getAiProviderOptions,\n} from \"../features/features-map\";\n\n/**\n * Single source of truth for every CLI flag's help text and default, so\n * `--help` can never drift out of sync with what the parser actually\n * understands (requirement: `--help` lists every flag together with its\n * default value).\n */\nexport type FlagDefinition = {\n /** How the flag reads on the command line, e.g. `--db=<driver>`. */\n usage: string;\n description: string;\n /** Human-readable default, or what happens when it is left unanswered. */\n defaultValue: string;\n};\n\nconst AI_FEATURE_KEYS = [\n ...getAiProviderOptions(),\n ...getAiPackageOptions(),\n].map(({ value }) => value);\n\nexport const FLAG_DEFINITIONS: FlagDefinition[] = [\n {\n usage: \"--name=<name>\",\n description: \"Project name (or pass it as the first positional arg)\",\n defaultValue:\n \"required — prompted if a TTY is available, otherwise fails naming this flag\",\n },\n {\n usage: \"--stack=<api|web>\",\n description:\n \"API-only or full-stack web — the one structural choice that changes the app's shape. Customizing everything else is a flow, not a stack: use --customize, where --stack=web arrives as a pre-ticked web feature\",\n defaultValue: \"api\",\n },\n {\n usage: \"--db=<driver> / --no-db\",\n description:\n \"Database driver (e.g. postgres, mongodb), or skip a database entirely\",\n defaultValue: \"mongodb\",\n },\n {\n usage: \"--features=<list>\",\n description: \"Comma-separated feature keys (e.g. test,herald)\",\n defaultValue:\n 'none (or [\"web\"] when --stack=web and --features is not given)',\n },\n {\n usage: \"--ai=<list>\",\n description: `Comma-separated AI provider or capability keys (${AI_FEATURE_KEYS.join(\", \")})`,\n defaultValue: \"none\",\n },\n {\n usage: \"--pm=<manager>\",\n description: \"Package manager to use (npm, yarn, pnpm, bun)\",\n defaultValue:\n \"inferred from the invoking agent (npm_config_user_agent) or the system\",\n },\n {\n usage: \"--agents=<list>\",\n description: \"Comma-separated agent-kit targets to derive docs/skills for\",\n defaultValue: \"claude\",\n },\n {\n usage: \"--git / --no-git\",\n description: \"Force-enable or force-disable git initialization\",\n defaultValue: \"false\",\n },\n {\n usage: \"--jwt / --no-jwt\",\n description: \"Force-enable or force-disable JWT secret generation\",\n defaultValue: \"false\",\n },\n {\n usage: \"-y, --yes\",\n description: \"Skip every prompt and accept defaults for anything unset\",\n defaultValue: \"false\",\n },\n {\n usage: \"--customize, --interactive\",\n description:\n 'Full long-form wizard — every choice asked one by one. Also selectable as \"Customize\" in the \"What are we building?\" menu. Any other flag you pass pre-selects its prompt; --yes is refused',\n defaultValue: \"false\",\n },\n {\n usage: \"-h, --help\",\n description: \"Show this help message and exit\",\n defaultValue: \"n/a\",\n },\n {\n usage: \"-v, --version\",\n description: \"Show the installed create-warlock version and exit\",\n defaultValue: \"n/a\",\n },\n];\n\n/** Render the full `--help` text, one line per flag with its default. */\nexport function buildHelpText(): string {\n const rows = FLAG_DEFINITIONS.map(({ usage, description, defaultValue }) => {\n const label = usage.padEnd(28);\n return ` ${label}${description} (default: ${defaultValue})`;\n }).join(\"\\n\");\n\n return `\n create-warlock — scaffold a new Warlock.js project\n\n Usage\n $ create-warlock [project-name] [options]\n\n Options\n${rows}\n\n Example\n $ create-warlock my-app --db=postgres --features=test,herald --yes\n`;\n}\n"],"mappings":";;;AAmBA,MAAM,kBAAkB,CACtB,GAAG,qBAAqB,GACxB,GAAG,oBAAoB,CACzB,CAAC,CAAC,KAAK,EAAE,YAAY,KAAK;AAE1B,MAAa,mBAAqC;CAChD;EACE,OAAO;EACP,aAAa;EACb,cACE;CACJ;CACA;EACE,OAAO;EACP,aACE;EACF,cAAc;CAChB;CACA;EACE,OAAO;EACP,aACE;EACF,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cACE;CACJ;CACA;EACE,OAAO;EACP,aAAa,mDAAmD,gBAAgB,KAAK,IAAI,EAAE;EAC3F,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cACE;CACJ;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aACE;EACF,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;AACF;;AAGA,SAAgB,gBAAwB;CAMtC,OAAO;;;;;;;EALM,iBAAiB,KAAK,EAAE,OAAO,aAAa,mBAAmB;EAE1E,OAAO,OADO,MAAM,OAAO,EACT,IAAI,YAAY,aAAa,aAAa;CAC9D,CAAC,CAAC,CAAC,KAAK,IASL,EAAE;;;;;AAKP"}
1
+ {"version":3,"file":"definitions.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/definitions.ts"],"sourcesContent":["import { getAiPackageOptions, getAiProviderOptions } from \"../features/features-map\";\n\n/**\n * Single source of truth for every CLI flag's help text and default, so\n * `--help` can never drift out of sync with what the parser actually\n * understands (requirement: `--help` lists every flag together with its\n * default value).\n */\nexport type FlagDefinition = {\n /** How the flag reads on the command line, e.g. `--db=<driver>`. */\n usage: string;\n description: string;\n /** Human-readable default, or what happens when it is left unanswered. */\n defaultValue: string;\n};\n\nconst AI_FEATURE_KEYS = [...getAiProviderOptions(), ...getAiPackageOptions()].map(\n ({ value }) => value,\n);\n\nexport const FLAG_DEFINITIONS: FlagDefinition[] = [\n {\n usage: \"--name=<name>\",\n description: \"Project name (or pass it as the first positional arg)\",\n defaultValue: \"required — prompted if a TTY is available, otherwise fails naming this flag\",\n },\n {\n usage: \"--stack=<api|web>\",\n description:\n \"API-only or full-stack web — the one structural choice that changes the app's shape. Customizing everything else is a flow, not a stack: use --customize, where --stack=web arrives as a pre-ticked web feature\",\n defaultValue: \"api\",\n },\n {\n usage: \"--db=<driver> / --no-db\",\n description: \"Database driver (e.g. postgres, mongodb), or skip a database entirely\",\n defaultValue: \"mongodb\",\n },\n {\n usage: \"--features=<list>\",\n description: \"Comma-separated feature keys (e.g. test,herald)\",\n defaultValue: 'none (or [\"web\"] when --stack=web and --features is not given)',\n },\n {\n usage: \"--ai=<list>\",\n description: `Comma-separated AI provider or capability keys (${AI_FEATURE_KEYS.join(\", \")})`,\n defaultValue: \"none\",\n },\n {\n usage: \"--pm=<manager>\",\n description: \"Package manager to use (npm, yarn, pnpm, bun)\",\n defaultValue: \"inferred from the invoking agent (npm_config_user_agent) or the system\",\n },\n {\n usage: \"--lint=<oxc|eslint>\",\n description: \"Linter and formatter: Oxlint/Oxfmt or ESLint/Prettier\",\n defaultValue: \"oxc\",\n },\n {\n usage: \"--agents=<list>\",\n description: \"Comma-separated agent-kit targets to derive docs/skills for\",\n defaultValue: \"claude\",\n },\n {\n usage: \"--git / --no-git\",\n description: \"Force-enable or force-disable git initialization\",\n defaultValue: \"false\",\n },\n {\n usage: \"--jwt / --no-jwt\",\n description: \"Force-enable or force-disable JWT secret generation\",\n defaultValue: \"false\",\n },\n {\n usage: \"-y, --yes\",\n description: \"Skip every prompt and accept defaults for anything unset\",\n defaultValue: \"false\",\n },\n {\n usage: \"--customize, --interactive\",\n description:\n 'Full long-form wizard — every choice asked one by one. Also selectable as \"Customize\" in the \"What are we building?\" menu. Any other flag you pass pre-selects its prompt; --yes is refused',\n defaultValue: \"false\",\n },\n {\n usage: \"-h, --help\",\n description: \"Show this help message and exit\",\n defaultValue: \"n/a\",\n },\n {\n usage: \"-v, --version\",\n description: \"Show the installed create-warlock version and exit\",\n defaultValue: \"n/a\",\n },\n];\n\n/** Render the full `--help` text, one line per flag with its default. */\nexport function buildHelpText(): string {\n const rows = FLAG_DEFINITIONS.map(({ usage, description, defaultValue }) => {\n const label = usage.padEnd(28);\n return ` ${label}${description} (default: ${defaultValue})`;\n }).join(\"\\n\");\n\n return `\n create-warlock — scaffold a new Warlock.js project\n\n Usage\n $ create-warlock [project-name] [options]\n\n Options\n${rows}\n\n Example\n $ create-warlock my-app --db=postgres --features=test,herald --yes\n`;\n}\n"],"mappings":";;;AAgBA,MAAM,kBAAkB,CAAC,GAAG,qBAAqB,GAAG,GAAG,oBAAoB,CAAC,CAAC,CAAC,KAC3E,EAAE,YAAY,KACjB;AAEA,MAAa,mBAAqC;CAChD;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aACE;EACF,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa,mDAAmD,gBAAgB,KAAK,IAAI,EAAE;EAC3F,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aACE;EACF,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;AACF;;AAGA,SAAgB,gBAAwB;CAMtC,OAAO;;;;;;;EALM,iBAAiB,KAAK,EAAE,OAAO,aAAa,mBAAmB;EAE1E,OAAO,OADO,MAAM,OAAO,EACT,IAAI,YAAY,aAAa,aAAa;CAC9D,CAAC,CAAC,CAAC,KAAK,IASL,EAAE;;;;;AAKP"}
@@ -5,6 +5,7 @@ import { getPreferredPackageManager } from "../helpers/package-manager.mjs";
5
5
  function seedFromFlags(cli) {
6
6
  return {
7
7
  packageManager: cli.pm ?? getPreferredPackageManager(),
8
+ lint: cli.lint === "eslint" ? "eslint" : "oxc",
8
9
  databaseDriver: cli.db,
9
10
  features: cli.features ?? defaultFeatures(cli),
10
11
  aiProviders: cli.ai ?? [],
@@ -20,7 +21,7 @@ function seedFromFlags(cli) {
20
21
  function defaultFeatures(cli) {
21
22
  const defaults = getDefaultFeatureKeys();
22
23
  if (cli.stack !== "web") return defaults;
23
- return [...new Set([...defaults, "web"])];
24
+ return [.../* @__PURE__ */ new Set([...defaults, "web"])];
24
25
  }
25
26
 
26
27
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"seed-from-flags.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/seed-from-flags.ts"],"sourcesContent":["import type { CliFlags } from \"../commands/create-new-app/types\";\nimport { getDefaultFeatureKeys } from \"../features/features-map\";\nimport { getPreferredPackageManager } from \"../helpers/package-manager\";\n\n/**\n * The wizard's prompts, pre-selected from whatever the user already spelled\n * out on the command line.\n *\n * Before this existed, `--customize --db=postgres` silently threw the driver\n * away: the wizard asked its own question with its own default and the flag\n * never reached anything. A flag that is accepted and then ignored is the\n * worst of both worlds — the run succeeds, and the answer is wrong.\n *\n * So a flag passed alongside `--customize` is not a contradiction to refuse,\n * it is an answer given early: the prompt opens on that value and the user\n * can still change it.\n */\nexport type WizardSeeds = {\n packageManager: string;\n databaseDriver: string | undefined;\n features: string[];\n aiProviders: string[];\n useGit: boolean;\n useJWT: boolean;\n};\n\nexport function seedFromFlags(cli: CliFlags): WizardSeeds {\n return {\n packageManager: cli.pm ?? getPreferredPackageManager(),\n databaseDriver: cli.db,\n features: cli.features ?? defaultFeatures(cli),\n aiProviders: cli.ai ?? [],\n // clack's confirm opens on `true` when nothing is passed, which is the\n // behaviour these two prompts already had.\n useGit: cli.git ?? true,\n useJWT: cli.jwt ?? true,\n };\n}\n\n/**\n * `--stack=web` is the one structural answer, and the wizard has no stack\n * question of its own — it asks for features instead. So the stack arrives as\n * a pre-ticked `web` feature rather than being dropped on the floor.\n */\nfunction defaultFeatures(cli: CliFlags): string[] {\n const defaults = getDefaultFeatureKeys();\n\n if (cli.stack !== \"web\") return defaults;\n\n return [...new Set([...defaults, \"web\"])];\n}\n"],"mappings":";;;;AA0BA,SAAgB,cAAc,KAA4B;CACxD,OAAO;EACL,gBAAgB,IAAI,MAAM,2BAA2B;EACrD,gBAAgB,IAAI;EACpB,UAAU,IAAI,YAAY,gBAAgB,GAAG;EAC7C,aAAa,IAAI,MAAM,CAAC;EAGxB,QAAQ,IAAI,OAAO;EACnB,QAAQ,IAAI,OAAO;CACrB;AACF;;;;;;AAOA,SAAS,gBAAgB,KAAyB;CAChD,MAAM,WAAW,sBAAsB;CAEvC,IAAI,IAAI,UAAU,OAAO,OAAO;CAEhC,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,UAAU,KAAK,CAAC,CAAC;AAC1C"}
1
+ {"version":3,"file":"seed-from-flags.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/seed-from-flags.ts"],"sourcesContent":["import type { CliFlags, LintToolchain } from \"../commands/create-new-app/types\";\nimport { getDefaultFeatureKeys } from \"../features/features-map\";\nimport { getPreferredPackageManager } from \"../helpers/package-manager\";\n\n/**\n * The wizard's prompts, pre-selected from whatever the user already spelled\n * out on the command line.\n *\n * Before this existed, `--customize --db=postgres` silently threw the driver\n * away: the wizard asked its own question with its own default and the flag\n * never reached anything. A flag that is accepted and then ignored is the\n * worst of both worlds — the run succeeds, and the answer is wrong.\n *\n * So a flag passed alongside `--customize` is not a contradiction to refuse,\n * it is an answer given early: the prompt opens on that value and the user\n * can still change it.\n */\nexport type WizardSeeds = {\n packageManager: string;\n databaseDriver: string | undefined;\n features: string[];\n aiProviders: string[];\n useGit: boolean;\n useJWT: boolean;\n lint: LintToolchain;\n};\n\nexport function seedFromFlags(cli: CliFlags): WizardSeeds {\n return {\n packageManager: cli.pm ?? getPreferredPackageManager(),\n lint: cli.lint === \"eslint\" ? \"eslint\" : \"oxc\",\n databaseDriver: cli.db,\n features: cli.features ?? defaultFeatures(cli),\n aiProviders: cli.ai ?? [],\n // clack's confirm opens on `true` when nothing is passed, which is the\n // behaviour these two prompts already had.\n useGit: cli.git ?? true,\n useJWT: cli.jwt ?? true,\n };\n}\n\n/**\n * `--stack=web` is the one structural answer, and the wizard has no stack\n * question of its own — it asks for features instead. So the stack arrives as\n * a pre-ticked `web` feature rather than being dropped on the floor.\n */\nfunction defaultFeatures(cli: CliFlags): string[] {\n const defaults = getDefaultFeatureKeys();\n\n if (cli.stack !== \"web\") return defaults;\n\n return [...new Set([...defaults, \"web\"])];\n}\n"],"mappings":";;;;AA2BA,SAAgB,cAAc,KAA4B;CACxD,OAAO;EACL,gBAAgB,IAAI,MAAM,2BAA2B;EACrD,MAAM,IAAI,SAAS,WAAW,WAAW;EACzC,gBAAgB,IAAI;EACpB,UAAU,IAAI,YAAY,gBAAgB,GAAG;EAC7C,aAAa,IAAI,MAAM,CAAC;EAGxB,QAAQ,IAAI,OAAO;EACnB,QAAQ,IAAI,OAAO;CACrB;AACF;;;;;;AAOA,SAAS,gBAAgB,KAAyB;CAChD,MAAM,WAAW,sBAAsB;CAEvC,IAAI,IAAI,UAAU,OAAO,OAAO;CAEhC,OAAO,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,UAAU,KAAK,CAAC,CAAC;AAC1C"}
@@ -63,7 +63,8 @@ var App = class {
63
63
  if (layout === "packageJson") {
64
64
  rmSync(workspacePath, { force: true });
65
65
  const packageJsonPath = path.join(this.path, "package.json");
66
- putJsonFile(packageJsonPath, mergePackageJsonBuildApprovals(getJsonFile(packageJsonPath), packages));
66
+ const packageJson = getJsonFile(packageJsonPath);
67
+ putJsonFile(packageJsonPath, mergePackageJsonBuildApprovals(packageJson, packages));
67
68
  delete this.jsonFiles[path.resolve(packageJsonPath)];
68
69
  return;
69
70
  }
@@ -124,6 +125,54 @@ var App = class {
124
125
  pkg.save();
125
126
  return this;
126
127
  }
128
+ /** Apply the selected development tools before dependency installation. */
129
+ configureLintToolchain(toolchain = this.options.lint ?? "oxc") {
130
+ delete this.jsonFiles[path.resolve(this.path, "package.json")];
131
+ const pkg = this.package;
132
+ const content = pkg.content;
133
+ const deps = content.devDependencies;
134
+ deps.typescript = toolchain === "eslint" ? "6.0.3" : "7.0.2";
135
+ if (toolchain === "eslint") {
136
+ pkg.save();
137
+ return this;
138
+ }
139
+ for (const dependency of [
140
+ "@types/prettier",
141
+ "@typescript-eslint/eslint-plugin",
142
+ "@typescript-eslint/parser",
143
+ "eslint",
144
+ "eslint-config-prettier",
145
+ "eslint-plugin-prettier",
146
+ "eslint-plugin-unused-imports",
147
+ "prettier",
148
+ "@ianvs/prettier-plugin-sort-imports"
149
+ ]) delete deps[dependency];
150
+ Object.assign(deps, {
151
+ oxlint: "1.88.0",
152
+ oxfmt: "0.73.0"
153
+ });
154
+ content.devDependencies = Object.fromEntries(Object.entries(deps).sort(([left], [right]) => left.localeCompare(right)));
155
+ Object.assign(content.scripts, {
156
+ lint: "oxlint --fix ./src --max-warnings=0",
157
+ "lint:check": "oxlint ./src --max-warnings=0",
158
+ format: "oxfmt --write ./src",
159
+ "format:check": "oxfmt --check ./src"
160
+ });
161
+ pkg.save();
162
+ for (const fileName of [
163
+ "eslint.config.js",
164
+ ".prettierrc.json",
165
+ ".prettierignore"
166
+ ]) rmSync(path.join(this.path, fileName), { force: true });
167
+ for (const fileName of [".oxlintrc.json", ".oxfmtrc.json"]) copyFile(packageRoot("templates", "toolchains", "oxc", fileName), path.join(this.path, fileName));
168
+ for (const fileName of ["settings.json", "extensions.json"]) copyFile(packageRoot("templates", "toolchains", "oxc", ".vscode", fileName), path.join(this.path, ".vscode", fileName));
169
+ const config = this.file("warlock.config.ts");
170
+ if (!config.content.includes("healthCheckers:")) config.replace("export default defineConfig({", `export default defineConfig({
171
+ devServer: {
172
+ healthCheckers: { eslint: false, oxlint: true },
173
+ },`).save();
174
+ return this;
175
+ }
127
176
  /**
128
177
  * Configure the chosen database driver: wire `DB_DRIVER` / `DB_PORT` into
129
178
  * `.env`, AND pin the driver's npm package (`mongodb` / `pg`) into the
@@ -182,7 +231,10 @@ var App = class {
182
231
  if (driverValue === "postgres" && fileExists(postgresVariantPath)) {
183
232
  putFile(databasePath, getFile(postgresVariantPath));
184
233
  const envPath = this.path + "/.env";
185
- if (fileExists(envPath)) putFile(envPath, getFile(envPath).replace(/^DB_AUTH=.*\r?\n?/m, ""));
234
+ if (fileExists(envPath)) {
235
+ const envContent = getFile(envPath);
236
+ putFile(envPath, envContent.replace(/^DB_AUTH=.*\r?\n?/m, ""));
237
+ }
186
238
  }
187
239
  if (fileExists(postgresVariantPath)) unlinkSync(postgresVariantPath);
188
240
  return this;
@@ -238,9 +290,11 @@ var App = class {
238
290
  "skills/web-code-standards"
239
291
  ]) remove(entry);
240
292
  const agentsPath = path.resolve(this.path, "AGENTS.md");
241
- putFile(agentsPath, getFile(agentsPath).replace(/^.*\*\*web-code-standards\*\*.*\r?\n/m, ""));
293
+ const agents = getFile(agentsPath);
294
+ putFile(agentsPath, agents.replace(/^.*\*\*web-code-standards\*\*.*\r?\n/m, ""));
242
295
  const tsconfigPath = path.resolve(this.path, "tsconfig.json");
243
- putFile(tsconfigPath, getFile(tsconfigPath).replace(" \"web/*\": [\"./src/web/*\"],\n", "").replace(" \"@shared/*\": [\"./src/shared/*\"]\n", ""));
296
+ const tsconfig = getFile(tsconfigPath);
297
+ putFile(tsconfigPath, tsconfig.replace(" \"web/*\": [\"./src/web/*\"],\n", "").replace(" \"@shared/*\": [\"./src/shared/*\"]\n", ""));
244
298
  return this;
245
299
  }
246
300
  remove("src/app/home/controllers/home-page.controller.ts");
@@ -260,7 +314,10 @@ var App = class {
260
314
  putJsonFile(packageJsonPath, packageJson);
261
315
  const configPath = path.resolve(this.path, "warlock.config.ts");
262
316
  const config = getFile(configPath);
263
- if (!config.includes("\"@warlock.js/web/connector\"")) putFile(configPath, insertImportInSortedPosition(config, "import { webConnector } from \"@warlock.js/web/connector\";", "@warlock.js/web/connector").replace("export default defineConfig({", "export default defineConfig({\n connectors: [webConnector()],"));
317
+ if (!config.includes("\"@warlock.js/web/connector\"")) {
318
+ const withConnector = insertImportInSortedPosition(config, "import { webConnector } from \"@warlock.js/web/connector\";", "@warlock.js/web/connector");
319
+ putFile(configPath, withConnector.replace("export default defineConfig({", "export default defineConfig({\n connectors: [webConnector()],"));
320
+ }
264
321
  return this;
265
322
  }
266
323
  /**
@@ -423,5 +480,5 @@ function jsonFile(path) {
423
480
  }
424
481
 
425
482
  //#endregion
426
- export { App };
483
+ export { App, FileManager, JsonFileManager, file, installEnvironment, isInsidePnpmWorkspace, jsonFile };
427
484
  //# sourceMappingURL=app.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"app.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/app.ts"],"sourcesContent":["import {\n copyDirectory,\n copyFile,\n fileExists,\n getFile,\n getJsonFile,\n putFile,\n putJsonFile,\n renameFile,\n} from \"@warlock.js/fs\";\nimport { existsSync, rmSync, unlinkSync } from \"node:fs\";\nimport path from \"path\";\nimport { Application, AppOptions } from \"../commands/create-new-app/types\";\nimport { getDatabaseDriver } from \"../features/database-drivers\";\nimport { executeCommand, runCommand } from \"./exec\";\nimport { insertImportInSortedPosition } from \"./insert-import\";\nimport { getPackageManager, getPnpmVersion } from \"./package-manager\";\nimport { packageRoot, Template, template } from \"./paths\";\nimport {\n BASE_PNPM_BUILD_APPROVALS,\n mergeOnlyBuiltDependencies,\n mergePackageJsonBuildApprovals,\n mergePnpmBuildApprovals,\n pnpmBuildApprovalLayout,\n requiredPnpmBuildApprovals,\n} from \"./pnpm-build-approvals\";\nimport { fallbackRange } from \"./warlock-versions\";\n\n/**\n * Environment for every install the scaffolder spawns.\n *\n * A shell that exports `NODE_ENV=production` makes the package managers skip\n * every devDependency — and still exit 0. The scaffolded project needs its dev\n * toolchain (typescript, vitest, eslint) to be usable at all, so the install\n * child is pinned to `development` regardless of the ambient environment. A\n * silently incomplete install that reports success is the exact failure mode\n * this file exists to prevent.\n */\nexport function installEnvironment(): NodeJS.ProcessEnv {\n return { NODE_ENV: \"development\" };\n}\n\n/** True when any ancestor of `targetPath` (starting at its parent) has a pnpm-workspace.yaml. */\nexport function isInsidePnpmWorkspace(targetPath: string): boolean {\n let dir = path.dirname(path.resolve(targetPath));\n\n while (true) {\n if (existsSync(path.join(dir, \"pnpm-workspace.yaml\"))) return true;\n const parent = path.dirname(dir);\n if (parent === dir) return false;\n dir = parent;\n }\n}\n\nexport class App {\n /**\n * Resolved files\n */\n protected files: Record<string, FileManager> = {};\n\n /**\n * Resolved JSON files\n */\n protected jsonFiles: Record<string, JsonFileManager> = {};\n\n public isInstalled = false;\n\n public constructor(protected app: Application) {}\n\n public get options(): AppOptions {\n return this.app.options;\n }\n\n public use(templateName: Template) {\n copyDirectory(template(templateName), this.path);\n\n if (fileExists(this.path + \"/.env.example\")) {\n copyFile(this.path + \"/.env.example\", this.path + \"/.env\");\n }\n\n renameFile(this.path + \"/_.gitignore\", this.path + \"/.gitignore\");\n\n // Inside an existing pnpm workspace the parent owns the policy; a per-app\n // workspace file would nest a second workspace root.\n if (isInsidePnpmWorkspace(this.path)) {\n rmSync(path.join(this.path, \"pnpm-workspace.yaml\"), { force: true });\n } else if (getPackageManager() === \"pnpm\") {\n this.fitPnpmSettingsToVersion(BASE_PNPM_BUILD_APPROVALS);\n }\n\n return this;\n }\n\n /**\n * Write install-script approvals where the user's pnpm reads them. The\n * template ships the pnpm 10.26+ layout (settings-only pnpm-workspace.yaml\n * with `allowBuilds`); see pnpmBuildApprovalLayout for what older versions\n * need and why.\n */\n protected fitPnpmSettingsToVersion(packages: readonly string[]) {\n const workspacePath = path.join(this.path, \"pnpm-workspace.yaml\");\n const layout = pnpmBuildApprovalLayout(getPnpmVersion());\n\n if (layout === \"packageJson\") {\n // pnpm 7-10.4 refuse a workspace file without `packages:`, and adding\n // one breaks `pnpm add` in the app. The template's other setting,\n // minimumReleaseAgeExclude, does not exist before pnpm 10.16.\n rmSync(workspacePath, { force: true });\n const packageJsonPath = path.join(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as Record<\n string,\n unknown\n >;\n putJsonFile(\n packageJsonPath,\n mergePackageJsonBuildApprovals(packageJson, packages),\n );\n // Drop any cached copy so later package.json edits start from this one.\n delete this.jsonFiles[path.resolve(packageJsonPath)];\n return;\n }\n\n if (!fileExists(workspacePath)) return;\n\n const current = getFile(workspacePath) as string;\n let next = mergePnpmBuildApprovals(current, packages);\n\n if (layout === \"onlyBuiltDependencies\") {\n next = mergeOnlyBuiltDependencies(next, packages);\n }\n\n if (next !== current) putFile(workspacePath, next);\n }\n\n public init() {\n return this;\n }\n\n public terminate() {\n // No longer using outro, using showSuccessScreen instead\n }\n\n public install() {\n return runCommand(getPackageManager(), [\"install\"], this.path, {\n env: installEnvironment(),\n });\n }\n\n /** Record native build approvals before pnpm installs selected feature dependencies. */\n public approveSelectedFeatureBuilds(features: readonly string[]) {\n if (getPackageManager() !== \"pnpm\") return this;\n const packages = requiredPnpmBuildApprovals(features);\n if (packages.length === 0) return this;\n // Inside a parent workspace the parent's policy decides; leave it alone.\n if (isInsidePnpmWorkspace(this.path)) return this;\n this.fitPnpmSettingsToVersion(packages);\n return this;\n }\n\n public async exec(command: string) {\n const [commandName, ...optionsList] = command.split(\" \");\n return await executeCommand(commandName, optionsList, this.path);\n }\n\n public async git() {\n const { initializeGitRepository } =\n await import(\"./project-builder-helpers\");\n return await initializeGitRepository(this.path);\n }\n\n /**\n * Write the project's `package.json`: the project name and the version of\n * every `@warlock.js/*` dependency. No template script names a package\n * manager, so nothing here depends on the one the user chose.\n *\n * `versions` comes from {@link resolveWarlockVersions} — versions the\n * registry has confirmed exist. It is optional because the fluent chain is\n * synchronous; without it every sibling gets the caret range floored to the\n * scaffolder's major, which is always satisfiable by a published release.\n *\n * What it must NEVER do again is stamp the scaffolder's own version blind:\n * the release tooling bumps that version on every build, published or not,\n * so an unverified pin resolves to nothing and the install dies with ETARGET.\n */\n public updatePackageJson(versions: Record<string, string> = {}) {\n const pkg = this.package.replace(\"name\", this.name.replaceAll(\"/\", \"-\"));\n\n const content: any = pkg.content;\n\n const warlockVersion: string = (\n getJsonFile(packageRoot(\"package.json\")) as { version: string }\n ).version;\n const defaultRange = fallbackRange(warlockVersion);\n\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\n const deps = content[field] as Record<string, string> | undefined;\n if (!deps) continue;\n\n for (const dependency of Object.keys(deps)) {\n if (dependency.startsWith(\"@warlock.js/\")) {\n deps[dependency] = versions[dependency] ?? defaultRange;\n }\n }\n }\n\n pkg.save();\n\n return this;\n }\n\n /**\n * Configure the chosen database driver: wire `DB_DRIVER` / `DB_PORT` into\n * `.env`, AND pin the driver's npm package (`mongodb` / `pg`) into the\n * project's `package.json` dependencies.\n *\n * The dependency is written HERE — before the base `yarn install` — so the\n * driver is pulled deterministically by the very first install. We do NOT\n * rely on the post-copy `warlock add <driver> --no-install` + separate\n * batched install, which can be skipped or fail and leave the driver\n * missing (the \"mongodb package is not installed\" runtime error).\n */\n public configureDatabaseEnv(driverValue: string) {\n const driver = getDatabaseDriver(driverValue);\n\n if (!driver) return this;\n\n // Pin the driver package into dependencies (idempotent — never downgrade).\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n };\n packageJson.dependencies = packageJson.dependencies ?? {};\n if (!packageJson.dependencies[driver.package]) {\n packageJson.dependencies[driver.package] = driver.packageVersion;\n putJsonFile(packageJsonPath, packageJson);\n }\n\n // Both files carry the driver defaults: `.env` is what runs, `.env.example`\n // is what the next developer copies.\n for (const fileName of [\".env\", \".env.example\"]) {\n const envPath = this.path + \"/\" + fileName;\n if (!fileExists(envPath)) continue;\n\n let envContent = getFile(envPath) as string;\n\n envContent = envContent.replace(\n /DB_PORT=\\d+/,\n `DB_PORT=${driver.defaultPort}`,\n );\n\n if (envContent.includes(\"DB_DRIVER=\")) {\n envContent = envContent.replace(\n /DB_DRIVER=\\w*/,\n `DB_DRIVER=${driver.value}`,\n );\n } else {\n envContent = envContent.replace(\n /DB_PORT=\\d+/,\n `DB_PORT=${driver.defaultPort}\\nDB_DRIVER=${driver.value}`,\n );\n }\n\n // DB_AUTH is Mongo's authSource; no other driver reads it.\n if (driver.value !== \"mongodb\") {\n envContent = envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\");\n }\n\n putFile(envPath, envContent);\n }\n\n return this;\n }\n\n /**\n * Write the correct `src/config/database.ts` for the chosen driver and remove\n * every per-driver variant so the scaffold ships exactly one config file.\n *\n * The template ships a Mongo-shaped `database.ts` (the default) plus a\n * `database.postgres.ts` variant. Mongo keeps the default in place; Postgres\n * overwrites `database.ts` with the Postgres-shaped variant (typed\n * `ConnectionOptions`, `driver: \"postgres\"`, port 5432, no Mongo-only keys —\n * no `authSource`, `driverOptions`, `replicaSet`, `randomIncrement` /\n * `initialId`). Either way the `.postgres` variant file is deleted afterwards\n * so no leftover config ships in the generated project.\n *\n * For Postgres it also corrects the working `.env`: the Mongo template ships\n * `DB_AUTH=admin`, an `authSource` value Postgres ignores and its config no\n * longer reads, so the line is dropped to keep `.env` and the config in sync.\n * The port is rewritten by {@link configureDatabaseEnv} (driver defaultPort),\n * which runs alongside this in the same branch.\n */\n public configureDatabaseConfig(driverValue: string) {\n const configDir = path.resolve(this.path, \"src/config\");\n const databasePath = path.resolve(configDir, \"database.ts\");\n const postgresVariantPath = path.resolve(configDir, \"database.postgres.ts\");\n\n if (driverValue === \"postgres\" && fileExists(postgresVariantPath)) {\n putFile(databasePath, getFile(postgresVariantPath) as string);\n\n const envPath = this.path + \"/.env\";\n\n if (fileExists(envPath)) {\n const envContent = getFile(envPath) as string;\n // Drop the Mongo-only DB_AUTH line entirely (its whole line, so no blank\n // gap is left behind). Postgres has no authSource concept.\n putFile(envPath, envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\"));\n }\n }\n\n // Whatever the driver, no per-driver variant may survive into the scaffold.\n if (fileExists(postgresVariantPath)) {\n unlinkSync(postgresVariantPath);\n }\n\n return this;\n }\n\n /**\n * Remove the database layer for a \"no database\" scaffold.\n *\n * Deletes `src/config/database.ts` (and a `.tsx` / per-driver variant if\n * present) from the freshly-copied template. The framework's database\n * connector is config-gated on that file — with it gone,\n * `config.get(\"database\")` is undefined and the connector no-ops, so the app\n * boots with no database wired and no driver package pulled. The `DB_*` lines\n * in `.env` are left in place (harmless: no config reads them) as a ready\n * template for adding a database back later.\n */\n public removeDatabaseConfig() {\n const configDir = path.resolve(this.path, \"src/config\");\n\n for (const fileName of [\n \"database.ts\",\n \"database.tsx\",\n \"database.postgres.ts\",\n ]) {\n const filePath = path.resolve(configDir, fileName);\n\n if (fileExists(filePath)) {\n unlinkSync(filePath);\n }\n }\n\n return this;\n }\n\n /**\n * Configure the web starter only when the web feature owns `/`.\n *\n * A dependency-free HTTP response is retained without web; otherwise the\n * SSR page loader owns the route.\n * Exactly one survives the scaffold: web projects keep the SSR page and API\n * example; every other project keeps the dependency-free JSON controller.\n */\n public configureWebStarter(useWeb: boolean) {\n const remove = (relativePath: string) => {\n const target = path.resolve(this.path, relativePath);\n if (existsSync(target)) rmSync(target, { recursive: true, force: true });\n };\n\n if (!useWeb) {\n for (const entry of [\n \"src/web\",\n \"src/app/contact\",\n \"src/app/locale\",\n \"src/shared/contact.schema.ts\",\n \"src/shared/locale.schema.ts\",\n \"src/shared/locales.ts\",\n \"postcss.config.mjs\",\n \"skills/web-code-standards\",\n ]) {\n remove(entry);\n }\n\n const agentsPath = path.resolve(this.path, \"AGENTS.md\");\n const agents = getFile(agentsPath) as string;\n putFile(\n agentsPath,\n agents.replace(/^.*\\*\\*web-code-standards\\*\\*.*\\r?\\n/m, \"\"),\n );\n\n const tsconfigPath = path.resolve(this.path, \"tsconfig.json\");\n const tsconfig = getFile(tsconfigPath) as string;\n putFile(\n tsconfigPath,\n tsconfig\n .replace(' \"web/*\": [\"./src/web/*\"],\\n', \"\")\n .replace(' \"@shared/*\": [\"./src/shared/*\"]\\n', \"\"),\n );\n\n return this;\n }\n\n // The SSR page owns `/`; its loader still uses src/app/home/services.\n remove(\"src/app/home/controllers/home-page.controller.ts\");\n remove(\"src/app/home/routes.ts\");\n\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n packageJson.dependencies = {\n ...packageJson.dependencies,\n \"@mongez/http\": \"^3.5.0\",\n \"@mongez/react-form\": \"^4.0.0\",\n };\n packageJson.devDependencies = {\n ...packageJson.devDependencies,\n \"@tailwindcss/postcss\": \"^4.1.16\",\n tailwindcss: \"^4.1.16\",\n };\n putJsonFile(packageJsonPath, packageJson);\n\n const configPath = path.resolve(this.path, \"warlock.config.ts\");\n const config = getFile(configPath) as string;\n if (!config.includes('\"@warlock.js/web/connector\"')) {\n // Inserted in sorted position, not prepended. The generated app formats\n // with `prettier-plugin-organize-imports`, so an import pushed to the top\n // of a file that already imports `@warlock.js/auth` and `@warlock.js/core`\n // is out of order the moment it is written — and the developer meets it as\n // a `prettier/prettier` error on their first `warlock dev`, in a file they\n // did not write.\n const withConnector = insertImportInSortedPosition(\n config,\n 'import { webConnector } from \"@warlock.js/web/connector\";',\n \"@warlock.js/web/connector\",\n );\n\n putFile(\n configPath,\n withConnector.replace(\n \"export default defineConfig({\",\n \"export default defineConfig({\\n connectors: [webConnector()],\",\n ),\n );\n }\n\n return this;\n }\n\n /**\n * Record non-default `agent-kit` targets into the generated `package.json`'s\n * `agentKit.targets` block.\n *\n * The template's own `postinstall` (`agent-kit init && agent-kit sync`)\n * already targets `claude` with no flags, so the single-target default case\n * needs no template edit at all — this only writes when the selection is\n * anything else, keeping the scaffold diff-free for the common path.\n */\n public configureAgentKitTargets(agents: string[]) {\n const isDefault = agents.length === 1 && agents[0] === \"claude\";\n if (isDefault) return this;\n\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n if (!fileExists(packageJsonPath)) return this;\n\n const packageJson = getJsonFile(packageJsonPath) as {\n agentKit?: { targets?: string[] };\n };\n packageJson.agentKit = { ...packageJson.agentKit, targets: agents };\n putJsonFile(packageJsonPath, packageJson);\n\n return this;\n }\n\n /**\n * Install the selected optional features by delegating to the project's own\n * `warlock add`. `--no-install` records every dependency in package.json and\n * ejects configs / scripts / setup hooks WITHOUT installing — the caller runs\n * one batched install afterwards. Versions come from core's feature map, so\n * the scaffolder never duplicates them.\n *\n * `--no-install` is passed LAST on purpose: the CLI parser treats the\n * positional after a bare flag as that flag's value, so it must follow the\n * feature list, not precede it.\n */\n public async installFeatures(features: string[]) {\n if (features.length === 0) return true;\n\n return this.exec(`npx warlock add ${features.join(\" \")} --no-install`);\n }\n\n /**\n * Force a single copy of `vite` when the project ends up with BOTH `vite`\n * (written by the `web` feature) and `vitest` (always in the template).\n *\n * `vitest` depends on vite through a wide range of its own\n * (`^6 || ^7 || ^8`), which resolves to a DIFFERENT major than the range the\n * feature map pins. Yarn 1 then has to nest the second copy under\n * `vitest/node_modules` and dies on its own linker invariant:\n *\n * error Invariant Violation: could not find a copy of vite to link in\n * <project>/node_modules/vitest/node_modules\n *\n * It aborts BEFORE `node_modules/.bin` is written, so `warlock` is never\n * linked and the very first `yarn dev` fails with \"'warlock' is not\n * recognized\". npm and pnpm nest happily and never hit this — for them the\n * `overrides` twin below is one copy instead of two, not a crash fix.\n *\n * The pin is READ BACK from whatever range the project's own package.json\n * declares, never re-stated as a literal here: the feature map in\n * `@warlock.js/core` owns the version, and a second copy of it would drift\n * the moment that one moves.\n *\n * Returns whether a pin was written, so the caller (and its spec) can assert\n * it instead of assuming it.\n */\n public pinViteResolution() {\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n\n if (!fileExists(packageJsonPath)) return false;\n\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n resolutions?: Record<string, string>;\n overrides?: Record<string, string>;\n };\n\n const declaredRange = (name: string) =>\n packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];\n\n const viteRange = declaredRange(\"vite\");\n\n // No vite, or no vitest to conflict with it — nothing to pin. Writing a\n // resolution for a package the project does not depend on would only\n // freeze a transitive tree nobody asked us to freeze.\n if (!viteRange || !declaredRange(\"vitest\")) return false;\n\n // yarn 1 / yarn berry read `resolutions`; npm 8+ and pnpm read `overrides`.\n packageJson.resolutions = { ...packageJson.resolutions, vite: viteRange };\n packageJson.overrides = { ...packageJson.overrides, vite: viteRange };\n\n putJsonFile(packageJsonPath, packageJson);\n\n return true;\n }\n\n /**\n * Get package json file\n */\n public get package() {\n return this.json(\"package.json\");\n }\n\n public updateDotEnv() {\n this.file(\".env\").replaceAll(\"appName\", this.name).save();\n\n return this;\n }\n\n /**\n * Get env file to update\n */\n public get env() {\n return this.file(\".env\");\n }\n\n public get name() {\n return this.app.appName;\n }\n\n public get path() {\n return this.app.appPath;\n }\n\n public file(relativePath: string) {\n const fullPath = path.resolve(this.path, relativePath);\n\n if (!this.files[fullPath]) {\n this.files[fullPath] = file(fullPath);\n }\n\n return this.files[fullPath];\n }\n\n public json(relativePath: string): JsonFileManager {\n const fullPath = path.resolve(this.path, relativePath);\n\n if (!this.jsonFiles[fullPath]) {\n this.jsonFiles[fullPath] = jsonFile(fullPath);\n }\n\n return this.jsonFiles[fullPath];\n }\n}\n\nexport function app(app: Application) {\n return new App(app);\n}\n\nexport class FileManager {\n public content!: string;\n public constructor(protected filePath: string) {\n this.parseContent();\n }\n\n protected parseContent() {\n this.content = getFile(this.filePath) as string;\n }\n\n public replace(search: string, replace: string) {\n this.content = this.content.replace(search, replace);\n\n return this;\n }\n\n public replaceAll(search: string, replace: string) {\n this.content = this.content.replaceAll(search, replace);\n\n return this;\n }\n\n public save() {\n putFile(this.filePath, this.content);\n }\n}\n\nexport class JsonFileManager extends FileManager {\n protected parseContent() {\n this.content = getJsonFile(this.filePath);\n }\n\n public save() {\n putJsonFile(this.filePath, this.content);\n }\n\n public has(key: string) {\n return this.content[key] !== undefined;\n }\n\n public replace(key: string, value: any) {\n this.content[key] = value;\n\n return this;\n }\n\n public replaceAll(key: string, value: any) {\n const contentAsString = JSON.stringify(this.content);\n\n this.content = JSON.parse(contentAsString.replaceAll(key, value));\n\n return this;\n }\n}\n\nexport function file(path: string) {\n return new FileManager(path);\n}\n\nexport function jsonFile(path: string) {\n return new JsonFileManager(path);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,qBAAwC;CACtD,OAAO,EAAE,UAAU,cAAc;AACnC;;AAGA,SAAgB,sBAAsB,YAA6B;CACjE,IAAI,MAAM,KAAK,QAAQ,KAAK,QAAQ,UAAU,CAAC;CAE/C,OAAO,MAAM;EACX,IAAI,WAAW,KAAK,KAAK,KAAK,qBAAqB,CAAC,GAAG,OAAO;EAC9D,MAAM,SAAS,KAAK,QAAQ,GAAG;EAC/B,IAAI,WAAW,KAAK,OAAO;EAC3B,MAAM;CACR;AACF;AAEA,IAAa,MAAb,MAAiB;CAaf,AAAO,YAAY,AAAU,KAAkB;EAAlB;eATkB,CAAC;mBAKO,CAAC;qBAEnC;CAE2B;CAEhD,IAAW,UAAsB;EAC/B,OAAO,KAAK,IAAI;CAClB;CAEA,AAAO,IAAI,cAAwB;EACjC,cAAc,SAAS,YAAY,GAAG,KAAK,IAAI;EAE/C,IAAI,WAAW,KAAK,OAAO,eAAe,GACxC,SAAS,KAAK,OAAO,iBAAiB,KAAK,OAAO,OAAO;EAG3D,WAAW,KAAK,OAAO,gBAAgB,KAAK,OAAO,aAAa;EAIhE,IAAI,sBAAsB,KAAK,IAAI,GACjC,OAAO,KAAK,KAAK,KAAK,MAAM,qBAAqB,GAAG,EAAE,OAAO,KAAK,CAAC;OAC9D,IAAI,kBAAkB,MAAM,QACjC,KAAK,yBAAyB,yBAAyB;EAGzD,OAAO;CACT;;;;;;;CAQA,AAAU,yBAAyB,UAA6B;EAC9D,MAAM,gBAAgB,KAAK,KAAK,KAAK,MAAM,qBAAqB;EAChE,MAAM,SAAS,wBAAwB,eAAe,CAAC;EAEvD,IAAI,WAAW,eAAe;GAI5B,OAAO,eAAe,EAAE,OAAO,KAAK,CAAC;GACrC,MAAM,kBAAkB,KAAK,KAAK,KAAK,MAAM,cAAc;GAK3D,YACE,iBACA,+BANkB,YAAY,eAMW,GAAG,QAAQ,CACtD;GAEA,OAAO,KAAK,UAAU,KAAK,QAAQ,eAAe;GAClD;EACF;EAEA,IAAI,CAAC,WAAW,aAAa,GAAG;EAEhC,MAAM,UAAU,QAAQ,aAAa;EACrC,IAAI,OAAO,wBAAwB,SAAS,QAAQ;EAEpD,IAAI,WAAW,yBACb,OAAO,2BAA2B,MAAM,QAAQ;EAGlD,IAAI,SAAS,SAAS,QAAQ,eAAe,IAAI;CACnD;CAEA,AAAO,OAAO;EACZ,OAAO;CACT;CAEA,AAAO,YAAY,CAEnB;CAEA,AAAO,UAAU;EACf,OAAO,WAAW,kBAAkB,GAAG,CAAC,SAAS,GAAG,KAAK,MAAM,EAC7D,KAAK,mBAAmB,EAC1B,CAAC;CACH;;CAGA,AAAO,6BAA6B,UAA6B;EAC/D,IAAI,kBAAkB,MAAM,QAAQ,OAAO;EAC3C,MAAM,WAAW,2BAA2B,QAAQ;EACpD,IAAI,SAAS,WAAW,GAAG,OAAO;EAElC,IAAI,sBAAsB,KAAK,IAAI,GAAG,OAAO;EAC7C,KAAK,yBAAyB,QAAQ;EACtC,OAAO;CACT;CAEA,MAAa,KAAK,SAAiB;EACjC,MAAM,CAAC,aAAa,GAAG,eAAe,QAAQ,MAAM,GAAG;EACvD,OAAO,MAAM,eAAe,aAAa,aAAa,KAAK,IAAI;CACjE;CAEA,MAAa,MAAM;EACjB,MAAM,EAAE,4BACN,MAAM,OAAO;EACf,OAAO,MAAM,wBAAwB,KAAK,IAAI;CAChD;;;;;;;;;;;;;;;CAgBA,AAAO,kBAAkB,WAAmC,CAAC,GAAG;EAC9D,MAAM,MAAM,KAAK,QAAQ,QAAQ,QAAQ,KAAK,KAAK,WAAW,KAAK,GAAG,CAAC;EAEvE,MAAM,UAAe,IAAI;EAEzB,MAAM,iBACJ,YAAY,YAAY,cAAc,CAAC,CAAC,CACxC;EACF,MAAM,eAAe,cAAc,cAAc;EAEjD,KAAK,MAAM,SAAS,CAAC,gBAAgB,iBAAiB,GAAY;GAChE,MAAM,OAAO,QAAQ;GACrB,IAAI,CAAC,MAAM;GAEX,KAAK,MAAM,cAAc,OAAO,KAAK,IAAI,GACvC,IAAI,WAAW,WAAW,cAAc,GACtC,KAAK,cAAc,SAAS,eAAe;EAGjD;EAEA,IAAI,KAAK;EAET,OAAO;CACT;;;;;;;;;;;;CAaA,AAAO,qBAAqB,aAAqB;EAC/C,MAAM,SAAS,kBAAkB,WAAW;EAE5C,IAAI,CAAC,QAAQ,OAAO;EAGpB,MAAM,kBAAkB,KAAK,QAAQ,KAAK,MAAM,cAAc;EAC9D,MAAM,cAAc,YAAY,eAAe;EAG/C,YAAY,eAAe,YAAY,gBAAgB,CAAC;EACxD,IAAI,CAAC,YAAY,aAAa,OAAO,UAAU;GAC7C,YAAY,aAAa,OAAO,WAAW,OAAO;GAClD,YAAY,iBAAiB,WAAW;EAC1C;EAIA,KAAK,MAAM,YAAY,CAAC,QAAQ,cAAc,GAAG;GAC/C,MAAM,UAAU,KAAK,OAAO,MAAM;GAClC,IAAI,CAAC,WAAW,OAAO,GAAG;GAE1B,IAAI,aAAa,QAAQ,OAAO;GAEhC,aAAa,WAAW,QACtB,eACA,WAAW,OAAO,aACpB;GAEA,IAAI,WAAW,SAAS,YAAY,GAClC,aAAa,WAAW,QACtB,iBACA,aAAa,OAAO,OACtB;QAEA,aAAa,WAAW,QACtB,eACA,WAAW,OAAO,YAAY,cAAc,OAAO,OACrD;GAIF,IAAI,OAAO,UAAU,WACnB,aAAa,WAAW,QAAQ,sBAAsB,EAAE;GAG1D,QAAQ,SAAS,UAAU;EAC7B;EAEA,OAAO;CACT;;;;;;;;;;;;;;;;;;;CAoBA,AAAO,wBAAwB,aAAqB;EAClD,MAAM,YAAY,KAAK,QAAQ,KAAK,MAAM,YAAY;EACtD,MAAM,eAAe,KAAK,QAAQ,WAAW,aAAa;EAC1D,MAAM,sBAAsB,KAAK,QAAQ,WAAW,sBAAsB;EAE1E,IAAI,gBAAgB,cAAc,WAAW,mBAAmB,GAAG;GACjE,QAAQ,cAAc,QAAQ,mBAAmB,CAAW;GAE5D,MAAM,UAAU,KAAK,OAAO;GAE5B,IAAI,WAAW,OAAO,GAIpB,QAAQ,SAHW,QAAQ,OAGD,CAAC,CAAC,QAAQ,sBAAsB,EAAE,CAAC;EAEjE;EAGA,IAAI,WAAW,mBAAmB,GAChC,WAAW,mBAAmB;EAGhC,OAAO;CACT;;;;;;;;;;;;CAaA,AAAO,uBAAuB;EAC5B,MAAM,YAAY,KAAK,QAAQ,KAAK,MAAM,YAAY;EAEtD,KAAK,MAAM,YAAY;GACrB;GACA;GACA;EACF,GAAG;GACD,MAAM,WAAW,KAAK,QAAQ,WAAW,QAAQ;GAEjD,IAAI,WAAW,QAAQ,GACrB,WAAW,QAAQ;EAEvB;EAEA,OAAO;CACT;;;;;;;;;CAUA,AAAO,oBAAoB,QAAiB;EAC1C,MAAM,UAAU,iBAAyB;GACvC,MAAM,SAAS,KAAK,QAAQ,KAAK,MAAM,YAAY;GACnD,IAAI,WAAW,MAAM,GAAG,OAAO,QAAQ;IAAE,WAAW;IAAM,OAAO;GAAK,CAAC;EACzE;EAEA,IAAI,CAAC,QAAQ;GACX,KAAK,MAAM,SAAS;IAClB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,GACE,OAAO,KAAK;GAGd,MAAM,aAAa,KAAK,QAAQ,KAAK,MAAM,WAAW;GAEtD,QACE,YAFa,QAAQ,UAGhB,CAAC,CAAC,QAAQ,yCAAyC,EAAE,CAC5D;GAEA,MAAM,eAAe,KAAK,QAAQ,KAAK,MAAM,eAAe;GAE5D,QACE,cAFe,QAAQ,YAGhB,CAAC,CACL,QAAQ,yCAAqC,EAAE,CAAC,CAChD,QAAQ,+CAA2C,EAAE,CAC1D;GAEA,OAAO;EACT;EAGA,OAAO,kDAAkD;EACzD,OAAO,wBAAwB;EAE/B,MAAM,kBAAkB,KAAK,QAAQ,KAAK,MAAM,cAAc;EAC9D,MAAM,cAAc,YAAY,eAAe;EAI/C,YAAY,eAAe;GACzB,GAAG,YAAY;GACf,gBAAgB;GAChB,sBAAsB;EACxB;EACA,YAAY,kBAAkB;GAC5B,GAAG,YAAY;GACf,wBAAwB;GACxB,aAAa;EACf;EACA,YAAY,iBAAiB,WAAW;EAExC,MAAM,aAAa,KAAK,QAAQ,KAAK,MAAM,mBAAmB;EAC9D,MAAM,SAAS,QAAQ,UAAU;EACjC,IAAI,CAAC,OAAO,SAAS,+BAA6B,GAahD,QACE,YAPoB,6BACpB,QACA,+DACA,2BAKY,CAAC,CAAC,QACZ,iCACA,gEACF,CACF;EAGF,OAAO;CACT;;;;;;;;;;CAWA,AAAO,yBAAyB,QAAkB;EAEhD,IADkB,OAAO,WAAW,KAAK,OAAO,OAAO,UACxC,OAAO;EAEtB,MAAM,kBAAkB,KAAK,QAAQ,KAAK,MAAM,cAAc;EAC9D,IAAI,CAAC,WAAW,eAAe,GAAG,OAAO;EAEzC,MAAM,cAAc,YAAY,eAAe;EAG/C,YAAY,WAAW;GAAE,GAAG,YAAY;GAAU,SAAS;EAAO;EAClE,YAAY,iBAAiB,WAAW;EAExC,OAAO;CACT;;;;;;;;;;;;CAaA,MAAa,gBAAgB,UAAoB;EAC/C,IAAI,SAAS,WAAW,GAAG,OAAO;EAElC,OAAO,KAAK,KAAK,mBAAmB,SAAS,KAAK,GAAG,EAAE,cAAc;CACvE;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BA,AAAO,oBAAoB;EACzB,MAAM,kBAAkB,KAAK,QAAQ,KAAK,MAAM,cAAc;EAE9D,IAAI,CAAC,WAAW,eAAe,GAAG,OAAO;EAEzC,MAAM,cAAc,YAAY,eAAe;EAO/C,MAAM,iBAAiB,SACrB,YAAY,eAAe,SAAS,YAAY,kBAAkB;EAEpE,MAAM,YAAY,cAAc,MAAM;EAKtC,IAAI,CAAC,aAAa,CAAC,cAAc,QAAQ,GAAG,OAAO;EAGnD,YAAY,cAAc;GAAE,GAAG,YAAY;GAAa,MAAM;EAAU;EACxE,YAAY,YAAY;GAAE,GAAG,YAAY;GAAW,MAAM;EAAU;EAEpE,YAAY,iBAAiB,WAAW;EAExC,OAAO;CACT;;;;CAKA,IAAW,UAAU;EACnB,OAAO,KAAK,KAAK,cAAc;CACjC;CAEA,AAAO,eAAe;EACpB,KAAK,KAAK,MAAM,CAAC,CAAC,WAAW,WAAW,KAAK,IAAI,CAAC,CAAC,KAAK;EAExD,OAAO;CACT;;;;CAKA,IAAW,MAAM;EACf,OAAO,KAAK,KAAK,MAAM;CACzB;CAEA,IAAW,OAAO;EAChB,OAAO,KAAK,IAAI;CAClB;CAEA,IAAW,OAAO;EAChB,OAAO,KAAK,IAAI;CAClB;CAEA,AAAO,KAAK,cAAsB;EAChC,MAAM,WAAW,KAAK,QAAQ,KAAK,MAAM,YAAY;EAErD,IAAI,CAAC,KAAK,MAAM,WACd,KAAK,MAAM,YAAY,KAAK,QAAQ;EAGtC,OAAO,KAAK,MAAM;CACpB;CAEA,AAAO,KAAK,cAAuC;EACjD,MAAM,WAAW,KAAK,QAAQ,KAAK,MAAM,YAAY;EAErD,IAAI,CAAC,KAAK,UAAU,WAClB,KAAK,UAAU,YAAY,SAAS,QAAQ;EAG9C,OAAO,KAAK,UAAU;CACxB;AACF;AAMA,IAAa,cAAb,MAAyB;CAEvB,AAAO,YAAY,AAAU,UAAkB;EAAlB;EAC3B,KAAK,aAAa;CACpB;CAEA,AAAU,eAAe;EACvB,KAAK,UAAU,QAAQ,KAAK,QAAQ;CACtC;CAEA,AAAO,QAAQ,QAAgB,SAAiB;EAC9C,KAAK,UAAU,KAAK,QAAQ,QAAQ,QAAQ,OAAO;EAEnD,OAAO;CACT;CAEA,AAAO,WAAW,QAAgB,SAAiB;EACjD,KAAK,UAAU,KAAK,QAAQ,WAAW,QAAQ,OAAO;EAEtD,OAAO;CACT;CAEA,AAAO,OAAO;EACZ,QAAQ,KAAK,UAAU,KAAK,OAAO;CACrC;AACF;AAEA,IAAa,kBAAb,cAAqC,YAAY;CAC/C,AAAU,eAAe;EACvB,KAAK,UAAU,YAAY,KAAK,QAAQ;CAC1C;CAEA,AAAO,OAAO;EACZ,YAAY,KAAK,UAAU,KAAK,OAAO;CACzC;CAEA,AAAO,IAAI,KAAa;EACtB,OAAO,KAAK,QAAQ,SAAS;CAC/B;CAEA,AAAO,QAAQ,KAAa,OAAY;EACtC,KAAK,QAAQ,OAAO;EAEpB,OAAO;CACT;CAEA,AAAO,WAAW,KAAa,OAAY;EACzC,MAAM,kBAAkB,KAAK,UAAU,KAAK,OAAO;EAEnD,KAAK,UAAU,KAAK,MAAM,gBAAgB,WAAW,KAAK,KAAK,CAAC;EAEhE,OAAO;CACT;AACF;AAEA,SAAgB,KAAK,MAAc;CACjC,OAAO,IAAI,YAAY,IAAI;AAC7B;AAEA,SAAgB,SAAS,MAAc;CACrC,OAAO,IAAI,gBAAgB,IAAI;AACjC"}
1
+ {"version":3,"file":"app.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/app.ts"],"sourcesContent":["import {\n copyDirectory,\n copyFile,\n fileExists,\n getFile,\n getJsonFile,\n putFile,\n putJsonFile,\n renameFile,\n} from \"@warlock.js/fs\";\nimport { existsSync, rmSync, unlinkSync } from \"node:fs\";\nimport path from \"path\";\nimport { Application, AppOptions, LintToolchain } from \"../commands/create-new-app/types\";\nimport { getDatabaseDriver } from \"../features/database-drivers\";\nimport { executeCommand, runCommand } from \"./exec\";\nimport { insertImportInSortedPosition } from \"./insert-import\";\nimport { getPackageManager, getPnpmVersion } from \"./package-manager\";\nimport { packageRoot, Template, template } from \"./paths\";\nimport {\n BASE_PNPM_BUILD_APPROVALS,\n mergeOnlyBuiltDependencies,\n mergePackageJsonBuildApprovals,\n mergePnpmBuildApprovals,\n pnpmBuildApprovalLayout,\n requiredPnpmBuildApprovals,\n} from \"./pnpm-build-approvals\";\nimport { fallbackRange } from \"./warlock-versions\";\n\n/**\n * Environment for every install the scaffolder spawns.\n *\n * A shell that exports `NODE_ENV=production` makes the package managers skip\n * every devDependency — and still exit 0. The scaffolded project needs its dev\n * toolchain (typescript, vitest, eslint) to be usable at all, so the install\n * child is pinned to `development` regardless of the ambient environment. A\n * silently incomplete install that reports success is the exact failure mode\n * this file exists to prevent.\n */\nexport function installEnvironment(): NodeJS.ProcessEnv {\n return { NODE_ENV: \"development\" };\n}\n\n/** True when any ancestor of `targetPath` (starting at its parent) has a pnpm-workspace.yaml. */\nexport function isInsidePnpmWorkspace(targetPath: string): boolean {\n let dir = path.dirname(path.resolve(targetPath));\n\n while (true) {\n if (existsSync(path.join(dir, \"pnpm-workspace.yaml\"))) return true;\n const parent = path.dirname(dir);\n if (parent === dir) return false;\n dir = parent;\n }\n}\n\nexport class App {\n /**\n * Resolved files\n */\n protected files: Record<string, FileManager> = {};\n\n /**\n * Resolved JSON files\n */\n protected jsonFiles: Record<string, JsonFileManager> = {};\n\n public isInstalled = false;\n\n public constructor(protected app: Application) {}\n\n public get options(): AppOptions {\n return this.app.options;\n }\n\n public use(templateName: Template) {\n copyDirectory(template(templateName), this.path);\n\n if (fileExists(this.path + \"/.env.example\")) {\n copyFile(this.path + \"/.env.example\", this.path + \"/.env\");\n }\n\n renameFile(this.path + \"/_.gitignore\", this.path + \"/.gitignore\");\n\n // Inside an existing pnpm workspace the parent owns the policy; a per-app\n // workspace file would nest a second workspace root.\n if (isInsidePnpmWorkspace(this.path)) {\n rmSync(path.join(this.path, \"pnpm-workspace.yaml\"), { force: true });\n } else if (getPackageManager() === \"pnpm\") {\n this.fitPnpmSettingsToVersion(BASE_PNPM_BUILD_APPROVALS);\n }\n\n return this;\n }\n\n /**\n * Write install-script approvals where the user's pnpm reads them. The\n * template ships the pnpm 10.26+ layout (settings-only pnpm-workspace.yaml\n * with `allowBuilds`); see pnpmBuildApprovalLayout for what older versions\n * need and why.\n */\n protected fitPnpmSettingsToVersion(packages: readonly string[]) {\n const workspacePath = path.join(this.path, \"pnpm-workspace.yaml\");\n const layout = pnpmBuildApprovalLayout(getPnpmVersion());\n\n if (layout === \"packageJson\") {\n // pnpm 7-10.4 refuse a workspace file without `packages:`, and adding\n // one breaks `pnpm add` in the app. The template's other setting,\n // minimumReleaseAgeExclude, does not exist before pnpm 10.16.\n rmSync(workspacePath, { force: true });\n const packageJsonPath = path.join(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as Record<string, unknown>;\n putJsonFile(packageJsonPath, mergePackageJsonBuildApprovals(packageJson, packages));\n // Drop any cached copy so later package.json edits start from this one.\n delete this.jsonFiles[path.resolve(packageJsonPath)];\n return;\n }\n\n if (!fileExists(workspacePath)) return;\n\n const current = getFile(workspacePath) as string;\n let next = mergePnpmBuildApprovals(current, packages);\n\n if (layout === \"onlyBuiltDependencies\") {\n next = mergeOnlyBuiltDependencies(next, packages);\n }\n\n if (next !== current) putFile(workspacePath, next);\n }\n\n public init() {\n return this;\n }\n\n public terminate() {\n // No longer using outro, using showSuccessScreen instead\n }\n\n public install() {\n return runCommand(getPackageManager(), [\"install\"], this.path, {\n env: installEnvironment(),\n });\n }\n\n /** Record native build approvals before pnpm installs selected feature dependencies. */\n public approveSelectedFeatureBuilds(features: readonly string[]) {\n if (getPackageManager() !== \"pnpm\") return this;\n const packages = requiredPnpmBuildApprovals(features);\n if (packages.length === 0) return this;\n // Inside a parent workspace the parent's policy decides; leave it alone.\n if (isInsidePnpmWorkspace(this.path)) return this;\n this.fitPnpmSettingsToVersion(packages);\n return this;\n }\n\n public async exec(command: string) {\n const [commandName, ...optionsList] = command.split(\" \");\n return await executeCommand(commandName, optionsList, this.path);\n }\n\n public async git() {\n const { initializeGitRepository } = await import(\"./project-builder-helpers\");\n return await initializeGitRepository(this.path);\n }\n\n /**\n * Write the project's `package.json`: the project name and the version of\n * every `@warlock.js/*` dependency. No template script names a package\n * manager, so nothing here depends on the one the user chose.\n *\n * `versions` comes from {@link resolveWarlockVersions} — versions the\n * registry has confirmed exist. It is optional because the fluent chain is\n * synchronous; without it every sibling gets the caret range floored to the\n * scaffolder's major, which is always satisfiable by a published release.\n *\n * What it must NEVER do again is stamp the scaffolder's own version blind:\n * the release tooling bumps that version on every build, published or not,\n * so an unverified pin resolves to nothing and the install dies with ETARGET.\n */\n public updatePackageJson(versions: Record<string, string> = {}) {\n const pkg = this.package.replace(\"name\", this.name.replaceAll(\"/\", \"-\"));\n\n const content: any = pkg.content;\n\n const warlockVersion: string = (getJsonFile(packageRoot(\"package.json\")) as { version: string })\n .version;\n const defaultRange = fallbackRange(warlockVersion);\n\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\n const deps = content[field] as Record<string, string> | undefined;\n if (!deps) continue;\n\n for (const dependency of Object.keys(deps)) {\n if (dependency.startsWith(\"@warlock.js/\")) {\n deps[dependency] = versions[dependency] ?? defaultRange;\n }\n }\n }\n\n pkg.save();\n\n return this;\n }\n\n /** Apply the selected development tools before dependency installation. */\n public configureLintToolchain(toolchain: LintToolchain = this.options.lint ?? \"oxc\") {\n // Database and web setup may have edited package.json after it was cached.\n delete this.jsonFiles[path.resolve(this.path, \"package.json\")];\n const pkg = this.package;\n const content = pkg.content as unknown as {\n devDependencies: Record<string, string>;\n scripts: Record<string, string>;\n };\n const deps = content.devDependencies;\n // Registry peer range: @typescript-eslint/parser@8.71.2 supports >=4.8.4 <6.1.0.\n // Keep npm, pnpm and yarn installs valid without peer-dependency overrides.\n deps.typescript = toolchain === \"eslint\" ? \"6.0.3\" : \"7.0.2\";\n if (toolchain === \"eslint\") {\n pkg.save();\n return this;\n }\n\n for (const dependency of [\n \"@types/prettier\",\n \"@typescript-eslint/eslint-plugin\",\n \"@typescript-eslint/parser\",\n \"eslint\",\n \"eslint-config-prettier\",\n \"eslint-plugin-prettier\",\n \"eslint-plugin-unused-imports\",\n \"prettier\",\n \"@ianvs/prettier-plugin-sort-imports\",\n ])\n delete deps[dependency];\n Object.assign(deps, { oxlint: \"1.88.0\", oxfmt: \"0.73.0\" });\n content.devDependencies = Object.fromEntries(\n Object.entries(deps).sort(([left], [right]) => left.localeCompare(right)),\n );\n Object.assign(content.scripts, {\n lint: \"oxlint --fix ./src --max-warnings=0\",\n \"lint:check\": \"oxlint ./src --max-warnings=0\",\n format: \"oxfmt --write ./src\",\n \"format:check\": \"oxfmt --check ./src\",\n });\n pkg.save();\n\n for (const fileName of [\"eslint.config.js\", \".prettierrc.json\", \".prettierignore\"]) {\n rmSync(path.join(this.path, fileName), { force: true });\n }\n for (const fileName of [\".oxlintrc.json\", \".oxfmtrc.json\"]) {\n copyFile(\n packageRoot(\"templates\", \"toolchains\", \"oxc\", fileName),\n path.join(this.path, fileName),\n );\n }\n\n for (const fileName of [\"settings.json\", \"extensions.json\"]) {\n copyFile(\n packageRoot(\"templates\", \"toolchains\", \"oxc\", \".vscode\", fileName),\n path.join(this.path, \".vscode\", fileName),\n );\n }\n\n const config = this.file(\"warlock.config.ts\");\n if (!config.content.includes(\"healthCheckers:\")) {\n config\n .replace(\n \"export default defineConfig({\",\n `export default defineConfig({\n devServer: {\n healthCheckers: { eslint: false, oxlint: true },\n },`,\n )\n .save();\n }\n return this;\n }\n\n /**\n * Configure the chosen database driver: wire `DB_DRIVER` / `DB_PORT` into\n * `.env`, AND pin the driver's npm package (`mongodb` / `pg`) into the\n * project's `package.json` dependencies.\n *\n * The dependency is written HERE — before the base `yarn install` — so the\n * driver is pulled deterministically by the very first install. We do NOT\n * rely on the post-copy `warlock add <driver> --no-install` + separate\n * batched install, which can be skipped or fail and leave the driver\n * missing (the \"mongodb package is not installed\" runtime error).\n */\n public configureDatabaseEnv(driverValue: string) {\n const driver = getDatabaseDriver(driverValue);\n\n if (!driver) return this;\n\n // Pin the driver package into dependencies (idempotent — never downgrade).\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n };\n packageJson.dependencies = packageJson.dependencies ?? {};\n if (!packageJson.dependencies[driver.package]) {\n packageJson.dependencies[driver.package] = driver.packageVersion;\n putJsonFile(packageJsonPath, packageJson);\n }\n\n // Both files carry the driver defaults: `.env` is what runs, `.env.example`\n // is what the next developer copies.\n for (const fileName of [\".env\", \".env.example\"]) {\n const envPath = this.path + \"/\" + fileName;\n if (!fileExists(envPath)) continue;\n\n let envContent = getFile(envPath) as string;\n\n envContent = envContent.replace(/DB_PORT=\\d+/, `DB_PORT=${driver.defaultPort}`);\n\n if (envContent.includes(\"DB_DRIVER=\")) {\n envContent = envContent.replace(/DB_DRIVER=\\w*/, `DB_DRIVER=${driver.value}`);\n } else {\n envContent = envContent.replace(\n /DB_PORT=\\d+/,\n `DB_PORT=${driver.defaultPort}\\nDB_DRIVER=${driver.value}`,\n );\n }\n\n // DB_AUTH is Mongo's authSource; no other driver reads it.\n if (driver.value !== \"mongodb\") {\n envContent = envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\");\n }\n\n putFile(envPath, envContent);\n }\n\n return this;\n }\n\n /**\n * Write the correct `src/config/database.ts` for the chosen driver and remove\n * every per-driver variant so the scaffold ships exactly one config file.\n *\n * The template ships a Mongo-shaped `database.ts` (the default) plus a\n * `database.postgres.ts` variant. Mongo keeps the default in place; Postgres\n * overwrites `database.ts` with the Postgres-shaped variant (typed\n * `ConnectionOptions`, `driver: \"postgres\"`, port 5432, no Mongo-only keys —\n * no `authSource`, `driverOptions`, `replicaSet`, `randomIncrement` /\n * `initialId`). Either way the `.postgres` variant file is deleted afterwards\n * so no leftover config ships in the generated project.\n *\n * For Postgres it also corrects the working `.env`: the Mongo template ships\n * `DB_AUTH=admin`, an `authSource` value Postgres ignores and its config no\n * longer reads, so the line is dropped to keep `.env` and the config in sync.\n * The port is rewritten by {@link configureDatabaseEnv} (driver defaultPort),\n * which runs alongside this in the same branch.\n */\n public configureDatabaseConfig(driverValue: string) {\n const configDir = path.resolve(this.path, \"src/config\");\n const databasePath = path.resolve(configDir, \"database.ts\");\n const postgresVariantPath = path.resolve(configDir, \"database.postgres.ts\");\n\n if (driverValue === \"postgres\" && fileExists(postgresVariantPath)) {\n putFile(databasePath, getFile(postgresVariantPath) as string);\n\n const envPath = this.path + \"/.env\";\n\n if (fileExists(envPath)) {\n const envContent = getFile(envPath) as string;\n // Drop the Mongo-only DB_AUTH line entirely (its whole line, so no blank\n // gap is left behind). Postgres has no authSource concept.\n putFile(envPath, envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\"));\n }\n }\n\n // Whatever the driver, no per-driver variant may survive into the scaffold.\n if (fileExists(postgresVariantPath)) {\n unlinkSync(postgresVariantPath);\n }\n\n return this;\n }\n\n /**\n * Remove the database layer for a \"no database\" scaffold.\n *\n * Deletes `src/config/database.ts` (and a `.tsx` / per-driver variant if\n * present) from the freshly-copied template. The framework's database\n * connector is config-gated on that file — with it gone,\n * `config.get(\"database\")` is undefined and the connector no-ops, so the app\n * boots with no database wired and no driver package pulled. The `DB_*` lines\n * in `.env` are left in place (harmless: no config reads them) as a ready\n * template for adding a database back later.\n */\n public removeDatabaseConfig() {\n const configDir = path.resolve(this.path, \"src/config\");\n\n for (const fileName of [\"database.ts\", \"database.tsx\", \"database.postgres.ts\"]) {\n const filePath = path.resolve(configDir, fileName);\n\n if (fileExists(filePath)) {\n unlinkSync(filePath);\n }\n }\n\n return this;\n }\n\n /**\n * Configure the web starter only when the web feature owns `/`.\n *\n * A dependency-free HTTP response is retained without web; otherwise the\n * SSR page loader owns the route.\n * Exactly one survives the scaffold: web projects keep the SSR page and API\n * example; every other project keeps the dependency-free JSON controller.\n */\n public configureWebStarter(useWeb: boolean) {\n const remove = (relativePath: string) => {\n const target = path.resolve(this.path, relativePath);\n if (existsSync(target)) rmSync(target, { recursive: true, force: true });\n };\n\n if (!useWeb) {\n for (const entry of [\n \"src/web\",\n \"src/app/contact\",\n \"src/app/locale\",\n \"src/shared/contact.schema.ts\",\n \"src/shared/locale.schema.ts\",\n \"src/shared/locales.ts\",\n \"postcss.config.mjs\",\n \"skills/web-code-standards\",\n ]) {\n remove(entry);\n }\n\n const agentsPath = path.resolve(this.path, \"AGENTS.md\");\n const agents = getFile(agentsPath) as string;\n putFile(agentsPath, agents.replace(/^.*\\*\\*web-code-standards\\*\\*.*\\r?\\n/m, \"\"));\n\n const tsconfigPath = path.resolve(this.path, \"tsconfig.json\");\n const tsconfig = getFile(tsconfigPath) as string;\n putFile(\n tsconfigPath,\n tsconfig\n .replace(' \"web/*\": [\"./src/web/*\"],\\n', \"\")\n .replace(' \"@shared/*\": [\"./src/shared/*\"]\\n', \"\"),\n );\n\n return this;\n }\n\n // The SSR page owns `/`; its loader still uses src/app/home/services.\n remove(\"src/app/home/controllers/home-page.controller.ts\");\n remove(\"src/app/home/routes.ts\");\n\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n packageJson.dependencies = {\n ...packageJson.dependencies,\n \"@mongez/http\": \"^3.5.0\",\n \"@mongez/react-form\": \"^4.0.0\",\n };\n packageJson.devDependencies = {\n ...packageJson.devDependencies,\n \"@tailwindcss/postcss\": \"^4.1.16\",\n tailwindcss: \"^4.1.16\",\n };\n putJsonFile(packageJsonPath, packageJson);\n\n const configPath = path.resolve(this.path, \"warlock.config.ts\");\n const config = getFile(configPath) as string;\n if (!config.includes('\"@warlock.js/web/connector\"')) {\n // Inserted in sorted position, not prepended. The generated app formats\n // with `@ianvs/prettier-plugin-sort-imports`, so an import pushed to the top\n // of a file that already imports `@warlock.js/auth` and `@warlock.js/core`\n // is out of order the moment it is written — and the developer meets it as\n // a `prettier/prettier` error on their first `warlock dev`, in a file they\n // did not write.\n const withConnector = insertImportInSortedPosition(\n config,\n 'import { webConnector } from \"@warlock.js/web/connector\";',\n \"@warlock.js/web/connector\",\n );\n\n putFile(\n configPath,\n withConnector.replace(\n \"export default defineConfig({\",\n \"export default defineConfig({\\n connectors: [webConnector()],\",\n ),\n );\n }\n\n return this;\n }\n\n /**\n * Record non-default `agent-kit` targets into the generated `package.json`'s\n * `agentKit.targets` block.\n *\n * The template's own `postinstall` (`agent-kit init && agent-kit sync`)\n * already targets `claude` with no flags, so the single-target default case\n * needs no template edit at all — this only writes when the selection is\n * anything else, keeping the scaffold diff-free for the common path.\n */\n public configureAgentKitTargets(agents: string[]) {\n const isDefault = agents.length === 1 && agents[0] === \"claude\";\n if (isDefault) return this;\n\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n if (!fileExists(packageJsonPath)) return this;\n\n const packageJson = getJsonFile(packageJsonPath) as {\n agentKit?: { targets?: string[] };\n };\n packageJson.agentKit = { ...packageJson.agentKit, targets: agents };\n putJsonFile(packageJsonPath, packageJson);\n\n return this;\n }\n\n /**\n * Install the selected optional features by delegating to the project's own\n * `warlock add`. `--no-install` records every dependency in package.json and\n * ejects configs / scripts / setup hooks WITHOUT installing — the caller runs\n * one batched install afterwards. Versions come from core's feature map, so\n * the scaffolder never duplicates them.\n *\n * `--no-install` is passed LAST on purpose: the CLI parser treats the\n * positional after a bare flag as that flag's value, so it must follow the\n * feature list, not precede it.\n */\n public async installFeatures(features: string[]) {\n if (features.length === 0) return true;\n\n return this.exec(`npx warlock add ${features.join(\" \")} --no-install`);\n }\n\n /**\n * Force a single copy of `vite` when the project ends up with BOTH `vite`\n * (written by the `web` feature) and `vitest` (always in the template).\n *\n * `vitest` depends on vite through a wide range of its own\n * (`^6 || ^7 || ^8`), which resolves to a DIFFERENT major than the range the\n * feature map pins. Yarn 1 then has to nest the second copy under\n * `vitest/node_modules` and dies on its own linker invariant:\n *\n * error Invariant Violation: could not find a copy of vite to link in\n * <project>/node_modules/vitest/node_modules\n *\n * It aborts BEFORE `node_modules/.bin` is written, so `warlock` is never\n * linked and the very first `yarn dev` fails with \"'warlock' is not\n * recognized\". npm and pnpm nest happily and never hit this — for them the\n * `overrides` twin below is one copy instead of two, not a crash fix.\n *\n * The pin is READ BACK from whatever range the project's own package.json\n * declares, never re-stated as a literal here: the feature map in\n * `@warlock.js/core` owns the version, and a second copy of it would drift\n * the moment that one moves.\n *\n * Returns whether a pin was written, so the caller (and its spec) can assert\n * it instead of assuming it.\n */\n public pinViteResolution() {\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n\n if (!fileExists(packageJsonPath)) return false;\n\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n resolutions?: Record<string, string>;\n overrides?: Record<string, string>;\n };\n\n const declaredRange = (name: string) =>\n packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];\n\n const viteRange = declaredRange(\"vite\");\n\n // No vite, or no vitest to conflict with it — nothing to pin. Writing a\n // resolution for a package the project does not depend on would only\n // freeze a transitive tree nobody asked us to freeze.\n if (!viteRange || !declaredRange(\"vitest\")) return false;\n\n // yarn 1 / yarn berry read `resolutions`; npm 8+ and pnpm read `overrides`.\n packageJson.resolutions = { ...packageJson.resolutions, vite: viteRange };\n packageJson.overrides = { ...packageJson.overrides, vite: viteRange };\n\n putJsonFile(packageJsonPath, packageJson);\n\n return true;\n }\n\n /**\n * Get package json file\n */\n public get package() {\n return this.json(\"package.json\");\n }\n\n public updateDotEnv() {\n this.file(\".env\").replaceAll(\"appName\", this.name).save();\n\n return this;\n }\n\n /**\n * Get env file to update\n */\n public get env() {\n return this.file(\".env\");\n }\n\n public get name() {\n return this.app.appName;\n }\n\n public get path() {\n return this.app.appPath;\n }\n\n public file(relativePath: string) {\n const fullPath = path.resolve(this.path, relativePath);\n\n if (!this.files[fullPath]) {\n this.files[fullPath] = file(fullPath);\n }\n\n return this.files[fullPath];\n }\n\n public json(relativePath: string): JsonFileManager {\n const fullPath = path.resolve(this.path, relativePath);\n\n if (!this.jsonFiles[fullPath]) {\n this.jsonFiles[fullPath] = jsonFile(fullPath);\n }\n\n return this.jsonFiles[fullPath];\n }\n}\n\nexport function app(app: Application) {\n return new App(app);\n}\n\nexport class FileManager {\n public content!: string;\n public constructor(protected filePath: string) {\n this.parseContent();\n }\n\n protected parseContent() {\n this.content = getFile(this.filePath) as string;\n }\n\n public replace(search: string, replace: string) {\n this.content = this.content.replace(search, replace);\n\n return this;\n }\n\n public replaceAll(search: string, replace: string) {\n this.content = this.content.replaceAll(search, replace);\n\n return this;\n }\n\n public save() {\n putFile(this.filePath, this.content);\n }\n}\n\nexport class JsonFileManager extends FileManager {\n protected parseContent() {\n this.content = getJsonFile(this.filePath);\n }\n\n public save() {\n putJsonFile(this.filePath, this.content);\n }\n\n public has(key: string) {\n return this.content[key] !== undefined;\n }\n\n public replace(key: string, value: any) {\n this.content[key] = value;\n\n return this;\n }\n\n public replaceAll(key: string, value: any) {\n const contentAsString = JSON.stringify(this.content);\n\n this.content = JSON.parse(contentAsString.replaceAll(key, value));\n\n return this;\n }\n}\n\nexport function file(path: string) {\n return new FileManager(path);\n}\n\nexport function jsonFile(path: string) {\n return new JsonFileManager(path);\n}\n"],"mappings":"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"}
@@ -195,5 +195,5 @@ function runCommand(cmd, args, cwd, options = {}) {
195
195
  }
196
196
 
197
197
  //#endregion
198
- export { executeCommand, runCommand, tail, takeLastCommandOutput };
198
+ export { executeCommand, runCapturedCommand, runCommand, tail, takeLastCommandOutput };
199
199
  //# sourceMappingURL=exec.mjs.map