create-warlock 5.32.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to `create-warlock` are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
6
 
7
+ ## 5.33.0 - 2026-10-19
8
+
9
+ ### Changed
10
+
11
+ - Label the explicit ESLint/Prettier prompt choice, flag help and docs as deprecated; retain TypeScript 6.0.3 compatibility and explicitly enable its dev-server checker. Package development scripts use root Oxlint/Oxfmt, and the template formatting gate defaults to Oxfmt. Oxc scripts explicitly select `.oxfmtignore`.
12
+
13
+ ### Deprecated
14
+
15
+ - `--lint=eslint` prints a clear removal warning for 5.34.0, recommends `--lint=oxc` and names the existing-app migration command. Explicit Prettier compatibility scripts also warn.
16
+
7
17
  ## 5.32.0 - 2026-10-18
8
18
 
9
19
  ### Changed
package/README.md CHANGED
@@ -34,22 +34,22 @@ answered that choice. The stack is the only choice that changes the generated
34
34
  app's shape. Everything else below is a flag with a default; a summary of what
35
35
  was decided prints after scaffolding, along with how to change each one.
36
36
 
37
- | Flag | Description | Default |
38
- | ------------------------------ | -------------------------------------------------------------------- | ------------------------------------------------------------------------ |
39
- | `--name=<name>` | Project name (or the first positional arg) | required — prompted with a TTY, otherwise fails naming this flag |
40
- | `--stack=<api\|web>` | API-only or full-stack web; the structural choice | `api` |
41
- | `--db=<driver>` / `--no-db` | Database driver (`postgres`, `mongodb`, …), or skip one entirely | `mongodb` |
42
- | `--features=<list>` | Comma-separated feature keys (e.g. `test,herald`) | none (or `["web"]` when `--stack=web` and `--features` is not given) |
43
- | `--lint=<oxc\|eslint>` | Development lint/format toolchain: Oxlint/Oxfmt or ESLint/Prettier | `oxc` |
44
- | `--ai=<list>` | Comma-separated AI provider keys (e.g. `openai,anthropic`) | none |
45
- | `--pm=<manager>` | Package manager (`npm`, `yarn`, `pnpm`, `bun`) | inferred from the invoking agent (`npm_config_user_agent`) or the system |
46
- | `--agents=<list>` | Comma-separated [agent-kit](https://mongez.js.org/agent-kit) targets | `claude` |
47
- | `--git` / `--no-git` | Force-enable or force-disable git initialization | `false` |
48
- | `--jwt` / `--no-jwt` | Force-enable or force-disable JWT secret generation | `false` |
49
- | `-y`, `--yes` | Skip every prompt and take defaults for anything unset | `false` |
50
- | `--interactive`, `--customize` | Restore the full long-form interactive wizard | `false` |
51
- | `-h`, `--help` | Print usage (every flag + its default) and exit | — |
52
- | `-v`, `--version` | Print the installed version and exit | — |
37
+ | Flag | Description | Default |
38
+ | ------------------------------ | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
39
+ | `--name=<name>` | Project name (or the first positional arg) | required — prompted with a TTY, otherwise fails naming this flag |
40
+ | `--stack=<api\|web>` | API-only or full-stack web; the structural choice | `api` |
41
+ | `--db=<driver>` / `--no-db` | Database driver (`postgres`, `mongodb`, …), or skip one entirely | `mongodb` |
42
+ | `--features=<list>` | Comma-separated feature keys (e.g. `test,herald`) | none (or `["web"]` when `--stack=web` and `--features` is not given) |
43
+ | `--lint=<oxc\|eslint>` | Development lint/format toolchain: Oxlint/Oxfmt or ESLint/Prettier (deprecated) | `oxc` |
44
+ | `--ai=<list>` | Comma-separated AI provider keys (e.g. `openai,anthropic`) | none |
45
+ | `--pm=<manager>` | Package manager (`npm`, `yarn`, `pnpm`, `bun`) | inferred from the invoking agent (`npm_config_user_agent`) or the system |
46
+ | `--agents=<list>` | Comma-separated [agent-kit](https://mongez.js.org/agent-kit) targets | `claude` |
47
+ | `--git` / `--no-git` | Force-enable or force-disable git initialization | `false` |
48
+ | `--jwt` / `--no-jwt` | Force-enable or force-disable JWT secret generation | `false` |
49
+ | `-y`, `--yes` | Skip every prompt and take defaults for anything unset | `false` |
50
+ | `--interactive`, `--customize` | Restore the full long-form interactive wizard | `false` |
51
+ | `-h`, `--help` | Print usage (every flag + its default) and exit | — |
52
+ | `-v`, `--version` | Print the installed version and exit | — |
53
53
 
54
54
  ```bash
55
55
  create-warlock my-app --db=postgres --features=test,herald --yes
@@ -225,5 +225,35 @@ supported by typescript-eslint. Its parser requires the JavaScript compiler API
225
225
  removed in TypeScript 7, and its declared peer range is `>=4.8.4 <6.1.0`
226
226
  ([parser package metadata](https://registry.npmjs.org/@typescript-eslint/parser/8.71.2)).
227
227
  Both modes use the same tsconfig settings above, which are valid in TypeScript 6.
228
- ESLint mode prints a compatibility notice when chosen and is planned for
229
- deprecation in 5.33 and removal in 5.34. Both modes install with npm, pnpm and yarn.
228
+ ESLint mode prints a deprecation warning naming removal in 5.34.0 and the Oxc migration command. Both modes install with npm, pnpm and yarn.
229
+
230
+ ## Migrate to Oxlint and Oxfmt in 5.33
231
+
232
+ ESLint + Prettier are deprecated in 5.33.0 and will be removed in **5.34.0**.
233
+ New apps should use `--lint=oxc`. To migrate an existing app, install the new
234
+ core release and `ts-morph`, then run:
235
+
236
+ ```bash
237
+ npx warlock upgrade --from 5.32 --to 5.33.0 --dry-run
238
+ npx warlock upgrade --from 5.32 --to 5.33.0
239
+ npm install
240
+ npm run format
241
+ npm run lint:check
242
+ npx warlock check
243
+ ```
244
+
245
+ Review every reported `file:line`: customized configs are preserved and need
246
+ manual translation before removal. The codemod installs the template's Oxlint
247
+ and Oxfmt versions and pins TypeScript to 7.0.2. It replaces standard lint/format
248
+ scripts, removes legacy dependencies, and deletes only recognized generated
249
+ configs. A generated `.prettierignore` becomes `.oxfmtignore`. Existing Oxc
250
+ configs are preserved. `--dry-run --json` reports the same planned changes
251
+ without writing files.
252
+
253
+ Explicit deprecated ESLint scaffolds set `devServer.healthCheckers.eslint: true`;
254
+ existing apps must set this themselves to retain background ESLint checking.
255
+
256
+ Oxc formatting scripts explicitly read `.gitignore` and `.oxfmtignore`. Oxfmt
257
+ 0.73 otherwise defaults to `.prettierignore` (`oxfmt --help`); the explicit
258
+ paths make migrated ignore rules effective. The package template format gate
259
+ also defaults to Oxfmt; `--lint=eslint` is its deprecated compatibility mode.
@@ -44,7 +44,7 @@ async function createWarlockApp(application) {
44
44
  });
45
45
  }
46
46
  templateSpinner.stop(spinnerMessages.templateCopied);
47
- showNotes([...notes, ...options.lint === "eslint" ? ["ESLint mode uses TypeScript 6.0.3 because typescript-eslint does not support TypeScript 7 yet; ESLint mode is planned for deprecation in 5.33 and removal in 5.34."] : []]);
47
+ showNotes([...notes, ...options.lint === "eslint" ? ["Deprecated: ESLint + Prettier support will be removed in 5.34.0. Use --lint=oxc for new apps; migrate existing apps with npx warlock upgrade --from 5.32 --to 5.33.0. ESLint mode uses TypeScript 6.0.3 because typescript-eslint does not support TypeScript 7 yet."] : []]);
48
48
  const installSpinner = spinner();
49
49
  installSpinner.start(spinnerMessages.installingDeps);
50
50
  const baseInstall = application.install();
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../packages/create-warlock/src/commands/create-warlock-app/index.ts"],"sourcesContent":["import { getDatabaseLabel, isNoDatabase } from \"../../features/database-drivers\";\nimport { App } from \"../../helpers/app\";\nimport { CommandResult, takeLastCommandOutput } from \"../../helpers/exec\";\nimport { getPackageManager, runPackageManagerCommand } from \"../../helpers/package-manager\";\nimport { resolveWarlockVersions } from \"../../helpers/warlock-versions\";\nimport { printDecisionsSummary } from \"../../summary\";\nimport { showSuccessScreen } from \"../../ui/banner\";\nimport {\n failFatally,\n installFailureHints,\n Problem,\n showNotes,\n showPartialScreen,\n showProblems,\n} from \"../../ui/report\";\nimport { spinner } from \"../../ui/spinner\";\nimport { spinnerMessages } from \"../../ui/spinners\";\n\n/**\n * What the scaffold actually achieved. `ok` is false when ANY step the user\n * asked for did not happen; the caller turns that into a non-zero exit code so\n * a script never mistakes a half-built project for a finished one.\n */\nexport type ScaffoldOutcome = {\n ok: boolean;\n problems: Problem[];\n};\n\n/** One-line \"why did this fail\" pulled out of a captured command. */\nfunction reasonFrom(result: CommandResult | undefined): string {\n if (!result) return \"the command reported a failure (no output captured)\";\n\n if (result.error) {\n return result.error.message || String(result.error);\n }\n\n const lastLine = `${result.stderr}\\n${result.stdout}`\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean)\n .pop();\n\n const status =\n result.code === null\n ? `killed by ${result.signal ?? \"an unknown signal\"}`\n : `exit code ${result.code}`;\n\n return lastLine ? `${status} — ${lastLine}` : status;\n}\n\nexport async function createWarlockApp(application: App): Promise<ScaffoldOutcome> {\n const options = application.options;\n const { useGit, useJWT, features, aiProviders, databaseDriver } = options;\n const noDatabase = isNoDatabase(databaseDriver);\n const problems: Problem[] = [];\n\n // Resolve the versions to pin BEFORE anything is written. The scaffolder's\n // own version is only used when the registry confirms it exists — see\n // helpers/warlock-versions.ts for why an unverified pin broke every install.\n const { versions, notes } = await resolveWarlockVersions();\n\n // Step 1: Initialize and copy template\n const templateSpinner = spinner();\n templateSpinner.start(spinnerMessages.copyingTemplate);\n\n try {\n application.init().use(\"warlock\").updatePackageJson(versions).updateDotEnv();\n\n // Wire the chosen database driver into .env — or, when the user opted out,\n // strip the database config entirely so the app boots with no database.\n if (noDatabase) {\n application.removeDatabaseConfig();\n } else {\n application.configureDatabaseEnv(databaseDriver);\n application.configureDatabaseConfig(databaseDriver);\n }\n\n application.configureWebStarter(features.includes(\"web\"));\n application.configureLintToolchain();\n application.configureAgentKitTargets(options.agents ?? [\"claude\"]);\n } catch (error) {\n templateSpinner.stop(spinnerMessages.templateFailed);\n\n failFatally({\n step: \"Template copy\",\n detail: `The project files could not be written: ${(error as Error).message}`,\n hints: [\n \"Check that the target directory is writable and that no file is locked by another process.\",\n ],\n });\n }\n\n templateSpinner.stop(spinnerMessages.templateCopied);\n\n showNotes([\n ...notes,\n ...(options.lint === \"eslint\"\n ? [\n \"ESLint mode uses TypeScript 6.0.3 because typescript-eslint does not support TypeScript 7 yet; ESLint mode is planned for deprecation in 5.33 and removal in 5.34.\",\n ]\n : []),\n ]);\n\n // Step 2: Install base dependencies (so the `warlock` binary is available).\n // Nothing downstream works without this, so a failure ends the run — loudly,\n // with the command, its exit code and its output.\n const installSpinner = spinner();\n installSpinner.start(spinnerMessages.installingDeps);\n\n const baseInstall = application.install();\n const baseInstalled = await baseInstall.install;\n const baseInstallResult = await baseInstall.result;\n\n if (!baseInstalled) {\n installSpinner.stop(spinnerMessages.depsFailed);\n\n failFatally({\n step: \"Dependency install\",\n detail: \"The project's dependencies were not installed.\",\n result: baseInstallResult,\n hints: installFailureHints(baseInstallResult),\n });\n }\n\n installSpinner.stop(spinnerMessages.depsInstalled);\n\n // Step 3: Add features via `warlock add --no-install`, then one batched install.\n // The DB driver, optional features, and AI providers all go through the single\n // source of truth (core's feature map) so versions never drift. When no\n // database was chosen, the driver is omitted (there is no `none` feature).\n const selectedFeatures = [...(noDatabase ? [] : [databaseDriver]), ...features, ...aiProviders];\n\n // Features the user asked for that are not in the project when we finish.\n const failedFeatures: { feature: string; reason: string }[] = [];\n let featureDependencyInstallFailed = false;\n\n if (selectedFeatures.length > 0) {\n const featuresSpinner = spinner();\n featuresSpinner.start(spinnerMessages.addingFeatures);\n\n let addedFeatures = selectedFeatures;\n\n if (!(await application.installFeatures(selectedFeatures))) {\n // The batch is all-or-nothing, so it cannot say WHICH feature broke it.\n // Retry them one at a time: every feature that can be added still gets\n // added, and every one that cannot gets its own reason.\n const batchFailure = takeLastCommandOutput();\n\n addedFeatures = [];\n\n for (const feature of selectedFeatures) {\n if (await application.installFeatures([feature])) {\n addedFeatures.push(feature);\n } else {\n failedFeatures.push({\n feature,\n reason: reasonFrom(takeLastCommandOutput() ?? batchFailure),\n });\n }\n }\n }\n\n let featureInstall: CommandResult | undefined;\n\n if (addedFeatures.length > 0) {\n // `warlock add` records the feature dependencies but does not reconcile\n // them with each other. The `web` feature brings `vite` while the\n // template always brings `vitest`, whose own vite range resolves to a\n // different major — two copies, and yarn 1 aborts the whole link phase\n // on them. Pin one copy BEFORE the batched install, not after.\n application.pinViteResolution();\n application.approveSelectedFeatureBuilds(addedFeatures);\n\n const install = application.install();\n const installed = await install.install;\n\n featureInstall = await install.result;\n\n if (!installed) {\n // The dependencies were recorded but never fetched — the features are\n // not usable, so they are failures, not successes.\n const reason = `their packages were not installed (${reasonFrom(featureInstall)})`;\n\n for (const feature of addedFeatures) {\n failedFeatures.push({ feature, reason });\n }\n\n addedFeatures = [];\n featureDependencyInstallFailed = true;\n }\n }\n\n if (failedFeatures.length === 0) {\n featuresSpinner.stop(spinnerMessages.featuresAdded);\n } else {\n featuresSpinner.stop(\n addedFeatures.length > 0 ? spinnerMessages.featuresPartial : spinnerMessages.featuresFailed,\n );\n\n problems.push({\n step: \"Features\",\n detail: failedFeatures\n .map(({ feature, reason }) => `${feature}: ${reason}`)\n .join(\"\\n \"),\n result: featureInstall?.ok === false ? featureInstall : undefined,\n hints: [\n `Retry inside the project with: npx warlock add ${failedFeatures\n .map(({ feature }) => feature)\n .join(\" \")}`,\n ],\n });\n }\n }\n\n // Step 4: Initialize Git repository if requested\n if (useGit) {\n const gitSpinner = spinner();\n gitSpinner.start(spinnerMessages.initializingGit);\n\n const initialized = await application.git();\n\n gitSpinner.stop(initialized ? spinnerMessages.gitInitialized : spinnerMessages.gitFailed);\n\n if (!initialized) {\n const failure = takeLastCommandOutput();\n\n problems.push({\n step: \"Git repository\",\n detail: `The repository was not initialized: ${reasonFrom(failure)}`,\n result: failure,\n hints: [\n \"Check that git is installed and that user.name / user.email are configured, then run `git init` yourself.\",\n ],\n });\n }\n }\n\n // Step 5: Generate JWT or warm cache\n if (useJWT && featureDependencyInstallFailed) {\n problems.push({\n step: \"JWT secrets\",\n detail: \"Skipped because the selected feature dependencies did not install.\",\n hints: [\n `Install the failed feature dependencies, then run \\`${runPackageManagerCommand(\"jwt\")}\\` inside the project.`,\n ],\n });\n } else if (useJWT) {\n const jwtSpinner = spinner();\n jwtSpinner.start(spinnerMessages.generatingJwt);\n\n const command = runPackageManagerCommand(\"jwt\");\n const generated = await application.exec(command);\n\n jwtSpinner.stop(generated ? spinnerMessages.jwtGenerated : spinnerMessages.jwtFailed);\n\n if (!generated) {\n const failure = takeLastCommandOutput();\n\n problems.push({\n step: \"JWT secrets\",\n detail: `No JWT secrets were written to .env: ${reasonFrom(failure)}`,\n result: failure,\n hints: [`Run \\`${command}\\` inside the project before starting it.`],\n });\n }\n } else {\n // The warm cache is a start-up optimisation, not something the user asked\n // for: report it, but it does not make the scaffold a failure.\n const warmSpinner = spinner();\n warmSpinner.start(spinnerMessages.warmingCache);\n\n const warmed = await application.exec(\"npx warlock --warm-cache\");\n\n warmSpinner.stop(warmed ? spinnerMessages.cacheWarmed : spinnerMessages.cacheWarmFailed);\n\n if (!warmed) takeLastCommandOutput();\n }\n\n // Step 6: Report what actually happened — the summary may only advertise\n // features that are really in the project.\n const requestedFeatures = [...features, ...aiProviders];\n const missingFeatures = failedFeatures\n .map(({ feature }) => feature)\n .filter((feature) => requestedFeatures.includes(feature));\n const installedFeatures = requestedFeatures.filter(\n (feature) => !missingFeatures.includes(feature),\n );\n\n if (problems.length > 0) {\n showProblems(problems);\n\n showPartialScreen({\n projectName: application.name,\n database: getDatabaseLabel(databaseDriver),\n features: installedFeatures,\n missingFeatures,\n packageManager: getPackageManager(),\n });\n\n printDecisionsSummary({ packageManager: getPackageManager(), options });\n\n return { ok: false, problems };\n }\n\n showSuccessScreen({\n projectName: application.name,\n database: getDatabaseLabel(databaseDriver),\n features: installedFeatures,\n packageManager: getPackageManager(),\n });\n\n printDecisionsSummary({ packageManager: getPackageManager(), options });\n\n return { ok: true, problems };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../packages/create-warlock/src/commands/create-warlock-app/index.ts"],"sourcesContent":["import { getDatabaseLabel, isNoDatabase } from \"../../features/database-drivers\";\nimport { App } from \"../../helpers/app\";\nimport { CommandResult, takeLastCommandOutput } from \"../../helpers/exec\";\nimport { getPackageManager, runPackageManagerCommand } from \"../../helpers/package-manager\";\nimport { resolveWarlockVersions } from \"../../helpers/warlock-versions\";\nimport { printDecisionsSummary } from \"../../summary\";\nimport { showSuccessScreen } from \"../../ui/banner\";\nimport {\n failFatally,\n installFailureHints,\n Problem,\n showNotes,\n showPartialScreen,\n showProblems,\n} from \"../../ui/report\";\nimport { spinner } from \"../../ui/spinner\";\nimport { spinnerMessages } from \"../../ui/spinners\";\n\n/**\n * What the scaffold actually achieved. `ok` is false when ANY step the user\n * asked for did not happen; the caller turns that into a non-zero exit code so\n * a script never mistakes a half-built project for a finished one.\n */\nexport type ScaffoldOutcome = {\n ok: boolean;\n problems: Problem[];\n};\n\n/** One-line \"why did this fail\" pulled out of a captured command. */\nfunction reasonFrom(result: CommandResult | undefined): string {\n if (!result) return \"the command reported a failure (no output captured)\";\n\n if (result.error) {\n return result.error.message || String(result.error);\n }\n\n const lastLine = `${result.stderr}\\n${result.stdout}`\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean)\n .pop();\n\n const status =\n result.code === null\n ? `killed by ${result.signal ?? \"an unknown signal\"}`\n : `exit code ${result.code}`;\n\n return lastLine ? `${status} — ${lastLine}` : status;\n}\n\nexport async function createWarlockApp(application: App): Promise<ScaffoldOutcome> {\n const options = application.options;\n const { useGit, useJWT, features, aiProviders, databaseDriver } = options;\n const noDatabase = isNoDatabase(databaseDriver);\n const problems: Problem[] = [];\n\n // Resolve the versions to pin BEFORE anything is written. The scaffolder's\n // own version is only used when the registry confirms it exists — see\n // helpers/warlock-versions.ts for why an unverified pin broke every install.\n const { versions, notes } = await resolveWarlockVersions();\n\n // Step 1: Initialize and copy template\n const templateSpinner = spinner();\n templateSpinner.start(spinnerMessages.copyingTemplate);\n\n try {\n application.init().use(\"warlock\").updatePackageJson(versions).updateDotEnv();\n\n // Wire the chosen database driver into .env — or, when the user opted out,\n // strip the database config entirely so the app boots with no database.\n if (noDatabase) {\n application.removeDatabaseConfig();\n } else {\n application.configureDatabaseEnv(databaseDriver);\n application.configureDatabaseConfig(databaseDriver);\n }\n\n application.configureWebStarter(features.includes(\"web\"));\n application.configureLintToolchain();\n application.configureAgentKitTargets(options.agents ?? [\"claude\"]);\n } catch (error) {\n templateSpinner.stop(spinnerMessages.templateFailed);\n\n failFatally({\n step: \"Template copy\",\n detail: `The project files could not be written: ${(error as Error).message}`,\n hints: [\n \"Check that the target directory is writable and that no file is locked by another process.\",\n ],\n });\n }\n\n templateSpinner.stop(spinnerMessages.templateCopied);\n\n showNotes([\n ...notes,\n ...(options.lint === \"eslint\"\n ? [\n \"Deprecated: ESLint + Prettier support will be removed in 5.34.0. Use --lint=oxc for new apps; migrate existing apps with npx warlock upgrade --from 5.32 --to 5.33.0. ESLint mode uses TypeScript 6.0.3 because typescript-eslint does not support TypeScript 7 yet.\",\n ]\n : []),\n ]);\n\n // Step 2: Install base dependencies (so the `warlock` binary is available).\n // Nothing downstream works without this, so a failure ends the run — loudly,\n // with the command, its exit code and its output.\n const installSpinner = spinner();\n installSpinner.start(spinnerMessages.installingDeps);\n\n const baseInstall = application.install();\n const baseInstalled = await baseInstall.install;\n const baseInstallResult = await baseInstall.result;\n\n if (!baseInstalled) {\n installSpinner.stop(spinnerMessages.depsFailed);\n\n failFatally({\n step: \"Dependency install\",\n detail: \"The project's dependencies were not installed.\",\n result: baseInstallResult,\n hints: installFailureHints(baseInstallResult),\n });\n }\n\n installSpinner.stop(spinnerMessages.depsInstalled);\n\n // Step 3: Add features via `warlock add --no-install`, then one batched install.\n // The DB driver, optional features, and AI providers all go through the single\n // source of truth (core's feature map) so versions never drift. When no\n // database was chosen, the driver is omitted (there is no `none` feature).\n const selectedFeatures = [...(noDatabase ? [] : [databaseDriver]), ...features, ...aiProviders];\n\n // Features the user asked for that are not in the project when we finish.\n const failedFeatures: { feature: string; reason: string }[] = [];\n let featureDependencyInstallFailed = false;\n\n if (selectedFeatures.length > 0) {\n const featuresSpinner = spinner();\n featuresSpinner.start(spinnerMessages.addingFeatures);\n\n let addedFeatures = selectedFeatures;\n\n if (!(await application.installFeatures(selectedFeatures))) {\n // The batch is all-or-nothing, so it cannot say WHICH feature broke it.\n // Retry them one at a time: every feature that can be added still gets\n // added, and every one that cannot gets its own reason.\n const batchFailure = takeLastCommandOutput();\n\n addedFeatures = [];\n\n for (const feature of selectedFeatures) {\n if (await application.installFeatures([feature])) {\n addedFeatures.push(feature);\n } else {\n failedFeatures.push({\n feature,\n reason: reasonFrom(takeLastCommandOutput() ?? batchFailure),\n });\n }\n }\n }\n\n let featureInstall: CommandResult | undefined;\n\n if (addedFeatures.length > 0) {\n // `warlock add` records the feature dependencies but does not reconcile\n // them with each other. The `web` feature brings `vite` while the\n // template always brings `vitest`, whose own vite range resolves to a\n // different major — two copies, and yarn 1 aborts the whole link phase\n // on them. Pin one copy BEFORE the batched install, not after.\n application.pinViteResolution();\n application.approveSelectedFeatureBuilds(addedFeatures);\n\n const install = application.install();\n const installed = await install.install;\n\n featureInstall = await install.result;\n\n if (!installed) {\n // The dependencies were recorded but never fetched — the features are\n // not usable, so they are failures, not successes.\n const reason = `their packages were not installed (${reasonFrom(featureInstall)})`;\n\n for (const feature of addedFeatures) {\n failedFeatures.push({ feature, reason });\n }\n\n addedFeatures = [];\n featureDependencyInstallFailed = true;\n }\n }\n\n if (failedFeatures.length === 0) {\n featuresSpinner.stop(spinnerMessages.featuresAdded);\n } else {\n featuresSpinner.stop(\n addedFeatures.length > 0 ? spinnerMessages.featuresPartial : spinnerMessages.featuresFailed,\n );\n\n problems.push({\n step: \"Features\",\n detail: failedFeatures\n .map(({ feature, reason }) => `${feature}: ${reason}`)\n .join(\"\\n \"),\n result: featureInstall?.ok === false ? featureInstall : undefined,\n hints: [\n `Retry inside the project with: npx warlock add ${failedFeatures\n .map(({ feature }) => feature)\n .join(\" \")}`,\n ],\n });\n }\n }\n\n // Step 4: Initialize Git repository if requested\n if (useGit) {\n const gitSpinner = spinner();\n gitSpinner.start(spinnerMessages.initializingGit);\n\n const initialized = await application.git();\n\n gitSpinner.stop(initialized ? spinnerMessages.gitInitialized : spinnerMessages.gitFailed);\n\n if (!initialized) {\n const failure = takeLastCommandOutput();\n\n problems.push({\n step: \"Git repository\",\n detail: `The repository was not initialized: ${reasonFrom(failure)}`,\n result: failure,\n hints: [\n \"Check that git is installed and that user.name / user.email are configured, then run `git init` yourself.\",\n ],\n });\n }\n }\n\n // Step 5: Generate JWT or warm cache\n if (useJWT && featureDependencyInstallFailed) {\n problems.push({\n step: \"JWT secrets\",\n detail: \"Skipped because the selected feature dependencies did not install.\",\n hints: [\n `Install the failed feature dependencies, then run \\`${runPackageManagerCommand(\"jwt\")}\\` inside the project.`,\n ],\n });\n } else if (useJWT) {\n const jwtSpinner = spinner();\n jwtSpinner.start(spinnerMessages.generatingJwt);\n\n const command = runPackageManagerCommand(\"jwt\");\n const generated = await application.exec(command);\n\n jwtSpinner.stop(generated ? spinnerMessages.jwtGenerated : spinnerMessages.jwtFailed);\n\n if (!generated) {\n const failure = takeLastCommandOutput();\n\n problems.push({\n step: \"JWT secrets\",\n detail: `No JWT secrets were written to .env: ${reasonFrom(failure)}`,\n result: failure,\n hints: [`Run \\`${command}\\` inside the project before starting it.`],\n });\n }\n } else {\n // The warm cache is a start-up optimisation, not something the user asked\n // for: report it, but it does not make the scaffold a failure.\n const warmSpinner = spinner();\n warmSpinner.start(spinnerMessages.warmingCache);\n\n const warmed = await application.exec(\"npx warlock --warm-cache\");\n\n warmSpinner.stop(warmed ? spinnerMessages.cacheWarmed : spinnerMessages.cacheWarmFailed);\n\n if (!warmed) takeLastCommandOutput();\n }\n\n // Step 6: Report what actually happened — the summary may only advertise\n // features that are really in the project.\n const requestedFeatures = [...features, ...aiProviders];\n const missingFeatures = failedFeatures\n .map(({ feature }) => feature)\n .filter((feature) => requestedFeatures.includes(feature));\n const installedFeatures = requestedFeatures.filter(\n (feature) => !missingFeatures.includes(feature),\n );\n\n if (problems.length > 0) {\n showProblems(problems);\n\n showPartialScreen({\n projectName: application.name,\n database: getDatabaseLabel(databaseDriver),\n features: installedFeatures,\n missingFeatures,\n packageManager: getPackageManager(),\n });\n\n printDecisionsSummary({ packageManager: getPackageManager(), options });\n\n return { ok: false, problems };\n }\n\n showSuccessScreen({\n projectName: application.name,\n database: getDatabaseLabel(databaseDriver),\n features: installedFeatures,\n packageManager: getPackageManager(),\n });\n\n printDecisionsSummary({ packageManager: getPackageManager(), options });\n\n return { ok: true, problems };\n}\n"],"mappings":"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"}
@@ -35,7 +35,7 @@ const FLAG_DEFINITIONS = [
35
35
  },
36
36
  {
37
37
  usage: "--lint=<oxc|eslint>",
38
- description: "Linter and formatter: Oxlint/Oxfmt or ESLint/Prettier",
38
+ description: "Linter and formatter: Oxlint/Oxfmt or ESLint/Prettier (deprecated; removed in 5.34.0)",
39
39
  defaultValue: "oxc"
40
40
  },
41
41
  {
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/definitions.ts"],"sourcesContent":["import { getAiPackageOptions, getAiProviderOptions } from \"../features/features-map\";\n\n/**\n * Single source of truth for every CLI flag's help text and default, so\n * `--help` can never drift out of sync with what the parser actually\n * understands (requirement: `--help` lists every flag together with its\n * default value).\n */\nexport type FlagDefinition = {\n /** How the flag reads on the command line, e.g. `--db=<driver>`. */\n usage: string;\n description: string;\n /** Human-readable default, or what happens when it is left unanswered. */\n defaultValue: string;\n};\n\nconst AI_FEATURE_KEYS = [...getAiProviderOptions(), ...getAiPackageOptions()].map(\n ({ value }) => value,\n);\n\nexport const FLAG_DEFINITIONS: FlagDefinition[] = [\n {\n usage: \"--name=<name>\",\n description: \"Project name (or pass it as the first positional arg)\",\n defaultValue: \"required — prompted if a TTY is available, otherwise fails naming this flag\",\n },\n {\n usage: \"--stack=<api|web>\",\n description:\n \"API-only or full-stack web — the one structural choice that changes the app's shape. Customizing everything else is a flow, not a stack: use --customize, where --stack=web arrives as a pre-ticked web feature\",\n defaultValue: \"api\",\n },\n {\n usage: \"--db=<driver> / --no-db\",\n description: \"Database driver (e.g. postgres, mongodb), or skip a database entirely\",\n defaultValue: \"mongodb\",\n },\n {\n usage: \"--features=<list>\",\n description: \"Comma-separated feature keys (e.g. test,herald)\",\n defaultValue: 'none (or [\"web\"] when --stack=web and --features is not given)',\n },\n {\n usage: \"--ai=<list>\",\n description: `Comma-separated AI provider or capability keys (${AI_FEATURE_KEYS.join(\", \")})`,\n defaultValue: \"none\",\n },\n {\n usage: \"--pm=<manager>\",\n description: \"Package manager to use (npm, yarn, pnpm, bun)\",\n defaultValue: \"inferred from the invoking agent (npm_config_user_agent) or the system\",\n },\n {\n usage: \"--lint=<oxc|eslint>\",\n description: \"Linter and formatter: Oxlint/Oxfmt or ESLint/Prettier\",\n defaultValue: \"oxc\",\n },\n {\n usage: \"--agents=<list>\",\n description: \"Comma-separated agent-kit targets to derive docs/skills for\",\n defaultValue: \"claude\",\n },\n {\n usage: \"--git / --no-git\",\n description: \"Force-enable or force-disable git initialization\",\n defaultValue: \"false\",\n },\n {\n usage: \"--jwt / --no-jwt\",\n description: \"Force-enable or force-disable JWT secret generation\",\n defaultValue: \"false\",\n },\n {\n usage: \"-y, --yes\",\n description: \"Skip every prompt and accept defaults for anything unset\",\n defaultValue: \"false\",\n },\n {\n usage: \"--customize, --interactive\",\n description:\n 'Full long-form wizard — every choice asked one by one. Also selectable as \"Customize\" in the \"What are we building?\" menu. Any other flag you pass pre-selects its prompt; --yes is refused',\n defaultValue: \"false\",\n },\n {\n usage: \"-h, --help\",\n description: \"Show this help message and exit\",\n defaultValue: \"n/a\",\n },\n {\n usage: \"-v, --version\",\n description: \"Show the installed create-warlock version and exit\",\n defaultValue: \"n/a\",\n },\n];\n\n/** Render the full `--help` text, one line per flag with its default. */\nexport function buildHelpText(): string {\n const rows = FLAG_DEFINITIONS.map(({ usage, description, defaultValue }) => {\n const label = usage.padEnd(28);\n return ` ${label}${description} (default: ${defaultValue})`;\n }).join(\"\\n\");\n\n return `\n create-warlock — scaffold a new Warlock.js project\n\n Usage\n $ create-warlock [project-name] [options]\n\n Options\n${rows}\n\n Example\n $ create-warlock my-app --db=postgres --features=test,herald --yes\n`;\n}\n"],"mappings":";;;AAgBA,MAAM,kBAAkB,CAAC,GAAG,qBAAqB,GAAG,GAAG,oBAAoB,CAAC,CAAC,CAAC,KAC3E,EAAE,YAAY,KACjB;AAEA,MAAa,mBAAqC;CAChD;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aACE;EACF,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa,mDAAmD,gBAAgB,KAAK,IAAI,EAAE;EAC3F,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aACE;EACF,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;AACF;;AAGA,SAAgB,gBAAwB;CAMtC,OAAO;;;;;;;EALM,iBAAiB,KAAK,EAAE,OAAO,aAAa,mBAAmB;EAE1E,OAAO,OADO,MAAM,OAAO,EACT,IAAI,YAAY,aAAa,aAAa;CAC9D,CAAC,CAAC,CAAC,KAAK,IASL,EAAE;;;;;AAKP"}
1
+ {"version":3,"file":"definitions.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/definitions.ts"],"sourcesContent":["import { getAiPackageOptions, getAiProviderOptions } from \"../features/features-map\";\n\n/**\n * Single source of truth for every CLI flag's help text and default, so\n * `--help` can never drift out of sync with what the parser actually\n * understands (requirement: `--help` lists every flag together with its\n * default value).\n */\nexport type FlagDefinition = {\n /** How the flag reads on the command line, e.g. `--db=<driver>`. */\n usage: string;\n description: string;\n /** Human-readable default, or what happens when it is left unanswered. */\n defaultValue: string;\n};\n\nconst AI_FEATURE_KEYS = [...getAiProviderOptions(), ...getAiPackageOptions()].map(\n ({ value }) => value,\n);\n\nexport const FLAG_DEFINITIONS: FlagDefinition[] = [\n {\n usage: \"--name=<name>\",\n description: \"Project name (or pass it as the first positional arg)\",\n defaultValue: \"required — prompted if a TTY is available, otherwise fails naming this flag\",\n },\n {\n usage: \"--stack=<api|web>\",\n description:\n \"API-only or full-stack web — the one structural choice that changes the app's shape. Customizing everything else is a flow, not a stack: use --customize, where --stack=web arrives as a pre-ticked web feature\",\n defaultValue: \"api\",\n },\n {\n usage: \"--db=<driver> / --no-db\",\n description: \"Database driver (e.g. postgres, mongodb), or skip a database entirely\",\n defaultValue: \"mongodb\",\n },\n {\n usage: \"--features=<list>\",\n description: \"Comma-separated feature keys (e.g. test,herald)\",\n defaultValue: 'none (or [\"web\"] when --stack=web and --features is not given)',\n },\n {\n usage: \"--ai=<list>\",\n description: `Comma-separated AI provider or capability keys (${AI_FEATURE_KEYS.join(\", \")})`,\n defaultValue: \"none\",\n },\n {\n usage: \"--pm=<manager>\",\n description: \"Package manager to use (npm, yarn, pnpm, bun)\",\n defaultValue: \"inferred from the invoking agent (npm_config_user_agent) or the system\",\n },\n {\n usage: \"--lint=<oxc|eslint>\",\n description:\n \"Linter and formatter: Oxlint/Oxfmt or ESLint/Prettier (deprecated; removed in 5.34.0)\",\n defaultValue: \"oxc\",\n },\n {\n usage: \"--agents=<list>\",\n description: \"Comma-separated agent-kit targets to derive docs/skills for\",\n defaultValue: \"claude\",\n },\n {\n usage: \"--git / --no-git\",\n description: \"Force-enable or force-disable git initialization\",\n defaultValue: \"false\",\n },\n {\n usage: \"--jwt / --no-jwt\",\n description: \"Force-enable or force-disable JWT secret generation\",\n defaultValue: \"false\",\n },\n {\n usage: \"-y, --yes\",\n description: \"Skip every prompt and accept defaults for anything unset\",\n defaultValue: \"false\",\n },\n {\n usage: \"--customize, --interactive\",\n description:\n 'Full long-form wizard — every choice asked one by one. Also selectable as \"Customize\" in the \"What are we building?\" menu. Any other flag you pass pre-selects its prompt; --yes is refused',\n defaultValue: \"false\",\n },\n {\n usage: \"-h, --help\",\n description: \"Show this help message and exit\",\n defaultValue: \"n/a\",\n },\n {\n usage: \"-v, --version\",\n description: \"Show the installed create-warlock version and exit\",\n defaultValue: \"n/a\",\n },\n];\n\n/** Render the full `--help` text, one line per flag with its default. */\nexport function buildHelpText(): string {\n const rows = FLAG_DEFINITIONS.map(({ usage, description, defaultValue }) => {\n const label = usage.padEnd(28);\n return ` ${label}${description} (default: ${defaultValue})`;\n }).join(\"\\n\");\n\n return `\n create-warlock — scaffold a new Warlock.js project\n\n Usage\n $ create-warlock [project-name] [options]\n\n Options\n${rows}\n\n Example\n $ create-warlock my-app --db=postgres --features=test,herald --yes\n`;\n}\n"],"mappings":";;;AAgBA,MAAM,kBAAkB,CAAC,GAAG,qBAAqB,GAAG,GAAG,oBAAoB,CAAC,CAAC,CAAC,KAC3E,EAAE,YAAY,KACjB;AAEA,MAAa,mBAAqC;CAChD;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aACE;EACF,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa,mDAAmD,gBAAgB,KAAK,IAAI,EAAE;EAC3F,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aACE;EACF,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aACE;EACF,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;AACF;;AAGA,SAAgB,gBAAwB;CAMtC,OAAO;;;;;;;EALM,iBAAiB,KAAK,EAAE,OAAO,aAAa,mBAAmB;EAE1E,OAAO,OADO,MAAM,OAAO,EACT,IAAI,YAAY,aAAa,aAAa;CAC9D,CAAC,CAAC,CAAC,KAAK,IASL,EAAE;;;;;AAKP"}
@@ -133,6 +133,11 @@ var App = class {
133
133
  const deps = content.devDependencies;
134
134
  deps.typescript = toolchain === "eslint" ? "6.0.3" : "7.0.2";
135
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();
136
141
  pkg.save();
137
142
  return this;
138
143
  }
@@ -155,8 +160,8 @@ var App = class {
155
160
  Object.assign(content.scripts, {
156
161
  lint: "oxlint --fix ./src --max-warnings=0",
157
162
  "lint:check": "oxlint ./src --max-warnings=0",
158
- format: "oxfmt --write ./src",
159
- "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"
160
165
  });
161
166
  pkg.save();
162
167
  for (const fileName of [
@@ -164,7 +169,11 @@ var App = class {
164
169
  ".prettierrc.json",
165
170
  ".prettierignore"
166
171
  ]) rmSync(path.join(this.path, fileName), { force: true });
167
- for (const fileName of [".oxlintrc.json", ".oxfmtrc.json"]) copyFile(packageRoot("templates", "toolchains", "oxc", fileName), path.join(this.path, fileName));
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));
168
177
  for (const fileName of ["settings.json", "extensions.json"]) copyFile(packageRoot("templates", "toolchains", "oxc", ".vscode", fileName), path.join(this.path, ".vscode", fileName));
169
178
  const config = this.file("warlock.config.ts");
170
179
  if (!config.content.includes("healthCheckers:")) config.replace("export default defineConfig({", `export default defineConfig({
@@ -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 ?? \"oxc\") {\n // Database and web setup may have edited package.json after it was cached.\n delete this.jsonFiles[path.resolve(this.path, \"package.json\")];\n const pkg = this.package;\n const content = pkg.content as unknown as {\n devDependencies: Record<string, string>;\n scripts: Record<string, string>;\n };\n const deps = content.devDependencies;\n // Registry peer range: @typescript-eslint/parser@8.71.2 supports >=4.8.4 <6.1.0.\n // Keep npm, pnpm and yarn installs valid without peer-dependency overrides.\n deps.typescript = toolchain === \"eslint\" ? \"6.0.3\" : \"7.0.2\";\n if (toolchain === \"eslint\") {\n pkg.save();\n return this;\n }\n\n for (const dependency of [\n \"@types/prettier\",\n \"@typescript-eslint/eslint-plugin\",\n \"@typescript-eslint/parser\",\n \"eslint\",\n \"eslint-config-prettier\",\n \"eslint-plugin-prettier\",\n \"eslint-plugin-unused-imports\",\n \"prettier\",\n \"@ianvs/prettier-plugin-sort-imports\",\n ])\n delete deps[dependency];\n Object.assign(deps, { oxlint: \"1.88.0\", oxfmt: \"0.73.0\" });\n content.devDependencies = Object.fromEntries(\n Object.entries(deps).sort(([left], [right]) => left.localeCompare(right)),\n );\n Object.assign(content.scripts, {\n lint: \"oxlint --fix ./src --max-warnings=0\",\n \"lint:check\": \"oxlint ./src --max-warnings=0\",\n format: \"oxfmt --write ./src\",\n \"format:check\": \"oxfmt --check ./src\",\n });\n pkg.save();\n\n for (const fileName of [\"eslint.config.js\", \".prettierrc.json\", \".prettierignore\"]) {\n rmSync(path.join(this.path, fileName), { force: true });\n }\n for (const fileName of [\".oxlintrc.json\", \".oxfmtrc.json\"]) {\n copyFile(\n packageRoot(\"templates\", \"toolchains\", \"oxc\", fileName),\n path.join(this.path, fileName),\n );\n }\n\n for (const fileName of [\"settings.json\", \"extensions.json\"]) {\n copyFile(\n packageRoot(\"templates\", \"toolchains\", \"oxc\", \".vscode\", fileName),\n path.join(this.path, \".vscode\", fileName),\n );\n }\n\n const config = this.file(\"warlock.config.ts\");\n if (!config.content.includes(\"healthCheckers:\")) {\n config\n .replace(\n \"export default defineConfig({\",\n `export default defineConfig({\n devServer: {\n healthCheckers: { eslint: false, oxlint: true },\n },`,\n )\n .save();\n }\n return this;\n }\n\n /**\n * Configure the chosen database driver: wire `DB_DRIVER` / `DB_PORT` into\n * `.env`, AND pin the driver's npm package (`mongodb` / `pg`) into the\n * project's `package.json` dependencies.\n *\n * The dependency is written HERE — before the base `yarn install` — so the\n * driver is pulled deterministically by the very first install. We do NOT\n * rely on the post-copy `warlock add <driver> --no-install` + separate\n * batched install, which can be skipped or fail and leave the driver\n * missing (the \"mongodb package is not installed\" runtime error).\n */\n public configureDatabaseEnv(driverValue: string) {\n const driver = getDatabaseDriver(driverValue);\n\n if (!driver) return this;\n\n // Pin the driver package into dependencies (idempotent — never downgrade).\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n };\n packageJson.dependencies = packageJson.dependencies ?? {};\n if (!packageJson.dependencies[driver.package]) {\n packageJson.dependencies[driver.package] = driver.packageVersion;\n putJsonFile(packageJsonPath, packageJson);\n }\n\n // Both files carry the driver defaults: `.env` is what runs, `.env.example`\n // is what the next developer copies.\n for (const fileName of [\".env\", \".env.example\"]) {\n const envPath = this.path + \"/\" + fileName;\n if (!fileExists(envPath)) continue;\n\n let envContent = getFile(envPath) as string;\n\n envContent = envContent.replace(/DB_PORT=\\d+/, `DB_PORT=${driver.defaultPort}`);\n\n if (envContent.includes(\"DB_DRIVER=\")) {\n envContent = envContent.replace(/DB_DRIVER=\\w*/, `DB_DRIVER=${driver.value}`);\n } else {\n envContent = envContent.replace(\n /DB_PORT=\\d+/,\n `DB_PORT=${driver.defaultPort}\\nDB_DRIVER=${driver.value}`,\n );\n }\n\n // DB_AUTH is Mongo's authSource; no other driver reads it.\n if (driver.value !== \"mongodb\") {\n envContent = envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\");\n }\n\n putFile(envPath, envContent);\n }\n\n return this;\n }\n\n /**\n * Write the correct `src/config/database.ts` for the chosen driver and remove\n * every per-driver variant so the scaffold ships exactly one config file.\n *\n * The template ships a Mongo-shaped `database.ts` (the default) plus a\n * `database.postgres.ts` variant. Mongo keeps the default in place; Postgres\n * overwrites `database.ts` with the Postgres-shaped variant (typed\n * `ConnectionOptions`, `driver: \"postgres\"`, port 5432, no Mongo-only keys —\n * no `authSource`, `driverOptions`, `replicaSet`, `randomIncrement` /\n * `initialId`). Either way the `.postgres` variant file is deleted afterwards\n * so no leftover config ships in the generated project.\n *\n * For Postgres it also corrects the working `.env`: the Mongo template ships\n * `DB_AUTH=admin`, an `authSource` value Postgres ignores and its config no\n * longer reads, so the line is dropped to keep `.env` and the config in sync.\n * The port is rewritten by {@link configureDatabaseEnv} (driver defaultPort),\n * which runs alongside this in the same branch.\n */\n public configureDatabaseConfig(driverValue: string) {\n const configDir = path.resolve(this.path, \"src/config\");\n const databasePath = path.resolve(configDir, \"database.ts\");\n const postgresVariantPath = path.resolve(configDir, \"database.postgres.ts\");\n\n if (driverValue === \"postgres\" && fileExists(postgresVariantPath)) {\n putFile(databasePath, getFile(postgresVariantPath) as string);\n\n const envPath = this.path + \"/.env\";\n\n if (fileExists(envPath)) {\n const envContent = getFile(envPath) as string;\n // Drop the Mongo-only DB_AUTH line entirely (its whole line, so no blank\n // gap is left behind). Postgres has no authSource concept.\n putFile(envPath, envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\"));\n }\n }\n\n // Whatever the driver, no per-driver variant may survive into the scaffold.\n if (fileExists(postgresVariantPath)) {\n unlinkSync(postgresVariantPath);\n }\n\n return this;\n }\n\n /**\n * Remove the database layer for a \"no database\" scaffold.\n *\n * Deletes `src/config/database.ts` (and a `.tsx` / per-driver variant if\n * present) from the freshly-copied template. The framework's database\n * connector is config-gated on that file — with it gone,\n * `config.get(\"database\")` is undefined and the connector no-ops, so the app\n * boots with no database wired and no driver package pulled. The `DB_*` lines\n * in `.env` are left in place (harmless: no config reads them) as a ready\n * template for adding a database back later.\n */\n public removeDatabaseConfig() {\n const configDir = path.resolve(this.path, \"src/config\");\n\n for (const fileName of [\"database.ts\", \"database.tsx\", \"database.postgres.ts\"]) {\n const filePath = path.resolve(configDir, fileName);\n\n if (fileExists(filePath)) {\n unlinkSync(filePath);\n }\n }\n\n return this;\n }\n\n /**\n * Configure the web starter only when the web feature owns `/`.\n *\n * A dependency-free HTTP response is retained without web; otherwise the\n * SSR page loader owns the route.\n * Exactly one survives the scaffold: web projects keep the SSR page and API\n * example; every other project keeps the dependency-free JSON controller.\n */\n public configureWebStarter(useWeb: boolean) {\n const remove = (relativePath: string) => {\n const target = path.resolve(this.path, relativePath);\n if (existsSync(target)) rmSync(target, { recursive: true, force: true });\n };\n\n if (!useWeb) {\n for (const entry of [\n \"src/web\",\n \"src/app/contact\",\n \"src/app/locale\",\n \"src/shared/contact.schema.ts\",\n \"src/shared/locale.schema.ts\",\n \"src/shared/locales.ts\",\n \"postcss.config.mjs\",\n \"skills/web-code-standards\",\n ]) {\n remove(entry);\n }\n\n const agentsPath = path.resolve(this.path, \"AGENTS.md\");\n const agents = getFile(agentsPath) as string;\n putFile(agentsPath, agents.replace(/^.*\\*\\*web-code-standards\\*\\*.*\\r?\\n/m, \"\"));\n\n const tsconfigPath = path.resolve(this.path, \"tsconfig.json\");\n const tsconfig = getFile(tsconfigPath) as string;\n putFile(\n tsconfigPath,\n tsconfig\n .replace(' \"web/*\": [\"./src/web/*\"],\\n', \"\")\n .replace(' \"@shared/*\": [\"./src/shared/*\"]\\n', \"\"),\n );\n\n return this;\n }\n\n // The SSR page owns `/`; its loader still uses src/app/home/services.\n remove(\"src/app/home/controllers/home-page.controller.ts\");\n remove(\"src/app/home/routes.ts\");\n\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n packageJson.dependencies = {\n ...packageJson.dependencies,\n \"@mongez/http\": \"^3.5.0\",\n \"@mongez/react-form\": \"^4.0.0\",\n };\n packageJson.devDependencies = {\n ...packageJson.devDependencies,\n \"@tailwindcss/postcss\": \"^4.1.16\",\n tailwindcss: \"^4.1.16\",\n };\n putJsonFile(packageJsonPath, packageJson);\n\n const configPath = path.resolve(this.path, \"warlock.config.ts\");\n const config = getFile(configPath) as string;\n if (!config.includes('\"@warlock.js/web/connector\"')) {\n // Inserted in sorted position, not prepended. The generated app formats\n // with `@ianvs/prettier-plugin-sort-imports`, so an import pushed to the top\n // of a file that already imports `@warlock.js/auth` and `@warlock.js/core`\n // is out of order the moment it is written — and the developer meets it as\n // a `prettier/prettier` error on their first `warlock dev`, in a file they\n // did not write.\n const withConnector = insertImportInSortedPosition(\n config,\n 'import { webConnector } from \"@warlock.js/web/connector\";',\n \"@warlock.js/web/connector\",\n );\n\n putFile(\n configPath,\n withConnector.replace(\n \"export default defineConfig({\",\n \"export default defineConfig({\\n connectors: [webConnector()],\",\n ),\n );\n }\n\n return this;\n }\n\n /**\n * Record non-default `agent-kit` targets into the generated `package.json`'s\n * `agentKit.targets` block.\n *\n * The template's own `postinstall` (`agent-kit init && agent-kit sync`)\n * already targets `claude` with no flags, so the single-target default case\n * needs no template edit at all — this only writes when the selection is\n * anything else, keeping the scaffold diff-free for the common path.\n */\n public configureAgentKitTargets(agents: string[]) {\n const isDefault = agents.length === 1 && agents[0] === \"claude\";\n if (isDefault) return this;\n\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n if (!fileExists(packageJsonPath)) return this;\n\n const packageJson = getJsonFile(packageJsonPath) as {\n agentKit?: { targets?: string[] };\n };\n packageJson.agentKit = { ...packageJson.agentKit, targets: agents };\n putJsonFile(packageJsonPath, packageJson);\n\n return this;\n }\n\n /**\n * Install the selected optional features by delegating to the project's own\n * `warlock add`. `--no-install` records every dependency in package.json and\n * ejects configs / scripts / setup hooks WITHOUT installing — the caller runs\n * one batched install afterwards. Versions come from core's feature map, so\n * the scaffolder never duplicates them.\n *\n * `--no-install` is passed LAST on purpose: the CLI parser treats the\n * positional after a bare flag as that flag's value, so it must follow the\n * feature list, not precede it.\n */\n public async installFeatures(features: string[]) {\n if (features.length === 0) return true;\n\n return this.exec(`npx warlock add ${features.join(\" \")} --no-install`);\n }\n\n /**\n * Force a single copy of `vite` when the project ends up with BOTH `vite`\n * (written by the `web` feature) and `vitest` (always in the template).\n *\n * `vitest` depends on vite through a wide range of its own\n * (`^6 || ^7 || ^8`), which resolves to a DIFFERENT major than the range the\n * feature map pins. Yarn 1 then has to nest the second copy under\n * `vitest/node_modules` and dies on its own linker invariant:\n *\n * error Invariant Violation: could not find a copy of vite to link in\n * <project>/node_modules/vitest/node_modules\n *\n * It aborts BEFORE `node_modules/.bin` is written, so `warlock` is never\n * linked and the very first `yarn dev` fails with \"'warlock' is not\n * recognized\". npm and pnpm nest happily and never hit this — for them the\n * `overrides` twin below is one copy instead of two, not a crash fix.\n *\n * The pin is READ BACK from whatever range the project's own package.json\n * declares, never re-stated as a literal here: the feature map in\n * `@warlock.js/core` owns the version, and a second copy of it would drift\n * the moment that one moves.\n *\n * Returns whether a pin was written, so the caller (and its spec) can assert\n * it instead of assuming it.\n */\n public pinViteResolution() {\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n\n if (!fileExists(packageJsonPath)) return false;\n\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n resolutions?: Record<string, string>;\n overrides?: Record<string, string>;\n };\n\n const declaredRange = (name: string) =>\n packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];\n\n const viteRange = declaredRange(\"vite\");\n\n // No vite, or no vitest to conflict with it — nothing to pin. Writing a\n // resolution for a package the project does not depend on would only\n // freeze a transitive tree nobody asked us to freeze.\n if (!viteRange || !declaredRange(\"vitest\")) return false;\n\n // yarn 1 / yarn berry read `resolutions`; npm 8+ and pnpm read `overrides`.\n packageJson.resolutions = { ...packageJson.resolutions, vite: viteRange };\n packageJson.overrides = { ...packageJson.overrides, vite: viteRange };\n\n putJsonFile(packageJsonPath, packageJson);\n\n return true;\n }\n\n /**\n * Get package json file\n */\n public get package() {\n return this.json(\"package.json\");\n }\n\n public updateDotEnv() {\n this.file(\".env\").replaceAll(\"appName\", this.name).save();\n\n return this;\n }\n\n /**\n * Get env file to update\n */\n public get env() {\n return this.file(\".env\");\n }\n\n public get name() {\n return this.app.appName;\n }\n\n public get path() {\n return this.app.appPath;\n }\n\n public file(relativePath: string) {\n const fullPath = path.resolve(this.path, relativePath);\n\n if (!this.files[fullPath]) {\n this.files[fullPath] = file(fullPath);\n }\n\n return this.files[fullPath];\n }\n\n public json(relativePath: string): JsonFileManager {\n const fullPath = path.resolve(this.path, relativePath);\n\n if (!this.jsonFiles[fullPath]) {\n this.jsonFiles[fullPath] = jsonFile(fullPath);\n }\n\n return this.jsonFiles[fullPath];\n }\n}\n\nexport function app(app: Application) {\n return new App(app);\n}\n\nexport class FileManager {\n public content!: string;\n public constructor(protected filePath: string) {\n this.parseContent();\n }\n\n protected parseContent() {\n this.content = getFile(this.filePath) as string;\n }\n\n public replace(search: string, replace: string) {\n this.content = this.content.replace(search, replace);\n\n return this;\n }\n\n public replaceAll(search: string, replace: string) {\n this.content = this.content.replaceAll(search, replace);\n\n return this;\n }\n\n public save() {\n putFile(this.filePath, this.content);\n }\n}\n\nexport class JsonFileManager extends FileManager {\n protected parseContent() {\n this.content = getJsonFile(this.filePath);\n }\n\n public save() {\n putJsonFile(this.filePath, this.content);\n }\n\n public has(key: string) {\n return this.content[key] !== undefined;\n }\n\n public replace(key: string, value: any) {\n this.content[key] = value;\n\n return this;\n }\n\n public replaceAll(key: string, value: any) {\n const contentAsString = JSON.stringify(this.content);\n\n this.content = JSON.parse(contentAsString.replaceAll(key, value));\n\n return this;\n }\n}\n\nexport function file(path: string) {\n return new FileManager(path);\n}\n\nexport function jsonFile(path: string) {\n return new JsonFileManager(path);\n}\n"],"mappings":"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"}
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":"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"}
@@ -7,7 +7,7 @@ function askLintToolchain(initialValue = "oxc") {
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 = \"oxc\") {\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,OAAO;CACpE,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/llms-full.txt CHANGED
@@ -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 | `oxc` | `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). |
@@ -237,7 +237,7 @@ double-quote, semicolon and trailing-comma settings. Import sorting is on in
237
237
  { "sortImports": { "newlinesBetween": false, "internalPattern": ["app/", "web/", "@shared/"] } }
238
238
  ```
239
239
 
240
- `format` (`oxfmt --write ./src`) orders built-ins, packages, internal aliases,
240
+ `format` (`npm run format`) orders built-ins, packages, internal aliases,
241
241
  then relative imports. Neither formatter deletes unused imports. ESLint reports
242
242
  them through `unused-imports/no-unused-imports`; Oxlint uses `no-unused-vars`.
243
243
  Scripts in both toolchains call local binaries
@@ -256,8 +256,7 @@ supported by typescript-eslint. Its parser requires the JavaScript compiler API
256
256
  removed in TypeScript 7, and its declared peer range is `>=4.8.4 <6.1.0`
257
257
  ([parser package metadata](https://registry.npmjs.org/@typescript-eslint/parser/8.71.2)).
258
258
  Both modes use the same tsconfig settings above, which are valid in TypeScript 6.
259
- ESLint mode prints a compatibility notice when chosen and is planned for
260
- deprecation in 5.33 and removal in 5.34. Both modes install with every supported package manager.
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.
261
260
 
262
261
  ### Prettier import order in ESLint mode
263
262
 
@@ -276,4 +275,35 @@ Leave `importOrderTypeScriptVersion` at its default to keep type and value impor
276
275
  separate. Run `npm run format`, then `npm run lint:check`; formatting preserves
277
276
  unused imports, and `npm run lint` can apply ESLint’s unused-import fixes.
278
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
+
279
309
 
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "dependencies": {
14
14
  "@clack/prompts": "^0.8.2",
15
15
  "@mongez/copper": "^2.1.3",
16
- "@warlock.js/fs": "5.32.0",
16
+ "@warlock.js/fs": "5.33.0",
17
17
  "@mongez/reinforcements": "^4.0.2",
18
18
  "cross-spawn": "^7.0.3",
19
19
  "rimraf": "^6.0.1",
@@ -22,7 +22,7 @@
22
22
  "engines": {
23
23
  "node": ">=22.15"
24
24
  },
25
- "version": "5.32.0",
25
+ "version": "5.33.0",
26
26
  "type": "module",
27
27
  "main": "./esm/index.mjs",
28
28
  "module": "./esm/index.mjs",
@@ -72,7 +72,7 @@ Features: none
72
72
  | `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
73
73
  | `--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. |
74
74
  | `--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. |
75
- | `--lint` | yes | `oxc` | `oxc` (Oxlint/Oxfmt) or `eslint` (ESLint/Prettier); unknown or missing values fail before writes. |
75
+ | `--lint` | yes | `oxc` | `oxc` (Oxlint/Oxfmt) or `eslint` (ESLint/Prettier, deprecated); unknown or missing values fail before writes. |
76
76
  | `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
77
77
  | `--agents` | yes (CSV) | `claude` | Comma-separated `agent-kit` targets — see below. |
78
78
  | `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit). |
@@ -229,7 +229,7 @@ double-quote, semicolon and trailing-comma settings. Import sorting is on in
229
229
  { "sortImports": { "newlinesBetween": false, "internalPattern": ["app/", "web/", "@shared/"] } }
230
230
  ```
231
231
 
232
- `format` (`oxfmt --write ./src`) orders built-ins, packages, internal aliases,
232
+ `format` (`npm run format`) orders built-ins, packages, internal aliases,
233
233
  then relative imports. Neither formatter deletes unused imports. ESLint reports
234
234
  them through `unused-imports/no-unused-imports`; Oxlint uses `no-unused-vars`.
235
235
  Scripts in both toolchains call local binaries
@@ -248,8 +248,7 @@ supported by typescript-eslint. Its parser requires the JavaScript compiler API
248
248
  removed in TypeScript 7, and its declared peer range is `>=4.8.4 <6.1.0`
249
249
  ([parser package metadata](https://registry.npmjs.org/@typescript-eslint/parser/8.71.2)).
250
250
  Both modes use the same tsconfig settings above, which are valid in TypeScript 6.
251
- ESLint mode prints a compatibility notice when chosen and is planned for
252
- deprecation in 5.33 and removal in 5.34. Both modes install with every supported package manager.
251
+ 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.
253
252
 
254
253
  ### Prettier import order in ESLint mode
255
254
 
@@ -267,3 +266,34 @@ the compiler API. Keep these options in `.prettierrc.json`:
267
266
  Leave `importOrderTypeScriptVersion` at its default to keep type and value imports
268
267
  separate. Run `npm run format`, then `npm run lint:check`; formatting preserves
269
268
  unused imports, and `npm run lint` can apply ESLint’s unused-import fixes.
269
+
270
+ ## Migrate to Oxlint and Oxfmt in 5.33
271
+
272
+ ESLint + Prettier are deprecated in 5.33.0 and will be removed in **5.34.0**.
273
+ New apps should use `--lint=oxc`. To migrate an existing app, install the new
274
+ core release and `ts-morph`, then run:
275
+
276
+ ```bash
277
+ npx warlock upgrade --from 5.32 --to 5.33.0 --dry-run
278
+ npx warlock upgrade --from 5.32 --to 5.33.0
279
+ npm install
280
+ npm run format
281
+ npm run lint:check
282
+ npx warlock check
283
+ ```
284
+
285
+ Review every reported `file:line`: customized configs are preserved and need
286
+ manual translation before removal. The codemod installs the template's Oxlint
287
+ and Oxfmt versions and pins TypeScript to 7.0.2. It replaces standard lint/format
288
+ scripts, removes legacy dependencies, and deletes only recognized generated
289
+ configs. A generated `.prettierignore` becomes `.oxfmtignore`. Existing Oxc
290
+ configs are preserved. `--dry-run --json` reports the same planned changes
291
+ without writing files.
292
+
293
+ Explicit deprecated ESLint scaffolds set `devServer.healthCheckers.eslint: true`;
294
+ existing apps must set this themselves to retain background ESLint checking.
295
+
296
+ Oxc formatting scripts explicitly read `.gitignore` and `.oxfmtignore`. Oxfmt
297
+ 0.73 otherwise defaults to `.prettierignore` (`oxfmt --help`); the explicit
298
+ paths make migrated ignore rules effective. The package template format gate
299
+ also defaults to Oxfmt; `--lint=eslint` is its deprecated compatibility mode.
@@ -0,0 +1,2 @@
1
+ node_modules
2
+ dist
@@ -20,13 +20,13 @@
20
20
  "gen.r": "warlock generate.repository",
21
21
  "gen.rs": "warlock generate.resource",
22
22
  "lint": "eslint --fix ./src --max-warnings=0",
23
- "format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx,css,md,json}\" --config ./.prettierrc.json",
23
+ "format": "node -e \"console.warn('Deprecated: Prettier support is removed in 5.34.0; migrate with npx warlock upgrade --from 5.32 --to 5.33.0.')\" && prettier --write \"./src/**/*.{js,jsx,ts,tsx,css,md,json}\" --config ./.prettierrc.json",
24
24
  "tsc": "warlock generate.typings && tsc --noEmit",
25
25
  "postinstall": "agent-kit init && agent-kit sync || echo \"warning: AI agent skills were NOT synced, so coding agents will not see the @warlock.js skills. Run the skills:sync script.\"",
26
26
  "skills:sync": "agent-kit sync",
27
27
  "skills:watch": "agent-kit watch",
28
28
  "lint:check": "eslint ./src --max-warnings=0",
29
- "format:check": "prettier --check \"./src/**/*.{js,jsx,ts,tsx,css,md,json}\" --config ./.prettierrc.json"
29
+ "format:check": "node -e \"console.warn('Deprecated: Prettier support is removed in 5.34.0; migrate with npx warlock upgrade --from 5.32 --to 5.33.0.')\" && prettier --check \"./src/**/*.{js,jsx,ts,tsx,css,md,json}\" --config ./.prettierrc.json"
30
30
  },
31
31
  "dependencies": {
32
32
  "@mongez/reinforcements": "^4.0.2",