create-warlock 5.32.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +26 -47
  2. package/README.md +63 -39
  3. package/esm/commands/create-new-app/index.mjs +10 -9
  4. package/esm/commands/create-new-app/index.mjs.map +1 -1
  5. package/esm/commands/create-new-app/types.d.mts +1 -1
  6. package/esm/commands/create-warlock-app/index.mjs +1 -1
  7. package/esm/commands/create-warlock-app/index.mjs.map +1 -1
  8. package/esm/flags/definitions.mjs +2 -2
  9. package/esm/flags/definitions.mjs.map +1 -1
  10. package/esm/flags/seed-from-flags.mjs +1 -1
  11. package/esm/flags/seed-from-flags.mjs.map +1 -1
  12. package/esm/helpers/app.mjs +10 -25
  13. package/esm/helpers/app.mjs.map +1 -1
  14. package/esm/helpers/insert-import.mjs +2 -2
  15. package/esm/helpers/insert-import.mjs.map +1 -1
  16. package/llms-full.txt +62 -61
  17. package/package.json +2 -2
  18. package/skills/create-a-warlock-project/SKILL.md +62 -61
  19. package/templates/{warlock/.prettierignore → toolchains/oxc/.oxfmtignore} +0 -2
  20. package/templates/toolchains/oxc/.oxlintrc.json +1 -1
  21. package/templates/warlock/.oxfmtignore +2 -0
  22. package/templates/warlock/.oxfmtrc.json +15 -0
  23. package/templates/warlock/.oxlintrc.json +24 -0
  24. package/templates/warlock/.vscode/extensions.json +3 -0
  25. package/templates/warlock/.vscode/settings.json +11 -17
  26. package/templates/warlock/README.md +18 -20
  27. package/templates/warlock/package.json +7 -14
  28. package/templates/warlock/skills/code-standards/SKILL.md +53 -36
  29. package/templates/warlock/skills/git-workflow/SKILL.md +25 -20
  30. package/templates/warlock/skills/module-boundaries/SKILL.md +33 -24
  31. package/templates/warlock/skills/web-code-standards/SKILL.md +1 -1
  32. package/templates/warlock/src/web/home/components/home-footer.tsx +3 -1
  33. package/esm/prompts/ask-lint-toolchain.mjs +0 -21
  34. package/esm/prompts/ask-lint-toolchain.mjs.map +0 -1
  35. package/templates/warlock/.prettierrc.json +0 -12
  36. package/templates/warlock/eslint.config.js +0 -98
@@ -0,0 +1,2 @@
1
+ node_modules
2
+ dist
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": ["oxc.oxc-vscode"]
3
+ }
@@ -1,41 +1,35 @@
1
1
  {
2
- "editor.defaultFormatter": "esbenp.prettier-vscode",
2
+ "editor.defaultFormatter": "oxc.oxc-vscode",
3
3
  "editor.formatOnSave": true,
4
4
  "editor.codeActionsOnSave": {
5
- "source.fixAll.eslint": "explicit",
5
+ "source.fixAll.oxc": "explicit",
6
6
  "source.organizeImports": "never"
7
7
  },
8
8
  "[typescript]": {
9
- "editor.defaultFormatter": "esbenp.prettier-vscode",
9
+ "editor.defaultFormatter": "oxc.oxc-vscode",
10
10
  "editor.formatOnSave": true
11
11
  },
12
12
  "[typescriptreact]": {
13
- "editor.defaultFormatter": "esbenp.prettier-vscode",
13
+ "editor.defaultFormatter": "oxc.oxc-vscode",
14
14
  "editor.formatOnSave": true
15
15
  },
16
16
  "[javascript]": {
17
- "editor.defaultFormatter": "esbenp.prettier-vscode",
17
+ "editor.defaultFormatter": "oxc.oxc-vscode",
18
18
  "editor.formatOnSave": true
19
19
  },
20
20
  "[javascriptreact]": {
21
- "editor.defaultFormatter": "esbenp.prettier-vscode",
21
+ "editor.defaultFormatter": "oxc.oxc-vscode",
22
22
  "editor.formatOnSave": true
23
23
  },
24
24
  "[json]": {
25
- "editor.defaultFormatter": "esbenp.prettier-vscode",
25
+ "editor.defaultFormatter": "oxc.oxc-vscode",
26
26
  "editor.formatOnSave": true
27
27
  },
28
28
  "[css]": {
29
- "editor.defaultFormatter": "esbenp.prettier-vscode",
29
+ "editor.defaultFormatter": "oxc.oxc-vscode",
30
30
  "editor.formatOnSave": true
31
31
  },
32
- "prettier.configPath": ".prettierrc.json",
33
- "prettier.requireConfig": true,
34
- "prettier.ignorePath": ".prettierignore",
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 | 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) |
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
- Use the Oxlint/Oxfmt lint mode with TS7. The optional ESLint scaffold is retained,
161
- but typescript-eslint's parser requires the removed JavaScript compiler API and
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": "eslint --fix ./src --max-warnings=0",
23
- "format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx,css,md,json}\" --config ./.prettierrc.json",
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": "eslint ./src --max-warnings=0",
29
- "format:check": "prettier --check \"./src/**/*.{js,jsx,ts,tsx,css,md,json}\" --config ./.prettierrc.json"
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, ESLint/Prettier or 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.'
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; // missing public
61
- items: CartItem[] = []; // missing private
62
- add(item: CartItem) {} // missing public
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 | Example |
166
- | -------------- | ------------------------------------ | ---------------------- |
167
- | `.contract.ts` | Interface only (blueprint) | `payment.contract.ts` |
168
- | `.type.ts` | Types only (data shapes) | `order.type.ts` |
169
- | `.service.ts` | Service class | `cart.service.ts` |
170
- | `.model.ts` | Domain model class | `user.model.ts` |
171
- | `.resource.ts` | Output mapper (model → wire format) | `order.resource.ts` |
172
- | `.spec.ts` | Co-located test for a sibling `.ts` | `cart.service.spec.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
- private async validate(): Promise<void> { /* ... */ }
221
- private async charge(): Promise<void> { /* ... */ }
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); // floating — if it rejects, you'll never know
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 }; // float money, ambiguous tz
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 selected toolchain: `eslint.config.js` + `.prettierrc.json`, or
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, in either toolchain: it
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 ESLint, consider enabling these additional rules:
582
+ For Oxlint, consider enabling these additional rules:
568
583
 
569
- - `@typescript-eslint/explicit-member-accessibility` — enforces § 1.2
570
- - `@typescript-eslint/no-explicit-any` — enforces § 1.3
571
- - `@typescript-eslint/consistent-type-definitions` — supports § 1.1
572
- - `@typescript-eslint/no-floating-promises` — enforces § 6.3
573
- - `@typescript-eslint/await-thenable`
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 the selected formatter config: `.prettierrc.json` or `.oxfmtrc.json`. Both use 100 columns, double quotes, semicolons and trailing commas. Prettier uses `@ianvs/prettier-plugin-sort-imports` (Babel, independent of TypeScript); it sorts built-ins, packages, aliases (`app/*`, `web/*`, `@shared/*`) and relatives in one block while preserving separate type imports. Oxfmt sorts imports through its own configuration. Both retain unused imports: ESLint reports them through `unused-imports/no-unused-imports`, and Oxlint through `no-unused-vars`. Run the formatter, then the linter.
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)
@@ -37,18 +37,18 @@ refactor(cart): extract totals calculation into a util
37
37
 
38
38
  ### 1.2 Types
39
39
 
40
- | Type | Use for |
41
- | ----------- | ------------------------------------------------ |
42
- | `feat` | New user-visible feature or capability |
43
- | `fix` | Bug fix |
44
- | `refactor` | Internal restructure with no behaviour change |
45
- | `docs` | Documentation only (skills, README, code docs) |
46
- | `test` | Adding or updating tests only |
47
- | `chore` | Tooling, config, scripts — no app code change |
48
- | `perf` | Performance improvement |
49
- | `build` | Build system or external dependencies |
50
- | `ci` | CI configuration |
51
- | `style` | Formatting only (rare — Prettier handles this) |
40
+ | Type | Use for |
41
+ | ---------- | ---------------------------------------------- |
42
+ | `feat` | New user-visible feature or capability |
43
+ | `fix` | Bug fix |
44
+ | `refactor` | Internal restructure with no behaviour change |
45
+ | `docs` | Documentation only (skills, README, code docs) |
46
+ | `test` | Adding or updating tests only |
47
+ | `chore` | Tooling, config, scripts — no app code change |
48
+ | `perf` | Performance improvement |
49
+ | `build` | Build system or external dependencies |
50
+ | `ci` | CI configuration |
51
+ | `style` | Formatting only (rare — Oxfmt handles this) |
52
52
 
53
53
  ### 1.3 Scope
54
54
 
@@ -143,18 +143,23 @@ Each PR is independently mergeable, independently reviewable.
143
143
 
144
144
  ```markdown
145
145
  ## What
146
+
146
147
  One-line summary of the change.
147
148
 
148
149
  ## Why
150
+
149
151
  The reason — link to a ticket, design doc, or bug report.
150
152
 
151
153
  ## How to verify
154
+
152
155
  Steps a reviewer can take to confirm it works.
153
156
 
154
157
  ## Screenshots / recordings
158
+
155
159
  If UI or output changed.
156
160
 
157
161
  ## Notes for reviewer
162
+
158
163
  Anything that's intentionally weird or worth knowing.
159
164
  ```
160
165
 
@@ -195,14 +200,14 @@ A drive-by approve is worse than no approve — it gives false confidence. If yo
195
200
 
196
201
  Every PR runs these. All must pass before merge. The exact commands match `package.json` scripts:
197
202
 
198
- | Gate | Command | Blocks on |
199
- | ----------------------------- | ----------------------------- | ---------------------------------- |
200
- | Type-check | `yarn tsc` | Any TS error |
201
- | Lint | `yarn lint` | Any ESLint error |
202
- | Tests | `yarn test --run` | Any failed test |
203
- | Coverage (recommended floor) | `yarn test:coverage` | TBD per team policy |
204
- | Dependency audit | `yarn audit --level=high` | High / critical vulnerabilities |
205
- | Format check | `yarn format --check` | Any unformatted file |
203
+ | Gate | Command | Blocks on |
204
+ | ---------------------------- | ------------------------- | ------------------------------- |
205
+ | Type-check | `yarn tsc` | Any TS error |
206
+ | Lint | `yarn lint` | Any Oxlint error |
207
+ | Tests | `yarn test --run` | Any failed test |
208
+ | Coverage (recommended floor) | `yarn test:coverage` | TBD per team policy |
209
+ | Dependency audit | `yarn audit --level=high` | High / critical vulnerabilities |
210
+ | Format check | `yarn format --check` | Any unformatted file |
206
211
 
207
212
  Additional rules:
208
213
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: module-boundaries
3
- description: 'Architecture rules for how modules under `src/app/**` relate — each module owns one domain noun (`orders`, `users`, `carts`, `payments`); standard layout per module (`controllers/`, `services/`, `models/<noun>/`, `repositories/`, `schema/`, `resources/`, `types/`, `utils/`, `events/`, `errors/`, `routes.ts`); public surface exposed through sub-folder barrels (`services/index.ts`, `utils/index.ts`, `types/index.ts`) — NEVER a module-root `index.ts` re-exporting everything; no module reaches into another module''s internal files; zero circular dependencies between modules; inter-module communication is explicit — direct service call OR `@mongez/events` bus, never shared mutable globals; cross-cutting helpers live in `src/app/shared/`. Triggers: adding a new module under `src/app/**`; importing from another module; deciding where new code belongs; circular import error; ESLint `import/no-restricted-paths` violation; user asks "can module A use module B''s internals", "how should modules talk", "where should this code live", "I''m getting a circular dependency", "what folders does a module need", "what goes in shared". Skip: intra-module file organization (load `skills/code-standards/SKILL.md` § 4); barrel mechanics within a module (load `skills/code-standards/SKILL.md` § 2.2); deploying / packaging / monorepo questions; framework primitive details — load relevant `@warlock.js/*` skill.'
3
+ description: 'Architecture rules for how modules under `src/app/**` relate — each module owns one domain noun (`orders`, `users`, `carts`, `payments`); standard layout per module (`controllers/`, `services/`, `models/<noun>/`, `repositories/`, `schema/`, `resources/`, `types/`, `utils/`, `events/`, `errors/`, `routes.ts`); public surface exposed through sub-folder barrels (`services/index.ts`, `utils/index.ts`, `types/index.ts`) — NEVER a module-root `index.ts` re-exporting everything; no module reaches into another module''s internal files; zero circular dependencies between modules; inter-module communication is explicit — direct service call OR `@mongez/events` bus, never shared mutable globals; cross-cutting helpers live in `src/app/shared/`. Triggers: adding a new module under `src/app/**`; importing from another module; deciding where new code belongs; circular import error; Oxlint `import/no-restricted-paths` violation; user asks "can module A use module B''s internals", "how should modules talk", "where should this code live", "I''m getting a circular dependency", "what folders does a module need", "what goes in shared". Skip: intra-module file organization (load `skills/code-standards/SKILL.md` § 4); barrel mechanics within a module (load `skills/code-standards/SKILL.md` § 2.2); deploying / packaging / monorepo questions; framework primitive details — load relevant `@warlock.js/*` skill.'
4
4
  ---
5
5
 
6
6
  # Module boundaries
@@ -116,26 +116,35 @@ See `skills/code-standards/SKILL.md` § 2.2 for the reasoning.
116
116
  - `src/app/<module>/schema/*` — schemas belong to the endpoint that uses them
117
117
  - `src/app/<module>/routes.ts` — registers routes for the framework, nobody imports it
118
118
 
119
- ### 3.3 Enforce with ESLint
119
+ ### 3.3 Enforce with Oxlint
120
120
 
121
- Add `eslint-plugin-import/no-restricted-paths` rules:
121
+ Add scoped native rules to `.oxlintrc.json`:
122
122
 
123
- ```js
124
- // eslint.config.js — proposed addition
123
+ ```json
125
124
  {
126
- rules: {
127
- "import/no-restricted-paths": ["error", {
128
- zones: [
129
- { target: "src/app/*/controllers", from: "src/app/*/controllers", except: ["./"] },
130
- { target: "src/app", from: "src/app/*/repositories", except: ["./*/services"] },
131
- { target: "src/app", from: "src/app/*/schema", except: ["./*/controllers"] },
132
- ],
133
- }],
134
- },
125
+ "overrides": [
126
+ {
127
+ "files": ["src/app/orders/**/*.ts"],
128
+ "rules": {
129
+ "no-restricted-imports": [
130
+ "error",
131
+ {
132
+ "patterns": [
133
+ {
134
+ "group": ["app/*/controllers/**", "app/*/repositories/**"],
135
+ "message": "Import public services instead"
136
+ }
137
+ ]
138
+ }
139
+ ]
140
+ }
141
+ }
142
+ ]
135
143
  }
136
144
  ```
137
145
 
138
- The exact rule shape depends on the eslint config — but the principle is: cross-module imports go through services / utils / types / resources / models / errors, never through the private folders.
146
+ Cross-module imports go through public services, utils, types, resources, models
147
+ and errors. Adapt each override to the module's public surface.
139
148
 
140
149
  ---
141
150
 
@@ -192,7 +201,7 @@ Or, if the shared concept is purely cross-cutting (not a domain), it goes to `ap
192
201
 
193
202
  ### 5.4 Detect in CI
194
203
 
195
- `eslint-plugin-import/no-cycle` catches cycles at lint time. Enable it.
204
+ `import/no-cycle` catches cycles at lint time. Enable it.
196
205
 
197
206
  ---
198
207
 
@@ -244,13 +253,13 @@ Top-level mutable exports across modules are the worst kind of coupling — invi
244
253
 
245
254
  ## 7. Where shared code lives
246
255
 
247
- | If the code is... | Put it in |
248
- | --------------------------------------- | --------------------------------------------- |
249
- | Used by 2+ modules, stable, cross-domain | `src/app/shared/` |
250
- | Used by 2+ modules, domain-specific | Extract a new domain module |
251
- | Used by exactly 1 module | Stays inside that module's `utils/` |
252
- | Third-party integration glue (SDK wrappers) | `src/app/shared/` (or a dedicated module) |
253
- | Build-time config | `src/config/` (one file per concern) |
256
+ | If the code is... | Put it in |
257
+ | ------------------------------------------- | ----------------------------------------- |
258
+ | Used by 2+ modules, stable, cross-domain | `src/app/shared/` |
259
+ | Used by 2+ modules, domain-specific | Extract a new domain module |
260
+ | Used by exactly 1 module | Stays inside that module's `utils/` |
261
+ | Third-party integration glue (SDK wrappers) | `src/app/shared/` (or a dedicated module) |
262
+ | Build-time config | `src/config/` (one file per concern) |
254
263
 
255
264
  ### 7.1 Promotion to `shared/` is a deliberate choice
256
265
 
@@ -285,7 +294,7 @@ Before merging a change that adds, restructures, or imports across modules:
285
294
  - [ ] No module-root `index.ts` created
286
295
  - [ ] Sub-folder barrels exist for `services/`, `utils/`, `types/` where applicable
287
296
  - [ ] No imports from another module's `controllers/`, `repositories/`, `schema/`
288
- - [ ] No circular imports between modules (ESLint `no-cycle` clean)
297
+ - [ ] No circular imports between modules (Oxlint `no-cycle` clean)
289
298
  - [ ] Cross-module communication uses direct service call or `@mongez/events` — never shared mutable globals
290
299
  - [ ] Shared code that's used by 2+ modules lives in `src/app/shared/`, not duplicated
291
300
  - [ ] Module has a `README.md` describing purpose, public surface, and ownership
@@ -292,7 +292,7 @@ and refresh primitives so state and loader data stay in sync.
292
292
 
293
293
  Apply the shared `code-standards` rules to web TypeScript as well: `type` for
294
294
  plain props/data, no `any`, no swallowed catches, no unsafe assertions used
295
- to hide an unknown data contract. Configure non-mutating ESLint and formatting
295
+ to hide an unknown data contract. Configure non-mutating Oxlint and formatting
296
296
  checks to enforce rules that can be automated; do not rely on a lint command
297
297
  that rewrites files as the only quality gate.
298
298
 
@@ -1,5 +1,7 @@
1
1
  import { HomeLogo } from "./logo";
2
2
 
3
+ const copyrightYear = new Date().getFullYear();
4
+
3
5
  export function HomeFooter() {
4
6
  return (
5
7
  <>
@@ -27,7 +29,7 @@ export function HomeFooter() {
27
29
  <a href="https://github.com/warlockjs" target="_blank" rel="noreferrer">
28
30
  GitHub
29
31
  </a>
30
- <span>MIT © {new Date().getFullYear()}</span>
32
+ <span>MIT © {copyrightYear}</span>
31
33
  </div>
32
34
  </footer>
33
35
  </>
@@ -1,21 +0,0 @@
1
- import { select } from "@clack/prompts";
2
-
3
- //#region ../../packages/create-warlock/src/prompts/ask-lint-toolchain.ts
4
- /** The full wizard offers the same toolchain choice as --lint. */
5
- function askLintToolchain(initialValue = "oxc") {
6
- return select({
7
- message: "Choose lint and formatting tools",
8
- options: [{
9
- value: "eslint",
10
- label: "ESLint + Prettier"
11
- }, {
12
- value: "oxc",
13
- label: "Oxlint + Oxfmt"
14
- }],
15
- initialValue
16
- });
17
- }
18
-
19
- //#endregion
20
- export { askLintToolchain };
21
- //# sourceMappingURL=ask-lint-toolchain.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ask-lint-toolchain.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/prompts/ask-lint-toolchain.ts"],"sourcesContent":["import { select } from \"@clack/prompts\";\nimport type { LintToolchain } from \"../commands/create-new-app/types\";\n\n/** The full wizard offers the same toolchain choice as --lint. */\nexport function askLintToolchain(initialValue: LintToolchain = \"oxc\") {\n return select<LintToolchain>({\n message: \"Choose lint and formatting tools\",\n options: [\n { value: \"eslint\", label: \"ESLint + Prettier\" },\n { value: \"oxc\", label: \"Oxlint + Oxfmt\" },\n ],\n initialValue,\n });\n}\n"],"mappings":";;;;AAIA,SAAgB,iBAAiB,eAA8B,OAAO;CACpE,OAAO,OAAsB;EAC3B,SAAS;EACT,SAAS,CACP;GAAE,OAAO;GAAU,OAAO;EAAoB,GAC9C;GAAE,OAAO;GAAO,OAAO;EAAiB,CAC1C;EACA;CACF,CAAC;AACH"}