create-warlock 5.33.0 → 5.34.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 +16 -47
- package/README.md +69 -75
- package/esm/commands/create-new-app/index.mjs +10 -9
- package/esm/commands/create-new-app/index.mjs.map +1 -1
- package/esm/commands/create-new-app/types.d.mts +1 -1
- package/esm/commands/create-warlock-app/index.mjs +1 -1
- package/esm/commands/create-warlock-app/index.mjs.map +1 -1
- package/esm/flags/definitions.mjs +2 -2
- package/esm/flags/definitions.mjs.map +1 -1
- package/esm/flags/seed-from-flags.mjs +1 -1
- package/esm/flags/seed-from-flags.mjs.map +1 -1
- package/esm/helpers/app.mjs +4 -28
- package/esm/helpers/app.mjs.map +1 -1
- package/esm/helpers/insert-import.mjs +2 -2
- package/esm/helpers/insert-import.mjs.map +1 -1
- package/llms-full.txt +55 -84
- package/package.json +2 -2
- package/skills/create-a-warlock-project/SKILL.md +55 -84
- package/templates/toolchains/oxc/.oxlintrc.json +1 -1
- package/templates/warlock/{.prettierignore → .oxfmtignore} +0 -2
- package/templates/warlock/.oxfmtrc.json +15 -0
- package/templates/warlock/.oxlintrc.json +24 -0
- package/templates/warlock/.vscode/extensions.json +3 -0
- package/templates/warlock/.vscode/settings.json +11 -17
- package/templates/warlock/README.md +18 -20
- package/templates/warlock/package.json +7 -14
- package/templates/warlock/skills/code-standards/SKILL.md +53 -36
- package/templates/warlock/skills/git-workflow/SKILL.md +25 -20
- package/templates/warlock/skills/module-boundaries/SKILL.md +33 -24
- package/templates/warlock/skills/web-code-standards/SKILL.md +1 -1
- package/templates/warlock/src/web/home/components/home-footer.tsx +3 -1
- package/esm/prompts/ask-lint-toolchain.mjs +0 -21
- package/esm/prompts/ask-lint-toolchain.mjs.map +0 -1
- package/templates/warlock/.prettierrc.json +0 -12
- package/templates/warlock/eslint.config.js +0 -98
|
@@ -46,7 +46,7 @@ that is just the project name, the one required answer with no default:
|
|
|
46
46
|
```
|
|
47
47
|
A project name is required and no terminal is available to ask for one. Pass it as
|
|
48
48
|
the first argument or --name=<name>. Non-interactive flags: --yes, --pm=<npm|yarn|pnpm>,
|
|
49
|
-
--db=<driver>|--no-db, --features=<list>, --ai=<list>, --lint=<oxc
|
|
49
|
+
--db=<driver>|--no-db, --features=<list>, --ai=<list>, --lint=<oxc>, --git|--no-git, --jwt|--no-jwt.
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
## After scaffolding: the decisions summary
|
|
@@ -72,7 +72,7 @@ Features: none
|
|
|
72
72
|
| `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
|
|
73
73
|
| `--pm` | yes | inferred (`npm_config_user_agent` / system) | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
|
|
74
74
|
| `--features` | yes (CSV) | none, or `["web"]` when `--stack=web` and `--features` is not given | Comma-separated optional feature keys. An explicit `--features` always wins over the `--stack` default. |
|
|
75
|
-
| `--lint` | yes | `oxc` | `oxc` (Oxlint/Oxfmt)
|
|
75
|
+
| `--lint` | yes | `oxc` | `oxc` (Oxlint/Oxfmt); removed or invalid values fail before writes. |
|
|
76
76
|
| `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
|
|
77
77
|
| `--agents` | yes (CSV) | `claude` | Comma-separated `agent-kit` targets — see below. |
|
|
78
78
|
| `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit). |
|
|
@@ -199,101 +199,72 @@ Flags preselect their existing feature keys. Both menus and checklists retain
|
|
|
199
199
|
terminal paging. The decisions overview previews up to six feature/AI keys with
|
|
200
200
|
a total count; the success screen shows the complete list.
|
|
201
201
|
|
|
202
|
-
##
|
|
202
|
+
## Oxlint and Oxfmt
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
`.
|
|
207
|
-
|
|
208
|
-
|
|
204
|
+
Oxlint **1.88.0** and Oxfmt **0.73.0** are the only supported tools. New apps
|
|
205
|
+
pin TypeScript **7.0.2** and include `.oxlintrc.json`, `.oxfmtrc.json`,
|
|
206
|
+
`.oxfmtignore`, and VS Code settings for `oxc.oxc-vscode`.
|
|
207
|
+
`--lint=oxc` is optional; `--lint=eslint` fails with the 5.34.0 removal message.
|
|
208
|
+
The wizard has no toolchain choice.
|
|
209
209
|
|
|
210
210
|
```bash
|
|
211
|
-
# Default: Oxlint + Oxfmt, TypeScript 7.0.2
|
|
212
211
|
npm create warlock@latest my-app -- --yes
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
npm
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
Both choices provide `lint`/`format` and non-mutating `lint:check`/`format:check`
|
|
219
|
-
scripts. Oxc projects also configure VS Code format-on-save with the official
|
|
220
|
-
`oxc.oxc-vscode` formatter, use `source.fixAll.oxc` for lint fixes and recommend
|
|
221
|
-
that extension. The ESLint toolchain retains its Prettier/ESLint editor settings.
|
|
222
|
-
Oxc config enables `devServer.healthCheckers: { eslint: false, oxlint: true }` and
|
|
223
|
-
leaves TypeScript health checks enabled. Style matches the template's 100-column,
|
|
224
|
-
double-quote, semicolon and trailing-comma settings. Import sorting is on in
|
|
225
|
-
`.oxfmtrc.json`, kept to one block like the ESLint toolchain's
|
|
226
|
-
`@ianvs/prettier-plugin-sort-imports`:
|
|
227
|
-
|
|
228
|
-
```json
|
|
229
|
-
{ "sortImports": { "newlinesBetween": false, "internalPattern": ["app/", "web/", "@shared/"] } }
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
`format` (`npm run format`) orders built-ins, packages, internal aliases,
|
|
233
|
-
then relative imports. Neither formatter deletes unused imports. ESLint reports
|
|
234
|
-
them through `unused-imports/no-unused-imports`; Oxlint uses `no-unused-vars`.
|
|
235
|
-
Scripts in both toolchains call local binaries
|
|
236
|
-
(`eslint --fix ./src --max-warnings=0`, `tsc --noEmit`), never `npx`.
|
|
237
|
-
|
|
238
|
-
## TypeScript 7 in 5.32
|
|
239
|
-
|
|
240
|
-
Default Oxlint/Oxfmt projects pin `typescript` to `7.0.2`; `tsc` is the native Go compiler.
|
|
241
|
-
The template uses `module: "ESNext"`, `moduleResolution: "bundler"`, explicit
|
|
242
|
-
Node types, and `paths` relative to its tsconfig, without `baseUrl`.
|
|
243
|
-
Existing apps must remove `baseUrl`, rebase alias targets, and replace
|
|
244
|
-
`moduleResolution: "node"` / `"node10"` before typechecking.
|
|
245
|
-
|
|
246
|
-
Explicit `--lint=eslint` projects pin `typescript` to **6.0.3**, the newest version
|
|
247
|
-
supported by typescript-eslint. Its parser requires the JavaScript compiler API
|
|
248
|
-
removed in TypeScript 7, and its declared peer range is `>=4.8.4 <6.1.0`
|
|
249
|
-
([parser package metadata](https://registry.npmjs.org/@typescript-eslint/parser/8.71.2)).
|
|
250
|
-
Both modes use the same tsconfig settings above, which are valid in TypeScript 6.
|
|
251
|
-
ESLint mode prints a deprecation warning naming removal in 5.34.0 and the Oxc migration command. Both modes install with every supported package manager.
|
|
252
|
-
|
|
253
|
-
### Prettier import order in ESLint mode
|
|
254
|
-
|
|
255
|
-
The ESLint scaffold’s `@ianvs/prettier-plugin-sort-imports` uses Babel instead of
|
|
256
|
-
the compiler API. Keep these options in `.prettierrc.json`:
|
|
257
|
-
|
|
258
|
-
```json
|
|
259
|
-
{
|
|
260
|
-
"plugins": ["@ianvs/prettier-plugin-sort-imports"],
|
|
261
|
-
"importOrder": ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "^(app|web|@shared)/", "^[.]"],
|
|
262
|
-
"importOrderParserPlugins": ["typescript", "jsx", "decorators"]
|
|
263
|
-
}
|
|
212
|
+
cd my-app
|
|
213
|
+
npm run lint:check
|
|
214
|
+
npm run format:check
|
|
264
215
|
```
|
|
265
216
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
217
|
+
Oxlint enables `"categories": { "correctness": "error" }`, unused-variable/import
|
|
218
|
+
checks, explicit class accessibility and type-import style. Oxfmt sorts imports
|
|
219
|
+
with `"sortImports": { "newlinesBetween": false, "internalPattern": ["app/", "web/", "@shared/"] }`.
|
|
220
|
+
It keeps built-ins, packages, internal aliases and relative imports in one block.
|
|
221
|
+
Formatting retains unused imports; lint reports them. Local package scripts use
|
|
222
|
+
`oxlint`, `oxfmt` and `tsc` directly. The non-mutating scripts are `lint:check`
|
|
223
|
+
and `format:check`. Background checks run TypeScript and configured Oxlint.
|
|
269
224
|
|
|
270
|
-
##
|
|
225
|
+
## Migration
|
|
271
226
|
|
|
272
|
-
ESLint
|
|
273
|
-
|
|
274
|
-
core release and `ts-morph`, then
|
|
227
|
+
ESLint and Prettier support was removed in **5.34.0**. Oxlint/Oxfmt is the only
|
|
228
|
+
supported toolchain. `--lint eslint` and `healthCheckers.eslint` are removed.
|
|
229
|
+
Install the new core release and `ts-morph`, then migrate an existing app:
|
|
275
230
|
|
|
276
231
|
```bash
|
|
277
|
-
|
|
278
|
-
npx warlock upgrade --from 5.
|
|
232
|
+
npm install --save-dev ts-morph
|
|
233
|
+
npx warlock upgrade --from 5.33 --to 5.34.0 --dry-run
|
|
234
|
+
npx warlock upgrade --from 5.33 --to 5.34.0
|
|
279
235
|
npm install
|
|
280
236
|
npm run format
|
|
281
237
|
npm run lint:check
|
|
282
238
|
npx warlock check
|
|
283
239
|
```
|
|
284
240
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
0.
|
|
298
|
-
|
|
299
|
-
|
|
241
|
+
The `oxc-toolchain-removal` step reapplies the preserved 5.33 `oxc-toolchain`
|
|
242
|
+
migration when legacy configs, scripts or dependencies remain. Apps upgrading
|
|
243
|
+
from 5.32 can still use `npx warlock upgrade --from 5.32 --to 5.33.0` first.
|
|
244
|
+
It replaces standard scripts/dependencies, pins TypeScript 7.0.2, updates generated
|
|
245
|
+
editor settings, removes `healthCheckers.eslint` and selects Oxlint. A generated
|
|
246
|
+
`.prettierignore` becomes `.oxfmtignore`; Oxfmt scripts read it explicitly.
|
|
247
|
+
|
|
248
|
+
Literal custom rules are translated without executing the config. Per-folder
|
|
249
|
+
`no-restricted-imports` and `no-restricted-exports` become Oxlint overrides with
|
|
250
|
+
file/exclusion patterns. Supported `@typescript-eslint/*` rules become native
|
|
251
|
+
`typescript/*` rules (or native base rules). Options are checked against the
|
|
252
|
+
pinned Oxlint 1.88.0 schema. Type-aware equivalents enable `options.typeAware`
|
|
253
|
+
and add `oxlint-tsgolint@7.0.2004` to the app's dev dependencies.
|
|
254
|
+
`unused-imports/no-unused-imports` uses `no-unused-vars` with safe import fixes;
|
|
255
|
+
Oxlint shares the severity for imports and variables, so differing active
|
|
256
|
+
severities are reported for review.
|
|
257
|
+
|
|
258
|
+
Only unsupported rules/options, computed or imported settings, selectors such
|
|
259
|
+
as `no-restricted-syntax`, and conflicting existing Oxc rules need manual review.
|
|
260
|
+
Each finding names its source `file:line`, rule and affected file patterns.
|
|
261
|
+
Fully ported lint configs are removed; configs with findings remain for reference
|
|
262
|
+
until those findings are addressed. Existing Oxc rules are kept on conflict.
|
|
263
|
+
`--dry-run --json` reports the same plan without writes. Customized formatting,
|
|
264
|
+
editor settings and nonstandard scripts can also require review.
|
|
265
|
+
|
|
266
|
+
**Upgrading:** new and migrated template configs enable
|
|
267
|
+
`"categories": { "correctness": "error" }`. Address the additional diagnostics;
|
|
268
|
+
existing Oxc configs retain their categories. A real lockfile beside a workspace
|
|
269
|
+
marker determines the installer: `package-lock.json` plus `pnpm-workspace.yaml`
|
|
270
|
+
without `pnpm-lock.yaml` now produces `npm install`.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
3
|
"plugins": ["typescript", "import", "react"],
|
|
4
4
|
"env": { "node": true, "es2021": true },
|
|
5
|
-
"categories": { "correctness": "
|
|
5
|
+
"categories": { "correctness": "error" },
|
|
6
6
|
"rules": {
|
|
7
7
|
"typescript/no-explicit-any": "off",
|
|
8
8
|
"no-unused-vars": [
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
|
3
|
+
"semi": true,
|
|
4
|
+
"tabWidth": 2,
|
|
5
|
+
"printWidth": 100,
|
|
6
|
+
"singleQuote": false,
|
|
7
|
+
"arrowParens": "always",
|
|
8
|
+
"trailingComma": "all",
|
|
9
|
+
"endOfLine": "lf",
|
|
10
|
+
"sortImports": {
|
|
11
|
+
"newlinesBetween": false,
|
|
12
|
+
"internalPattern": ["app/", "web/", "@shared/"]
|
|
13
|
+
},
|
|
14
|
+
"ignorePatterns": ["node_modules/", "dist/"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"plugins": ["typescript", "import", "react"],
|
|
4
|
+
"env": { "node": true, "es2021": true },
|
|
5
|
+
"categories": { "correctness": "error" },
|
|
6
|
+
"rules": {
|
|
7
|
+
"typescript/no-explicit-any": "off",
|
|
8
|
+
"no-unused-vars": [
|
|
9
|
+
"warn",
|
|
10
|
+
{
|
|
11
|
+
"vars": "all",
|
|
12
|
+
"varsIgnorePattern": "^_",
|
|
13
|
+
"args": "after-used",
|
|
14
|
+
"argsIgnorePattern": "^_"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"typescript/explicit-member-accessibility": ["error", { "accessibility": "explicit" }],
|
|
18
|
+
"typescript/consistent-type-imports": [
|
|
19
|
+
"error",
|
|
20
|
+
{ "prefer": "type-imports", "disallowTypeAnnotations": false }
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"ignorePatterns": ["node_modules/", "dist/"]
|
|
24
|
+
}
|
|
@@ -1,41 +1,35 @@
|
|
|
1
1
|
{
|
|
2
|
-
"editor.defaultFormatter": "
|
|
2
|
+
"editor.defaultFormatter": "oxc.oxc-vscode",
|
|
3
3
|
"editor.formatOnSave": true,
|
|
4
4
|
"editor.codeActionsOnSave": {
|
|
5
|
-
"source.fixAll.
|
|
5
|
+
"source.fixAll.oxc": "explicit",
|
|
6
6
|
"source.organizeImports": "never"
|
|
7
7
|
},
|
|
8
8
|
"[typescript]": {
|
|
9
|
-
"editor.defaultFormatter": "
|
|
9
|
+
"editor.defaultFormatter": "oxc.oxc-vscode",
|
|
10
10
|
"editor.formatOnSave": true
|
|
11
11
|
},
|
|
12
12
|
"[typescriptreact]": {
|
|
13
|
-
"editor.defaultFormatter": "
|
|
13
|
+
"editor.defaultFormatter": "oxc.oxc-vscode",
|
|
14
14
|
"editor.formatOnSave": true
|
|
15
15
|
},
|
|
16
16
|
"[javascript]": {
|
|
17
|
-
"editor.defaultFormatter": "
|
|
17
|
+
"editor.defaultFormatter": "oxc.oxc-vscode",
|
|
18
18
|
"editor.formatOnSave": true
|
|
19
19
|
},
|
|
20
20
|
"[javascriptreact]": {
|
|
21
|
-
"editor.defaultFormatter": "
|
|
21
|
+
"editor.defaultFormatter": "oxc.oxc-vscode",
|
|
22
22
|
"editor.formatOnSave": true
|
|
23
23
|
},
|
|
24
24
|
"[json]": {
|
|
25
|
-
"editor.defaultFormatter": "
|
|
25
|
+
"editor.defaultFormatter": "oxc.oxc-vscode",
|
|
26
26
|
"editor.formatOnSave": true
|
|
27
27
|
},
|
|
28
28
|
"[css]": {
|
|
29
|
-
"editor.defaultFormatter": "
|
|
29
|
+
"editor.defaultFormatter": "oxc.oxc-vscode",
|
|
30
30
|
"editor.formatOnSave": true
|
|
31
31
|
},
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"prettier.documentSelectors": ["**/*.{ts,tsx,js,jsx,json,css,md}"],
|
|
36
|
-
"prettier.enable": true,
|
|
37
|
-
"prettier.useEditorConfig": false,
|
|
38
|
-
"prettier.resolveGlobalModules": false,
|
|
39
|
-
"eslint.format.enable": false,
|
|
40
|
-
"eslint.codeActionsOnSave.mode": "all"
|
|
32
|
+
"oxc.enable.oxlint": true,
|
|
33
|
+
"oxc.enable.oxfmt": true,
|
|
34
|
+
"oxc.requireConfig": true
|
|
41
35
|
}
|
|
@@ -29,22 +29,22 @@ It restarts on file changes.
|
|
|
29
29
|
|
|
30
30
|
## Scripts
|
|
31
31
|
|
|
32
|
-
| Command
|
|
33
|
-
|
|
|
34
|
-
| `npm run dev`
|
|
35
|
-
| `npm run build`
|
|
36
|
-
| `npm run start`
|
|
37
|
-
| `npm run migrate`
|
|
38
|
-
| `npm run migrate.fresh` | Drop everything and re-run all migrations
|
|
39
|
-
| `npm run migrate.list`
|
|
40
|
-
| `npm run seed`
|
|
41
|
-
| `npm run jwt`
|
|
42
|
-
| `npm run tsc`
|
|
43
|
-
| `npm run lint`
|
|
44
|
-
| `npm run format`
|
|
45
|
-
| `npm run lint:check`
|
|
46
|
-
| `npm run format:check`
|
|
47
|
-
| `npm run skills:sync`
|
|
32
|
+
| Command | What it does |
|
|
33
|
+
| ----------------------- | ---------------------------------------------- |
|
|
34
|
+
| `npm run dev` | Development server with live reload |
|
|
35
|
+
| `npm run build` | Type-check and build for production |
|
|
36
|
+
| `npm run start` | Run the production build (run `build` first) |
|
|
37
|
+
| `npm run migrate` | Run pending database migrations |
|
|
38
|
+
| `npm run migrate.fresh` | Drop everything and re-run all migrations |
|
|
39
|
+
| `npm run migrate.list` | List migrations and their status |
|
|
40
|
+
| `npm run seed` | Run the database seeders |
|
|
41
|
+
| `npm run jwt` | Generate a JWT secret into `.env` |
|
|
42
|
+
| `npm run tsc` | Type-check without building |
|
|
43
|
+
| `npm run lint` | The configured linter, auto-fixing what it can |
|
|
44
|
+
| `npm run format` | The configured formatter over `src/` |
|
|
45
|
+
| `npm run lint:check` | Lint without changing files |
|
|
46
|
+
| `npm run format:check` | Check formatting without changing files |
|
|
47
|
+
| `npm run skills:sync` | Refresh the AI agent skills (see below) |
|
|
48
48
|
|
|
49
49
|
The `warlock` CLI has more than the scripts expose — run `npx warlock --help`
|
|
50
50
|
for the full list. Two worth knowing:
|
|
@@ -157,7 +157,5 @@ Node types, and `paths` relative to its tsconfig, without `baseUrl`.
|
|
|
157
157
|
Existing apps must remove `baseUrl`, rebase alias targets, and replace
|
|
158
158
|
`moduleResolution: "node"` / `"node10"` before typechecking.
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
does not support TS7. ESLint mode is scheduled for deprecation in 5.33 and removal
|
|
163
|
-
in 5.34. A parser's bundled older TypeScript is separate from the app's native `tsc`.
|
|
160
|
+
Oxlint/Oxfmt is the only toolchain in 5.34.0. Correctness rules report errors;
|
|
161
|
+
use `npm run lint:check` and `npm run format:check` before committing.
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
"gen.mig": "warlock generate.migration",
|
|
20
20
|
"gen.r": "warlock generate.repository",
|
|
21
21
|
"gen.rs": "warlock generate.resource",
|
|
22
|
-
"lint": "
|
|
23
|
-
"format": "
|
|
22
|
+
"lint": "oxlint --fix ./src --max-warnings=0",
|
|
23
|
+
"format": "oxfmt --ignore-path .gitignore --ignore-path .oxfmtignore --write ./src",
|
|
24
24
|
"tsc": "warlock generate.typings && tsc --noEmit",
|
|
25
25
|
"postinstall": "agent-kit init && agent-kit sync || echo \"warning: AI agent skills were NOT synced, so coding agents will not see the @warlock.js skills. Run the skills:sync script.\"",
|
|
26
26
|
"skills:sync": "agent-kit sync",
|
|
27
27
|
"skills:watch": "agent-kit watch",
|
|
28
|
-
"lint:check": "
|
|
29
|
-
"format:check": "
|
|
28
|
+
"lint:check": "oxlint ./src --max-warnings=0",
|
|
29
|
+
"format:check": "oxfmt --ignore-path .gitignore --ignore-path .oxfmtignore --check ./src"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@mongez/reinforcements": "^4.0.2",
|
|
@@ -43,18 +43,11 @@
|
|
|
43
43
|
"dayjs": "^1.11.19"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@ianvs/prettier-plugin-sort-imports": "^4.7.1",
|
|
47
46
|
"@mongez/agent-kit": "^1.3.1",
|
|
48
47
|
"@types/node": "^25.0.3",
|
|
49
|
-
"@types/prettier": "^3.0.0",
|
|
50
|
-
"@typescript-eslint/eslint-plugin": "^8.50.0",
|
|
51
|
-
"@typescript-eslint/parser": "^8.50.0",
|
|
52
|
-
"eslint": "^9.39.2",
|
|
53
|
-
"eslint-config-prettier": "^10.1.8",
|
|
54
|
-
"eslint-plugin-prettier": "^5.5.4",
|
|
55
|
-
"eslint-plugin-unused-imports": "^4.3.0",
|
|
56
|
-
"prettier": "^3.7.4",
|
|
57
48
|
"typescript": "7.0.2",
|
|
58
|
-
"vitest": "4.0.5"
|
|
49
|
+
"vitest": "4.0.5",
|
|
50
|
+
"oxlint": "1.88.0",
|
|
51
|
+
"oxfmt": "0.73.0"
|
|
59
52
|
}
|
|
60
53
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-standards
|
|
3
|
-
description: 'Project-level TypeScript code standards for apps built on Warlock.js — `interface` vs `type`, access modifiers, no `any`, no inline-duplicated unions, file-naming suffixes (`.contract.ts` / `.type.ts` / `.service.ts` / `.model.ts` / `.resource.ts` / `.spec.ts`), single-responsibility files, FP-factory + internal-class pattern, brace-every-control-block readability with blank lines between consecutive blocks, JSDoc on public surface and non-obvious logic, error classes extending framework errors, async/await + `Promise.all`, `undefined` over `null`, Vitest co-located,
|
|
3
|
+
description: 'Project-level TypeScript code standards for apps built on Warlock.js — `interface` vs `type`, access modifiers, no `any`, no inline-duplicated unions, file-naming suffixes (`.contract.ts` / `.type.ts` / `.service.ts` / `.model.ts` / `.resource.ts` / `.spec.ts`), single-responsibility files, FP-factory + internal-class pattern, brace-every-control-block readability with blank lines between consecutive blocks, JSDoc on public surface and non-obvious logic, error classes extending framework errors, async/await + `Promise.all`, `undefined` over `null`, Vitest co-located, Oxlint/Oxfmt. Triggers: writing/editing any `.ts` / `.tsx` file in this project; user asks "what are the code standards / style rules / conventions"; spawning a sub-agent that will write code; reviewing a diff for style issues; deciding `interface` vs `type`, when to use a class, where helpers live, how to name a file, or whether something needs JSDoc. Skip: pure-Markdown / JSON / YAML edits with no code change; runtime / behavior questions about a specific framework primitive — load the relevant `@warlock.js/<pkg>` skill instead; package-publishing or CI workflow questions.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Code Standards
|
|
@@ -52,14 +52,16 @@ Never omit `public` / `private` / `protected`. Explicit is faster to read than i
|
|
|
52
52
|
export class CartService {
|
|
53
53
|
public readonly currency: string;
|
|
54
54
|
private items: CartItem[] = [];
|
|
55
|
-
public add(item: CartItem) {
|
|
55
|
+
public add(item: CartItem) {
|
|
56
|
+
/* ... */
|
|
57
|
+
}
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
// ❌
|
|
59
61
|
export class CartService {
|
|
60
|
-
readonly currency: string;
|
|
61
|
-
items: CartItem[] = [];
|
|
62
|
-
add(item: CartItem) {}
|
|
62
|
+
readonly currency: string; // missing public
|
|
63
|
+
items: CartItem[] = []; // missing private
|
|
64
|
+
add(item: CartItem) {} // missing public
|
|
63
65
|
}
|
|
64
66
|
```
|
|
65
67
|
|
|
@@ -75,12 +77,12 @@ If a union appears in more than one place, extract it to a named exported type o
|
|
|
75
77
|
// ✅
|
|
76
78
|
export type OrderStatus = "pending" | "paid" | "shipped" | "cancelled";
|
|
77
79
|
|
|
78
|
-
export type Order = { status: OrderStatus
|
|
79
|
-
export type OrderEvent = { status: OrderStatus
|
|
80
|
+
export type Order = { status: OrderStatus /* ... */ };
|
|
81
|
+
export type OrderEvent = { status: OrderStatus /* ... */ };
|
|
80
82
|
|
|
81
83
|
// ❌ inline duplication — changing the set means N edits and N chances to miss one
|
|
82
|
-
export type Order = { status: "pending" | "paid" | "shipped" | "cancelled"
|
|
83
|
-
export type OrderEvent = { status: "pending" | "paid" | "shipped" | "cancelled"
|
|
84
|
+
export type Order = { status: "pending" | "paid" | "shipped" | "cancelled" /* ... */ };
|
|
85
|
+
export type OrderEvent = { status: "pending" | "paid" | "shipped" | "cancelled" /* ... */ };
|
|
84
86
|
```
|
|
85
87
|
|
|
86
88
|
### 1.5 Constrain generics
|
|
@@ -89,10 +91,14 @@ Open generics are almost always wrong. If `T` has a contract, declare it.
|
|
|
89
91
|
|
|
90
92
|
```typescript
|
|
91
93
|
// ✅
|
|
92
|
-
export function publish<T extends DomainEvent>(event: T): void {
|
|
94
|
+
export function publish<T extends DomainEvent>(event: T): void {
|
|
95
|
+
/* ... */
|
|
96
|
+
}
|
|
93
97
|
|
|
94
98
|
// ❌
|
|
95
|
-
export function publish<T>(event: T): void {
|
|
99
|
+
export function publish<T>(event: T): void {
|
|
100
|
+
/* ... */
|
|
101
|
+
}
|
|
96
102
|
```
|
|
97
103
|
|
|
98
104
|
### 1.6 Prefer `undefined` for "absent"
|
|
@@ -102,7 +108,9 @@ Use `undefined` everywhere — optional properties, optional parameters, "not fo
|
|
|
102
108
|
```typescript
|
|
103
109
|
// ✅
|
|
104
110
|
export type User = { id: string; phone?: string };
|
|
105
|
-
function find(id: string): User | undefined {
|
|
111
|
+
function find(id: string): User | undefined {
|
|
112
|
+
/* ... */
|
|
113
|
+
}
|
|
106
114
|
|
|
107
115
|
// ❌ mixed
|
|
108
116
|
export type User = { id: string; phone: string | null };
|
|
@@ -162,14 +170,14 @@ Always bare: `from "./order.service"`, never `from "./order.service.ts"` or `.js
|
|
|
162
170
|
|
|
163
171
|
## 3. File naming
|
|
164
172
|
|
|
165
|
-
| Suffix | Purpose
|
|
166
|
-
| -------------- |
|
|
167
|
-
| `.contract.ts` | Interface only (blueprint)
|
|
168
|
-
| `.type.ts` | Types only (data shapes)
|
|
169
|
-
| `.service.ts` | Service class
|
|
170
|
-
| `.model.ts` | Domain model class
|
|
171
|
-
| `.resource.ts` | Output mapper (model → wire format)
|
|
172
|
-
| `.spec.ts` | Co-located test for a sibling `.ts`
|
|
173
|
+
| Suffix | Purpose | Example |
|
|
174
|
+
| -------------- | ----------------------------------- | ---------------------- |
|
|
175
|
+
| `.contract.ts` | Interface only (blueprint) | `payment.contract.ts` |
|
|
176
|
+
| `.type.ts` | Types only (data shapes) | `order.type.ts` |
|
|
177
|
+
| `.service.ts` | Service class | `cart.service.ts` |
|
|
178
|
+
| `.model.ts` | Domain model class | `user.model.ts` |
|
|
179
|
+
| `.resource.ts` | Output mapper (model → wire format) | `order.resource.ts` |
|
|
180
|
+
| `.spec.ts` | Co-located test for a sibling `.ts` | `cart.service.spec.ts` |
|
|
173
181
|
|
|
174
182
|
**One primary export per file.** If a `.contract.ts` also defines closely-related data types used only by that contract, that's fine — but the primary export is the interface.
|
|
175
183
|
|
|
@@ -216,9 +224,15 @@ export function placeOrder(config: OrderConfig) {
|
|
|
216
224
|
class PlacementRun {
|
|
217
225
|
private items: ResolvedItem[] = [];
|
|
218
226
|
private total = 0;
|
|
219
|
-
public async run(): Promise<OrderResult> {
|
|
220
|
-
|
|
221
|
-
|
|
227
|
+
public async run(): Promise<OrderResult> {
|
|
228
|
+
/* ... */
|
|
229
|
+
}
|
|
230
|
+
private async validate(): Promise<void> {
|
|
231
|
+
/* ... */
|
|
232
|
+
}
|
|
233
|
+
private async charge(): Promise<void> {
|
|
234
|
+
/* ... */
|
|
235
|
+
}
|
|
222
236
|
}
|
|
223
237
|
```
|
|
224
238
|
|
|
@@ -283,7 +297,9 @@ Identifiers carry the meaning. Add a comment only when the **why** is non-obviou
|
|
|
283
297
|
```typescript
|
|
284
298
|
// ❌ noise
|
|
285
299
|
// Loop over items
|
|
286
|
-
for (const item of items) {
|
|
300
|
+
for (const item of items) {
|
|
301
|
+
/* ... */
|
|
302
|
+
}
|
|
287
303
|
|
|
288
304
|
// ✅ the WHY isn't obvious from the code
|
|
289
305
|
// Stripe returns idempotency_key on retry but not on first call —
|
|
@@ -362,7 +378,7 @@ Every promise is either awaited, returned, or explicitly `.catch()`'d. A "fire a
|
|
|
362
378
|
|
|
363
379
|
```typescript
|
|
364
380
|
// ❌
|
|
365
|
-
sendWelcomeEmail(user);
|
|
381
|
+
sendWelcomeEmail(user); // floating — if it rejects, you'll never know
|
|
366
382
|
|
|
367
383
|
// ✅
|
|
368
384
|
await sendWelcomeEmail(user);
|
|
@@ -481,7 +497,7 @@ Validation runs once at the route boundary; everything inward consumes the typed
|
|
|
481
497
|
type Order = { totalCents: number; placedAtUtc: Date };
|
|
482
498
|
|
|
483
499
|
// ❌
|
|
484
|
-
type Order = { total: number; placedAt: string };
|
|
500
|
+
type Order = { total: number; placedAt: string }; // float money, ambiguous tz
|
|
485
501
|
```
|
|
486
502
|
|
|
487
503
|
See `skills/data-and-persistence/SKILL.md`.
|
|
@@ -551,12 +567,11 @@ A secret with no safe default (a payment key, a signing secret) should fail loud
|
|
|
551
567
|
|
|
552
568
|
## 11. Tooling
|
|
553
569
|
|
|
554
|
-
Use the project's
|
|
555
|
-
`.oxlintrc.json` + `.oxfmtrc.json`. Run `npm run lint:check` and
|
|
570
|
+
Use the project's toolchain: `.oxlintrc.json` + `.oxfmtrc.json`. Run `npm run lint:check` and
|
|
556
571
|
`npm run format:check` for non-mutating checks. The Oxc scaffold enables the
|
|
557
572
|
Oxlint development health checker and retains TypeScript checking.
|
|
558
573
|
|
|
559
|
-
`npx warlock check` is the one verify command,
|
|
574
|
+
`npx warlock check` is the one verify command, with Oxlint: it
|
|
560
575
|
typechecks, runs the configured linter and the affected tests, and checks the
|
|
561
576
|
framework's conventions — `request.validated()` instead of `request.all()`,
|
|
562
577
|
guarded or `public: true` mutating routes, safe redirects, redacted logs,
|
|
@@ -564,17 +579,17 @@ bounded regexes on input, one controller per file, `type` (not `kind`) as a
|
|
|
564
579
|
union's discriminator. Each problem has a `WL` code and its fix
|
|
565
580
|
(`npx warlock explain <CODE>`).
|
|
566
581
|
|
|
567
|
-
For
|
|
582
|
+
For Oxlint, consider enabling these additional rules:
|
|
568
583
|
|
|
569
|
-
-
|
|
570
|
-
-
|
|
571
|
-
-
|
|
572
|
-
-
|
|
573
|
-
-
|
|
584
|
+
- `typescript/explicit-member-accessibility` — enforces § 1.2
|
|
585
|
+
- `typescript/no-explicit-any` — enforces § 1.3
|
|
586
|
+
- `typescript/consistent-type-definitions` — supports § 1.1
|
|
587
|
+
- `typescript/no-floating-promises` — enforces § 6.3 (requires `oxlint-tsgolint` and `options.typeAware: true`)
|
|
588
|
+
- `typescript/await-thenable` (same type-aware engine requirement)
|
|
574
589
|
- `curly` — enforces braces (§ 4.4)
|
|
575
590
|
- `no-console` — enforces § 8
|
|
576
591
|
|
|
577
|
-
Match
|
|
592
|
+
Match `.oxfmtrc.json`: 100 columns, double quotes, semicolons, trailing commas and built-in import sorting.
|
|
578
593
|
|
|
579
594
|
---
|
|
580
595
|
|
|
@@ -583,6 +598,7 @@ Match the selected formatter config: `.prettierrc.json` or `.oxfmtrc.json`. Both
|
|
|
583
598
|
Before any change ships:
|
|
584
599
|
|
|
585
600
|
**Craft**
|
|
601
|
+
|
|
586
602
|
- [ ] All class members have access modifiers
|
|
587
603
|
- [ ] No `any`
|
|
588
604
|
- [ ] No inline-duplicated unions or literal sets
|
|
@@ -600,6 +616,7 @@ Before any change ships:
|
|
|
600
616
|
- [ ] `npx warlock check` exits 0
|
|
601
617
|
|
|
602
618
|
**Non-negotiable defaults**
|
|
619
|
+
|
|
603
620
|
- [ ] Every untrusted input has a `@warlock.js/seal` schema at the boundary
|
|
604
621
|
- [ ] Money fields are integer minor units, time stored as UTC
|
|
605
622
|
- [ ] IDs exposed in URLs are opaque (UUID / nanoid)
|