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
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cancel, isCancel, select } from "@clack/prompts";
|
|
2
|
+
|
|
3
|
+
//#region ../create-warlock/src/prompts/ask-stack.ts
|
|
4
|
+
/**
|
|
5
|
+
* The ONE structural question the default (non-`--interactive`) interactive
|
|
6
|
+
* path is allowed to ask: does the generated app need server-rendered web
|
|
7
|
+
* pages, or is it an API only? This is the sole fork that changes the shape
|
|
8
|
+
* of the generated project — every other choice (database, features,
|
|
9
|
+
* package manager, agent-kit targets, …) is reversible within minutes and
|
|
10
|
+
* ships as a flag with a default instead.
|
|
11
|
+
*/
|
|
12
|
+
async function askStack() {
|
|
13
|
+
const answer = await select({
|
|
14
|
+
message: "What are we building?",
|
|
15
|
+
options: [{
|
|
16
|
+
value: "api",
|
|
17
|
+
label: "API only",
|
|
18
|
+
hint: "A REST/GraphQL API, no server-rendered pages"
|
|
19
|
+
}, {
|
|
20
|
+
value: "web",
|
|
21
|
+
label: "Full-stack web",
|
|
22
|
+
hint: "API + server-rendered React pages (SSR)"
|
|
23
|
+
}],
|
|
24
|
+
initialValue: "api"
|
|
25
|
+
});
|
|
26
|
+
if (isCancel(answer)) {
|
|
27
|
+
cancel("Setup cancelled");
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
return answer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { askStack };
|
|
35
|
+
//# sourceMappingURL=ask-stack.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ask-stack.mjs","names":[],"sources":["../../../../../../create-warlock/src/prompts/ask-stack.ts"],"sourcesContent":["import { cancel, isCancel, select } from \"@clack/prompts\";\nimport type { Stack } from \"../commands/create-new-app/types\";\n\n/**\n * The ONE structural question the default (non-`--interactive`) interactive\n * path is allowed to ask: does the generated app need server-rendered web\n * pages, or is it an API only? This is the sole fork that changes the shape\n * of the generated project — every other choice (database, features,\n * package manager, agent-kit targets, …) is reversible within minutes and\n * ships as a flag with a default instead.\n */\nexport async function askStack(): Promise<Stack> {\n const answer = await select({\n message: \"What are we building?\",\n options: [\n {\n value: \"api\",\n label: \"API only\",\n hint: \"A REST/GraphQL API, no server-rendered pages\",\n },\n {\n value: \"web\",\n label: \"Full-stack web\",\n hint: \"API + server-rendered React pages (SSR)\",\n },\n ],\n initialValue: \"api\",\n });\n\n if (isCancel(answer)) {\n cancel(\"Setup cancelled\");\n process.exit(0);\n }\n\n return answer as Stack;\n}\n"],"mappings":";;;;;;;;;;;AAWA,eAAsB,WAA2B;CAC/C,MAAM,SAAS,MAAM,OAAO;EAC1B,SAAS;EACT,SAAS,CACP;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR,GACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR,CACF;EACA,cAAc;CAChB,CAAC;CAED,IAAI,SAAS,MAAM,GAAG;EACpB,OAAO,iBAAiB;EACxB,QAAQ,KAAK,CAAC;CAChB;CAEA,OAAO;AACT"}
|
package/esm/summary.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getDatabaseLabel } from "./features/database-drivers.mjs";
|
|
2
|
+
import { colors } from "@mongez/copper";
|
|
3
|
+
|
|
4
|
+
//#region ../create-warlock/src/summary.ts
|
|
5
|
+
/**
|
|
6
|
+
* Print what the scaffolder decided on the user's behalf, and how to change
|
|
7
|
+
* each decision — required because almost every choice is now a default
|
|
8
|
+
* rather than a prompt, so this is the only place a user sees them.
|
|
9
|
+
*/
|
|
10
|
+
function printDecisionsSummary({ packageManager, options }) {
|
|
11
|
+
const databaseLabel = getDatabaseLabel(options.databaseDriver);
|
|
12
|
+
const headline = [
|
|
13
|
+
`Using ${packageManager}`,
|
|
14
|
+
`database: ${databaseLabel}`,
|
|
15
|
+
`agent-kit targets: ${options.agents.join(", ")}`
|
|
16
|
+
].join(" · ");
|
|
17
|
+
console.log();
|
|
18
|
+
console.log(colors.cyan(headline));
|
|
19
|
+
console.log(colors.dim(" change these — package manager: --pm, database: --db/--no-db, agents: --agents, features: --features, ai: --ai, git: --git/--no-git, jwt: --jwt/--no-jwt — or re-run with --interactive for the full wizard. Package-manager-level choices (husky, lint config, etc.) can also be edited directly in the generated project."));
|
|
20
|
+
console.log();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { printDecisionsSummary };
|
|
25
|
+
//# sourceMappingURL=summary.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.mjs","names":[],"sources":["../../../../../create-warlock/src/summary.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { getDatabaseLabel } from \"./features/database-drivers\";\nimport { AppOptions } from \"./commands/create-new-app/types\";\n\n/** What decisions.spec.ts / the terminal actually needs to render the summary. */\nexport type Decisions = {\n packageManager: string;\n options: AppOptions;\n};\n\n/**\n * Print what the scaffolder decided on the user's behalf, and how to change\n * each decision — required because almost every choice is now a default\n * rather than a prompt, so this is the only place a user sees them.\n */\nexport function printDecisionsSummary({ packageManager, options }: Decisions): void {\n const databaseLabel = getDatabaseLabel(options.databaseDriver);\n\n const headline = [\n `Using ${packageManager}`,\n `database: ${databaseLabel}`,\n `agent-kit targets: ${options.agents.join(\", \")}`,\n ].join(\" · \");\n\n console.log();\n console.log(colors.cyan(headline));\n console.log(\n colors.dim(\n \" change these — package manager: --pm, database: --db/--no-db, agents: --agents, \" +\n \"features: --features, ai: --ai, git: --git/--no-git, jwt: --jwt/--no-jwt — \" +\n \"or re-run with --interactive for the full wizard. Package-manager-level choices \" +\n \"(husky, lint config, etc.) can also be edited directly in the generated project.\",\n ),\n );\n console.log();\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,sBAAsB,EAAE,gBAAgB,WAA4B;CAClF,MAAM,gBAAgB,iBAAiB,QAAQ,cAAc;CAE7D,MAAM,WAAW;EACf,SAAS;EACT,aAAa;EACb,sBAAsB,QAAQ,OAAO,KAAK,IAAI;CAChD,CAAC,CAAC,KAAK,KAAK;CAEZ,QAAQ,IAAI;CACZ,QAAQ,IAAI,OAAO,KAAK,QAAQ,CAAC;CACjC,QAAQ,IACN,OAAO,IACL,+TAIF,CACF;CACA,QAAQ,IAAI;AACd"}
|
package/llms-full.txt
CHANGED
|
@@ -8,52 +8,48 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
name: create-a-warlock-project
|
|
11
|
-
description: 'Scaffold a brand-new Warlock.js project with `create-warlock` —
|
|
11
|
+
description: 'Scaffold a brand-new Warlock.js project with `create-warlock` — fully headless by default (at most one prompt — API-only vs full-stack web) plus `--interactive`/`--customize` for the full long-form wizard. Every flag (`--name`, `--stack`, `--db`, `--pm`, `--features`, `--ai`, `--agents`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`, `--interactive`), every valid database-driver / feature / AI-provider / agent-kit-target key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), the post-scaffold decisions summary, and the directory tree the template emits. Triggers: `create-warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "scaffold without a database", "which features can I pass to create-warlock", "agent-kit targets". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).'
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# create-warlock — scaffold a new project
|
|
15
15
|
|
|
16
|
-
`create-warlock` builds a fresh Warlock.js project: it copies the `warlock` template, substitutes the project name, wires the chosen database driver into `.env` (or, when you opt out with `--db=none`, removes `src/config/database.ts`), then delegates every optional package install to the project's own `warlock add` so dependency versions are never duplicated by the scaffolder.
|
|
16
|
+
`create-warlock` builds a fresh Warlock.js project: it copies the `warlock` template, substitutes the project name, wires the chosen database driver into `.env` (or, when you opt out with `--db=none`, removes `src/config/database.ts`), derives `agent-kit` targets into the generated project, then delegates every optional package install to the project's own `warlock add` so dependency versions are never duplicated by the scaffolder.
|
|
17
17
|
|
|
18
18
|
## The shape
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
# or: npx create-warlock
|
|
21
|
+
# Default: at most ONE prompt (API-only vs full-stack web) — everything else is a flag/default
|
|
22
|
+
npx create-warlock my-app
|
|
24
23
|
|
|
25
|
-
#
|
|
24
|
+
# Fully headless — one command scaffolds the whole app, no prompts at all
|
|
26
25
|
npx create-warlock my-app --yes --db=postgres --features=test,redis --ai=ai-openai
|
|
27
26
|
|
|
27
|
+
# The one structural fork
|
|
28
|
+
npx create-warlock my-web-app --yes --stack=web
|
|
29
|
+
|
|
28
30
|
# No database at all
|
|
29
31
|
npx create-warlock my-api --yes --no-db --features=test
|
|
32
|
+
|
|
33
|
+
# The full long-form wizard, one prompt at a time
|
|
34
|
+
npx create-warlock --interactive
|
|
30
35
|
```
|
|
31
36
|
|
|
32
37
|
The first positional argument is the project name; it also becomes the directory created under the current working directory. If that directory already exists, the command aborts — it never overwrites.
|
|
33
38
|
|
|
34
|
-
##
|
|
35
|
-
|
|
36
|
-
The wizard asks, in order:
|
|
37
|
-
|
|
38
|
-
1. **Project name** — required.
|
|
39
|
-
2. **Package manager** — chosen from the managers detected on the system (npm is always offered; yarn / pnpm appear if installed).
|
|
40
|
-
3. **Database driver** — a single select: a driver, or **None** to scaffold without a database (see keys below).
|
|
41
|
-
4. **Features** — a multiselect of optional packages (`react` is pre-checked).
|
|
42
|
-
5. **AI providers** — a multiselect; picking any one pulls `@warlock.js/ai` automatically.
|
|
43
|
-
6. **Initialize Git?** — yes/no.
|
|
44
|
-
7. **Generate JWT secret keys?** — yes/no. If no, the cache is warmed instead.
|
|
39
|
+
## Three modes
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
1. **Default (a TTY, no `--yes`, no `--interactive`)** — asks the project name (if not already given) plus AT MOST ONE structural question: API-only or full-stack web (`--stack`). This is the only choice that changes the generated app's shape; every other choice below is reversible within minutes of scaffolding, so it is a flag with a sensible default, never a prompt.
|
|
42
|
+
2. **`--yes` (or no TTY at all — CI, a script, an agent harness)** — fully headless. Every answer comes from a flag or its default; nothing is ever prompted, and stdin is never read.
|
|
43
|
+
3. **`--interactive` (alias `--customize`)** — the full long-form wizard: project name, package manager, database driver, features, AI providers, git, JWT — one prompt after another, exactly like earlier `create-warlock` releases.
|
|
49
44
|
|
|
50
45
|
**Without a terminal, `--yes` is not required.** When stdin is not a TTY — CI, a
|
|
51
|
-
script, a container, an agent harness — the scaffolder does not try to prompt
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
script, a container, an agent harness — the scaffolder does not try to prompt,
|
|
47
|
+
regardless of which of the three modes was requested. If the flags already
|
|
48
|
+
answer everything it needs, it simply scaffolds. Prompting is the fallback,
|
|
49
|
+
not the precondition.
|
|
54
50
|
|
|
55
51
|
Only a question that genuinely cannot be answered stops the run, and in practice
|
|
56
|
-
that is just the project name, the one
|
|
52
|
+
that is just the project name, the one required answer with no default:
|
|
57
53
|
|
|
58
54
|
```
|
|
59
55
|
A project name is required and no terminal is available to ask for one. Pass it as
|
|
@@ -61,21 +57,51 @@ the first argument or --name=<name>. Non-interactive flags: --yes, --pm=<npm|yar
|
|
|
61
57
|
--db=<driver>|--no-db, --features=<list>, --ai=<list>, --git|--no-git, --jwt|--no-jwt.
|
|
62
58
|
```
|
|
63
59
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
| `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
|
|
69
|
-
| `--pm` | yes | auto-detected | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
|
|
70
|
-
| `--features` | yes (CSV) | none | Comma-separated optional feature keys. |
|
|
71
|
-
| `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
|
|
72
|
-
| `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit).|
|
|
73
|
-
| `--jwt` / `--no-jwt` | no | off | Generate JWT secrets via the project's `jwt` script. |
|
|
74
|
-
| `--yes` / `-y` | no | off | Non-interactive mode. |
|
|
60
|
+
## After scaffolding: the decisions summary
|
|
61
|
+
|
|
62
|
+
Because almost every choice defaults silently, `create-warlock` prints what it
|
|
63
|
+
decided right after scaffolding, e.g.:
|
|
75
64
|
|
|
76
|
-
|
|
65
|
+
```
|
|
66
|
+
Using npm · database: none · agent-kit targets: claude
|
|
67
|
+
change these — package manager: --pm, database: --db/--no-db, agents: --agents,
|
|
68
|
+
features: --features, ai: --ai, git: --git/--no-git, jwt: --jwt/--no-jwt — or
|
|
69
|
+
re-run with --interactive for the full wizard.
|
|
70
|
+
```
|
|
77
71
|
|
|
78
|
-
|
|
72
|
+
## Flags
|
|
73
|
+
|
|
74
|
+
| Flag | Takes value | Default | Purpose |
|
|
75
|
+
| --------------------------- | ----------- | -------------- | ------------------------------------------------------------------- |
|
|
76
|
+
| `<positional>` / `--name` | yes | — (required) | Project name + target directory. |
|
|
77
|
+
| `--stack` | yes | `api` | `api` or `web` — the one structural question; `web` seeds the `web` feature by default. |
|
|
78
|
+
| `--db` | yes | `mongodb` | Database driver key (`mongodb`, `postgres`, or `none`). |
|
|
79
|
+
| `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
|
|
80
|
+
| `--pm` | yes | inferred (`npm_config_user_agent` / system) | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
|
|
81
|
+
| `--features` | yes (CSV) | none, or `["web"]` when `--stack=web` and `--features` is not given | Comma-separated optional feature keys. An explicit `--features` always wins over the `--stack` default. |
|
|
82
|
+
| `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
|
|
83
|
+
| `--agents` | yes (CSV) | `claude` | Comma-separated `agent-kit` targets — see below. |
|
|
84
|
+
| `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit).|
|
|
85
|
+
| `--jwt` / `--no-jwt` | no | off | Generate JWT secrets via the project's `jwt` script. |
|
|
86
|
+
| `--yes` / `-y` | no | off | Fully headless mode. |
|
|
87
|
+
| `--interactive`, `--customize` | no | off | Restore the full long-form wizard. |
|
|
88
|
+
|
|
89
|
+
Value flags accept either `--db=postgres` or `--db postgres`. Unknown `--features` / `--ai` / `--agents` keys fail fast before any install.
|
|
90
|
+
|
|
91
|
+
`--help`/`-h` and `--version`/`-v` short-circuit before anything else — no prompt, filesystem write, or network call, and `--help` wins even over a positional project name. `--version` prints the installed `create-warlock` version. `--help` lists every flag above together with its default.
|
|
92
|
+
|
|
93
|
+
## `--agents` and agent-kit targets
|
|
94
|
+
|
|
95
|
+
`--agents` picks which `agent-kit` targets the generated project derives
|
|
96
|
+
docs/skills for (written into the scaffolded `package.json`'s `agentKit.targets`
|
|
97
|
+
when it differs from the default). The default, `claude`, is a tiny built-in
|
|
98
|
+
fallback that always works — even fully offline. Any other target
|
|
99
|
+
(`cursor`, `codex`, `kiro`, `copilot`, `antigravity`, `opencode`, `amp`,
|
|
100
|
+
`goose`, …) is validated against the list published at
|
|
101
|
+
https://mongez.js.org/agent-kit/llms.txt, fetched once and cached locally. If
|
|
102
|
+
that fetch fails for an explicitly requested non-default target, the run fails
|
|
103
|
+
visibly and names every target it *could* determine — it never silently
|
|
104
|
+
guesses.
|
|
79
105
|
|
|
80
106
|
## Valid keys
|
|
81
107
|
|
|
@@ -118,11 +144,12 @@ The template ships a working auth module (login / logout / logout-all / refresh
|
|
|
118
144
|
|
|
119
145
|
## Setup steps the scaffolder runs
|
|
120
146
|
|
|
121
|
-
1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env` (or, for `--db=none`, remove `src/config/database.ts`).
|
|
147
|
+
1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env` (or, for `--db=none`, remove `src/config/database.ts`) → record non-default `--agents` targets into `package.json`'s `agentKit.targets`.
|
|
122
148
|
2. Install base dependencies (so the `warlock` binary exists).
|
|
123
149
|
3. `warlock add <driver> <features...> <ai...> --no-install`, then one batched install. The `<driver>` is omitted for `--db=none`.
|
|
124
150
|
4. `git init` (if `--git`).
|
|
125
151
|
5. `jwt` script (if `--jwt`) — otherwise `warlock --warm-cache`.
|
|
152
|
+
6. Print the decisions summary.
|
|
126
153
|
|
|
127
154
|
Failures surface the error and halt; there is no rollback. After it finishes, `cd my-app` and run the dev server.
|
|
128
155
|
|
package/llms.txt
CHANGED
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
|
|
7
7
|
## Skills
|
|
8
8
|
|
|
9
|
-
- [create-a-warlock-project](create-warlock/create-a-warlock-project/SKILL.md): Scaffold a brand-new Warlock.js project with `create-warlock` —
|
|
9
|
+
- [create-a-warlock-project](create-warlock/create-a-warlock-project/SKILL.md): Scaffold a brand-new Warlock.js project with `create-warlock` — fully headless by default (at most one prompt — API-only vs full-stack web) plus `--interactive`/`--customize` for the full long-form wizard. Every flag (`--name`, `--stack`, `--db`, `--pm`, `--features`, `--ai`, `--agents`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`, `--interactive`), every valid database-driver / feature / AI-provider / agent-kit-target key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), the post-scaffold decisions summary, and the directory tree the template emits. Triggers: `create-warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "scaffold without a database", "which features can I pass to create-warlock", "agent-kit targets". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@clack/prompts": "^0.7.0",
|
|
14
14
|
"@mongez/copper": "^2.1.2",
|
|
15
|
-
"@warlock.js/fs": "5.
|
|
15
|
+
"@warlock.js/fs": "5.13.0",
|
|
16
16
|
"@mongez/reinforcements": "^4.0.1",
|
|
17
17
|
"cross-spawn": "^7.0.3",
|
|
18
18
|
"rimraf": "^6.0.1",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=22.13"
|
|
23
23
|
},
|
|
24
|
-
"version": "5.
|
|
24
|
+
"version": "5.13.0",
|
|
25
25
|
"type": "module",
|
|
26
26
|
"main": "./esm/index.mjs",
|
|
27
27
|
"module": "./esm/index.mjs",
|
|
@@ -1,51 +1,47 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: create-a-warlock-project
|
|
3
|
-
description: 'Scaffold a brand-new Warlock.js project with `create-warlock` —
|
|
3
|
+
description: 'Scaffold a brand-new Warlock.js project with `create-warlock` — fully headless by default (at most one prompt — API-only vs full-stack web) plus `--interactive`/`--customize` for the full long-form wizard. Every flag (`--name`, `--stack`, `--db`, `--pm`, `--features`, `--ai`, `--agents`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`, `--interactive`), every valid database-driver / feature / AI-provider / agent-kit-target key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), the post-scaffold decisions summary, and the directory tree the template emits. Triggers: `create-warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "scaffold without a database", "which features can I pass to create-warlock", "agent-kit targets". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# create-warlock — scaffold a new project
|
|
7
7
|
|
|
8
|
-
`create-warlock` builds a fresh Warlock.js project: it copies the `warlock` template, substitutes the project name, wires the chosen database driver into `.env` (or, when you opt out with `--db=none`, removes `src/config/database.ts`), then delegates every optional package install to the project's own `warlock add` so dependency versions are never duplicated by the scaffolder.
|
|
8
|
+
`create-warlock` builds a fresh Warlock.js project: it copies the `warlock` template, substitutes the project name, wires the chosen database driver into `.env` (or, when you opt out with `--db=none`, removes `src/config/database.ts`), derives `agent-kit` targets into the generated project, then delegates every optional package install to the project's own `warlock add` so dependency versions are never duplicated by the scaffolder.
|
|
9
9
|
|
|
10
10
|
## The shape
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
# or: npx create-warlock
|
|
13
|
+
# Default: at most ONE prompt (API-only vs full-stack web) — everything else is a flag/default
|
|
14
|
+
npx create-warlock my-app
|
|
16
15
|
|
|
17
|
-
#
|
|
16
|
+
# Fully headless — one command scaffolds the whole app, no prompts at all
|
|
18
17
|
npx create-warlock my-app --yes --db=postgres --features=test,redis --ai=ai-openai
|
|
19
18
|
|
|
19
|
+
# The one structural fork
|
|
20
|
+
npx create-warlock my-web-app --yes --stack=web
|
|
21
|
+
|
|
20
22
|
# No database at all
|
|
21
23
|
npx create-warlock my-api --yes --no-db --features=test
|
|
24
|
+
|
|
25
|
+
# The full long-form wizard, one prompt at a time
|
|
26
|
+
npx create-warlock --interactive
|
|
22
27
|
```
|
|
23
28
|
|
|
24
29
|
The first positional argument is the project name; it also becomes the directory created under the current working directory. If that directory already exists, the command aborts — it never overwrites.
|
|
25
30
|
|
|
26
|
-
##
|
|
27
|
-
|
|
28
|
-
The wizard asks, in order:
|
|
29
|
-
|
|
30
|
-
1. **Project name** — required.
|
|
31
|
-
2. **Package manager** — chosen from the managers detected on the system (npm is always offered; yarn / pnpm appear if installed).
|
|
32
|
-
3. **Database driver** — a single select: a driver, or **None** to scaffold without a database (see keys below).
|
|
33
|
-
4. **Features** — a multiselect of optional packages (`react` is pre-checked).
|
|
34
|
-
5. **AI providers** — a multiselect; picking any one pulls `@warlock.js/ai` automatically.
|
|
35
|
-
6. **Initialize Git?** — yes/no.
|
|
36
|
-
7. **Generate JWT secret keys?** — yes/no. If no, the cache is warmed instead.
|
|
31
|
+
## Three modes
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
1. **Default (a TTY, no `--yes`, no `--interactive`)** — asks the project name (if not already given) plus AT MOST ONE structural question: API-only or full-stack web (`--stack`). This is the only choice that changes the generated app's shape; every other choice below is reversible within minutes of scaffolding, so it is a flag with a sensible default, never a prompt.
|
|
34
|
+
2. **`--yes` (or no TTY at all — CI, a script, an agent harness)** — fully headless. Every answer comes from a flag or its default; nothing is ever prompted, and stdin is never read.
|
|
35
|
+
3. **`--interactive` (alias `--customize`)** — the full long-form wizard: project name, package manager, database driver, features, AI providers, git, JWT — one prompt after another, exactly like earlier `create-warlock` releases.
|
|
41
36
|
|
|
42
37
|
**Without a terminal, `--yes` is not required.** When stdin is not a TTY — CI, a
|
|
43
|
-
script, a container, an agent harness — the scaffolder does not try to prompt
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
script, a container, an agent harness — the scaffolder does not try to prompt,
|
|
39
|
+
regardless of which of the three modes was requested. If the flags already
|
|
40
|
+
answer everything it needs, it simply scaffolds. Prompting is the fallback,
|
|
41
|
+
not the precondition.
|
|
46
42
|
|
|
47
43
|
Only a question that genuinely cannot be answered stops the run, and in practice
|
|
48
|
-
that is just the project name, the one
|
|
44
|
+
that is just the project name, the one required answer with no default:
|
|
49
45
|
|
|
50
46
|
```
|
|
51
47
|
A project name is required and no terminal is available to ask for one. Pass it as
|
|
@@ -53,21 +49,51 @@ the first argument or --name=<name>. Non-interactive flags: --yes, --pm=<npm|yar
|
|
|
53
49
|
--db=<driver>|--no-db, --features=<list>, --ai=<list>, --git|--no-git, --jwt|--no-jwt.
|
|
54
50
|
```
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
| `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
|
|
61
|
-
| `--pm` | yes | auto-detected | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
|
|
62
|
-
| `--features` | yes (CSV) | none | Comma-separated optional feature keys. |
|
|
63
|
-
| `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
|
|
64
|
-
| `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit).|
|
|
65
|
-
| `--jwt` / `--no-jwt` | no | off | Generate JWT secrets via the project's `jwt` script. |
|
|
66
|
-
| `--yes` / `-y` | no | off | Non-interactive mode. |
|
|
52
|
+
## After scaffolding: the decisions summary
|
|
53
|
+
|
|
54
|
+
Because almost every choice defaults silently, `create-warlock` prints what it
|
|
55
|
+
decided right after scaffolding, e.g.:
|
|
67
56
|
|
|
68
|
-
|
|
57
|
+
```
|
|
58
|
+
Using npm · database: none · agent-kit targets: claude
|
|
59
|
+
change these — package manager: --pm, database: --db/--no-db, agents: --agents,
|
|
60
|
+
features: --features, ai: --ai, git: --git/--no-git, jwt: --jwt/--no-jwt — or
|
|
61
|
+
re-run with --interactive for the full wizard.
|
|
62
|
+
```
|
|
69
63
|
|
|
70
|
-
|
|
64
|
+
## Flags
|
|
65
|
+
|
|
66
|
+
| Flag | Takes value | Default | Purpose |
|
|
67
|
+
| --------------------------- | ----------- | -------------- | ------------------------------------------------------------------- |
|
|
68
|
+
| `<positional>` / `--name` | yes | — (required) | Project name + target directory. |
|
|
69
|
+
| `--stack` | yes | `api` | `api` or `web` — the one structural question; `web` seeds the `web` feature by default. |
|
|
70
|
+
| `--db` | yes | `mongodb` | Database driver key (`mongodb`, `postgres`, or `none`). |
|
|
71
|
+
| `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
|
|
72
|
+
| `--pm` | yes | inferred (`npm_config_user_agent` / system) | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
|
|
73
|
+
| `--features` | yes (CSV) | none, or `["web"]` when `--stack=web` and `--features` is not given | Comma-separated optional feature keys. An explicit `--features` always wins over the `--stack` default. |
|
|
74
|
+
| `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
|
|
75
|
+
| `--agents` | yes (CSV) | `claude` | Comma-separated `agent-kit` targets — see below. |
|
|
76
|
+
| `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit).|
|
|
77
|
+
| `--jwt` / `--no-jwt` | no | off | Generate JWT secrets via the project's `jwt` script. |
|
|
78
|
+
| `--yes` / `-y` | no | off | Fully headless mode. |
|
|
79
|
+
| `--interactive`, `--customize` | no | off | Restore the full long-form wizard. |
|
|
80
|
+
|
|
81
|
+
Value flags accept either `--db=postgres` or `--db postgres`. Unknown `--features` / `--ai` / `--agents` keys fail fast before any install.
|
|
82
|
+
|
|
83
|
+
`--help`/`-h` and `--version`/`-v` short-circuit before anything else — no prompt, filesystem write, or network call, and `--help` wins even over a positional project name. `--version` prints the installed `create-warlock` version. `--help` lists every flag above together with its default.
|
|
84
|
+
|
|
85
|
+
## `--agents` and agent-kit targets
|
|
86
|
+
|
|
87
|
+
`--agents` picks which `agent-kit` targets the generated project derives
|
|
88
|
+
docs/skills for (written into the scaffolded `package.json`'s `agentKit.targets`
|
|
89
|
+
when it differs from the default). The default, `claude`, is a tiny built-in
|
|
90
|
+
fallback that always works — even fully offline. Any other target
|
|
91
|
+
(`cursor`, `codex`, `kiro`, `copilot`, `antigravity`, `opencode`, `amp`,
|
|
92
|
+
`goose`, …) is validated against the list published at
|
|
93
|
+
https://mongez.js.org/agent-kit/llms.txt, fetched once and cached locally. If
|
|
94
|
+
that fetch fails for an explicitly requested non-default target, the run fails
|
|
95
|
+
visibly and names every target it *could* determine — it never silently
|
|
96
|
+
guesses.
|
|
71
97
|
|
|
72
98
|
## Valid keys
|
|
73
99
|
|
|
@@ -110,11 +136,12 @@ The template ships a working auth module (login / logout / logout-all / refresh
|
|
|
110
136
|
|
|
111
137
|
## Setup steps the scaffolder runs
|
|
112
138
|
|
|
113
|
-
1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env` (or, for `--db=none`, remove `src/config/database.ts`).
|
|
139
|
+
1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env` (or, for `--db=none`, remove `src/config/database.ts`) → record non-default `--agents` targets into `package.json`'s `agentKit.targets`.
|
|
114
140
|
2. Install base dependencies (so the `warlock` binary exists).
|
|
115
141
|
3. `warlock add <driver> <features...> <ai...> --no-install`, then one batched install. The `<driver>` is omitted for `--db=none`.
|
|
116
142
|
4. `git init` (if `--git`).
|
|
117
143
|
5. `jwt` script (if `--jwt`) — otherwise `warlock --warm-cache`.
|
|
144
|
+
6. Print the decisions summary.
|
|
118
145
|
|
|
119
146
|
Failures surface the error and halt; there is no rollback. After it finishes, `cd my-app` and run the dev server.
|
|
120
147
|
|
|
@@ -110,7 +110,7 @@ Three things that trip everyone up on a fresh app:
|
|
|
110
110
|
field is required" message), but it is redundant on a plain field, so the
|
|
111
111
|
generated code and these docs don't use it.
|
|
112
112
|
- **Declare server-set foreign keys on the MODEL schema.** A controller that
|
|
113
|
-
sets a column the client doesn't send (e.g. `authorId: request.user.id`) must
|
|
113
|
+
sets a column the client doesn't send (e.g. `authorId: request.locals.user.id`) must
|
|
114
114
|
still have that column declared on the model's seal schema
|
|
115
115
|
(`authorId: v.number()`), or `Model.create()` rejects it as an unknown key.
|
|
116
116
|
The client-facing request schema and the model schema are separate, so this
|
|
@@ -354,11 +354,11 @@ createLeadController.validation = {
|
|
|
354
354
|
|
|
355
355
|
| Route kind | Controller type | What it adds |
|
|
356
356
|
| -------------------------------- | ------------------------------------- | ------------------------------------- |
|
|
357
|
-
| Guarded (auth required) + body | `GuardedRequestHandler<Schema>` | typed `request.validated()` + `request.user` |
|
|
357
|
+
| Guarded (auth required) + body | `GuardedRequestHandler<Schema>` | typed `request.validated()` + `request.locals.user` |
|
|
358
358
|
| Public + body | `RequestHandler<Request<Schema>>` | typed `request.validated()` |
|
|
359
359
|
| Read / list (no body) | `RequestHandler` | nothing extra |
|
|
360
360
|
|
|
361
|
-
`GuardedRequestHandler<Schema>` (defined in `app/auth/requests/guarded.request.ts`) is `RequestHandler<GuardedRequest<Schema>>` — it both types the validated payload and guarantees `request.user` is present.
|
|
361
|
+
`GuardedRequestHandler<Schema>` (defined in `app/auth/requests/guarded.request.ts`) is `RequestHandler<GuardedRequest<Schema>>` — it both types the validated payload and guarantees `request.locals.user` is present.
|
|
362
362
|
|
|
363
363
|
### 6.3 Accessing inputs
|
|
364
364
|
|
|
@@ -95,7 +95,7 @@ AuthN proves who you are. AuthZ decides what you can do. Conflating them is how
|
|
|
95
95
|
const order = await ordersRepository.find({ id, userId: request.input("userId") });
|
|
96
96
|
|
|
97
97
|
// ✅ identity from the authenticated session
|
|
98
|
-
const user = request.user;
|
|
98
|
+
const user = request.locals.user;
|
|
99
99
|
const order = await ordersRepository.find({ id, userId: user.id });
|
|
100
100
|
```
|
|
101
101
|
|
|
@@ -9,7 +9,7 @@ import { logoutAllService } from "../services/auth.service";
|
|
|
9
9
|
* Guarded route — see the note on `logoutController`.
|
|
10
10
|
*/
|
|
11
11
|
export const logoutAllController: GuardedRequestHandler = async ({ request, response }) => {
|
|
12
|
-
await logoutAllService(request.user);
|
|
12
|
+
await logoutAllService(request.locals.user);
|
|
13
13
|
|
|
14
14
|
return response.success({
|
|
15
15
|
message: t("auth.loggedOutAll"),
|
|
@@ -7,11 +7,11 @@ import { logoutService } from "../services/auth.service";
|
|
|
7
7
|
* POST /auth/logout
|
|
8
8
|
*
|
|
9
9
|
* Typed as `GuardedRequestHandler` because the route sits inside `guarded()`
|
|
10
|
-
* (see `../routes.ts`). A plain `RequestHandler` types `request.user`
|
|
11
|
-
* `RequestUser | undefined`, which is not assignable to `logoutService`.
|
|
10
|
+
* (see `../routes.ts`). A plain `RequestHandler` types `request.locals.user`
|
|
11
|
+
* as `RequestUser | undefined`, which is not assignable to `logoutService`.
|
|
12
12
|
*/
|
|
13
13
|
export const logoutController: GuardedRequestHandler = async ({ request, response }) => {
|
|
14
|
-
await logoutService(request.user);
|
|
14
|
+
await logoutService(request.locals.user);
|
|
15
15
|
|
|
16
16
|
return response.success({
|
|
17
17
|
message: t("auth.loggedOut"),
|
|
@@ -11,7 +11,7 @@ import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
|
|
|
11
11
|
*/
|
|
12
12
|
export const meController: GuardedRequestHandler = async ({ request, response }) => {
|
|
13
13
|
return response.success({
|
|
14
|
-
user: request.user,
|
|
14
|
+
user: request.locals.user,
|
|
15
15
|
});
|
|
16
16
|
};
|
|
17
17
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { Request, RequestHandler } from "@warlock.js/core";
|
|
1
|
+
import type { Request, RequestHandler, RequestLocals } from "@warlock.js/core";
|
|
2
2
|
import type { User } from "app/users/models/user";
|
|
3
3
|
|
|
4
4
|
export type GuardedRequest<RequestPayload = unknown> = Request<RequestPayload> & {
|
|
5
|
-
user: User;
|
|
5
|
+
locals: RequestLocals & { user: User };
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
export type GuardedRequestHandler<RequestPayload = unknown> = RequestHandler<
|
|
@@ -7,13 +7,21 @@ import { UserResource } from "app/users/resources/user.resource";
|
|
|
7
7
|
export const userSchema = v.object({
|
|
8
8
|
name: v.string().required(),
|
|
9
9
|
email: v.email().requiredIfEmpty("id"),
|
|
10
|
-
|
|
10
|
+
// Required at the controller layer (`create-user.schema.ts`) on
|
|
11
|
+
// registration, but not here: a freshly-registered user's record is
|
|
12
|
+
// written before that upload completes, and a seeded/social-login user may
|
|
13
|
+
// never get an avatar at all.
|
|
14
|
+
image: v.string().optional(),
|
|
11
15
|
password: v.string().min(6).requiredIfEmpty("id").addTransformer(useHashedPassword()),
|
|
12
16
|
// Written by the social-login handler (`app/users/services/login-social.ts`).
|
|
13
17
|
// A field only reachable through `save({ merge })` still has to be declared
|
|
14
18
|
// here — `merge` is typed against this schema, so an undeclared key is a
|
|
15
|
-
// compile error, not a silent write.
|
|
16
|
-
lastLogin
|
|
19
|
+
// compile error, not a silent write. Optional: a user who has never logged
|
|
20
|
+
// in (freshly registered or freshly seeded) has no lastLogin yet.
|
|
21
|
+
lastLogin: v.date().optional(),
|
|
22
|
+
// Stamped by `@warlock.js/auth`'s `verifyEmail()` (the `auth.verification.field`
|
|
23
|
+
// default). Absent/undefined means unverified — see the `verified` scope below.
|
|
24
|
+
emailVerifiedAt: v.date().optional(),
|
|
17
25
|
});
|
|
18
26
|
|
|
19
27
|
export type UserSchema = Infer<typeof userSchema>;
|
|
@@ -54,7 +62,7 @@ export class User extends Auth<UserSchema> {
|
|
|
54
62
|
});
|
|
55
63
|
|
|
56
64
|
this.addScope("verified", (query) => {
|
|
57
|
-
query.
|
|
65
|
+
query.whereNotNull("emailVerifiedAt");
|
|
58
66
|
});
|
|
59
67
|
}
|
|
60
68
|
}
|
|
@@ -5,11 +5,12 @@ import type { GuardedRequestHandler } from "app/auth/requests/guarded.request";
|
|
|
5
5
|
*
|
|
6
6
|
* Despite living under `services/`, this is a route handler: it consumes the
|
|
7
7
|
* request and returns a response. It is typed as a `GuardedRequestHandler` so
|
|
8
|
-
* it can be wired straight into a route, and so `request.user`
|
|
9
|
-
* app's `User` model rather than core's optional
|
|
8
|
+
* it can be wired straight into a route, and so `request.locals.user`
|
|
9
|
+
* resolves to the app's `User` model rather than core's optional
|
|
10
|
+
* `RequestUser`.
|
|
10
11
|
*/
|
|
11
12
|
const loginSocial: GuardedRequestHandler = async ({ request, response }) => {
|
|
12
|
-
const user = request.user;
|
|
13
|
+
const user = request.locals.user;
|
|
13
14
|
|
|
14
15
|
const auth = await user.generateAccessToken();
|
|
15
16
|
|
|
@@ -14,6 +14,10 @@ const databaseConfigurations: ConnectionOptions<never, PostgresPoolConfig> = {
|
|
|
14
14
|
defaultDeleteStrategy: "permanent",
|
|
15
15
|
|
|
16
16
|
clientOptions: {
|
|
17
|
+
// `PostgresPoolConfig` extends the connection config, so `database` is
|
|
18
|
+
// required here too even though it duplicates the top-level value above.
|
|
19
|
+
database: env("DB_NAME"),
|
|
20
|
+
|
|
17
21
|
// Native `pg` pool options — tune these for your workload.
|
|
18
22
|
// max: 10,
|
|
19
23
|
// min: 0,
|