create-warlock 5.31.0 → 5.33.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 (58) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +78 -25
  3. package/esm/commands/create-new-app/index.mjs +2 -2
  4. package/esm/commands/create-new-app/index.mjs.map +1 -1
  5. package/esm/commands/create-new-app/types.d.mts +14 -9
  6. package/esm/commands/create-warlock-app/index.mjs +1 -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/features-map.mjs +1 -1
  12. package/esm/flags/assert-package-manager.mjs +1 -1
  13. package/esm/flags/definitions.mjs +3 -3
  14. package/esm/flags/definitions.mjs.map +1 -1
  15. package/esm/flags/seed-from-flags.mjs +2 -2
  16. package/esm/flags/seed-from-flags.mjs.map +1 -1
  17. package/esm/helpers/app.mjs +34 -12
  18. package/esm/helpers/app.mjs.map +1 -1
  19. package/esm/helpers/exec.mjs +1 -1
  20. package/esm/helpers/insert-import.mjs +1 -1
  21. package/esm/helpers/insert-import.mjs.map +1 -1
  22. package/esm/helpers/paths.mjs.map +1 -1
  23. package/esm/helpers/warlock-versions.mjs +1 -1
  24. package/esm/index.d.mts +3 -5
  25. package/esm/index.mjs +1 -3
  26. package/esm/index.mjs.map +1 -1
  27. package/esm/prompts/ask-lint-toolchain.mjs +2 -2
  28. package/esm/prompts/ask-lint-toolchain.mjs.map +1 -1
  29. package/esm/summary.mjs +2 -2
  30. package/esm/summary.mjs.map +1 -1
  31. package/esm/ui/banner.mjs +1 -1
  32. package/llms-full.txt +79 -10
  33. package/package.json +2 -2
  34. package/skills/create-a-warlock-project/SKILL.md +79 -10
  35. package/templates/toolchains/oxc/.oxfmtignore +2 -0
  36. package/templates/toolchains/oxc/.oxfmtrc.json +4 -1
  37. package/templates/warlock/.prettierrc.json +3 -1
  38. package/templates/warlock/README.md +13 -0
  39. package/templates/warlock/package.json +4 -4
  40. package/templates/warlock/skills/code-standards/SKILL.md +1 -1
  41. package/templates/warlock/src/app/auth/controllers/login.controller.ts +1 -1
  42. package/templates/warlock/src/app/contact/controllers/contact.controller.ts +1 -1
  43. package/templates/warlock/src/app/locale/controllers/locale.controller.ts +1 -1
  44. package/templates/warlock/src/app/posts/controllers/create-new-post.controller.ts +1 -1
  45. package/templates/warlock/src/app/posts/controllers/update-post.controller.ts +1 -1
  46. package/templates/warlock/src/app/posts/models/post/post.model.ts +1 -1
  47. package/templates/warlock/src/app/posts/schema/create-post.schema.ts +1 -1
  48. package/templates/warlock/src/app/posts/schema/update-post.schema.ts +1 -1
  49. package/templates/warlock/src/app/users/controllers/create-new-user.controller.ts +1 -1
  50. package/templates/warlock/src/app/users/controllers/list-users.controller.ts +1 -1
  51. package/templates/warlock/src/app/users/models/user/user.model.ts +1 -1
  52. package/templates/warlock/src/app/users/schema/create-user.schema.ts +1 -1
  53. package/templates/warlock/src/app/users/schema/list-users.schema.ts +1 -1
  54. package/templates/warlock/src/config/storage.ts +1 -1
  55. package/templates/warlock/src/web/assets.d.ts +2 -0
  56. package/templates/warlock/src/web/home/components/contact-form-controls.tsx +1 -1
  57. package/templates/warlock/src/web/home/components/hero-section.tsx +1 -0
  58. package/templates/warlock/tsconfig.json +1 -1
@@ -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
  }
@@ -125,12 +126,21 @@ var App = class {
125
126
  return this;
126
127
  }
127
128
  /** Apply the selected development tools before dependency installation. */
128
- configureLintToolchain(toolchain = this.options.lint ?? "eslint") {
129
- if (toolchain === "eslint") return this;
129
+ configureLintToolchain(toolchain = this.options.lint ?? "oxc") {
130
130
  delete this.jsonFiles[path.resolve(this.path, "package.json")];
131
131
  const pkg = this.package;
132
132
  const content = pkg.content;
133
133
  const deps = content.devDependencies;
134
+ deps.typescript = toolchain === "eslint" ? "6.0.3" : "7.0.2";
135
+ if (toolchain === "eslint") {
136
+ const config = this.file("warlock.config.ts");
137
+ if (!config.content.includes("healthCheckers:")) config.replace("export default defineConfig({", `export default defineConfig({
138
+ devServer: {
139
+ healthCheckers: { eslint: true, oxlint: false },
140
+ },`).save();
141
+ pkg.save();
142
+ return this;
143
+ }
134
144
  for (const dependency of [
135
145
  "@types/prettier",
136
146
  "@typescript-eslint/eslint-plugin",
@@ -140,7 +150,7 @@ var App = class {
140
150
  "eslint-plugin-prettier",
141
151
  "eslint-plugin-unused-imports",
142
152
  "prettier",
143
- "prettier-plugin-organize-imports"
153
+ "@ianvs/prettier-plugin-sort-imports"
144
154
  ]) delete deps[dependency];
145
155
  Object.assign(deps, {
146
156
  oxlint: "1.88.0",
@@ -150,8 +160,8 @@ var App = class {
150
160
  Object.assign(content.scripts, {
151
161
  lint: "oxlint --fix ./src --max-warnings=0",
152
162
  "lint:check": "oxlint ./src --max-warnings=0",
153
- format: "oxfmt --write ./src",
154
- "format:check": "oxfmt --check ./src"
163
+ format: "oxfmt --ignore-path .gitignore --ignore-path .oxfmtignore --write ./src",
164
+ "format:check": "oxfmt --ignore-path .gitignore --ignore-path .oxfmtignore --check ./src"
155
165
  });
156
166
  pkg.save();
157
167
  for (const fileName of [
@@ -159,7 +169,11 @@ var App = class {
159
169
  ".prettierrc.json",
160
170
  ".prettierignore"
161
171
  ]) rmSync(path.join(this.path, fileName), { force: true });
162
- for (const fileName of [".oxlintrc.json", ".oxfmtrc.json"]) copyFile(packageRoot("templates", "toolchains", "oxc", fileName), path.join(this.path, fileName));
172
+ for (const fileName of [
173
+ ".oxlintrc.json",
174
+ ".oxfmtrc.json",
175
+ ".oxfmtignore"
176
+ ]) copyFile(packageRoot("templates", "toolchains", "oxc", fileName), path.join(this.path, fileName));
163
177
  for (const fileName of ["settings.json", "extensions.json"]) copyFile(packageRoot("templates", "toolchains", "oxc", ".vscode", fileName), path.join(this.path, ".vscode", fileName));
164
178
  const config = this.file("warlock.config.ts");
165
179
  if (!config.content.includes("healthCheckers:")) config.replace("export default defineConfig({", `export default defineConfig({
@@ -226,7 +240,10 @@ var App = class {
226
240
  if (driverValue === "postgres" && fileExists(postgresVariantPath)) {
227
241
  putFile(databasePath, getFile(postgresVariantPath));
228
242
  const envPath = this.path + "/.env";
229
- if (fileExists(envPath)) putFile(envPath, getFile(envPath).replace(/^DB_AUTH=.*\r?\n?/m, ""));
243
+ if (fileExists(envPath)) {
244
+ const envContent = getFile(envPath);
245
+ putFile(envPath, envContent.replace(/^DB_AUTH=.*\r?\n?/m, ""));
246
+ }
230
247
  }
231
248
  if (fileExists(postgresVariantPath)) unlinkSync(postgresVariantPath);
232
249
  return this;
@@ -282,9 +299,11 @@ var App = class {
282
299
  "skills/web-code-standards"
283
300
  ]) remove(entry);
284
301
  const agentsPath = path.resolve(this.path, "AGENTS.md");
285
- putFile(agentsPath, getFile(agentsPath).replace(/^.*\*\*web-code-standards\*\*.*\r?\n/m, ""));
302
+ const agents = getFile(agentsPath);
303
+ putFile(agentsPath, agents.replace(/^.*\*\*web-code-standards\*\*.*\r?\n/m, ""));
286
304
  const tsconfigPath = path.resolve(this.path, "tsconfig.json");
287
- putFile(tsconfigPath, getFile(tsconfigPath).replace(" \"web/*\": [\"./src/web/*\"],\n", "").replace(" \"@shared/*\": [\"./src/shared/*\"]\n", ""));
305
+ const tsconfig = getFile(tsconfigPath);
306
+ putFile(tsconfigPath, tsconfig.replace(" \"web/*\": [\"./src/web/*\"],\n", "").replace(" \"@shared/*\": [\"./src/shared/*\"]\n", ""));
288
307
  return this;
289
308
  }
290
309
  remove("src/app/home/controllers/home-page.controller.ts");
@@ -304,7 +323,10 @@ var App = class {
304
323
  putJsonFile(packageJsonPath, packageJson);
305
324
  const configPath = path.resolve(this.path, "warlock.config.ts");
306
325
  const config = getFile(configPath);
307
- 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()],"));
326
+ if (!config.includes("\"@warlock.js/web/connector\"")) {
327
+ const withConnector = insertImportInSortedPosition(config, "import { webConnector } from \"@warlock.js/web/connector\";", "@warlock.js/web/connector");
328
+ putFile(configPath, withConnector.replace("export default defineConfig({", "export default defineConfig({\n connectors: [webConnector()],"));
329
+ }
308
330
  return this;
309
331
  }
310
332
  /**
@@ -467,5 +489,5 @@ function jsonFile(path) {
467
489
  }
468
490
 
469
491
  //#endregion
470
- export { App };
492
+ export { App, FileManager, JsonFileManager, file, installEnvironment, isInsidePnpmWorkspace, jsonFile };
471
493
  //# 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, 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 ?? \"eslint\") {\n if (toolchain === \"eslint\") return this;\n\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 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 \"prettier-plugin-organize-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 `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;GAE3D,YAAY,iBAAiB,+BADT,YAAY,eACsC,GAAG,QAAQ,CAAC;GAElF,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,4BAA4B,MAAM,OAAO;EACjD,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,iBAA0B,YAAY,YAAY,cAAc,CAAC,CAAC,CACrE;EACH,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;;CAGA,AAAO,uBAAuB,YAA2B,KAAK,QAAQ,QAAQ,UAAU;EACtF,IAAI,cAAc,UAAU,OAAO;EAGnC,OAAO,KAAK,UAAU,KAAK,QAAQ,KAAK,MAAM,cAAc;EAC5D,MAAM,MAAM,KAAK;EACjB,MAAM,UAAU,IAAI;EAIpB,MAAM,OAAO,QAAQ;EACrB,KAAK,MAAM,cAAc;GACvB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF,GACE,OAAO,KAAK;EACd,OAAO,OAAO,MAAM;GAAE,QAAQ;GAAU,OAAO;EAAS,CAAC;EACzD,QAAQ,kBAAkB,OAAO,YAC/B,OAAO,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,KAAK,cAAc,KAAK,CAAC,CAC1E;EACA,OAAO,OAAO,QAAQ,SAAS;GAC7B,MAAM;GACN,cAAc;GACd,QAAQ;GACR,gBAAgB;EAClB,CAAC;EACD,IAAI,KAAK;EAET,KAAK,MAAM,YAAY;GAAC;GAAoB;GAAoB;EAAiB,GAC/E,OAAO,KAAK,KAAK,KAAK,MAAM,QAAQ,GAAG,EAAE,OAAO,KAAK,CAAC;EAExD,KAAK,MAAM,YAAY,CAAC,kBAAkB,eAAe,GACvD,SACE,YAAY,aAAa,cAAc,OAAO,QAAQ,GACtD,KAAK,KAAK,KAAK,MAAM,QAAQ,CAC/B;EAGF,KAAK,MAAM,YAAY,CAAC,iBAAiB,iBAAiB,GACxD,SACE,YAAY,aAAa,cAAc,OAAO,WAAW,QAAQ,GACjE,KAAK,KAAK,KAAK,MAAM,WAAW,QAAQ,CAC1C;EAGF,MAAM,SAAS,KAAK,KAAK,mBAAmB;EAC5C,IAAI,CAAC,OAAO,QAAQ,SAAS,iBAAiB,GAC5C,OACG,QACC,iCACA;;;KAIF,CAAC,CACA,KAAK;EAEV,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,QAAQ,eAAe,WAAW,OAAO,aAAa;GAE9E,IAAI,WAAW,SAAS,YAAY,GAClC,aAAa,WAAW,QAAQ,iBAAiB,aAAa,OAAO,OAAO;QAE5E,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;GAAC;GAAe;GAAgB;EAAsB,GAAG;GAC9E,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,QAAQ,YADO,QAAQ,UACE,CAAC,CAAC,QAAQ,yCAAyC,EAAE,CAAC;GAE/E,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 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: true, oxlint: false },\n },`,\n )\n .save();\n }\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 --ignore-path .gitignore --ignore-path .oxfmtignore --write ./src\",\n \"format:check\": \"oxfmt --ignore-path .gitignore --ignore-path .oxfmtignore --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\", \".oxfmtignore\"]) {\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":";;;;;;;;;;;;;;;;;;;;;;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;GAC3D,MAAM,cAAc,YAAY,eAAe;GAC/C,YAAY,iBAAiB,+BAA+B,aAAa,QAAQ,CAAC;GAElF,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,4BAA4B,MAAM,OAAO;EACjD,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,iBAA0B,YAAY,YAAY,cAAc,CAAC,CAAC,CACrE;EACH,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;;CAGA,AAAO,uBAAuB,YAA2B,KAAK,QAAQ,QAAQ,OAAO;EAEnF,OAAO,KAAK,UAAU,KAAK,QAAQ,KAAK,MAAM,cAAc;EAC5D,MAAM,MAAM,KAAK;EACjB,MAAM,UAAU,IAAI;EAIpB,MAAM,OAAO,QAAQ;EAGrB,KAAK,aAAa,cAAc,WAAW,UAAU;EACrD,IAAI,cAAc,UAAU;GAC1B,MAAM,SAAS,KAAK,KAAK,mBAAmB;GAC5C,IAAI,CAAC,OAAO,QAAQ,SAAS,iBAAiB,GAC5C,OACG,QACC,iCACA;;;KAIF,CAAC,CACA,KAAK;GAEV,IAAI,KAAK;GACT,OAAO;EACT;EAEA,KAAK,MAAM,cAAc;GACvB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF,GACE,OAAO,KAAK;EACd,OAAO,OAAO,MAAM;GAAE,QAAQ;GAAU,OAAO;EAAS,CAAC;EACzD,QAAQ,kBAAkB,OAAO,YAC/B,OAAO,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,KAAK,cAAc,KAAK,CAAC,CAC1E;EACA,OAAO,OAAO,QAAQ,SAAS;GAC7B,MAAM;GACN,cAAc;GACd,QAAQ;GACR,gBAAgB;EAClB,CAAC;EACD,IAAI,KAAK;EAET,KAAK,MAAM,YAAY;GAAC;GAAoB;GAAoB;EAAiB,GAC/E,OAAO,KAAK,KAAK,KAAK,MAAM,QAAQ,GAAG,EAAE,OAAO,KAAK,CAAC;EAExD,KAAK,MAAM,YAAY;GAAC;GAAkB;GAAiB;EAAc,GACvE,SACE,YAAY,aAAa,cAAc,OAAO,QAAQ,GACtD,KAAK,KAAK,KAAK,MAAM,QAAQ,CAC/B;EAGF,KAAK,MAAM,YAAY,CAAC,iBAAiB,iBAAiB,GACxD,SACE,YAAY,aAAa,cAAc,OAAO,WAAW,QAAQ,GACjE,KAAK,KAAK,KAAK,MAAM,WAAW,QAAQ,CAC1C;EAGF,MAAM,SAAS,KAAK,KAAK,mBAAmB;EAC5C,IAAI,CAAC,OAAO,QAAQ,SAAS,iBAAiB,GAC5C,OACG,QACC,iCACA;;;KAIF,CAAC,CACA,KAAK;EAEV,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,QAAQ,eAAe,WAAW,OAAO,aAAa;GAE9E,IAAI,WAAW,SAAS,YAAY,GAClC,aAAa,WAAW,QAAQ,iBAAiB,aAAa,OAAO,OAAO;QAE5E,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,GAAG;IACvB,MAAM,aAAa,QAAQ,OAAO;IAGlC,QAAQ,SAAS,WAAW,QAAQ,sBAAsB,EAAE,CAAC;GAC/D;EACF;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;GAAC;GAAe;GAAgB;EAAsB,GAAG;GAC9E,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;GACtD,MAAM,SAAS,QAAQ,UAAU;GACjC,QAAQ,YAAY,OAAO,QAAQ,yCAAyC,EAAE,CAAC;GAE/E,MAAM,eAAe,KAAK,QAAQ,KAAK,MAAM,eAAe;GAC5D,MAAM,WAAW,QAAQ,YAAY;GACrC,QACE,cACA,SACG,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,GAAG;GAOnD,MAAM,gBAAgB,6BACpB,QACA,+DACA,2BACF;GAEA,QACE,YACA,cAAc,QACZ,iCACA,gEACF,CACF;EACF;EAEA,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"}
@@ -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
@@ -3,7 +3,7 @@
3
3
  * Splice an `import` statement into a generated source file **in sorted
4
4
  * position**, rather than pushing it to the top.
5
5
  *
6
- * The scaffolded app formats with `prettier-plugin-organize-imports`, which
6
+ * The scaffolded app formats with `@ianvs/prettier-plugin-sort-imports`, which
7
7
  * orders imports alphabetically by module specifier. So an import prepended to
8
8
  * a file that already has some is, by construction, in the wrong place — and
9
9
  * the developer meets it as a `prettier/prettier` error on their very first
@@ -1 +1 @@
1
- {"version":3,"file":"insert-import.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/insert-import.ts"],"sourcesContent":["/**\n * Splice an `import` statement into a generated source file **in sorted\n * position**, rather than pushing it to the top.\n *\n * The scaffolded app formats with `prettier-plugin-organize-imports`, which\n * orders imports alphabetically by module specifier. So an import prepended to\n * a file that already has some is, by construction, in the wrong place — and\n * the developer meets it as a `prettier/prettier` error on their very first\n * `warlock dev`, in a file they did not write, about code they did not add.\n *\n * That is the defect class this scaffolder keeps paying for: a failure that\n * waits for the user's first ordinary action and then arrives attributed to\n * them.\n */\n\n/**\n * Matches a top-level import statement and captures its module specifier.\n *\n * Deliberately narrow: this reads files THIS scaffolder generates from its own\n * templates, not arbitrary user source. It handles the two forms those\n * templates actually contain — a single-line import and a braced multi-line one\n * — and is not a general TypeScript parser. If a template ever grows an import\n * shape this misses, the insertion falls back to appending after the last match,\n * which is still ordered relative to everything it did recognise.\n */\nconst IMPORT_STATEMENT = /^import\\s[\\s\\S]*?from\\s+[\"']([^\"']+)[\"'];?$/gm;\n\n/**\n * Insert `statement` into `source` so the import block stays alphabetically\n * ordered by module specifier.\n *\n * @param source the file's current contents.\n * @param statement the whole import line to add, without a trailing newline.\n * @param specifier the module specifier `statement` imports from — passed\n * rather than re-parsed, because the caller already knows it\n * and a second parser is a second thing to disagree.\n * @returns the file contents with the import in place. A file with no imports\n * at all gets it at the top, which is the only ordering available.\n */\nexport function insertImportInSortedPosition(\n source: string,\n statement: string,\n specifier: string,\n): string {\n const matches = [...source.matchAll(IMPORT_STATEMENT)];\n\n if (matches.length === 0) {\n return `${statement}\\n${source}`;\n }\n\n const successor = matches.find((match) => (match[1] as string).localeCompare(specifier) > 0);\n\n if (successor) {\n return `${source.slice(0, successor.index)}${statement}\\n${source.slice(successor.index)}`;\n }\n\n const last = matches[matches.length - 1] as RegExpMatchArray;\n const endOfLast = (last.index as number) + last[0].length;\n\n return `${source.slice(0, endOfLast)}\\n${statement}${source.slice(endOfLast)}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAM,mBAAmB;;;;;;;;;;;;;AAczB,SAAgB,6BACd,QACA,WACA,WACQ;CACR,MAAM,UAAU,CAAC,GAAG,OAAO,SAAS,gBAAgB,CAAC;CAErD,IAAI,QAAQ,WAAW,GACrB,OAAO,GAAG,UAAU,IAAI;CAG1B,MAAM,YAAY,QAAQ,MAAM,UAAW,MAAM,EAAE,CAAY,cAAc,SAAS,IAAI,CAAC;CAE3F,IAAI,WACF,OAAO,GAAG,OAAO,MAAM,GAAG,UAAU,KAAK,IAAI,UAAU,IAAI,OAAO,MAAM,UAAU,KAAK;CAGzF,MAAM,OAAO,QAAQ,QAAQ,SAAS;CACtC,MAAM,YAAa,KAAK,QAAmB,KAAK,EAAE,CAAC;CAEnD,OAAO,GAAG,OAAO,MAAM,GAAG,SAAS,EAAE,IAAI,YAAY,OAAO,MAAM,SAAS;AAC7E"}
1
+ {"version":3,"file":"insert-import.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/insert-import.ts"],"sourcesContent":["/**\n * Splice an `import` statement into a generated source file **in sorted\n * position**, rather than pushing it to the top.\n *\n * The scaffolded app formats with `@ianvs/prettier-plugin-sort-imports`, which\n * orders imports alphabetically by module specifier. So an import prepended to\n * a file that already has some is, by construction, in the wrong place — and\n * the developer meets it as a `prettier/prettier` error on their very first\n * `warlock dev`, in a file they did not write, about code they did not add.\n *\n * That is the defect class this scaffolder keeps paying for: a failure that\n * waits for the user's first ordinary action and then arrives attributed to\n * them.\n */\n\n/**\n * Matches a top-level import statement and captures its module specifier.\n *\n * Deliberately narrow: this reads files THIS scaffolder generates from its own\n * templates, not arbitrary user source. It handles the two forms those\n * templates actually contain — a single-line import and a braced multi-line one\n * — and is not a general TypeScript parser. If a template ever grows an import\n * shape this misses, the insertion falls back to appending after the last match,\n * which is still ordered relative to everything it did recognise.\n */\nconst IMPORT_STATEMENT = /^import\\s[\\s\\S]*?from\\s+[\"']([^\"']+)[\"'];?$/gm;\n\n/**\n * Insert `statement` into `source` so the import block stays alphabetically\n * ordered by module specifier.\n *\n * @param source the file's current contents.\n * @param statement the whole import line to add, without a trailing newline.\n * @param specifier the module specifier `statement` imports from — passed\n * rather than re-parsed, because the caller already knows it\n * and a second parser is a second thing to disagree.\n * @returns the file contents with the import in place. A file with no imports\n * at all gets it at the top, which is the only ordering available.\n */\nexport function insertImportInSortedPosition(\n source: string,\n statement: string,\n specifier: string,\n): string {\n const matches = [...source.matchAll(IMPORT_STATEMENT)];\n\n if (matches.length === 0) {\n return `${statement}\\n${source}`;\n }\n\n const successor = matches.find((match) => (match[1] as string).localeCompare(specifier) > 0);\n\n if (successor) {\n return `${source.slice(0, successor.index)}${statement}\\n${source.slice(successor.index)}`;\n }\n\n const last = matches[matches.length - 1] as RegExpMatchArray;\n const endOfLast = (last.index as number) + last[0].length;\n\n return `${source.slice(0, endOfLast)}\\n${statement}${source.slice(endOfLast)}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAM,mBAAmB;;;;;;;;;;;;;AAczB,SAAgB,6BACd,QACA,WACA,WACQ;CACR,MAAM,UAAU,CAAC,GAAG,OAAO,SAAS,gBAAgB,CAAC;CAErD,IAAI,QAAQ,WAAW,GACrB,OAAO,GAAG,UAAU,IAAI;CAG1B,MAAM,YAAY,QAAQ,MAAM,UAAW,MAAM,EAAE,CAAY,cAAc,SAAS,IAAI,CAAC;CAE3F,IAAI,WACF,OAAO,GAAG,OAAO,MAAM,GAAG,UAAU,KAAK,IAAI,UAAU,IAAI,OAAO,MAAM,UAAU,KAAK;CAGzF,MAAM,OAAO,QAAQ,QAAQ,SAAS;CACtC,MAAM,YAAa,KAAK,QAAmB,KAAK,EAAE,CAAC;CAEnD,OAAO,GAAG,OAAO,MAAM,GAAG,SAAS,EAAE,IAAI,YAAY,OAAO,MAAM,SAAS;AAC7E"}
@@ -1 +1 @@
1
- {"version":3,"file":"paths.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/paths.ts"],"sourcesContent":["import path from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst __filename = fileURLToPath(import.meta.url);\n\nconst __dirname = path.dirname(__filename);\n\nexport type Template = \"warlock\";\n\nexport function template(templateName: Template): string {\n return path.resolve(__dirname, \"../\", \"../\", \"templates\", templateName);\n}\n\nexport function packageRoot(...paths: string[]): string {\n return path.resolve(__dirname, \"../\", \"../\", ...paths);\n}\n"],"mappings":";;;;AAGA,MAAM,aAAa,cAAc,OAAO,KAAK,GAAG;AAEhD,MAAM,YAAY,KAAK,QAAQ,UAAU;AAIzC,SAAgB,SAAS,cAAgC;CACvD,OAAO,KAAK,QAAQ,WAAW,OAAO,OAAO,aAAa,YAAY;AACxE;AAEA,SAAgB,YAAY,GAAG,OAAyB;CACtD,OAAO,KAAK,QAAQ,WAAW,OAAO,OAAO,GAAG,KAAK;AACvD"}
1
+ {"version":3,"file":"paths.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/paths.ts"],"sourcesContent":["import path from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst __filename = fileURLToPath(import.meta.url);\n\nconst __dirname = path.dirname(__filename);\n\nexport type Template = \"warlock\";\n\nexport function template(templateName: Template): string {\n return path.resolve(__dirname, \"../\", \"../\", \"templates\", templateName);\n}\n\nexport function packageRoot(...paths: string[]): string {\n return path.resolve(__dirname, \"../\", \"../\", ...paths);\n}\n"],"mappings":";;;;AAGA,MAAM,aAAa,cAAc,YAAY,GAAG;AAEhD,MAAM,YAAY,KAAK,QAAQ,UAAU;AAIzC,SAAgB,SAAS,cAAgC;CACvD,OAAO,KAAK,QAAQ,WAAW,OAAO,OAAO,aAAa,YAAY;AACxE;AAEA,SAAgB,YAAY,GAAG,OAAyB;CACtD,OAAO,KAAK,QAAQ,WAAW,OAAO,OAAO,GAAG,KAAK;AACvD"}
@@ -162,5 +162,5 @@ function resolveWarlockVersions(packages = templateWarlockDependencies(), ownVer
162
162
  }
163
163
 
164
164
  //#endregion
165
- export { fallbackRange, resolveWarlockVersions };
165
+ export { fallbackRange, resolveWarlockVersions, scaffolderVersion, templateWarlockDependencies };
166
166
  //# sourceMappingURL=warlock-versions.mjs.map
package/esm/index.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { CliFlags } from "./commands/create-new-app/types.mjs";
2
-
3
2
  //#region ../../packages/create-warlock/src/index.d.ts
4
3
  /**
5
4
  * Parse the scaffolder's own CLI flags for non-interactive mode.
@@ -7,14 +6,13 @@ import { CliFlags } from "./commands/create-new-app/types.mjs";
7
6
  * @example
8
7
  * create-warlock my-app --db=postgres --features=test,herald --ai=ai-openai,ai-anthropic --yes
9
8
  */
10
- declare function parseFlags(argv: string[]): CliFlags;
9
+ export declare function parseFlags(argv: string[]): CliFlags;
11
10
  /**
12
11
  * Read this package's own `version` field. A plain JSON read — no network
13
12
  * call, no write, nothing else touched — so `--version` stays a pure,
14
13
  * side-effect-free exit.
15
14
  */
16
- declare function packageVersion(): string;
17
- declare function createApp(): void;
15
+ export declare function packageVersion(): string;
16
+ export default function createApp(): void;
18
17
  //#endregion
19
- export { createApp as default, packageVersion, parseFlags };
20
18
  //# sourceMappingURL=index.d.mts.map
package/esm/index.mjs CHANGED
@@ -94,9 +94,7 @@ function parseFlags(argv) {
94
94
  flags.stack = value;
95
95
  break;
96
96
  case "interactive":
97
- case "customize":
98
- flags.interactive = true;
99
- break;
97
+ case "customize": flags.interactive = true;
100
98
  }
101
99
  }
102
100
  if (!flags.name && positionals.length > 0) flags.name = positionals[0];
package/esm/index.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../packages/create-warlock/src/index.ts"],"sourcesContent":["import { getJsonFile } from \"@warlock.js/fs\";\nimport createNewApp from \"./commands/create-new-app\";\nimport { CliFlags, Stack } from \"./commands/create-new-app/types\";\nimport { NO_DATABASE } from \"./features/database-drivers\";\nimport { buildHelpText } from \"./flags/definitions\";\nimport { packageRoot } from \"./helpers/paths\";\n\nconst valueFlags = [\"name\", \"db\", \"pm\", \"features\", \"ai\", \"agents\", \"stack\", \"lint\"];\n\nconst HELP_TEXT = buildHelpText();\n\n/**\n * Parse the scaffolder's own CLI flags for non-interactive mode.\n *\n * @example\n * create-warlock my-app --db=postgres --features=test,herald --ai=ai-openai,ai-anthropic --yes\n */\nexport function parseFlags(argv: string[]): CliFlags {\n const flags: CliFlags = {};\n const positionals: string[] = [];\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n\n if (!arg.startsWith(\"-\")) {\n positionals.push(arg);\n continue;\n }\n\n const equalIndex = arg.indexOf(\"=\");\n const key = (equalIndex === -1 ? arg : arg.slice(0, equalIndex)).replace(/^-+/, \"\");\n let value: string | undefined = equalIndex === -1 ? undefined : arg.slice(equalIndex + 1);\n\n // Value-taking flags may use either `--key=value` or `--key value`.\n if (valueFlags.includes(key) && value === undefined) {\n const next = argv[i + 1];\n\n if (next && !next.startsWith(\"-\")) {\n value = next;\n i++;\n }\n }\n\n switch (key) {\n case \"help\":\n case \"h\":\n flags.help = true;\n break;\n case \"version\":\n case \"v\":\n flags.version = true;\n break;\n case \"yes\":\n case \"y\":\n flags.yes = true;\n break;\n case \"git\":\n flags.git = true;\n break;\n case \"no-git\":\n flags.git = false;\n break;\n case \"jwt\":\n flags.jwt = true;\n break;\n case \"no-jwt\":\n flags.jwt = false;\n break;\n case \"name\":\n flags.name = value;\n break;\n case \"db\":\n flags.db = value;\n break;\n case \"no-db\":\n // Opt out of a database entirely — equivalent to `--db=none`.\n flags.db = NO_DATABASE;\n break;\n case \"pm\":\n // Keep an explicitly supplied but valueless `--pm` distinguishable\n // from omitting the flag. The command validator rejects this empty\n // value instead of silently selecting the preferred manager.\n flags.pm = value ?? \"\";\n break;\n case \"lint\":\n flags.lint = value ?? \"\";\n break;\n case \"features\":\n flags.features = splitList(value);\n break;\n case \"ai\":\n flags.ai = splitList(value);\n break;\n case \"agents\":\n flags.agents = splitList(value);\n break;\n case \"stack\":\n flags.stack = value as Stack;\n break;\n case \"interactive\":\n case \"customize\":\n flags.interactive = true;\n break;\n }\n }\n\n if (!flags.name && positionals.length > 0) {\n flags.name = positionals[0];\n }\n\n return flags;\n}\n\nfunction splitList(value: string | undefined): string[] {\n if (!value) return [];\n\n return value\n .split(\",\")\n .map((item) => item.trim())\n .filter(Boolean);\n}\n\n/**\n * Read this package's own `version` field. A plain JSON read — no network\n * call, no write, nothing else touched — so `--version` stays a pure,\n * side-effect-free exit.\n */\nexport function packageVersion(): string {\n return (getJsonFile(packageRoot(\"package.json\")) as { version: string }).version;\n}\n\nexport default function createApp() {\n const flags = parseFlags(process.argv.slice(2));\n\n // `--help` and `--version` must exit before anything that touches the\n // filesystem, the network, or a prompt — they win over every other flag,\n // including a positional project name.\n if (flags.help) {\n console.log(HELP_TEXT);\n process.exit(0);\n return;\n }\n\n if (flags.version) {\n console.log(packageVersion());\n process.exit(0);\n return;\n }\n\n // An unexpected throw must surface as a readable error AND a non-zero exit\n // code — never as a stack trace the user scrolls past on the way to a green\n // banner (there is no banner after this point).\n Promise.resolve(createNewApp(flags)).catch((error: unknown) => {\n console.error();\n console.error(` create-warlock failed: ${(error as Error)?.message ?? String(error)}`);\n console.error();\n\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;;AAOA,MAAM,aAAa;CAAC;CAAQ;CAAM;CAAM;CAAY;CAAM;CAAU;CAAS;AAAM;AAEnF,MAAM,YAAY,cAAc;;;;;;;AAQhC,SAAgB,WAAW,MAA0B;CACnD,MAAM,QAAkB,CAAC;CACzB,MAAM,cAAwB,CAAC;CAE/B,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EAEjB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG;GACxB,YAAY,KAAK,GAAG;GACpB;EACF;EAEA,MAAM,aAAa,IAAI,QAAQ,GAAG;EAClC,MAAM,OAAO,eAAe,KAAK,MAAM,IAAI,MAAM,GAAG,UAAU,EAAC,CAAE,QAAQ,OAAO,EAAE;EAClF,IAAI,QAA4B,eAAe,KAAK,SAAY,IAAI,MAAM,aAAa,CAAC;EAGxF,IAAI,WAAW,SAAS,GAAG,KAAK,UAAU,QAAW;GACnD,MAAM,OAAO,KAAK,IAAI;GAEtB,IAAI,QAAQ,CAAC,KAAK,WAAW,GAAG,GAAG;IACjC,QAAQ;IACR;GACF;EACF;EAEA,QAAQ,KAAR;GACE,KAAK;GACL,KAAK;IACH,MAAM,OAAO;IACb;GACF,KAAK;GACL,KAAK;IACH,MAAM,UAAU;IAChB;GACF,KAAK;GACL,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,OAAO;IACb;GACF,KAAK;IACH,MAAM,KAAK;IACX;GACF,KAAK;IAEH,MAAM,KAAK;IACX;GACF,KAAK;IAIH,MAAM,KAAK,SAAS;IACpB;GACF,KAAK;IACH,MAAM,OAAO,SAAS;IACtB;GACF,KAAK;IACH,MAAM,WAAW,UAAU,KAAK;IAChC;GACF,KAAK;IACH,MAAM,KAAK,UAAU,KAAK;IAC1B;GACF,KAAK;IACH,MAAM,SAAS,UAAU,KAAK;IAC9B;GACF,KAAK;IACH,MAAM,QAAQ;IACd;GACF,KAAK;GACL,KAAK;IACH,MAAM,cAAc;IACpB;EACJ;CACF;CAEA,IAAI,CAAC,MAAM,QAAQ,YAAY,SAAS,GACtC,MAAM,OAAO,YAAY;CAG3B,OAAO;AACT;AAEA,SAAS,UAAU,OAAqC;CACtD,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,OAAO,MACJ,MAAM,GAAG,CAAC,CACV,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAC1B,OAAO,OAAO;AACnB;;;;;;AAOA,SAAgB,iBAAyB;CACvC,OAAQ,YAAY,YAAY,cAAc,CAAC,CAAC,CAAyB;AAC3E;AAEA,SAAwB,YAAY;CAClC,MAAM,QAAQ,WAAW,QAAQ,KAAK,MAAM,CAAC,CAAC;CAK9C,IAAI,MAAM,MAAM;EACd,QAAQ,IAAI,SAAS;EACrB,QAAQ,KAAK,CAAC;EACd;CACF;CAEA,IAAI,MAAM,SAAS;EACjB,QAAQ,IAAI,eAAe,CAAC;EAC5B,QAAQ,KAAK,CAAC;EACd;CACF;CAKA,QAAQ,QAAQ,aAAa,KAAK,CAAC,CAAC,CAAC,OAAO,UAAmB;EAC7D,QAAQ,MAAM;EACd,QAAQ,MAAM,4BAA6B,OAAiB,WAAW,OAAO,KAAK,GAAG;EACtF,QAAQ,MAAM;EAEd,QAAQ,KAAK,CAAC;CAChB,CAAC;AACH"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../packages/create-warlock/src/index.ts"],"sourcesContent":["import { getJsonFile } from \"@warlock.js/fs\";\nimport createNewApp from \"./commands/create-new-app\";\nimport { CliFlags, Stack } from \"./commands/create-new-app/types\";\nimport { NO_DATABASE } from \"./features/database-drivers\";\nimport { buildHelpText } from \"./flags/definitions\";\nimport { packageRoot } from \"./helpers/paths\";\n\nconst valueFlags = [\"name\", \"db\", \"pm\", \"features\", \"ai\", \"agents\", \"stack\", \"lint\"];\n\nconst HELP_TEXT = buildHelpText();\n\n/**\n * Parse the scaffolder's own CLI flags for non-interactive mode.\n *\n * @example\n * create-warlock my-app --db=postgres --features=test,herald --ai=ai-openai,ai-anthropic --yes\n */\nexport function parseFlags(argv: string[]): CliFlags {\n const flags: CliFlags = {};\n const positionals: string[] = [];\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n\n if (!arg.startsWith(\"-\")) {\n positionals.push(arg);\n continue;\n }\n\n const equalIndex = arg.indexOf(\"=\");\n const key = (equalIndex === -1 ? arg : arg.slice(0, equalIndex)).replace(/^-+/, \"\");\n let value: string | undefined = equalIndex === -1 ? undefined : arg.slice(equalIndex + 1);\n\n // Value-taking flags may use either `--key=value` or `--key value`.\n if (valueFlags.includes(key) && value === undefined) {\n const next = argv[i + 1];\n\n if (next && !next.startsWith(\"-\")) {\n value = next;\n i++;\n }\n }\n\n switch (key) {\n case \"help\":\n case \"h\":\n flags.help = true;\n break;\n case \"version\":\n case \"v\":\n flags.version = true;\n break;\n case \"yes\":\n case \"y\":\n flags.yes = true;\n break;\n case \"git\":\n flags.git = true;\n break;\n case \"no-git\":\n flags.git = false;\n break;\n case \"jwt\":\n flags.jwt = true;\n break;\n case \"no-jwt\":\n flags.jwt = false;\n break;\n case \"name\":\n flags.name = value;\n break;\n case \"db\":\n flags.db = value;\n break;\n case \"no-db\":\n // Opt out of a database entirely — equivalent to `--db=none`.\n flags.db = NO_DATABASE;\n break;\n case \"pm\":\n // Keep an explicitly supplied but valueless `--pm` distinguishable\n // from omitting the flag. The command validator rejects this empty\n // value instead of silently selecting the preferred manager.\n flags.pm = value ?? \"\";\n break;\n case \"lint\":\n flags.lint = value ?? \"\";\n break;\n case \"features\":\n flags.features = splitList(value);\n break;\n case \"ai\":\n flags.ai = splitList(value);\n break;\n case \"agents\":\n flags.agents = splitList(value);\n break;\n case \"stack\":\n flags.stack = value as Stack;\n break;\n case \"interactive\":\n case \"customize\":\n flags.interactive = true;\n break;\n }\n }\n\n if (!flags.name && positionals.length > 0) {\n flags.name = positionals[0];\n }\n\n return flags;\n}\n\nfunction splitList(value: string | undefined): string[] {\n if (!value) return [];\n\n return value\n .split(\",\")\n .map((item) => item.trim())\n .filter(Boolean);\n}\n\n/**\n * Read this package's own `version` field. A plain JSON read — no network\n * call, no write, nothing else touched — so `--version` stays a pure,\n * side-effect-free exit.\n */\nexport function packageVersion(): string {\n return (getJsonFile(packageRoot(\"package.json\")) as { version: string }).version;\n}\n\nexport default function createApp() {\n const flags = parseFlags(process.argv.slice(2));\n\n // `--help` and `--version` must exit before anything that touches the\n // filesystem, the network, or a prompt — they win over every other flag,\n // including a positional project name.\n if (flags.help) {\n console.log(HELP_TEXT);\n process.exit(0);\n return;\n }\n\n if (flags.version) {\n console.log(packageVersion());\n process.exit(0);\n return;\n }\n\n // An unexpected throw must surface as a readable error AND a non-zero exit\n // code — never as a stack trace the user scrolls past on the way to a green\n // banner (there is no banner after this point).\n Promise.resolve(createNewApp(flags)).catch((error: unknown) => {\n console.error();\n console.error(` create-warlock failed: ${(error as Error)?.message ?? String(error)}`);\n console.error();\n\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;;AAOA,MAAM,aAAa;CAAC;CAAQ;CAAM;CAAM;CAAY;CAAM;CAAU;CAAS;AAAM;AAEnF,MAAM,YAAY,cAAc;;;;;;;AAQhC,SAAgB,WAAW,MAA0B;CACnD,MAAM,QAAkB,CAAC;CACzB,MAAM,cAAwB,CAAC;CAE/B,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EAEjB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG;GACxB,YAAY,KAAK,GAAG;GACpB;EACF;EAEA,MAAM,aAAa,IAAI,QAAQ,GAAG;EAClC,MAAM,OAAO,eAAe,KAAK,MAAM,IAAI,MAAM,GAAG,UAAU,EAAC,CAAE,QAAQ,OAAO,EAAE;EAClF,IAAI,QAA4B,eAAe,KAAK,SAAY,IAAI,MAAM,aAAa,CAAC;EAGxF,IAAI,WAAW,SAAS,GAAG,KAAK,UAAU,QAAW;GACnD,MAAM,OAAO,KAAK,IAAI;GAEtB,IAAI,QAAQ,CAAC,KAAK,WAAW,GAAG,GAAG;IACjC,QAAQ;IACR;GACF;EACF;EAEA,QAAQ,KAAR;GACE,KAAK;GACL,KAAK;IACH,MAAM,OAAO;IACb;GACF,KAAK;GACL,KAAK;IACH,MAAM,UAAU;IAChB;GACF,KAAK;GACL,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,OAAO;IACb;GACF,KAAK;IACH,MAAM,KAAK;IACX;GACF,KAAK;IAEH,MAAM,KAAK;IACX;GACF,KAAK;IAIH,MAAM,KAAK,SAAS;IACpB;GACF,KAAK;IACH,MAAM,OAAO,SAAS;IACtB;GACF,KAAK;IACH,MAAM,WAAW,UAAU,KAAK;IAChC;GACF,KAAK;IACH,MAAM,KAAK,UAAU,KAAK;IAC1B;GACF,KAAK;IACH,MAAM,SAAS,UAAU,KAAK;IAC9B;GACF,KAAK;IACH,MAAM,QAAQ;IACd;GACF,KAAK;GACL,KAAK,aACH,MAAM,cAAc;EAExB;CACF;CAEA,IAAI,CAAC,MAAM,QAAQ,YAAY,SAAS,GACtC,MAAM,OAAO,YAAY;CAG3B,OAAO;AACT;AAEA,SAAS,UAAU,OAAqC;CACtD,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,OAAO,MACJ,MAAM,GAAG,CAAC,CACV,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAC1B,OAAO,OAAO;AACnB;;;;;;AAOA,SAAgB,iBAAyB;CACvC,OAAQ,YAAY,YAAY,cAAc,CAAC,CAAC,CAAyB;AAC3E;AAEA,SAAwB,YAAY;CAClC,MAAM,QAAQ,WAAW,QAAQ,KAAK,MAAM,CAAC,CAAC;CAK9C,IAAI,MAAM,MAAM;EACd,QAAQ,IAAI,SAAS;EACrB,QAAQ,KAAK,CAAC;EACd;CACF;CAEA,IAAI,MAAM,SAAS;EACjB,QAAQ,IAAI,eAAe,CAAC;EAC5B,QAAQ,KAAK,CAAC;EACd;CACF;CAKA,QAAQ,QAAQ,aAAa,KAAK,CAAC,CAAC,CAAC,OAAO,UAAmB;EAC7D,QAAQ,MAAM;EACd,QAAQ,MAAM,4BAA6B,OAAiB,WAAW,OAAO,KAAK,GAAG;EACtF,QAAQ,MAAM;EAEd,QAAQ,KAAK,CAAC;CAChB,CAAC;AACH"}
@@ -2,12 +2,12 @@ import { select } from "@clack/prompts";
2
2
 
3
3
  //#region ../../packages/create-warlock/src/prompts/ask-lint-toolchain.ts
4
4
  /** The full wizard offers the same toolchain choice as --lint. */
5
- function askLintToolchain(initialValue = "eslint") {
5
+ function askLintToolchain(initialValue = "oxc") {
6
6
  return select({
7
7
  message: "Choose lint and formatting tools",
8
8
  options: [{
9
9
  value: "eslint",
10
- label: "ESLint + Prettier"
10
+ label: "ESLint + Prettier (deprecated)"
11
11
  }, {
12
12
  value: "oxc",
13
13
  label: "Oxlint + Oxfmt"
@@ -1 +1 @@
1
- {"version":3,"file":"ask-lint-toolchain.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/prompts/ask-lint-toolchain.ts"],"sourcesContent":["import { select } from \"@clack/prompts\";\nimport type { LintToolchain } from \"../commands/create-new-app/types\";\n\n/** The full wizard offers the same toolchain choice as --lint. */\nexport function askLintToolchain(initialValue: LintToolchain = \"eslint\") {\n return select<LintToolchain>({\n message: \"Choose lint and formatting tools\",\n options: [\n { value: \"eslint\", label: \"ESLint + Prettier\" },\n { value: \"oxc\", label: \"Oxlint + Oxfmt\" },\n ],\n initialValue,\n });\n}\n"],"mappings":";;;;AAIA,SAAgB,iBAAiB,eAA8B,UAAU;CACvE,OAAO,OAAsB;EAC3B,SAAS;EACT,SAAS,CACP;GAAE,OAAO;GAAU,OAAO;EAAoB,GAC9C;GAAE,OAAO;GAAO,OAAO;EAAiB,CAC1C;EACA;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"ask-lint-toolchain.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/prompts/ask-lint-toolchain.ts"],"sourcesContent":["import { select } from \"@clack/prompts\";\nimport type { LintToolchain } from \"../commands/create-new-app/types\";\n\n/** The full wizard offers the same toolchain choice as --lint. */\nexport function askLintToolchain(initialValue: LintToolchain = \"oxc\") {\n return select<LintToolchain>({\n message: \"Choose lint and formatting tools\",\n options: [\n { value: \"eslint\", label: \"ESLint + Prettier (deprecated)\" },\n { value: \"oxc\", label: \"Oxlint + Oxfmt\" },\n ],\n initialValue,\n });\n}\n"],"mappings":";;;;AAIA,SAAgB,iBAAiB,eAA8B,OAAO;CACpE,OAAO,OAAsB;EAC3B,SAAS;EACT,SAAS,CACP;GAAE,OAAO;GAAU,OAAO;EAAiC,GAC3D;GAAE,OAAO;GAAO,OAAO;EAAiB,CAC1C;EACA;CACF,CAAC;AACH"}
package/esm/summary.mjs CHANGED
@@ -12,7 +12,7 @@ function printDecisionsSummary({ packageManager, options }) {
12
12
  const headline = [
13
13
  `Using ${packageManager}`,
14
14
  `database: ${databaseLabel}`,
15
- `lint: ${options.lint ?? "eslint"}`,
15
+ `lint: ${options.lint ?? "oxc"}`,
16
16
  `agent-kit targets: ${options.agents.join(", ")}`
17
17
  ].join(" · ");
18
18
  console.log();
@@ -31,5 +31,5 @@ function summarizeFeatures(keys) {
31
31
  }
32
32
 
33
33
  //#endregion
34
- export { printDecisionsSummary };
34
+ export { printDecisionsSummary, summarizeFeatures };
35
35
  //# sourceMappingURL=summary.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"summary.mjs","names":[],"sources":["../../../../../../packages/create-warlock/src/summary.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { AppOptions } from \"./commands/create-new-app/types\";\nimport { getDatabaseLabel } from \"./features/database-drivers\";\n\n/** What decisions.spec.ts / the terminal actually needs to render the summary. */\nexport type Decisions = {\n packageManager: string;\n options: AppOptions;\n};\n\n/**\n * Print what the scaffolder decided on the user's behalf, and how to change\n * each decision — required because almost every choice is now a default\n * rather than a prompt, so this is the only place a user sees them.\n */\nexport function printDecisionsSummary({ packageManager, options }: Decisions): void {\n const databaseLabel = getDatabaseLabel(options.databaseDriver);\n\n const headline = [\n `Using ${packageManager}`,\n `database: ${databaseLabel}`,\n `lint: ${options.lint ?? \"eslint\"}`,\n `agent-kit targets: ${options.agents.join(\", \")}`,\n ].join(\" · \");\n\n console.log();\n console.log(colors.cyan(headline));\n console.log(colors.cyan(summarizeFeatures([...options.features, ...options.aiProviders])));\n console.log(\n colors.dim(\n \" change these — package manager: --pm, database: --db/--no-db, agents: --agents, \" +\n \"features: --features, ai: --ai, lint: --lint, git: --git/--no-git, jwt: --jwt/--no-jwt — \" +\n \"or re-run with --interactive for the full wizard. Package-manager-level choices \" +\n \"(husky, lint config, etc.) can also be edited directly in the generated project.\",\n ),\n );\n console.log();\n}\n\n/** Keep the decision overview short; the final success screen lists every key. */\nexport function summarizeFeatures(keys: readonly string[]): string {\n const selected = [...new Set(keys)];\n if (selected.length === 0) return \"Features: none\";\n const preview = selected.slice(0, 6).join(\", \");\n const remaining = selected.length - 6;\n return `Features (${selected.length}): ${preview}${remaining > 0 ? `, +${remaining} more` : \"\"}`;\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,sBAAsB,EAAE,gBAAgB,WAA4B;CAClF,MAAM,gBAAgB,iBAAiB,QAAQ,cAAc;CAE7D,MAAM,WAAW;EACf,SAAS;EACT,aAAa;EACb,SAAS,QAAQ,QAAQ;EACzB,sBAAsB,QAAQ,OAAO,KAAK,IAAI;CAChD,CAAC,CAAC,KAAK,KAAK;CAEZ,QAAQ,IAAI;CACZ,QAAQ,IAAI,OAAO,KAAK,QAAQ,CAAC;CACjC,QAAQ,IAAI,OAAO,KAAK,kBAAkB,CAAC,GAAG,QAAQ,UAAU,GAAG,QAAQ,WAAW,CAAC,CAAC,CAAC;CACzF,QAAQ,IACN,OAAO,IACL,6UAIF,CACF;CACA,QAAQ,IAAI;AACd;;AAGA,SAAgB,kBAAkB,MAAiC;CACjE,MAAM,WAAW,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC;CAClC,IAAI,SAAS,WAAW,GAAG,OAAO;CAClC,MAAM,UAAU,SAAS,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;CAC9C,MAAM,YAAY,SAAS,SAAS;CACpC,OAAO,aAAa,SAAS,OAAO,KAAK,UAAU,YAAY,IAAI,MAAM,UAAU,SAAS;AAC9F"}
1
+ {"version":3,"file":"summary.mjs","names":[],"sources":["../../../../../../packages/create-warlock/src/summary.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { AppOptions } from \"./commands/create-new-app/types\";\nimport { getDatabaseLabel } from \"./features/database-drivers\";\n\n/** What decisions.spec.ts / the terminal actually needs to render the summary. */\nexport type Decisions = {\n packageManager: string;\n options: AppOptions;\n};\n\n/**\n * Print what the scaffolder decided on the user's behalf, and how to change\n * each decision — required because almost every choice is now a default\n * rather than a prompt, so this is the only place a user sees them.\n */\nexport function printDecisionsSummary({ packageManager, options }: Decisions): void {\n const databaseLabel = getDatabaseLabel(options.databaseDriver);\n\n const headline = [\n `Using ${packageManager}`,\n `database: ${databaseLabel}`,\n `lint: ${options.lint ?? \"oxc\"}`,\n `agent-kit targets: ${options.agents.join(\", \")}`,\n ].join(\" · \");\n\n console.log();\n console.log(colors.cyan(headline));\n console.log(colors.cyan(summarizeFeatures([...options.features, ...options.aiProviders])));\n console.log(\n colors.dim(\n \" change these — package manager: --pm, database: --db/--no-db, agents: --agents, \" +\n \"features: --features, ai: --ai, lint: --lint, git: --git/--no-git, jwt: --jwt/--no-jwt — \" +\n \"or re-run with --interactive for the full wizard. Package-manager-level choices \" +\n \"(husky, lint config, etc.) can also be edited directly in the generated project.\",\n ),\n );\n console.log();\n}\n\n/** Keep the decision overview short; the final success screen lists every key. */\nexport function summarizeFeatures(keys: readonly string[]): string {\n const selected = [...new Set(keys)];\n if (selected.length === 0) return \"Features: none\";\n const preview = selected.slice(0, 6).join(\", \");\n const remaining = selected.length - 6;\n return `Features (${selected.length}): ${preview}${remaining > 0 ? `, +${remaining} more` : \"\"}`;\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,sBAAsB,EAAE,gBAAgB,WAA4B;CAClF,MAAM,gBAAgB,iBAAiB,QAAQ,cAAc;CAE7D,MAAM,WAAW;EACf,SAAS;EACT,aAAa;EACb,SAAS,QAAQ,QAAQ;EACzB,sBAAsB,QAAQ,OAAO,KAAK,IAAI;CAChD,CAAC,CAAC,KAAK,KAAK;CAEZ,QAAQ,IAAI;CACZ,QAAQ,IAAI,OAAO,KAAK,QAAQ,CAAC;CACjC,QAAQ,IAAI,OAAO,KAAK,kBAAkB,CAAC,GAAG,QAAQ,UAAU,GAAG,QAAQ,WAAW,CAAC,CAAC,CAAC;CACzF,QAAQ,IACN,OAAO,IACL,6UAIF,CACF;CACA,QAAQ,IAAI;AACd;;AAGA,SAAgB,kBAAkB,MAAiC;CACjE,MAAM,WAAW,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC;CAClC,IAAI,SAAS,WAAW,GAAG,OAAO;CAClC,MAAM,UAAU,SAAS,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;CAC9C,MAAM,YAAY,SAAS,SAAS;CACpC,OAAO,aAAa,SAAS,OAAO,KAAK,UAAU,YAAY,IAAI,MAAM,UAAU,SAAS;AAC9F"}
package/esm/ui/banner.mjs CHANGED
@@ -190,5 +190,5 @@ function showSuccessScreen(options) {
190
190
  }
191
191
 
192
192
  //#endregion
193
- export { showIntroBanner, showSuccessScreen };
193
+ export { getIntroBanner, getSuccessScreen, showIntroBanner, showSuccessScreen, warlockLogoAnsi, warlockTextAnsi };
194
194
  //# sourceMappingURL=banner.mjs.map
package/llms-full.txt CHANGED
@@ -63,7 +63,7 @@ Because almost every choice defaults silently, `create-warlock` prints what it
63
63
  decided right after scaffolding, e.g.:
64
64
 
65
65
  ```
66
- Using npm · database: none · lint: eslint · agent-kit targets: claude
66
+ Using npm · database: none · lint: oxc · agent-kit targets: claude
67
67
  Features: none
68
68
  change these — package manager: --pm, database: --db/--no-db, agents: --agents,
69
69
  features: --features, ai: --ai, lint: --lint, git: --git/--no-git, jwt: --jwt/--no-jwt — or
@@ -80,7 +80,7 @@ Features: none
80
80
  | `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
81
81
  | `--pm` | yes | inferred (`npm_config_user_agent` / system) | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
82
82
  | `--features` | yes (CSV) | none, or `["web"]` when `--stack=web` and `--features` is not given | Comma-separated optional feature keys. An explicit `--features` always wins over the `--stack` default. |
83
- | `--lint` | yes | `eslint` | `oxc` (Oxlint/Oxfmt) or `eslint` (ESLint/Prettier); unknown or missing values fail before writes. |
83
+ | `--lint` | yes | `oxc` | `oxc` (Oxlint/Oxfmt) or `eslint` (ESLint/Prettier, deprecated); unknown or missing values fail before writes. |
84
84
  | `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
85
85
  | `--agents` | yes (CSV) | `claude` | Comma-separated `agent-kit` targets — see below. |
86
86
  | `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit). |
@@ -209,13 +209,18 @@ a total count; the success screen shows the complete list.
209
209
 
210
210
  ## Choose the linter and formatter
211
211
 
212
- `--lint=eslint` is the default. `--lint=oxc` (also `--lint oxc`) installs exact
212
+ `--lint=oxc` is the default. It also accepts `--lint oxc` and installs exact
213
213
  `oxlint@1.88.0` and `oxfmt@0.73.0` versions, emits `.oxlintrc.json` and
214
214
  `.oxfmtrc.json`, and removes the template's ESLint/Prettier configs and dependencies.
215
- The custom wizard prompts for the same choice; a flag preselects it.
215
+ Pass `--lint=eslint` to opt in to ESLint/Prettier. The custom wizard prompts for
216
+ the same choice; a flag preselects it.
216
217
 
217
218
  ```bash
218
- npm create warlock@latest my-app -- --lint=oxc --yes
219
+ # Default: Oxlint + Oxfmt, TypeScript 7.0.2
220
+ npm create warlock@latest my-app -- --yes
221
+
222
+ # Explicit opt-in: ESLint + Prettier, TypeScript 6.0.3
223
+ npm create warlock@latest my-eslint-app -- --lint=eslint --yes
219
224
  ```
220
225
 
221
226
  Both choices provide `lint`/`format` and non-mutating `lint:check`/`format:check`
@@ -226,15 +231,79 @@ Oxc config enables `devServer.healthCheckers: { eslint: false, oxlint: true }` a
226
231
  leaves TypeScript health checks enabled. Style matches the template's 100-column,
227
232
  double-quote, semicolon and trailing-comma settings. Import sorting is on in
228
233
  `.oxfmtrc.json`, kept to one block like the ESLint toolchain's
229
- `prettier-plugin-organize-imports`:
234
+ `@ianvs/prettier-plugin-sort-imports`:
230
235
 
231
236
  ```json
232
- { "sortImports": { "newlinesBetween": false } }
237
+ { "sortImports": { "newlinesBetween": false, "internalPattern": ["app/", "web/", "@shared/"] } }
233
238
  ```
234
239
 
235
- `format` (`oxfmt --write ./src`) orders built-ins, then packages, then relative
236
- imports. Oxfmt does not delete unused imports, so remove them from Oxlint's
237
- `no-unused-vars` diagnostics. Scripts in both toolchains call local binaries
240
+ `format` (`npm run format`) orders built-ins, packages, internal aliases,
241
+ then relative imports. Neither formatter deletes unused imports. ESLint reports
242
+ them through `unused-imports/no-unused-imports`; Oxlint uses `no-unused-vars`.
243
+ Scripts in both toolchains call local binaries
238
244
  (`eslint --fix ./src --max-warnings=0`, `tsc --noEmit`), never `npx`.
239
245
 
246
+ ## TypeScript 7 in 5.32
247
+
248
+ Default Oxlint/Oxfmt projects pin `typescript` to `7.0.2`; `tsc` is the native Go compiler.
249
+ The template uses `module: "ESNext"`, `moduleResolution: "bundler"`, explicit
250
+ Node types, and `paths` relative to its tsconfig, without `baseUrl`.
251
+ Existing apps must remove `baseUrl`, rebase alias targets, and replace
252
+ `moduleResolution: "node"` / `"node10"` before typechecking.
253
+
254
+ Explicit `--lint=eslint` projects pin `typescript` to **6.0.3**, the newest version
255
+ supported by typescript-eslint. Its parser requires the JavaScript compiler API
256
+ removed in TypeScript 7, and its declared peer range is `>=4.8.4 <6.1.0`
257
+ ([parser package metadata](https://registry.npmjs.org/@typescript-eslint/parser/8.71.2)).
258
+ Both modes use the same tsconfig settings above, which are valid in TypeScript 6.
259
+ ESLint mode prints a deprecation warning naming removal in 5.34.0 and the Oxc migration command. Both modes install with every supported package manager.
260
+
261
+ ### Prettier import order in ESLint mode
262
+
263
+ The ESLint scaffold’s `@ianvs/prettier-plugin-sort-imports` uses Babel instead of
264
+ the compiler API. Keep these options in `.prettierrc.json`:
265
+
266
+ ```json
267
+ {
268
+ "plugins": ["@ianvs/prettier-plugin-sort-imports"],
269
+ "importOrder": ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "^(app|web|@shared)/", "^[.]"],
270
+ "importOrderParserPlugins": ["typescript", "jsx", "decorators"]
271
+ }
272
+ ```
273
+
274
+ Leave `importOrderTypeScriptVersion` at its default to keep type and value imports
275
+ separate. Run `npm run format`, then `npm run lint:check`; formatting preserves
276
+ unused imports, and `npm run lint` can apply ESLint’s unused-import fixes.
277
+
278
+ ## Migrate to Oxlint and Oxfmt in 5.33
279
+
280
+ ESLint + Prettier are deprecated in 5.33.0 and will be removed in **5.34.0**.
281
+ New apps should use `--lint=oxc`. To migrate an existing app, install the new
282
+ core release and `ts-morph`, then run:
283
+
284
+ ```bash
285
+ npx warlock upgrade --from 5.32 --to 5.33.0 --dry-run
286
+ npx warlock upgrade --from 5.32 --to 5.33.0
287
+ npm install
288
+ npm run format
289
+ npm run lint:check
290
+ npx warlock check
291
+ ```
292
+
293
+ Review every reported `file:line`: customized configs are preserved and need
294
+ manual translation before removal. The codemod installs the template's Oxlint
295
+ and Oxfmt versions and pins TypeScript to 7.0.2. It replaces standard lint/format
296
+ scripts, removes legacy dependencies, and deletes only recognized generated
297
+ configs. A generated `.prettierignore` becomes `.oxfmtignore`. Existing Oxc
298
+ configs are preserved. `--dry-run --json` reports the same planned changes
299
+ without writing files.
300
+
301
+ Explicit deprecated ESLint scaffolds set `devServer.healthCheckers.eslint: true`;
302
+ existing apps must set this themselves to retain background ESLint checking.
303
+
304
+ Oxc formatting scripts explicitly read `.gitignore` and `.oxfmtignore`. Oxfmt
305
+ 0.73 otherwise defaults to `.prettierignore` (`oxfmt --help`); the explicit
306
+ paths make migrated ignore rules effective. The package template format gate
307
+ also defaults to Oxfmt; `--lint=eslint` is its deprecated compatibility mode.
308
+
240
309