create-warlock 5.29.0 → 5.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +66 -15
  3. package/esm/commands/create-new-app/get-app-path.mjs +1 -1
  4. package/esm/commands/create-new-app/get-app-path.mjs.map +1 -1
  5. package/esm/commands/create-new-app/index.mjs +20 -14
  6. package/esm/commands/create-new-app/index.mjs.map +1 -1
  7. package/esm/commands/create-new-app/types.d.mts +3 -2
  8. package/esm/commands/create-warlock-app/index.mjs +2 -1
  9. package/esm/commands/create-warlock-app/index.mjs.map +1 -1
  10. package/esm/features/agent-kit-targets.mjs +1 -1
  11. package/esm/features/agent-kit-targets.mjs.map +1 -1
  12. package/esm/features/database-drivers.mjs +1 -1
  13. package/esm/features/database-drivers.mjs.map +1 -1
  14. package/esm/features/features-map.mjs +13 -13
  15. package/esm/features/features-map.mjs.map +1 -1
  16. package/esm/flags/assert-flag-combinations.mjs +1 -1
  17. package/esm/flags/assert-flag-combinations.mjs.map +1 -1
  18. package/esm/flags/assert-package-manager.mjs +1 -1
  19. package/esm/flags/assert-package-manager.mjs.map +1 -1
  20. package/esm/flags/definitions.mjs +6 -1
  21. package/esm/flags/definitions.mjs.map +1 -1
  22. package/esm/flags/seed-from-flags.mjs +2 -1
  23. package/esm/flags/seed-from-flags.mjs.map +1 -1
  24. package/esm/helpers/app.mjs +72 -6
  25. package/esm/helpers/app.mjs.map +1 -1
  26. package/esm/helpers/exec.mjs +1 -1
  27. package/esm/helpers/exec.mjs.map +1 -1
  28. package/esm/helpers/insert-import.mjs +1 -1
  29. package/esm/helpers/insert-import.mjs.map +1 -1
  30. package/esm/helpers/package-manager.mjs +22 -2
  31. package/esm/helpers/package-manager.mjs.map +1 -1
  32. package/esm/helpers/paths.mjs +1 -1
  33. package/esm/helpers/paths.mjs.map +1 -1
  34. package/esm/helpers/pnpm-build-approvals.mjs +58 -3
  35. package/esm/helpers/pnpm-build-approvals.mjs.map +1 -1
  36. package/esm/helpers/project-builder-helpers.mjs +1 -1
  37. package/esm/helpers/project-builder-helpers.mjs.map +1 -1
  38. package/esm/helpers/tty.mjs +1 -1
  39. package/esm/helpers/tty.mjs.map +1 -1
  40. package/esm/helpers/warlock-versions.mjs +1 -1
  41. package/esm/helpers/warlock-versions.mjs.map +1 -1
  42. package/esm/index.d.mts +1 -1
  43. package/esm/index.mjs +6 -2
  44. package/esm/index.mjs.map +1 -1
  45. package/esm/prompts/ask-agent-targets.mjs +4 -2
  46. package/esm/prompts/ask-agent-targets.mjs.map +1 -1
  47. package/esm/prompts/ask-ai-features.mjs +5 -3
  48. package/esm/prompts/ask-ai-features.mjs.map +1 -1
  49. package/esm/prompts/ask-features.mjs +53 -0
  50. package/esm/prompts/ask-features.mjs.map +1 -0
  51. package/esm/prompts/ask-lint-toolchain.mjs +21 -0
  52. package/esm/prompts/ask-lint-toolchain.mjs.map +1 -0
  53. package/esm/prompts/ask-project-name.mjs +1 -1
  54. package/esm/prompts/ask-project-name.mjs.map +1 -1
  55. package/esm/prompts/ask-stack.mjs +1 -1
  56. package/esm/prompts/ask-stack.mjs.map +1 -1
  57. package/esm/prompts/fit-to-terminal.mjs +46 -0
  58. package/esm/prompts/fit-to-terminal.mjs.map +1 -0
  59. package/esm/summary.mjs +12 -2
  60. package/esm/summary.mjs.map +1 -1
  61. package/esm/ui/banner.mjs +115 -47
  62. package/esm/ui/banner.mjs.map +1 -1
  63. package/esm/ui/report.mjs +1 -1
  64. package/esm/ui/report.mjs.map +1 -1
  65. package/esm/ui/spinner.mjs +1 -1
  66. package/esm/ui/spinner.mjs.map +1 -1
  67. package/esm/ui/spinners.mjs +1 -1
  68. package/esm/ui/spinners.mjs.map +1 -1
  69. package/esm/ui/wrap-text.mjs +55 -0
  70. package/esm/ui/wrap-text.mjs.map +1 -0
  71. package/llms-full.txt +79 -29
  72. package/package.json +6 -5
  73. package/skills/create-a-warlock-project/SKILL.md +79 -29
  74. package/templates/toolchains/oxc/.oxfmtrc.json +12 -0
  75. package/templates/toolchains/oxc/.oxlintrc.json +24 -0
  76. package/templates/toolchains/oxc/.vscode/extensions.json +3 -0
  77. package/templates/toolchains/oxc/.vscode/settings.json +35 -0
  78. package/templates/warlock/AGENTS.md +22 -2
  79. package/templates/warlock/README.md +5 -3
  80. package/templates/warlock/package.json +6 -4
  81. package/templates/warlock/skills/api-design/SKILL.md +2 -2
  82. package/templates/warlock/skills/code-standards/SKILL.md +18 -4
  83. package/templates/warlock/skills/security-baseline/SKILL.md +12 -1
  84. package/templates/warlock/skills/testing-strategy/SKILL.md +17 -0
  85. package/templates/warlock/src/app/auth/routes.ts +25 -5
  86. package/templates/warlock/src/app/auth/utils/types.ts +6 -1
  87. package/templates/warlock/src/app/contact/routes.ts +1 -1
  88. package/templates/warlock/src/app/locale/routes.ts +1 -1
  89. package/templates/warlock/src/app/users/controllers/list-users.controller.ts +10 -2
  90. package/templates/warlock/src/app/users/schema/list-users.schema.ts +15 -0
  91. package/templates/warlock/src/config/tests.ts +8 -0
  92. package/templates/warlock/src/web/404.css +2 -6
  93. package/templates/warlock/src/web/root.tsx +5 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,44 @@ 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.31.0 - 2026-10-18
8
+
9
+ ### Added
10
+
11
+ - The starter's `src/config/tests.ts` documents the test fakes that are on by default and how to opt out (`fakes: false`, or per fake such as `{ cache: false }`).
12
+ - The generated `AGENTS.md` and the `code-standards` / `security-baseline` skills tell coding agents to run `npx warlock check`, which reports unvalidated `request.all()` input, unguarded mutating routes, unsafe redirects, sensitive values in logs and unbounded regexes on input with a `WL` code, in both `--lint=eslint` and `--lint=oxc` projects.
13
+ - The scaffold's sign-in and password-recovery routes, contact form and locale switch are marked `public: true`; `/auth/login` uses `loginThrottleMiddleware()`, `/auth/forgot-password` `tokenIssueThrottleMiddleware()`, `/auth/reset-password` `tokenConsumeThrottleMiddleware()`, and `/auth/refresh-token` a 30-per-minute route limit. The user list validates its query (`page`, `limit`, `id`, `name`, `email`) and passes `request.validated()` to the repository.
14
+ - Opt-in `--lint=oxc` / `--lint oxc` flag and custom-wizard prompt install pinned Oxlint 1.88.0/Oxfmt 0.73.0, matching lint/style configs and the Oxlint development health checker. ESLint/Prettier remain the default. Both choices include non-mutating `lint:check` and `format:check` scripts. Oxc projects configure and recommend the official VS Code extension for format-on-save and lint fixes; default projects retain their Prettier/ESLint editor settings. Help and headless diagnostics include `--lint`.
15
+ - Custom-mode optional features now use paged sections with selected counts and a select-all action per section. The decisions overview previews the selected feature/AI count and keys.
16
+
17
+ ### Changed
18
+
19
+ - **BREAKING:** Node.js 22.15 or newer is required, both in `engines` and in the scaffolder's own startup check (which used to accept Node 20), matching what the `warlock` CLI needs. In an existing app, `warlock upgrade --from 5.30 --to 5.31.0` raises `engines.node`, `.nvmrc` and `.node-version` (codemod `node-22-15`).
20
+ - `--lint=oxc` projects sort imports on `format`: the generated `.oxfmtrc.json` sets `"sortImports": { "newlinesBetween": false }`, keeping imports in one block as `prettier-plugin-organize-imports` does in the ESLint toolchain. Oxfmt does not remove unused imports; Oxlint reports them.
21
+
22
+ ### Fixed
23
+
24
+ - Scaffolder source formatting and tests use the workspace Oxfmt configuration, including standalone installs; template formatting continues to use each generated app toolchain.
25
+ - Group feature selection preserves explicit unticks when selecting the remaining group, and returns the category cursor to the group last edited.
26
+ - The project-ready summary wraps feature lists and next steps to the terminal width, measures visible emoji width, and uses a plain list on narrow terminals. Piped output uses an 80-column fallback. Wrapped values align under their labels, and the summary retains its label, command and link styling.
27
+ - On terminals narrower than 50 columns the project-ready heading keeps its bold green styling, as in the boxed layout.
28
+ - The ESLint-toolchain `lint`, `format` and `tsc` scripts call the local `eslint`, `prettier` and `tsc` binaries instead of `npx`, matching `lint:check`/`format:check` and the Oxc scripts. `format` now quotes its glob, so Prettier formats nested files under `src/` rather than relying on the shell's expansion.
29
+ - The starter's `AGENTS.md` ends with a `## This app (generated)` section whose `warlock:generated` markers `warlock dev` (and `warlock agents-md --write`) fills with the app's modules, routes, models, installed skills and verify commands.
30
+ - Opt-in `--lint=oxc` / `--lint oxc` flag and custom-wizard prompt install pinned Oxlint 1.88.0/Oxfmt 0.73.0, matching lint/style configs and the Oxlint development health checker. ESLint/Prettier remain the default. Both choices include non-mutating `lint:check` and `format:check` scripts. Oxc projects configure and recommend the official VS Code extension for format-on-save and lint fixes; default projects retain their Prettier/ESLint editor settings. Help and headless diagnostics include `--lint`.
31
+ - Custom-mode optional features now use paged sections with selected counts and a select-all action per section. The decisions overview previews the selected feature/AI count and keys.
32
+
33
+ ## 5.30.0 - 2026-10-16
34
+
35
+ ### Fixed
36
+
37
+ - New apps get the viewport tag from `<Head />` (`@warlock.js/web` 5.30.0), so phones lay pages out at device width and responsive CSS applies; the scaffolded `src/web/root.tsx` says so and does not hand-write a second one.
38
+ - **pnpm before 10.5 could not install a new app** (`ERROR packages field missing or empty`): pnpm 7–9 and 10.0–10.4 reject the settings-only `pnpm-workspace.yaml`. The scaffold now reads `pnpm --version` and writes approvals where that version reads them: no workspace file and `pnpm.onlyBuiltDependencies` in `package.json` before 10.5; the workspace file plus `onlyBuiltDependencies` on 10.5–10.25 (which ignore `allowBuilds`); the unchanged `allowBuilds` file on 10.26+, 11 and 12. It does not add `packages: ["."]`, which fails every later `pnpm add` on those versions (`ERR_PNPM_ADDING_TO_ROOT`). Feature approvals (sharp) follow the same rule; apps inside an existing workspace still get neither.
39
+ - **The feature multiselect no longer prints stacked copies of itself** in a short or narrow terminal: the 22-option list pages to the terminal's height, and each row's hint is shortened to fit one line (a wrapped row made clack redraw below the previous frame). The AI and agent-target multiselects use the same sizing. `@clack/prompts` is now `^0.8.2`, the first version whose multiselect pages.
40
+
41
+ ### Changed
42
+
43
+ - Selecting notifications scaffolds the smaller, convention-following `src/app/notifications` module that `warlock add notifications` now writes (see `@warlock.js/core`).
44
+
7
45
  ## 5.29.0 - 2026-10-01
8
46
 
9
47
  ### Changed
@@ -89,6 +127,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
89
127
  ### Changed
90
128
 
91
129
  - Lockstep release maintenance and dependency refresh.
130
+
92
131
  ## 5.20.0 - 2026-09-24
93
132
 
94
133
  ### Changed
package/README.md CHANGED
@@ -34,21 +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
- | `--ai=<list>` | Comma-separated AI provider keys (e.g. `openai,anthropic`) | none |
44
- | `--pm=<manager>` | Package manager (`npm`, `yarn`, `pnpm`, `bun`) | inferred from the invoking agent (`npm_config_user_agent`) or the system |
45
- | `--agents=<list>` | Comma-separated [agent-kit](https://mongez.js.org/agent-kit) targets | `claude` |
46
- | `--git` / `--no-git` | Force-enable or force-disable git initialization | `false` |
47
- | `--jwt` / `--no-jwt` | Force-enable or force-disable JWT secret generation | `false` |
48
- | `-y`, `--yes` | Skip every prompt and take defaults for anything unset | `false` |
49
- | `--interactive`, `--customize` | Restore the full long-form interactive wizard | `false` |
50
- | `-h`, `--help` | Print usage (every flag + its default) and exit | — |
51
- | `-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 | `eslint` |
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 | — |
52
53
 
53
54
  ```bash
54
55
  create-warlock my-app --db=postgres --features=test,herald --yes
@@ -153,3 +154,53 @@ pure logic is exported as `findResolverBoundaryViolations` and covered by
153
154
  ## License
154
155
 
155
156
  This project is licensed under the MIT License.
157
+
158
+ The project-ready summary wraps the complete feature list to your terminal width,
159
+ aligning continuation lines under the value while keeping commands indented.
160
+ Terminals narrower than 50 columns get a plain list; piped output uses 80 columns.
161
+
162
+ ### Grouped custom feature selection
163
+
164
+ In `--customize` mode, optional features are organized into sections such as
165
+ Auth & Access, Rendering & Mail, and Jobs & Messaging. Open a section to edit its
166
+ checklist or choose **Select all** for that section, then return to the menu.
167
+ The picker remembers the last group; selecting all remaining features preserves
168
+ items you explicitly unticked.
169
+ Each section shows its selected count; **Continue** keeps the current choices.
170
+ Existing `--features` and `--stack=web` selections stay preselected. Lists remain
171
+ paged to the terminal height and hints fit the available width.
172
+
173
+ The decisions overview shows the total selected feature/AI count and up to six
174
+ keys, followed by `+N more` for longer selections. The final success screen lists
175
+ all selected feature keys.
176
+
177
+ ### Optional Oxc tooling
178
+
179
+ Pass `--lint=oxc` (or `--lint oxc`) to scaffold with Oxlint **1.88.0** and Oxfmt
180
+ **0.73.0**. The full `--customize` wizard offers the same choice and preselects
181
+ any supplied flag. The default is `--lint=eslint`, which keeps ESLint/Prettier.
182
+ Unknown or missing `--lint` values fail before copying the project.
183
+
184
+ ```bash
185
+ npm create warlock@latest my-app -- --lint=oxc --yes
186
+ ```
187
+
188
+ Oxc projects receive `.oxlintrc.json` and `.oxfmtrc.json`, replace the template's
189
+ ESLint/Prettier dependencies and configs, and enable the Oxlint development health
190
+ checker while retaining TypeScript checks. Both choices provide `lint`, `format`,
191
+ `lint:check` and `format:check` scripts. The check variants do not modify files.
192
+ Oxc projects also receive VS Code format-on-save settings and an extension
193
+ recommendation for [Oxc](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode).
194
+ Its formatter is `oxc.oxc-vscode` and lint fixes use `source.fixAll.oxc`;
195
+ ESLint/Prettier editor settings remain the default for the ESLint toolchain.
196
+
197
+ The Oxlint config preserves the template's unused-variable/import checks,
198
+ explicit class accessibility and type-import style. Oxfmt uses the same 100-column,
199
+ double-quote, semicolon and trailing-comma settings, and sorts imports with
200
+ `"sortImports": { "newlinesBetween": false }` — built-ins, packages, then relative
201
+ paths, in one block like `prettier-plugin-organize-imports`. Oxfmt does not delete
202
+ unused imports; Oxlint reports them. Every generated script calls the local binary
203
+ (`eslint`, `oxlint`, `prettier`, `oxfmt`, `tsc`) rather than `npx`. See the
204
+ [Oxlint config reference](https://oxc.rs/docs/guide/usage/linter/config-file-reference)
205
+ and [Oxfmt config reference](https://oxc.rs/docs/guide/usage/formatter/config-file-reference)
206
+ when adding project rules.
@@ -2,7 +2,7 @@ import { directoryExists } from "@warlock.js/fs";
2
2
  import { colors } from "@mongez/copper";
3
3
  import * as path$1 from "path";
4
4
 
5
- //#region ../create-warlock/src/commands/create-new-app/get-app-path.ts
5
+ //#region ../../packages/create-warlock/src/commands/create-new-app/get-app-path.ts
6
6
  function getAppPath(appName) {
7
7
  const appPath = path$1.resolve(process.cwd(), appName);
8
8
  if (directoryExists(appPath)) {
@@ -1 +1 @@
1
- {"version":3,"file":"get-app-path.mjs","names":["path"],"sources":["../../../../../../../create-warlock/src/commands/create-new-app/get-app-path.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { directoryExists } from \"@warlock.js/fs\";\nimport * as path from \"path\";\n\nexport default function getAppPath(appName: string) {\n const appPath: string = path.resolve(process.cwd(), appName);\n\n if (directoryExists(appPath)) {\n console.log(\n colors.redBright(\n `${process.cwd()} has an existing directory \\`${colors.cyan(\n appName,\n )}\\`, please choose another app name or another directory to run the command from.`,\n ),\n );\n\n process.exit(1);\n }\n\n return appPath;\n}\n"],"mappings":";;;;;AAIA,SAAwB,WAAW,SAAiB;CAClD,MAAM,UAAkBA,OAAK,QAAQ,QAAQ,IAAI,GAAG,OAAO;CAE3D,IAAI,gBAAgB,OAAO,GAAG;EAC5B,QAAQ,IACN,OAAO,UACL,GAAG,QAAQ,IAAI,EAAE,+BAA+B,OAAO,KACrD,OACF,EAAE,iFACJ,CACF;EAEA,QAAQ,KAAK,CAAC;CAChB;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"get-app-path.mjs","names":["path"],"sources":["../../../../../../../../packages/create-warlock/src/commands/create-new-app/get-app-path.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { directoryExists } from \"@warlock.js/fs\";\nimport * as path from \"path\";\n\nexport default function getAppPath(appName: string) {\n const appPath: string = path.resolve(process.cwd(), appName);\n\n if (directoryExists(appPath)) {\n console.log(\n colors.redBright(\n `${process.cwd()} has an existing directory \\`${colors.cyan(\n appName,\n )}\\`, please choose another app name or another directory to run the command from.`,\n ),\n );\n\n process.exit(1);\n }\n\n return appPath;\n}\n"],"mappings":";;;;;AAIA,SAAwB,WAAW,SAAiB;CAClD,MAAM,UAAkBA,OAAK,QAAQ,QAAQ,IAAI,GAAG,OAAO;CAE3D,IAAI,gBAAgB,OAAO,GAAG;EAC5B,QAAQ,IACN,OAAO,UACL,GAAG,QAAQ,IAAI,EAAE,+BAA+B,OAAO,KACrD,OACF,EAAE,iFACJ,CACF;EAEA,QAAQ,KAAK,CAAC;CAChB;CAEA,OAAO;AACT"}
@@ -1,6 +1,6 @@
1
1
  import { resolveAgentTargets } from "../../features/agent-kit-targets.mjs";
2
2
  import { getDatabaseDriver, getDatabaseDriverOptions, isNoDatabase } from "../../features/database-drivers.mjs";
3
- import { getAiPackageOptions, getAiProviderOptions, getAllFeatureKeys, getFeatureOptions } from "../../features/features-map.mjs";
3
+ import { getAiPackageOptions, getAiProviderOptions, getAllFeatureKeys } from "../../features/features-map.mjs";
4
4
  import { hasInteractiveStdin } from "../../helpers/tty.mjs";
5
5
  import { assertFlagCombinations } from "../../flags/assert-flag-combinations.mjs";
6
6
  import { detectPackageManagers, getPackageManager, getPreferredPackageManager, getSystemPackageManagers, setPackageManager } from "../../helpers/package-manager.mjs";
@@ -10,24 +10,26 @@ import { packageRoot } from "../../helpers/paths.mjs";
10
10
  import { App } from "../../helpers/app.mjs";
11
11
  import { askAgentTargets } from "../../prompts/ask-agent-targets.mjs";
12
12
  import { askAiFeatures } from "../../prompts/ask-ai-features.mjs";
13
+ import { askFeatures } from "../../prompts/ask-features.mjs";
14
+ import { askLintToolchain } from "../../prompts/ask-lint-toolchain.mjs";
13
15
  import { askProjectName } from "../../prompts/ask-project-name.mjs";
14
16
  import { askStack } from "../../prompts/ask-stack.mjs";
15
17
  import { showIntroBanner } from "../../ui/banner.mjs";
16
18
  import { createWarlockApp } from "../create-warlock-app/index.mjs";
17
19
  import getAppPath from "./get-app-path.mjs";
18
20
  import { getJsonFile } from "@warlock.js/fs";
19
- import { cancel, confirm, isCancel, multiselect, select } from "@clack/prompts";
21
+ import { cancel, confirm, isCancel, select } from "@clack/prompts";
20
22
  import { colors } from "@mongez/copper";
21
23
 
22
- //#region ../create-warlock/src/commands/create-new-app/index.ts
24
+ //#region ../../packages/create-warlock/src/commands/create-new-app/index.ts
23
25
  async function createNewApp(cli = {}) {
24
26
  const pmDetectionPromise = detectPackageManagers();
25
27
  const createWarlockVersion = getJsonFile(packageRoot("package.json")).version;
26
28
  showIntroBanner(createWarlockVersion);
27
29
  console.log(colors.cyan("Let's create something magical! \n"));
28
- const [major] = process.versions.node.split(".").map(Number);
29
- if (major < 20) {
30
- cancel("Node.js version must be at least 20.0.0");
30
+ const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
31
+ if (major < 22 || major === 22 && minor < 15) {
32
+ cancel(`Node.js 22.15.0 or newer is required (found ${process.versions.node}).`);
31
33
  process.exit(0);
32
34
  }
33
35
  assertFlagCombinations(cli);
@@ -73,6 +75,11 @@ async function createFullWizard(cli, pmDetectionPromise) {
73
75
  process.exit(0);
74
76
  }
75
77
  setPackageManager(packageManager);
78
+ const lint = await askLintToolchain(seeds.lint);
79
+ if (isCancel(lint)) {
80
+ cancel("Toolchain selection cancelled");
81
+ process.exit(0);
82
+ }
76
83
  const databaseDriver = await select({
77
84
  message: "Choose your database driver",
78
85
  options: getDatabaseDriverOptions(),
@@ -83,12 +90,7 @@ async function createFullWizard(cli, pmDetectionPromise) {
83
90
  process.exit(0);
84
91
  }
85
92
  const selectedDriver = getDatabaseDriver(databaseDriver);
86
- const selectedFeatures = await multiselect({
87
- message: "Select optional features to include",
88
- options: getFeatureOptions(),
89
- initialValues: seeds.features,
90
- required: false
91
- });
93
+ const selectedFeatures = await askFeatures(seeds.features);
92
94
  if (isCancel(selectedFeatures)) {
93
95
  cancel("Feature selection cancelled");
94
96
  process.exit(0);
@@ -127,7 +129,8 @@ async function createFullWizard(cli, pmDetectionPromise) {
127
129
  aiProviders: selectedAiProviders,
128
130
  useGit,
129
131
  useJWT,
130
- agents
132
+ agents,
133
+ lint
131
134
  }
132
135
  });
133
136
  }
@@ -210,6 +213,8 @@ async function scaffold(appDetails) {
210
213
  * before any file is written.
211
214
  */
212
215
  function resolveNonInteractiveOptions(cli) {
216
+ const lint = cli.lint ?? "eslint";
217
+ if (lint !== "eslint" && lint !== "oxc") throw new Error(`Unknown lint toolchain "${lint}". Use --lint=eslint or --lint=oxc.`);
213
218
  const databaseDriver = cli.db ?? "mongodb";
214
219
  const noDatabase = isNoDatabase(databaseDriver);
215
220
  const driver = noDatabase ? void 0 : getDatabaseDriver(databaseDriver);
@@ -230,6 +235,7 @@ function resolveNonInteractiveOptions(cli) {
230
235
  databasePort: driver?.defaultPort ?? 27017,
231
236
  features,
232
237
  aiProviders,
238
+ lint,
233
239
  useGit: cli.git ?? false,
234
240
  useJWT: cli.jwt ?? false
235
241
  };
@@ -257,7 +263,7 @@ async function resolveAppOptions(cli) {
257
263
  async function createNonInteractive(cli) {
258
264
  const appName = (cli.name ?? "").trim();
259
265
  if (!appName) {
260
- cancel("A project name is required and no terminal is available to ask for one. Pass it as the first argument or --name=<name>. Non-interactive flags: --yes, --pm=<npm|yarn|pnpm|bun>, --db=<driver>|--no-db, --features=<list>, --ai=<list>, --git|--no-git, --jwt|--no-jwt.");
266
+ cancel("A project name is required and no terminal is available to ask for one. Pass it as the first argument or --name=<name>. Non-interactive flags: --yes, --pm=<npm|yarn|pnpm|bun>, --db=<driver>|--no-db, --features=<list>, --ai=<list>, --lint=<oxc|eslint>, --git|--no-git, --jwt|--no-jwt.");
261
267
  process.exit(1);
262
268
  }
263
269
  const appPath = getAppPath(appName);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../create-warlock/src/commands/create-new-app/index.ts"],"sourcesContent":["import { cancel, confirm, isCancel, multiselect, select } from \"@clack/prompts\";\nimport { colors } from \"@mongez/copper\";\nimport { getJsonFile } from \"@warlock.js/fs\";\nimport { resolveAgentTargets } from \"../../features/agent-kit-targets\";\nimport {\n getDatabaseDriver,\n getDatabaseDriverOptions,\n isNoDatabase,\n} from \"../../features/database-drivers\";\nimport {\n getAiPackageOptions,\n getAiProviderOptions,\n getAllFeatureKeys,\n getFeatureOptions,\n} from \"../../features/features-map\";\nimport { assertFlagCombinations } from \"../../flags/assert-flag-combinations\";\nimport { assertPackageManagerAvailable } from \"../../flags/assert-package-manager\";\nimport { seedFromFlags } from \"../../flags/seed-from-flags\";\nimport { App } from \"../../helpers/app\";\nimport {\n detectPackageManagers,\n getPackageManager,\n getPreferredPackageManager,\n getSystemPackageManagers,\n setPackageManager,\n} from \"../../helpers/package-manager\";\nimport { packageRoot } from \"../../helpers/paths\";\nimport { hasInteractiveStdin } from \"../../helpers/tty\";\nimport { askAgentTargets } from \"../../prompts/ask-agent-targets\";\nimport { askAiFeatures } from \"../../prompts/ask-ai-features\";\nimport { askProjectName } from \"../../prompts/ask-project-name\";\nimport { askStack } from \"../../prompts/ask-stack\";\nimport { showIntroBanner } from \"../../ui/banner\";\nimport { createWarlockApp } from \"../create-warlock-app\";\nimport getAppPath from \"./get-app-path\";\nimport {\n AppOptions,\n App as AppType,\n CliFlags,\n SetupChoice,\n Stack,\n} from \"./types\";\n\nexport default async function createNewApp(cli: CliFlags = {}) {\n // Start detecting package managers in the background to avoid delay later\n const pmDetectionPromise = detectPackageManagers();\n\n // Get version from package.json\n const packageJson: any = getJsonFile(packageRoot(\"package.json\"));\n const createWarlockVersion = packageJson.version;\n\n // Show the intro banner\n showIntroBanner(createWarlockVersion);\n\n console.log(colors.cyan(\"Let's create something magical! \\n\"));\n\n // Validate Node.js version (minimum v20)\n const [major] = process.versions.node.split(\".\").map(Number);\n if (major < 20) {\n cancel(\"Node.js version must be at least 20.0.0\");\n process.exit(0);\n }\n\n // Refuse contradictory flag combinations LOUDLY, before anything else looks\n // at them — `--customize --yes` used to drop `--customize` without a word.\n assertFlagCombinations(cli);\n\n // Non-interactive path: build everything from flags and skip the prompts.\n //\n // Triggered by --yes, OR automatically the moment stdin has no TTY — CI, a\n // script, an agent harness. This check MUST run before the first prompt\n // call: attempting `text()`/`select()` on a non-TTY stdin does not fail\n // cleanly, it crashes with a libuv internal (\"TTY initialization failed:\n // uv_tty_init returned EBADF\") before any app directory exists, no matter\n // how the run was invoked.\n //\n // Prompting is the fallback, not the precondition: a project name is the\n // only answer with no sane default, so when a name is already available\n // (positional arg or --name) the run proceeds exactly as --yes would, TTY\n // or not. When the name is ALSO missing and there is no TTY to ask for one,\n // createNonInteractive fails loudly — naming --yes and every non-interactive\n // flag — instead of ever reaching a prompt. Every other prompt already has a\n // default; see resolveNonInteractiveOptions below for the full table.\n if (cli.yes || !hasInteractiveStdin()) {\n await createNonInteractive(cli);\n return;\n }\n\n // `--interactive` (alias `--customize`) restores the full long-form wizard\n // below. Everyone else with a TTY falls through to the default path, which\n // asks the preset plus database choice — see createDefaultInteractive.\n if (cli.interactive) {\n await createFullWizard(cli, pmDetectionPromise);\n return;\n }\n\n await createDefaultInteractive(cli, pmDetectionPromise);\n}\n\n/**\n * The full long-form wizard (`--interactive` / `--customize`): every prompt,\n * one after another. This is the ORIGINAL default flow, preserved verbatim —\n * only its trigger moved from \"any TTY\" to an explicit opt-in flag, per the\n * rule that a reversible choice never prompts by default.\n */\nasync function createFullWizard(\n cli: CliFlags,\n pmDetectionPromise: Promise<void>,\n) {\n // Step 1: Project name — but only when it is not already known. It arrives\n // answered from `--name`/the positional arg, and from the default path when\n // the user picked \"Customize\" AFTER being asked for a name. Re-asking for an\n // answer we already hold is the double-prompt this branch exists to avoid.\n const appName = cli.name ?? (await askProjectName());\n\n const appPath = getAppPath(appName);\n if (!appPath) return;\n\n // A flag passed alongside --customize is an answer given early, not a\n // contradiction: it pre-selects the matching prompt instead of being\n // dropped. Validate the ones that CAN be wrong through the same function\n // the non-interactive path uses, so an unknown driver or feature key fails\n // here rather than showing a prompt that quietly ignores it.\n try {\n resolveNonInteractiveOptions(cli);\n } catch (error) {\n cancel((error as Error).message);\n process.exit(1);\n }\n\n const seeds = seedFromFlags(cli);\n\n // Step 2: Package Manager selection\n await pmDetectionPromise; // Ensure detection is complete\n\n // Only now can availability be judged: detection is what fills\n // getSystemPackageManagers(), and the prompt below is built from it.\n assertPackageManagerAvailable(cli.pm);\n\n const packageManager = await select({\n message: \"Which package manager do you want to use?\",\n options: getSystemPackageManagers().map(pm => ({\n value: pm,\n label: pm,\n })),\n initialValue: seeds.packageManager,\n });\n\n if (isCancel(packageManager)) {\n cancel(\"Package manager selection cancelled\");\n process.exit(0);\n }\n\n setPackageManager(packageManager as string);\n\n // Step 3: Database driver selection\n const databaseDriver = await select({\n message: \"Choose your database driver\",\n options: getDatabaseDriverOptions(),\n initialValue: seeds.databaseDriver,\n });\n\n if (isCancel(databaseDriver)) {\n cancel(\"Database selection cancelled\");\n process.exit(0);\n }\n\n const selectedDriver = getDatabaseDriver(databaseDriver as string);\n\n // Step 4: Features selection\n const selectedFeatures = await multiselect({\n message: \"Select optional features to include\",\n options: getFeatureOptions(),\n initialValues: seeds.features,\n required: false,\n });\n\n if (isCancel(selectedFeatures)) {\n cancel(\"Feature selection cancelled\");\n process.exit(0);\n }\n\n // Step 4b: AI providers + capability packages — selecting any pulls\n // @warlock.js/ai automatically. The result still flows through the\n // `selectedAiProviders` → `aiProviders` pipeline, which now also carries the\n // capability packages (ai-tools / ai-panoptic / ai-workspace).\n const selectedAiProviders = await askAiFeatures(seeds.aiProviders);\n\n if (isCancel(selectedAiProviders)) {\n cancel(\"AI provider selection cancelled\");\n process.exit(0);\n }\n\n // Step 5: Git initialization\n const useGit =\n (await confirm({\n message: \"Initialize a Git repository?\",\n initialValue: seeds.useGit,\n })) === true;\n\n if (isCancel(useGit)) {\n cancel(\"Setup cancelled\");\n process.exit(0);\n }\n\n // Step 6: JWT secret generation\n const useJWT =\n (await confirm({\n message: \"Generate JWT secret keys?\",\n initialValue: seeds.useJWT,\n })) === true;\n\n if (isCancel(useJWT)) {\n cancel(\"Setup cancelled\");\n process.exit(0);\n }\n\n // Step 7: agent-kit targets. Asked here rather than left to --agents,\n // because Customize means every choice is offered (owner ruling, seq 1028).\n // --agents pre-ticks the selection, like every other flag.\n const agents = await askAgentTargets(cli.agents);\n\n // Build app details\n const appDetails: Required<AppType> = {\n appName: appName,\n appType: \"warlock\",\n appPath: appPath,\n pkgManager: getPackageManager(),\n options: {\n databaseDriver: databaseDriver as string,\n databasePort: selectedDriver?.defaultPort || 27017,\n features: selectedFeatures as string[],\n aiProviders: selectedAiProviders as string[],\n useGit,\n useJWT,\n agents,\n },\n };\n\n // Create the app\n await scaffold(appDetails);\n}\n\n/**\n * The default TTY path: neither `--yes` nor `--interactive` was given, but a\n * terminal is available. Asks the project name when missing (a required\n * answer with no sane default — same treatment as the non-interactive path)\n * plus the preset's database choice when `--db`/`--no-db` was not supplied.\n * Every other choice is resolved exactly like `createNonInteractive` — flags,\n * with defaults, never a prompt.\n */\nasync function createDefaultInteractive(\n cli: CliFlags,\n pmDetectionPromise: Promise<void>,\n) {\n const appName = cli.name ?? (await askProjectName());\n\n const appPath = getAppPath(appName);\n if (!appPath) return;\n\n const choice: SetupChoice = cli.stack ?? (await askStack());\n\n // \"Customize\" is not a third stack — it is the wizard, chosen from the menu\n // instead of from a flag nobody was told about. Hand over the name we just\n // collected so the wizard starts at its SECOND question.\n if (choice === \"customize\") {\n await createFullWizard({ ...cli, name: appName }, pmDetectionPromise);\n return;\n }\n\n const stack: Stack = choice;\n\n await pmDetectionPromise;\n\n const packageManager = cli.pm ?? getPreferredPackageManager();\n\n assertPackageManagerAvailable(cli.pm);\n\n setPackageManager(packageManager);\n\n const databaseDriver =\n cli.db ??\n (await select({\n message: \"Choose your database driver\",\n options: getDatabaseDriverOptions(),\n initialValue: \"mongodb\",\n }));\n\n if (isCancel(databaseDriver)) {\n cancel(\"Database selection cancelled\");\n process.exit(0);\n }\n\n let options: AppOptions;\n\n try {\n options = await resolveAppOptions({ ...cli, stack, db: databaseDriver });\n } catch (error) {\n cancel((error as Error).message);\n process.exit(1);\n }\n\n const appDetails: Required<AppType> = {\n appName,\n appType: \"warlock\",\n appPath,\n pkgManager: getPackageManager(),\n options,\n };\n\n await scaffold(appDetails);\n}\n\n/**\n * Run the scaffold and make the process's exit code tell the truth: anything\n * the user asked for that did not happen leaves the shell with a non-zero\n * status, so scripts and CI cannot mistake a half-built project for a finished\n * one. The problems themselves are printed by the scaffolder.\n */\nasync function scaffold(appDetails: Required<AppType>) {\n const outcome = await createWarlockApp(new App(appDetails));\n\n if (outcome && !outcome.ok) {\n process.exitCode = 1;\n }\n}\n\n/** Everything {@link resolveNonInteractiveOptions} can resolve without the network. */\nexport type SyncAppOptions = Omit<AppOptions, \"agents\">;\n\n/**\n * Resolve every flag-or-default `AppOptions` field EXCEPT `agents` from parsed\n * CLI flags — applying the non-interactive defaults, validating the database\n * driver and feature/provider keys, and handling the `none` (no database)\n * selection. `agents` is resolved separately by {@link resolveAppOptions}\n * because it can require a network fetch; this function stays synchronous so\n * the rest of the flag → app mapping remains trivially unit-testable.\n *\n * `--stack=web` (or the equivalent wizard answer) seeds the `web` feature by\n * default — the one structural choice — but an explicit `--features` always\n * wins, so a user who spells out the feature list is never second-guessed.\n *\n * Throws on an unknown driver, feature, or AI key so the caller can fail fast\n * before any file is written.\n */\nexport function resolveNonInteractiveOptions(cli: CliFlags): SyncAppOptions {\n const databaseDriver = cli.db ?? \"mongodb\";\n const noDatabase = isNoDatabase(databaseDriver);\n const driver = noDatabase ? undefined : getDatabaseDriver(databaseDriver);\n\n if (!noDatabase && !driver) {\n throw new Error(`Unknown database driver \"${databaseDriver}\"`);\n }\n\n const stack: Stack = cli.stack ?? \"api\";\n const features = cli.features ?? (stack === \"web\" ? [\"web\"] : []);\n const aiProviders = cli.ai ?? [];\n\n const allowedKeys = getAllFeatureKeys();\n const invalidFeatureKeys = features.filter(key => !allowedKeys.includes(key));\n const errors: string[] = [];\n\n if (invalidFeatureKeys.length > 0) {\n errors.push(`Unknown feature(s): ${invalidFeatureKeys.join(\",\")}`);\n }\n\n const aiFeatureKeys = [\n ...getAiProviderOptions(),\n ...getAiPackageOptions(),\n ].map(option => option.value);\n const invalidAiKeys = aiProviders.filter(key => !aiFeatureKeys.includes(key));\n\n if (invalidAiKeys.length > 0) {\n errors.push(\n `Unknown AI feature(s): ${invalidAiKeys.join(\",\")}. ` +\n `Valid AI choices: ${aiFeatureKeys.join(\", \")}`,\n );\n }\n\n if (errors.length > 0) {\n throw new Error(errors.join(\"\\n\"));\n }\n\n return {\n databaseDriver,\n databasePort: driver?.defaultPort ?? 27017,\n features,\n aiProviders,\n useGit: cli.git ?? false,\n useJWT: cli.jwt ?? false,\n };\n}\n\n/**\n * Resolve the FULL `AppOptions` — {@link resolveNonInteractiveOptions} plus\n * the validated `agents` list. Async because a non-default `--agents`\n * selection may need {@link resolveAgentTargets}'s network fetch; the default\n * (`claude`) never does.\n */\nexport async function resolveAppOptions(cli: CliFlags): Promise<AppOptions> {\n const options = resolveNonInteractiveOptions(cli);\n const agents = await resolveAgentTargets(cli.agents);\n\n return { ...options, agents };\n}\n\n/**\n * Non-interactive scaffold path for `--yes`. Builds the app from flags with\n * sensible defaults and skips every prompt — a single command scaffolds the\n * entire app. Validation lives in `resolveNonInteractiveOptions`; a typo fails\n * fast here instead of mid-install.\n */\nasync function createNonInteractive(cli: CliFlags) {\n const appName = (cli.name ?? \"\").trim();\n\n if (!appName) {\n // Reached either because --yes was passed with no name, or because stdin\n // has no TTY to prompt for one — either way, nothing is left to fall back\n // to. Name --yes AND every non-interactive flag so a CI log is enough to\n // fix the invocation without hunting for docs.\n cancel(\n \"A project name is required and no terminal is available to ask for one. \" +\n \"Pass it as the first argument or --name=<name>. Non-interactive flags: \" +\n \"--yes, --pm=<npm|yarn|pnpm|bun>, --db=<driver>|--no-db, --features=<list>, \" +\n \"--ai=<list>, --git|--no-git, --jwt|--no-jwt.\",\n );\n process.exit(1);\n }\n\n const appPath = getAppPath(appName);\n\n if (!appPath) return;\n\n await detectPackageManagers();\n\n const packageManager = cli.pm ?? getPreferredPackageManager();\n\n assertPackageManagerAvailable(cli.pm);\n\n setPackageManager(packageManager);\n\n let options: AppOptions;\n\n try {\n options = await resolveAppOptions(cli);\n } catch (error) {\n cancel((error as Error).message);\n process.exit(1);\n }\n\n const appDetails: Required<AppType> = {\n appName,\n appType: \"warlock\",\n appPath,\n pkgManager: getPackageManager(),\n options,\n };\n\n await scaffold(appDetails);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../packages/create-warlock/src/commands/create-new-app/index.ts"],"sourcesContent":["import { cancel, confirm, isCancel, select } from \"@clack/prompts\";\nimport { colors } from \"@mongez/copper\";\nimport { getJsonFile } from \"@warlock.js/fs\";\nimport { resolveAgentTargets } from \"../../features/agent-kit-targets\";\nimport {\n getDatabaseDriver,\n getDatabaseDriverOptions,\n isNoDatabase,\n} from \"../../features/database-drivers\";\nimport {\n getAiPackageOptions,\n getAiProviderOptions,\n getAllFeatureKeys,\n} from \"../../features/features-map\";\nimport { assertFlagCombinations } from \"../../flags/assert-flag-combinations\";\nimport { assertPackageManagerAvailable } from \"../../flags/assert-package-manager\";\nimport { seedFromFlags } from \"../../flags/seed-from-flags\";\nimport { App } from \"../../helpers/app\";\nimport {\n detectPackageManagers,\n getPackageManager,\n getPreferredPackageManager,\n getSystemPackageManagers,\n setPackageManager,\n} from \"../../helpers/package-manager\";\nimport { packageRoot } from \"../../helpers/paths\";\nimport { hasInteractiveStdin } from \"../../helpers/tty\";\nimport { askAgentTargets } from \"../../prompts/ask-agent-targets\";\nimport { askAiFeatures } from \"../../prompts/ask-ai-features\";\nimport { askFeatures } from \"../../prompts/ask-features\";\nimport { askLintToolchain } from \"../../prompts/ask-lint-toolchain\";\nimport { askProjectName } from \"../../prompts/ask-project-name\";\nimport { askStack } from \"../../prompts/ask-stack\";\nimport { showIntroBanner } from \"../../ui/banner\";\nimport { createWarlockApp } from \"../create-warlock-app\";\nimport getAppPath from \"./get-app-path\";\nimport { AppOptions, App as AppType, CliFlags, SetupChoice, Stack } from \"./types\";\n\nexport default async function createNewApp(cli: CliFlags = {}) {\n // Start detecting package managers in the background to avoid delay later\n const pmDetectionPromise = detectPackageManagers();\n\n // Get version from package.json\n const packageJson: any = getJsonFile(packageRoot(\"package.json\"));\n const createWarlockVersion = packageJson.version;\n\n // Show the intro banner\n showIntroBanner(createWarlockVersion);\n\n console.log(colors.cyan(\"Let's create something magical! \\n\"));\n\n // Validate Node.js version (minimum 22.15: the warlock CLI uses module.registerHooks)\n const [major = 0, minor = 0] = process.versions.node.split(\".\").map(Number);\n if (major < 22 || (major === 22 && minor < 15)) {\n cancel(`Node.js 22.15.0 or newer is required (found ${process.versions.node}).`);\n process.exit(0);\n }\n\n // Refuse contradictory flag combinations LOUDLY, before anything else looks\n // at them — `--customize --yes` used to drop `--customize` without a word.\n assertFlagCombinations(cli);\n\n // Non-interactive path: build everything from flags and skip the prompts.\n //\n // Triggered by --yes, OR automatically the moment stdin has no TTY — CI, a\n // script, an agent harness. This check MUST run before the first prompt\n // call: attempting `text()`/`select()` on a non-TTY stdin does not fail\n // cleanly, it crashes with a libuv internal (\"TTY initialization failed:\n // uv_tty_init returned EBADF\") before any app directory exists, no matter\n // how the run was invoked.\n //\n // Prompting is the fallback, not the precondition: a project name is the\n // only answer with no sane default, so when a name is already available\n // (positional arg or --name) the run proceeds exactly as --yes would, TTY\n // or not. When the name is ALSO missing and there is no TTY to ask for one,\n // createNonInteractive fails loudly — naming --yes and every non-interactive\n // flag — instead of ever reaching a prompt. Every other prompt already has a\n // default; see resolveNonInteractiveOptions below for the full table.\n if (cli.yes || !hasInteractiveStdin()) {\n await createNonInteractive(cli);\n return;\n }\n\n // `--interactive` (alias `--customize`) restores the full long-form wizard\n // below. Everyone else with a TTY falls through to the default path, which\n // asks the preset plus database choice — see createDefaultInteractive.\n if (cli.interactive) {\n await createFullWizard(cli, pmDetectionPromise);\n return;\n }\n\n await createDefaultInteractive(cli, pmDetectionPromise);\n}\n\n/**\n * The full long-form wizard (`--interactive` / `--customize`): every prompt,\n * one after another. This is the ORIGINAL default flow, preserved verbatim —\n * only its trigger moved from \"any TTY\" to an explicit opt-in flag, per the\n * rule that a reversible choice never prompts by default.\n */\nasync function createFullWizard(cli: CliFlags, pmDetectionPromise: Promise<void>) {\n // Step 1: Project name — but only when it is not already known. It arrives\n // answered from `--name`/the positional arg, and from the default path when\n // the user picked \"Customize\" AFTER being asked for a name. Re-asking for an\n // answer we already hold is the double-prompt this branch exists to avoid.\n const appName = cli.name ?? (await askProjectName());\n\n const appPath = getAppPath(appName);\n if (!appPath) return;\n\n // A flag passed alongside --customize is an answer given early, not a\n // contradiction: it pre-selects the matching prompt instead of being\n // dropped. Validate the ones that CAN be wrong through the same function\n // the non-interactive path uses, so an unknown driver or feature key fails\n // here rather than showing a prompt that quietly ignores it.\n try {\n resolveNonInteractiveOptions(cli);\n } catch (error) {\n cancel((error as Error).message);\n process.exit(1);\n }\n\n const seeds = seedFromFlags(cli);\n\n // Step 2: Package Manager selection\n await pmDetectionPromise; // Ensure detection is complete\n\n // Only now can availability be judged: detection is what fills\n // getSystemPackageManagers(), and the prompt below is built from it.\n assertPackageManagerAvailable(cli.pm);\n\n const packageManager = await select({\n message: \"Which package manager do you want to use?\",\n options: getSystemPackageManagers().map((pm) => ({\n value: pm,\n label: pm,\n })),\n initialValue: seeds.packageManager,\n });\n\n if (isCancel(packageManager)) {\n cancel(\"Package manager selection cancelled\");\n process.exit(0);\n }\n\n setPackageManager(packageManager as string);\n\n const lint = await askLintToolchain(seeds.lint);\n if (isCancel(lint)) {\n cancel(\"Toolchain selection cancelled\");\n process.exit(0);\n }\n\n // Step 3: Database driver selection\n const databaseDriver = await select({\n message: \"Choose your database driver\",\n options: getDatabaseDriverOptions(),\n initialValue: seeds.databaseDriver,\n });\n\n if (isCancel(databaseDriver)) {\n cancel(\"Database selection cancelled\");\n process.exit(0);\n }\n\n const selectedDriver = getDatabaseDriver(databaseDriver as string);\n\n // Step 4: Edit optional features in paged groups.\n const selectedFeatures = await askFeatures(seeds.features);\n\n if (isCancel(selectedFeatures)) {\n cancel(\"Feature selection cancelled\");\n process.exit(0);\n }\n\n // Step 4b: AI providers + capability packages — selecting any pulls\n // @warlock.js/ai automatically. The result still flows through the\n // `selectedAiProviders` → `aiProviders` pipeline, which now also carries the\n // capability packages (ai-tools / ai-panoptic / ai-workspace).\n const selectedAiProviders = await askAiFeatures(seeds.aiProviders);\n\n if (isCancel(selectedAiProviders)) {\n cancel(\"AI provider selection cancelled\");\n process.exit(0);\n }\n\n // Step 5: Git initialization\n const useGit =\n (await confirm({\n message: \"Initialize a Git repository?\",\n initialValue: seeds.useGit,\n })) === true;\n\n if (isCancel(useGit)) {\n cancel(\"Setup cancelled\");\n process.exit(0);\n }\n\n // Step 6: JWT secret generation\n const useJWT =\n (await confirm({\n message: \"Generate JWT secret keys?\",\n initialValue: seeds.useJWT,\n })) === true;\n\n if (isCancel(useJWT)) {\n cancel(\"Setup cancelled\");\n process.exit(0);\n }\n\n // Step 7: agent-kit targets. Asked here rather than left to --agents,\n // because Customize means every choice is offered (owner ruling, seq 1028).\n // --agents pre-ticks the selection, like every other flag.\n const agents = await askAgentTargets(cli.agents);\n\n // Build app details\n const appDetails: Required<AppType> = {\n appName: appName,\n appType: \"warlock\",\n appPath: appPath,\n pkgManager: getPackageManager(),\n options: {\n databaseDriver: databaseDriver as string,\n databasePort: selectedDriver?.defaultPort || 27017,\n features: selectedFeatures as string[],\n aiProviders: selectedAiProviders as string[],\n useGit,\n useJWT,\n agents,\n lint,\n },\n };\n\n // Create the app\n await scaffold(appDetails);\n}\n\n/**\n * The default TTY path: neither `--yes` nor `--interactive` was given, but a\n * terminal is available. Asks the project name when missing (a required\n * answer with no sane default — same treatment as the non-interactive path)\n * plus the preset's database choice when `--db`/`--no-db` was not supplied.\n * Every other choice is resolved exactly like `createNonInteractive` — flags,\n * with defaults, never a prompt.\n */\nasync function createDefaultInteractive(cli: CliFlags, pmDetectionPromise: Promise<void>) {\n const appName = cli.name ?? (await askProjectName());\n\n const appPath = getAppPath(appName);\n if (!appPath) return;\n\n const choice: SetupChoice = cli.stack ?? (await askStack());\n\n // \"Customize\" is not a third stack — it is the wizard, chosen from the menu\n // instead of from a flag nobody was told about. Hand over the name we just\n // collected so the wizard starts at its SECOND question.\n if (choice === \"customize\") {\n await createFullWizard({ ...cli, name: appName }, pmDetectionPromise);\n return;\n }\n\n const stack: Stack = choice;\n\n await pmDetectionPromise;\n\n const packageManager = cli.pm ?? getPreferredPackageManager();\n\n assertPackageManagerAvailable(cli.pm);\n\n setPackageManager(packageManager);\n\n const databaseDriver =\n cli.db ??\n (await select({\n message: \"Choose your database driver\",\n options: getDatabaseDriverOptions(),\n initialValue: \"mongodb\",\n }));\n\n if (isCancel(databaseDriver)) {\n cancel(\"Database selection cancelled\");\n process.exit(0);\n }\n\n let options: AppOptions;\n\n try {\n options = await resolveAppOptions({ ...cli, stack, db: databaseDriver });\n } catch (error) {\n cancel((error as Error).message);\n process.exit(1);\n }\n\n const appDetails: Required<AppType> = {\n appName,\n appType: \"warlock\",\n appPath,\n pkgManager: getPackageManager(),\n options,\n };\n\n await scaffold(appDetails);\n}\n\n/**\n * Run the scaffold and make the process's exit code tell the truth: anything\n * the user asked for that did not happen leaves the shell with a non-zero\n * status, so scripts and CI cannot mistake a half-built project for a finished\n * one. The problems themselves are printed by the scaffolder.\n */\nasync function scaffold(appDetails: Required<AppType>) {\n const outcome = await createWarlockApp(new App(appDetails));\n\n if (outcome && !outcome.ok) {\n process.exitCode = 1;\n }\n}\n\n/** Everything {@link resolveNonInteractiveOptions} can resolve without the network. */\nexport type SyncAppOptions = Omit<AppOptions, \"agents\">;\n\n/**\n * Resolve every flag-or-default `AppOptions` field EXCEPT `agents` from parsed\n * CLI flags — applying the non-interactive defaults, validating the database\n * driver and feature/provider keys, and handling the `none` (no database)\n * selection. `agents` is resolved separately by {@link resolveAppOptions}\n * because it can require a network fetch; this function stays synchronous so\n * the rest of the flag → app mapping remains trivially unit-testable.\n *\n * `--stack=web` (or the equivalent wizard answer) seeds the `web` feature by\n * default — the one structural choice — but an explicit `--features` always\n * wins, so a user who spells out the feature list is never second-guessed.\n *\n * Throws on an unknown driver, feature, or AI key so the caller can fail fast\n * before any file is written.\n */\nexport function resolveNonInteractiveOptions(cli: CliFlags): SyncAppOptions {\n const lint = cli.lint ?? \"eslint\";\n if (lint !== \"eslint\" && lint !== \"oxc\") {\n throw new Error(`Unknown lint toolchain \"${lint}\". Use --lint=eslint or --lint=oxc.`);\n }\n const databaseDriver = cli.db ?? \"mongodb\";\n const noDatabase = isNoDatabase(databaseDriver);\n const driver = noDatabase ? undefined : getDatabaseDriver(databaseDriver);\n\n if (!noDatabase && !driver) {\n throw new Error(`Unknown database driver \"${databaseDriver}\"`);\n }\n\n const stack: Stack = cli.stack ?? \"api\";\n const features = cli.features ?? (stack === \"web\" ? [\"web\"] : []);\n const aiProviders = cli.ai ?? [];\n\n const allowedKeys = getAllFeatureKeys();\n const invalidFeatureKeys = features.filter((key) => !allowedKeys.includes(key));\n const errors: string[] = [];\n\n if (invalidFeatureKeys.length > 0) {\n errors.push(`Unknown feature(s): ${invalidFeatureKeys.join(\",\")}`);\n }\n\n const aiFeatureKeys = [...getAiProviderOptions(), ...getAiPackageOptions()].map(\n (option) => option.value,\n );\n const invalidAiKeys = aiProviders.filter((key) => !aiFeatureKeys.includes(key));\n\n if (invalidAiKeys.length > 0) {\n errors.push(\n `Unknown AI feature(s): ${invalidAiKeys.join(\",\")}. ` +\n `Valid AI choices: ${aiFeatureKeys.join(\", \")}`,\n );\n }\n\n if (errors.length > 0) {\n throw new Error(errors.join(\"\\n\"));\n }\n\n return {\n databaseDriver,\n databasePort: driver?.defaultPort ?? 27017,\n features,\n aiProviders,\n lint,\n useGit: cli.git ?? false,\n useJWT: cli.jwt ?? false,\n };\n}\n\n/**\n * Resolve the FULL `AppOptions` — {@link resolveNonInteractiveOptions} plus\n * the validated `agents` list. Async because a non-default `--agents`\n * selection may need {@link resolveAgentTargets}'s network fetch; the default\n * (`claude`) never does.\n */\nexport async function resolveAppOptions(cli: CliFlags): Promise<AppOptions> {\n const options = resolveNonInteractiveOptions(cli);\n const agents = await resolveAgentTargets(cli.agents);\n\n return { ...options, agents };\n}\n\n/**\n * Non-interactive scaffold path for `--yes`. Builds the app from flags with\n * sensible defaults and skips every prompt — a single command scaffolds the\n * entire app. Validation lives in `resolveNonInteractiveOptions`; a typo fails\n * fast here instead of mid-install.\n */\nasync function createNonInteractive(cli: CliFlags) {\n const appName = (cli.name ?? \"\").trim();\n\n if (!appName) {\n // Reached either because --yes was passed with no name, or because stdin\n // has no TTY to prompt for one — either way, nothing is left to fall back\n // to. Name --yes AND every non-interactive flag so a CI log is enough to\n // fix the invocation without hunting for docs.\n cancel(\n \"A project name is required and no terminal is available to ask for one. \" +\n \"Pass it as the first argument or --name=<name>. Non-interactive flags: \" +\n \"--yes, --pm=<npm|yarn|pnpm|bun>, --db=<driver>|--no-db, --features=<list>, \" +\n \"--ai=<list>, --lint=<oxc|eslint>, --git|--no-git, --jwt|--no-jwt.\",\n );\n process.exit(1);\n }\n\n const appPath = getAppPath(appName);\n\n if (!appPath) return;\n\n await detectPackageManagers();\n\n const packageManager = cli.pm ?? getPreferredPackageManager();\n\n assertPackageManagerAvailable(cli.pm);\n\n setPackageManager(packageManager);\n\n let options: AppOptions;\n\n try {\n options = await resolveAppOptions(cli);\n } catch (error) {\n cancel((error as Error).message);\n process.exit(1);\n }\n\n const appDetails: Required<AppType> = {\n appName,\n appType: \"warlock\",\n appPath,\n pkgManager: getPackageManager(),\n options,\n };\n\n await scaffold(appDetails);\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- //#region ../create-warlock/src/commands/create-new-app/types.d.ts
1
+ //#region ../../packages/create-warlock/src/commands/create-new-app/types.d.ts
2
2
  /** The one structural fork that changes the generated app's shape. */
3
3
  type Stack = "api" | "web";
4
4
  /**
@@ -12,7 +12,8 @@ type CliFlags = {
12
12
  db?: string;
13
13
  features?: string[];
14
14
  ai?: string[];
15
- pm?: string; /** `agent-kit` targets for `--agents`. Comma-separated on the CLI. */
15
+ pm?: string; /** `--lint=eslint|oxc`; validated before scaffolding. */
16
+ lint?: string; /** `agent-kit` targets for `--agents`. Comma-separated on the CLI. */
16
17
  agents?: string[];
17
18
  git?: boolean;
18
19
  jwt?: boolean; /** `--interactive` / `--customize` — restore the full long-form prompt flow. */
@@ -8,7 +8,7 @@ import { failFatally, installFailureHints, showNotes, showPartialScreen, showPro
8
8
  import { spinner } from "../../ui/spinner.mjs";
9
9
  import { spinnerMessages } from "../../ui/spinners.mjs";
10
10
 
11
- //#region ../create-warlock/src/commands/create-warlock-app/index.ts
11
+ //#region ../../packages/create-warlock/src/commands/create-warlock-app/index.ts
12
12
  /** One-line "why did this fail" pulled out of a captured command. */
13
13
  function reasonFrom(result) {
14
14
  if (!result) return "the command reported a failure (no output captured)";
@@ -33,6 +33,7 @@ async function createWarlockApp(application) {
33
33
  application.configureDatabaseConfig(databaseDriver);
34
34
  }
35
35
  application.configureWebStarter(features.includes("web"));
36
+ application.configureLintToolchain();
36
37
  application.configureAgentKitTargets(options.agents ?? ["claude"]);
37
38
  } catch (error) {
38
39
  templateSpinner.stop(spinnerMessages.templateFailed);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../create-warlock/src/commands/create-warlock-app/index.ts"],"sourcesContent":["import {\n getDatabaseLabel,\n isNoDatabase,\n} from \"../../features/database-drivers\";\nimport { App } from \"../../helpers/app\";\nimport { CommandResult, takeLastCommandOutput } from \"../../helpers/exec\";\nimport {\n getPackageManager,\n runPackageManagerCommand,\n} 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(\n application: App,\n): 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\n .init()\n .use(\"warlock\")\n .updatePackageJson(versions)\n .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.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(notes);\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 = [\n ...(noDatabase ? [] : [databaseDriver]),\n ...features,\n ...aiProviders,\n ];\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\n ? spinnerMessages.featuresPartial\n : 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(\n initialized ? spinnerMessages.gitInitialized : spinnerMessages.gitFailed,\n );\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:\n \"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(\n generated ? spinnerMessages.jwtGenerated : spinnerMessages.jwtFailed,\n );\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(\n warmed ? spinnerMessages.cacheWarmed : spinnerMessages.cacheWarmFailed,\n );\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(notes);\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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"}
@@ -3,7 +3,7 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { homedir, tmpdir } from "node:os";
4
4
  import path from "node:path";
5
5
 
6
- //#region ../create-warlock/src/features/agent-kit-targets.ts
6
+ //#region ../../packages/create-warlock/src/features/agent-kit-targets.ts
7
7
  const requireFromHere = createRequire(import.meta.url);
8
8
  /**
9
9
  * `agent-kit` target selection for the scaffolder's `--agents` flag.
@@ -1 +1 @@
1
- {"version":3,"file":"agent-kit-targets.mjs","names":[],"sources":["../../../../../../create-warlock/src/features/agent-kit-targets.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir, tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nconst requireFromHere = createRequire(import.meta.url);\n\n/**\n * `agent-kit` target selection for the scaffolder's `--agents` flag.\n *\n * The valid target list is NOT hardcoded here as a literal source of truth —\n * it is derived, in order of preference, from:\n *\n * 1. The `@mongez/agent-kit` package installed alongside `create-warlock`\n * itself, when its own metadata exposes a target list.\n * 2. A runtime fetch of https://mongez.js.org/agent-kit/llms.txt, cached\n * locally so repeated runs do not refetch.\n *\n * The one exception is `claude`, the default target: it MUST work fully\n * offline, so it is a tiny built-in fallback rather than something that ever\n * depends on a successful network call.\n */\n\n/** The only target guaranteed valid with no network access and no cache. */\nexport const BUILTIN_AGENT_KIT_TARGETS: readonly string[] = [\"claude\"];\n\n/** The default `--agents` value when the flag is not passed. */\nexport const DEFAULT_AGENT_KIT_TARGET = \"claude\";\n\nconst AGENT_KIT_LLMS_URL = \"https://mongez.js.org/agent-kit/llms.txt\";\n\n/**\n * How long the whole remote lookup may take.\n *\n * An unbounded `fetch` does not fail on a stalled network — it WAITS, and a\n * caller that only handles rejection never gets to run its fallback. The\n * scaffolder's wizard asks this question six answers in, so \"hangs forever\"\n * is strictly worse than \"gives up and offers the built-ins\".\n *\n * Each request carries an abort signal so the socket is actually torn down,\n * and {@link getValidAgentKitTargets} additionally races the whole operation,\n * because an injected `fetchImpl` is under no obligation to honour a signal.\n */\nexport const AGENT_KIT_FETCH_TIMEOUT_MS = 5_000;\n\n/** Local cache of the last successfully fetched/derived target list. */\nfunction cacheFilePath(): string {\n const base = process.env.XDG_CACHE_HOME ?? path.join(homedir(), \".cache\");\n return path.join(base, \"create-warlock\", \"agent-kit-targets.json\");\n}\n\nfunction readCache(): string[] | undefined {\n try {\n const raw = readFileSync(cacheFilePath(), \"utf8\");\n const parsed = JSON.parse(raw) as { targets?: unknown };\n if (Array.isArray(parsed.targets)) {\n return parsed.targets.filter(\n (value): value is string => typeof value === \"string\",\n );\n }\n return undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeCache(targets: string[]): void {\n try {\n const filePath = cacheFilePath();\n mkdirSync(path.dirname(filePath), { recursive: true });\n writeFileSync(\n filePath,\n JSON.stringify({ targets, fetchedAt: new Date().toISOString() }),\n \"utf8\",\n );\n } catch {\n // The cache is a best-effort convenience — a read-only filesystem must\n // never turn a successful fetch into a failed run.\n }\n}\n\n/**\n * Best-effort read of the installed `@mongez/agent-kit` package's own\n * declared target list, when it publishes one. Returns `undefined` (never\n * throws) when the package is not installed or exposes no such metadata —\n * the caller falls back to the network source.\n */\nfunction readInstalledPackageTargets(): string[] | undefined {\n for (const base of [process.cwd(), tmpdir()]) {\n try {\n const packageJsonPath = requireFromHere.resolve(\n \"@mongez/agent-kit/package.json\",\n { paths: [base] },\n );\n const pkg = JSON.parse(readFileSync(packageJsonPath, \"utf8\")) as {\n agentKit?: { targets?: unknown };\n };\n if (Array.isArray(pkg.agentKit?.targets)) {\n return pkg.agentKit.targets.filter(\n (value): value is string => typeof value === \"string\",\n );\n }\n } catch {\n // Not installed, or no metadata — try the next base / fall through.\n }\n }\n return undefined;\n}\n\n/**\n * Pull the agent-integrations doc's raw `--target <slug>` occurrences out of\n * `agent-kit`'s own llms.txt index (following the link it documents for\n * itself), so the concrete slug list is parsed from the source rather than\n * retyped here.\n */\nexport async function fetchRemoteAgentKitTargets(\n fetchImpl: typeof fetch = fetch,\n timeoutMs: number = AGENT_KIT_FETCH_TIMEOUT_MS,\n): Promise<string[]> {\n const indexResponse = await fetchImpl(AGENT_KIT_LLMS_URL, {\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!indexResponse.ok) {\n throw new Error(\n `agent-kit llms.txt request failed with HTTP ${indexResponse.status}`,\n );\n }\n const index = await indexResponse.text();\n\n const rawLinkMatch = index.match(\n /\\[Agent integrations\\]\\((https:\\/\\/[^)]+agent-integrations[^)]*)\\)/,\n );\n\n const docUrl = rawLinkMatch?.[1]\n ?.replace(\"github.com\", \"raw.githubusercontent.com\")\n .replace(\"/blob/\", \"/\");\n\n const targets = new Set<string>();\n\n if (docUrl) {\n const docResponse = await fetchImpl(docUrl, {\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (docResponse.ok) {\n const doc = await docResponse.text();\n for (const match of doc.matchAll(/--target[= ]([a-z][a-z0-9-]*)/gi)) {\n const target = match[1];\n\n if (target) targets.add(target.toLowerCase());\n }\n }\n }\n\n if (targets.size === 0) {\n throw new Error(\n \"Could not parse any agent-kit target names out of the fetched docs\",\n );\n }\n\n return [...targets];\n}\n\n/**\n * Resolve the full list of valid `agent-kit` targets, trying the installed\n * package first, then the cache-backed network fetch.\n *\n * Throws when neither source succeeds — the caller only reaches this for an\n * explicitly requested NON-default target, where a silent fallback to a\n * stale or guessed list would be worse than a clear failure.\n */\nexport async function getValidAgentKitTargets(\n fetchImpl: typeof fetch = fetch,\n timeoutMs: number = AGENT_KIT_FETCH_TIMEOUT_MS,\n): Promise<string[]> {\n const installed = readInstalledPackageTargets();\n if (installed && installed.length > 0) {\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...installed]);\n }\n\n try {\n const fetched = await withTimeout(\n fetchRemoteAgentKitTargets(fetchImpl, timeoutMs),\n timeoutMs,\n );\n writeCache(fetched);\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...fetched]);\n } catch (fetchError) {\n const cached = readCache();\n if (cached && cached.length > 0) {\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...cached]);\n }\n throw new Error(\n `Could not determine the valid agent-kit targets (fetch failed and no cache exists): ${\n (fetchError as Error).message\n }`,\n );\n }\n}\n\n/**\n * Reject if `work` has not settled within `timeoutMs`.\n *\n * The abort signals on each request cover the real network; this covers the\n * case they cannot — a `fetchImpl` that ignores its signal, or a promise that\n * simply never settles. Without it \"bounded\" would be a property of the\n * happy path only.\n */\nexport async function withTimeout<T>(\n work: Promise<T>,\n timeoutMs: number,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n try {\n return await Promise.race([\n work,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new Error(\n `agent-kit target lookup timed out after ${timeoutMs}ms`,\n ),\n ),\n timeoutMs,\n );\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nfunction dedupe(values: string[]): string[] {\n return [...new Set(values)];\n}\n\n/**\n * Resolve and validate the `--agents` selection.\n *\n * An unset or empty selection resolves to `[DEFAULT_AGENT_KIT_TARGET]`\n * without touching the disk or the network. A selection made entirely of\n * built-in targets (today, just `claude`) is likewise resolved offline. Only\n * an explicit NON-default target triggers {@link getValidAgentKitTargets},\n * and an invalid one throws, naming every valid target it found.\n */\nexport async function resolveAgentTargets(\n requested: string[] | undefined,\n fetchImpl: typeof fetch = fetch,\n): Promise<string[]> {\n const targets =\n requested && requested.length > 0 ? requested : [DEFAULT_AGENT_KIT_TARGET];\n\n const needsValidation = targets.some(\n target => !BUILTIN_AGENT_KIT_TARGETS.includes(target),\n );\n\n if (!needsValidation) {\n return targets;\n }\n\n const valid = await getValidAgentKitTargets(fetchImpl);\n const invalid = targets.filter(target => !valid.includes(target));\n\n if (invalid.length > 0) {\n throw new Error(\n `Unknown agent-kit target(s): ${invalid.join(\", \")} — valid targets: ${valid\n .slice()\n .sort()\n .join(\", \")}`,\n );\n }\n\n return targets;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"agent-kit-targets.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/features/agent-kit-targets.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir, tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nconst requireFromHere = createRequire(import.meta.url);\n\n/**\n * `agent-kit` target selection for the scaffolder's `--agents` flag.\n *\n * The valid target list is NOT hardcoded here as a literal source of truth —\n * it is derived, in order of preference, from:\n *\n * 1. The `@mongez/agent-kit` package installed alongside `create-warlock`\n * itself, when its own metadata exposes a target list.\n * 2. A runtime fetch of https://mongez.js.org/agent-kit/llms.txt, cached\n * locally so repeated runs do not refetch.\n *\n * The one exception is `claude`, the default target: it MUST work fully\n * offline, so it is a tiny built-in fallback rather than something that ever\n * depends on a successful network call.\n */\n\n/** The only target guaranteed valid with no network access and no cache. */\nexport const BUILTIN_AGENT_KIT_TARGETS: readonly string[] = [\"claude\"];\n\n/** The default `--agents` value when the flag is not passed. */\nexport const DEFAULT_AGENT_KIT_TARGET = \"claude\";\n\nconst AGENT_KIT_LLMS_URL = \"https://mongez.js.org/agent-kit/llms.txt\";\n\n/**\n * How long the whole remote lookup may take.\n *\n * An unbounded `fetch` does not fail on a stalled network — it WAITS, and a\n * caller that only handles rejection never gets to run its fallback. The\n * scaffolder's wizard asks this question six answers in, so \"hangs forever\"\n * is strictly worse than \"gives up and offers the built-ins\".\n *\n * Each request carries an abort signal so the socket is actually torn down,\n * and {@link getValidAgentKitTargets} additionally races the whole operation,\n * because an injected `fetchImpl` is under no obligation to honour a signal.\n */\nexport const AGENT_KIT_FETCH_TIMEOUT_MS = 5_000;\n\n/** Local cache of the last successfully fetched/derived target list. */\nfunction cacheFilePath(): string {\n const base = process.env.XDG_CACHE_HOME ?? path.join(homedir(), \".cache\");\n return path.join(base, \"create-warlock\", \"agent-kit-targets.json\");\n}\n\nfunction readCache(): string[] | undefined {\n try {\n const raw = readFileSync(cacheFilePath(), \"utf8\");\n const parsed = JSON.parse(raw) as { targets?: unknown };\n if (Array.isArray(parsed.targets)) {\n return parsed.targets.filter((value): value is string => typeof value === \"string\");\n }\n return undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeCache(targets: string[]): void {\n try {\n const filePath = cacheFilePath();\n mkdirSync(path.dirname(filePath), { recursive: true });\n writeFileSync(\n filePath,\n JSON.stringify({ targets, fetchedAt: new Date().toISOString() }),\n \"utf8\",\n );\n } catch {\n // The cache is a best-effort convenience — a read-only filesystem must\n // never turn a successful fetch into a failed run.\n }\n}\n\n/**\n * Best-effort read of the installed `@mongez/agent-kit` package's own\n * declared target list, when it publishes one. Returns `undefined` (never\n * throws) when the package is not installed or exposes no such metadata —\n * the caller falls back to the network source.\n */\nfunction readInstalledPackageTargets(): string[] | undefined {\n for (const base of [process.cwd(), tmpdir()]) {\n try {\n const packageJsonPath = requireFromHere.resolve(\"@mongez/agent-kit/package.json\", {\n paths: [base],\n });\n const pkg = JSON.parse(readFileSync(packageJsonPath, \"utf8\")) as {\n agentKit?: { targets?: unknown };\n };\n if (Array.isArray(pkg.agentKit?.targets)) {\n return pkg.agentKit.targets.filter((value): value is string => typeof value === \"string\");\n }\n } catch {\n // Not installed, or no metadata — try the next base / fall through.\n }\n }\n return undefined;\n}\n\n/**\n * Pull the agent-integrations doc's raw `--target <slug>` occurrences out of\n * `agent-kit`'s own llms.txt index (following the link it documents for\n * itself), so the concrete slug list is parsed from the source rather than\n * retyped here.\n */\nexport async function fetchRemoteAgentKitTargets(\n fetchImpl: typeof fetch = fetch,\n timeoutMs: number = AGENT_KIT_FETCH_TIMEOUT_MS,\n): Promise<string[]> {\n const indexResponse = await fetchImpl(AGENT_KIT_LLMS_URL, {\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!indexResponse.ok) {\n throw new Error(`agent-kit llms.txt request failed with HTTP ${indexResponse.status}`);\n }\n const index = await indexResponse.text();\n\n const rawLinkMatch = index.match(\n /\\[Agent integrations\\]\\((https:\\/\\/[^)]+agent-integrations[^)]*)\\)/,\n );\n\n const docUrl = rawLinkMatch?.[1]\n ?.replace(\"github.com\", \"raw.githubusercontent.com\")\n .replace(\"/blob/\", \"/\");\n\n const targets = new Set<string>();\n\n if (docUrl) {\n const docResponse = await fetchImpl(docUrl, {\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (docResponse.ok) {\n const doc = await docResponse.text();\n for (const match of doc.matchAll(/--target[= ]([a-z][a-z0-9-]*)/gi)) {\n const target = match[1];\n\n if (target) targets.add(target.toLowerCase());\n }\n }\n }\n\n if (targets.size === 0) {\n throw new Error(\"Could not parse any agent-kit target names out of the fetched docs\");\n }\n\n return [...targets];\n}\n\n/**\n * Resolve the full list of valid `agent-kit` targets, trying the installed\n * package first, then the cache-backed network fetch.\n *\n * Throws when neither source succeeds — the caller only reaches this for an\n * explicitly requested NON-default target, where a silent fallback to a\n * stale or guessed list would be worse than a clear failure.\n */\nexport async function getValidAgentKitTargets(\n fetchImpl: typeof fetch = fetch,\n timeoutMs: number = AGENT_KIT_FETCH_TIMEOUT_MS,\n): Promise<string[]> {\n const installed = readInstalledPackageTargets();\n if (installed && installed.length > 0) {\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...installed]);\n }\n\n try {\n const fetched = await withTimeout(fetchRemoteAgentKitTargets(fetchImpl, timeoutMs), timeoutMs);\n writeCache(fetched);\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...fetched]);\n } catch (fetchError) {\n const cached = readCache();\n if (cached && cached.length > 0) {\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...cached]);\n }\n throw new Error(\n `Could not determine the valid agent-kit targets (fetch failed and no cache exists): ${\n (fetchError as Error).message\n }`,\n );\n }\n}\n\n/**\n * Reject if `work` has not settled within `timeoutMs`.\n *\n * The abort signals on each request cover the real network; this covers the\n * case they cannot — a `fetchImpl` that ignores its signal, or a promise that\n * simply never settles. Without it \"bounded\" would be a property of the\n * happy path only.\n */\nexport async function withTimeout<T>(work: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n try {\n return await Promise.race([\n work,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`agent-kit target lookup timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nfunction dedupe(values: string[]): string[] {\n return [...new Set(values)];\n}\n\n/**\n * Resolve and validate the `--agents` selection.\n *\n * An unset or empty selection resolves to `[DEFAULT_AGENT_KIT_TARGET]`\n * without touching the disk or the network. A selection made entirely of\n * built-in targets (today, just `claude`) is likewise resolved offline. Only\n * an explicit NON-default target triggers {@link getValidAgentKitTargets},\n * and an invalid one throws, naming every valid target it found.\n */\nexport async function resolveAgentTargets(\n requested: string[] | undefined,\n fetchImpl: typeof fetch = fetch,\n): Promise<string[]> {\n const targets = requested && requested.length > 0 ? requested : [DEFAULT_AGENT_KIT_TARGET];\n\n const needsValidation = targets.some((target) => !BUILTIN_AGENT_KIT_TARGETS.includes(target));\n\n if (!needsValidation) {\n return targets;\n }\n\n const valid = await getValidAgentKitTargets(fetchImpl);\n const invalid = targets.filter((target) => !valid.includes(target));\n\n if (invalid.length > 0) {\n throw new Error(\n `Unknown agent-kit target(s): ${invalid.join(\", \")} — valid targets: ${valid\n .slice()\n .sort()\n .join(\", \")}`,\n );\n }\n\n return targets;\n}\n"],"mappings":";;;;;;AAKA,MAAM,kBAAkB,cAAc,OAAO,KAAK,GAAG;;;;;;;;;;;;;;;;;AAmBrD,MAAa,4BAA+C,CAAC,QAAQ;;AAGrE,MAAa,2BAA2B;AAExC,MAAM,qBAAqB;;;;;;;;;;;;;AAc3B,MAAa,6BAA6B;;AAG1C,SAAS,gBAAwB;CAC/B,MAAM,OAAO,QAAQ,IAAI,kBAAkB,KAAK,KAAK,QAAQ,GAAG,QAAQ;CACxE,OAAO,KAAK,KAAK,MAAM,kBAAkB,wBAAwB;AACnE;AAEA,SAAS,YAAkC;CACzC,IAAI;EACF,MAAM,MAAM,aAAa,cAAc,GAAG,MAAM;EAChD,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,IAAI,MAAM,QAAQ,OAAO,OAAO,GAC9B,OAAO,OAAO,QAAQ,QAAQ,UAA2B,OAAO,UAAU,QAAQ;EAEpF;CACF,QAAQ;EACN;CACF;AACF;AAEA,SAAS,WAAW,SAAyB;CAC3C,IAAI;EACF,MAAM,WAAW,cAAc;EAC/B,UAAU,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;EACrD,cACE,UACA,KAAK,UAAU;GAAE;GAAS,4BAAW,IAAI,KAAK,EAAC,CAAC,YAAY;EAAE,CAAC,GAC/D,MACF;CACF,QAAQ,CAGR;AACF;;;;;;;AAQA,SAAS,8BAAoD;CAC3D,KAAK,MAAM,QAAQ,CAAC,QAAQ,IAAI,GAAG,OAAO,CAAC,GACzC,IAAI;EACF,MAAM,kBAAkB,gBAAgB,QAAQ,kCAAkC,EAChF,OAAO,CAAC,IAAI,EACd,CAAC;EACD,MAAM,MAAM,KAAK,MAAM,aAAa,iBAAiB,MAAM,CAAC;EAG5D,IAAI,MAAM,QAAQ,IAAI,UAAU,OAAO,GACrC,OAAO,IAAI,SAAS,QAAQ,QAAQ,UAA2B,OAAO,UAAU,QAAQ;CAE5F,QAAQ,CAER;AAGJ;;;;;;;AAQA,eAAsB,2BACpB,YAA0B,OAC1B,YAAoB,4BACD;CACnB,MAAM,gBAAgB,MAAM,UAAU,oBAAoB,EACxD,QAAQ,YAAY,QAAQ,SAAS,EACvC,CAAC;CACD,IAAI,CAAC,cAAc,IACjB,MAAM,IAAI,MAAM,+CAA+C,cAAc,QAAQ;CAQvF,MAAM,UAJe,MAFD,cAAc,KAAK,EAEb,CAAC,MACzB,oEAGwB,CAAC,GAAG,EAAE,EAC5B,QAAQ,cAAc,2BAA2B,CAAC,CACnD,QAAQ,UAAU,GAAG;CAExB,MAAM,0BAAU,IAAI,IAAY;CAEhC,IAAI,QAAQ;EACV,MAAM,cAAc,MAAM,UAAU,QAAQ,EAC1C,QAAQ,YAAY,QAAQ,SAAS,EACvC,CAAC;EACD,IAAI,YAAY,IAAI;GAClB,MAAM,MAAM,MAAM,YAAY,KAAK;GACnC,KAAK,MAAM,SAAS,IAAI,SAAS,iCAAiC,GAAG;IACnE,MAAM,SAAS,MAAM;IAErB,IAAI,QAAQ,QAAQ,IAAI,OAAO,YAAY,CAAC;GAC9C;EACF;CACF;CAEA,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,MAAM,oEAAoE;CAGtF,OAAO,CAAC,GAAG,OAAO;AACpB;;;;;;;;;AAUA,eAAsB,wBACpB,YAA0B,OAC1B,YAAoB,4BACD;CACnB,MAAM,YAAY,4BAA4B;CAC9C,IAAI,aAAa,UAAU,SAAS,GAClC,OAAO,OAAO,CAAC,GAAG,2BAA2B,GAAG,SAAS,CAAC;CAG5D,IAAI;EACF,MAAM,UAAU,MAAM,YAAY,2BAA2B,WAAW,SAAS,GAAG,SAAS;EAC7F,WAAW,OAAO;EAClB,OAAO,OAAO,CAAC,GAAG,2BAA2B,GAAG,OAAO,CAAC;CAC1D,SAAS,YAAY;EACnB,MAAM,SAAS,UAAU;EACzB,IAAI,UAAU,OAAO,SAAS,GAC5B,OAAO,OAAO,CAAC,GAAG,2BAA2B,GAAG,MAAM,CAAC;EAEzD,MAAM,IAAI,MACR,uFACG,WAAqB,SAE1B;CACF;AACF;;;;;;;;;AAUA,eAAsB,YAAe,MAAkB,WAA+B;CACpF,IAAI;CAEJ,IAAI;EACF,OAAO,MAAM,QAAQ,KAAK,CACxB,MACA,IAAI,SAAgB,UAAU,WAAW;GACvC,QAAQ,iBACA,uBAAO,IAAI,MAAM,2CAA2C,UAAU,GAAG,CAAC,GAChF,SACF;EACF,CAAC,CACH,CAAC;CACH,UAAU;EACR,IAAI,OAAO,aAAa,KAAK;CAC/B;AACF;AAEA,SAAS,OAAO,QAA4B;CAC1C,OAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;AAC5B;;;;;;;;;;AAWA,eAAsB,oBACpB,WACA,YAA0B,OACP;CACnB,MAAM,UAAU,aAAa,UAAU,SAAS,IAAI,YAAY,CAAC,wBAAwB;CAIzF,IAAI,CAFoB,QAAQ,MAAM,WAAW,CAAC,0BAA0B,SAAS,MAAM,CAExE,GACjB,OAAO;CAGT,MAAM,QAAQ,MAAM,wBAAwB,SAAS;CACrD,MAAM,UAAU,QAAQ,QAAQ,WAAW,CAAC,MAAM,SAAS,MAAM,CAAC;CAElE,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,MACR,gCAAgC,QAAQ,KAAK,IAAI,EAAE,oBAAoB,MACpE,MAAM,CAAC,CACP,KAAK,CAAC,CACN,KAAK,IAAI,GACd;CAGF,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- //#region ../create-warlock/src/features/database-drivers.ts
1
+ //#region ../../packages/create-warlock/src/features/database-drivers.ts
2
2
  /**
3
3
  * Sentinel `--db` value / select option that means "no database at all".
4
4
  *
@@ -1 +1 @@
1
- {"version":3,"file":"database-drivers.mjs","names":[],"sources":["../../../../../../create-warlock/src/features/database-drivers.ts"],"sourcesContent":["/**\n * Database driver options and configuration\n */\nexport type DatabaseDriver = {\n value: string;\n label: string;\n package: string;\n packageVersion: string;\n defaultPort: number;\n disabled?: boolean;\n hint?: string;\n};\n\n/**\n * Sentinel `--db` value / select option that means \"no database at all\".\n *\n * Picking it wires no driver, pulls no driver package, and deletes the\n * template's `src/config/database.ts` — the framework's database connector is\n * config-gated on that file, so with it gone the app boots with no database.\n */\nexport const NO_DATABASE = \"none\";\n\nexport const databaseDrivers: DatabaseDriver[] = [\n {\n value: \"mongodb\",\n label: \"MongoDB\",\n package: \"mongodb\",\n packageVersion: \"^7.0.0\",\n defaultPort: 27017,\n },\n {\n value: \"postgres\",\n label: \"PostgreSQL\",\n package: \"pg\",\n packageVersion: \"^8.11.0\",\n defaultPort: 5432,\n },\n {\n value: \"mysql\",\n label: \"MySQL (Coming Soon)\",\n package: \"mysql2\",\n packageVersion: \"^3.5.0\",\n defaultPort: 3306,\n disabled: true,\n hint: \"MySQL support coming in a future release\",\n },\n];\n\n/**\n * Get database driver options for the select prompt.\n *\n * The real drivers come first, followed by a \"None\" opt-out so the user can\n * scaffold an app with no database.\n */\nexport function getDatabaseDriverOptions() {\n return [\n ...databaseDrivers.map(driver => ({\n value: driver.value,\n label: driver.label,\n hint: driver.hint,\n disabled: driver.disabled,\n })),\n {\n value: NO_DATABASE,\n label: \"None\",\n hint: \"No database — skips the driver and removes src/config/database.ts\",\n disabled: undefined,\n },\n ];\n}\n\n/**\n * Get database driver config by value\n */\nexport function getDatabaseDriver(value: string): DatabaseDriver | undefined {\n return databaseDrivers.find(driver => driver.value === value);\n}\n\n/**\n * Whether a `--db` value / selection means \"no database\".\n */\nexport function isNoDatabase(value: string | undefined): boolean {\n return value === NO_DATABASE;\n}\n\n/**\n * Human-readable database label for the success screen: `\"None\"` when no\n * database was chosen, otherwise the driver's own label (falling back to the\n * raw value for an unrecognized driver).\n */\nexport function getDatabaseLabel(value: string): string {\n if (isNoDatabase(value)) return \"None\";\n\n return getDatabaseDriver(value)?.label ?? value;\n}\n\n/**\n * Get database driver dependency\n */\nexport function getDatabaseDependency(\n driverValue: string,\n): Record<string, string> {\n const driver = getDatabaseDriver(driverValue);\n if (!driver) return {};\n\n return {\n [driver.package]: driver.packageVersion,\n };\n}\n"],"mappings":";;;;;;;;AAoBA,MAAa,cAAc;AAE3B,MAAa,kBAAoC;CAC/C;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;CACf;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;CACf;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;EACb,UAAU;EACV,MAAM;CACR;AACF;;;;;;;AAQA,SAAgB,2BAA2B;CACzC,OAAO,CACL,GAAG,gBAAgB,KAAI,YAAW;EAChC,OAAO,OAAO;EACd,OAAO,OAAO;EACd,MAAM,OAAO;EACb,UAAU,OAAO;CACnB,EAAE,GACF;EACE,OAAO;EACP,OAAO;EACP,MAAM;EACN,UAAU;CACZ,CACF;AACF;;;;AAKA,SAAgB,kBAAkB,OAA2C;CAC3E,OAAO,gBAAgB,MAAK,WAAU,OAAO,UAAU,KAAK;AAC9D;;;;AAKA,SAAgB,aAAa,OAAoC;CAC/D,OAAO,UAAU;AACnB;;;;;;AAOA,SAAgB,iBAAiB,OAAuB;CACtD,IAAI,aAAa,KAAK,GAAG,OAAO;CAEhC,OAAO,kBAAkB,KAAK,CAAC,EAAE,SAAS;AAC5C"}
1
+ {"version":3,"file":"database-drivers.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/features/database-drivers.ts"],"sourcesContent":["/**\n * Database driver options and configuration\n */\nexport type DatabaseDriver = {\n value: string;\n label: string;\n package: string;\n packageVersion: string;\n defaultPort: number;\n disabled?: boolean;\n hint?: string;\n};\n\n/**\n * Sentinel `--db` value / select option that means \"no database at all\".\n *\n * Picking it wires no driver, pulls no driver package, and deletes the\n * template's `src/config/database.ts` — the framework's database connector is\n * config-gated on that file, so with it gone the app boots with no database.\n */\nexport const NO_DATABASE = \"none\";\n\nexport const databaseDrivers: DatabaseDriver[] = [\n {\n value: \"mongodb\",\n label: \"MongoDB\",\n package: \"mongodb\",\n packageVersion: \"^7.0.0\",\n defaultPort: 27017,\n },\n {\n value: \"postgres\",\n label: \"PostgreSQL\",\n package: \"pg\",\n packageVersion: \"^8.11.0\",\n defaultPort: 5432,\n },\n {\n value: \"mysql\",\n label: \"MySQL (Coming Soon)\",\n package: \"mysql2\",\n packageVersion: \"^3.5.0\",\n defaultPort: 3306,\n disabled: true,\n hint: \"MySQL support coming in a future release\",\n },\n];\n\n/**\n * Get database driver options for the select prompt.\n *\n * The real drivers come first, followed by a \"None\" opt-out so the user can\n * scaffold an app with no database.\n */\nexport function getDatabaseDriverOptions() {\n return [\n ...databaseDrivers.map((driver) => ({\n value: driver.value,\n label: driver.label,\n hint: driver.hint,\n disabled: driver.disabled,\n })),\n {\n value: NO_DATABASE,\n label: \"None\",\n hint: \"No database — skips the driver and removes src/config/database.ts\",\n disabled: undefined,\n },\n ];\n}\n\n/**\n * Get database driver config by value\n */\nexport function getDatabaseDriver(value: string): DatabaseDriver | undefined {\n return databaseDrivers.find((driver) => driver.value === value);\n}\n\n/**\n * Whether a `--db` value / selection means \"no database\".\n */\nexport function isNoDatabase(value: string | undefined): boolean {\n return value === NO_DATABASE;\n}\n\n/**\n * Human-readable database label for the success screen: `\"None\"` when no\n * database was chosen, otherwise the driver's own label (falling back to the\n * raw value for an unrecognized driver).\n */\nexport function getDatabaseLabel(value: string): string {\n if (isNoDatabase(value)) return \"None\";\n\n return getDatabaseDriver(value)?.label ?? value;\n}\n\n/**\n * Get database driver dependency\n */\nexport function getDatabaseDependency(driverValue: string): Record<string, string> {\n const driver = getDatabaseDriver(driverValue);\n if (!driver) return {};\n\n return {\n [driver.package]: driver.packageVersion,\n };\n}\n"],"mappings":";;;;;;;;AAoBA,MAAa,cAAc;AAE3B,MAAa,kBAAoC;CAC/C;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;CACf;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;CACf;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;EACb,UAAU;EACV,MAAM;CACR;AACF;;;;;;;AAQA,SAAgB,2BAA2B;CACzC,OAAO,CACL,GAAG,gBAAgB,KAAK,YAAY;EAClC,OAAO,OAAO;EACd,OAAO,OAAO;EACd,MAAM,OAAO;EACb,UAAU,OAAO;CACnB,EAAE,GACF;EACE,OAAO;EACP,OAAO;EACP,MAAM;EACN,UAAU;CACZ,CACF;AACF;;;;AAKA,SAAgB,kBAAkB,OAA2C;CAC3E,OAAO,gBAAgB,MAAM,WAAW,OAAO,UAAU,KAAK;AAChE;;;;AAKA,SAAgB,aAAa,OAAoC;CAC/D,OAAO,UAAU;AACnB;;;;;;AAOA,SAAgB,iBAAiB,OAAuB;CACtD,IAAI,aAAa,KAAK,GAAG,OAAO;CAEhC,OAAO,kBAAkB,KAAK,CAAC,EAAE,SAAS;AAC5C"}