@skyf0xx/hedgehog 4.2.0 → 4.2.2
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/README.md +20 -0
- package/bin/cli.mjs +12 -0
- package/package.json +2 -2
- package/src/agents/backend-eng.md +30 -16
- package/src/agents/front-end-eng.md +29 -12
- package/src/db/next.mjs +46 -8
- package/src/golden-cores/full-stack-app/apps/api/package.json +19 -1
- package/src/golden-cores/full-stack-app/apps/api/src/app/app.module.spec.ts +15 -0
- package/src/golden-cores/full-stack-app/apps/api/src/app/app.module.ts +6 -1
- package/src/golden-cores/full-stack-app/apps/api/src/app/feature-modules.ts +8 -0
- package/src/golden-cores/full-stack-app/apps/api/tsconfig.app.json +3 -0
- package/src/golden-cores/full-stack-app/apps/api/tsconfig.json +3 -0
- package/src/golden-cores/full-stack-app/apps/api/tsconfig.spec.json +36 -0
- package/src/golden-cores/full-stack-app/apps/api/vitest.config.mts +18 -0
- package/src/golden-cores/full-stack-app/apps/web/src/components/theme-toggle.spec.tsx +20 -0
- package/src/golden-cores/full-stack-app/apps/web/src/test-setup.ts +1 -0
- package/src/golden-cores/full-stack-app/apps/web/tsconfig.json +6 -0
- package/src/golden-cores/full-stack-app/apps/web/tsconfig.spec.json +37 -0
- package/src/golden-cores/full-stack-app/apps/web/vitest.config.mts +27 -0
- package/src/golden-cores/full-stack-app/nx.json +4 -1
- package/src/golden-cores/full-stack-app/package.json +8 -0
- package/src/golden-cores/full-stack-app/pnpm-lock.yaml +8735 -2907
- package/src/golden-cores/full-stack-app/tools/generate-feature-modules.cjs +104 -0
- package/src/golden-cores/full-stack-app/tools/generators/contract/generator.ts +283 -0
- package/src/golden-cores/full-stack-app/tools/generators/contract/schema.json +20 -0
- package/src/golden-cores/full-stack-app/tools/generators/controller/generator.ts +323 -0
- package/src/golden-cores/full-stack-app/tools/generators/controller/schema.json +20 -0
- package/src/golden-cores/full-stack-app/tools/generators/fields.ts +126 -0
- package/src/golden-cores/full-stack-app/tools/generators/generators.json +42 -0
- package/src/golden-cores/full-stack-app/tools/generators/hook/generator.ts +274 -0
- package/src/golden-cores/full-stack-app/tools/generators/hook/schema.json +19 -0
- package/src/golden-cores/full-stack-app/tools/generators/lib-shell.ts +124 -0
- package/src/golden-cores/full-stack-app/tools/generators/naming.ts +84 -0
- package/src/golden-cores/full-stack-app/tools/generators/package.json +6 -0
- package/src/golden-cores/full-stack-app/tools/generators/repository/generator.ts +298 -0
- package/src/golden-cores/full-stack-app/tools/generators/repository/schema.json +15 -0
- package/src/golden-cores/full-stack-app/tools/generators/schema/generator.ts +169 -0
- package/src/golden-cores/full-stack-app/tools/generators/schema/schema.json +20 -0
- package/src/golden-cores/full-stack-app/tools/generators/screen/generator.ts +218 -0
- package/src/golden-cores/full-stack-app/tools/generators/screen/schema.json +15 -0
- package/src/golden-cores/full-stack-app/tools/generators/service/generator.ts +194 -0
- package/src/golden-cores/full-stack-app/tools/generators/service/schema.json +15 -0
- package/src/skills/hedgehog-bootstrap-full-stack-app-core/SKILL.md +67 -6
- package/src/skills/hedgehog-loop/SKILL.md +119 -53
package/README.md
CHANGED
|
@@ -68,6 +68,19 @@ Every dependency is explicit, so Hedgehog knows which tasks can run in parallel.
|
|
|
68
68
|
|
|
69
69
|
Agents fan out to give you great outcomes at **faster speeds**.
|
|
70
70
|
|
|
71
|
+
## Deterministic Code Generation
|
|
72
|
+
|
|
73
|
+
Where a piece of code has one correct shape, Hedgehog generates it instead of asking the AI to write it freehand.
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+
- **Generators over freehand**: schemas, boilerplate, and scaffolding come from generators that create the code
|
|
78
|
+
- **Faster**: no reasoning required to produce a known shape
|
|
79
|
+
- **More correct**: the same input always produces the same, tested output
|
|
80
|
+
- **Fewer tokens**: nothing spent generating code that a template already covers
|
|
81
|
+
|
|
82
|
+
The AI is reserved for the parts a generator can't cover.
|
|
83
|
+
|
|
71
84
|
## What Hedgehog builds
|
|
72
85
|
|
|
73
86
|
### Full-stack applications
|
|
@@ -138,6 +151,13 @@ npx @skyf0xx/hedgehog init
|
|
|
138
151
|
|
|
139
152
|
Then open your coding agent and describe what you want to build.
|
|
140
153
|
|
|
154
|
+
The golden cores print a `pnpm install` step as part of their next steps.
|
|
155
|
+
On a fresh project with no warm pnpm store, that first install can take
|
|
156
|
+
several minutes — it's pulling a full monorepo toolchain (Nx, webpack,
|
|
157
|
+
sass-embedded, Playwright, etc.) and, on first commit, running the commit
|
|
158
|
+
gate against the whole workspace. A quiet stretch of output during that
|
|
159
|
+
step is expected, not a hang.
|
|
160
|
+
|
|
141
161
|
### Coding agents
|
|
142
162
|
|
|
143
163
|
Hedgehog installs for **Claude Code** by default. Add a host flag to
|
package/bin/cli.mjs
CHANGED
|
@@ -504,6 +504,18 @@ async function init({ force, core, explicitCore, host = DEFAULT_HOST, hostOnly =
|
|
|
504
504
|
// runs the whole workspace (see lefthook.yml), so expect it to take
|
|
505
505
|
// as long as a full typecheck/lint/test — that is the gate working.
|
|
506
506
|
console.log(` 1. ${bold('pnpm install')}`);
|
|
507
|
+
console.log(
|
|
508
|
+
dim(' First run can take several minutes — full dependency install plus'),
|
|
509
|
+
);
|
|
510
|
+
console.log(dim(' the commit gate running against the whole workspace. This is'));
|
|
511
|
+
console.log(dim(' expected; let it finish rather than treating a quiet stretch as stuck.'));
|
|
512
|
+
console.log(
|
|
513
|
+
dim(' If an agent is running this: tell the user up front that first install'),
|
|
514
|
+
);
|
|
515
|
+
console.log(
|
|
516
|
+
dim(' can take several minutes and why, then check on progress every ~30s'),
|
|
517
|
+
);
|
|
518
|
+
console.log(dim(' instead of polling tightly or narrating the wait.'));
|
|
507
519
|
console.log(` 2. ${bold('git add -A && git commit -m "chore: install Hedgehog"')}`);
|
|
508
520
|
console.log(` 3. Open ${HOSTS[host].label} and describe what you want to build.`);
|
|
509
521
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyf0xx/hedgehog",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.2",
|
|
4
4
|
"description": "Install the Hedgehog build discipline (agents + skills) into a repo, for Claude Code, Cursor, or Gemini CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -44,4 +44,4 @@
|
|
|
44
44
|
"hedgehog"
|
|
45
45
|
],
|
|
46
46
|
"license": "MIT"
|
|
47
|
-
}
|
|
47
|
+
}
|
|
@@ -34,10 +34,19 @@ build exactly what its ALLOWED SCOPE names, one layer at a time, gated by
|
|
|
34
34
|
Use `nx-run-tasks` (build/lint/test/typecheck), `nx-workspace` (inspecting
|
|
35
35
|
project/target config), `nx-generate` (scaffolding a new library/app), and
|
|
36
36
|
`link-workspace-packages` (wiring a new package into a consumer) as
|
|
37
|
-
needed.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
needed.
|
|
38
|
+
|
|
39
|
+
**Every layer you own starts from its generator in `tools/generators/`** —
|
|
40
|
+
one per layer (`schema`, `contract`, `repository`, `service`,
|
|
41
|
+
`controller`), each landing that layer's package shell, `nx.tags`,
|
|
42
|
+
port-discipline file suffixes, Nest module/controller pair, and barrel
|
|
43
|
+
wiring in one step. The claimed packet's LAYER SHAPE section prints the
|
|
44
|
+
exact command for the layer you're on; `hedgehog-loop`'s "Scaffolding a
|
|
45
|
+
layer" section owns the full flag contract and the workspace wiring a new
|
|
46
|
+
package needs. Generate first, then author this entity's delta — the field
|
|
47
|
+
list and its types, and the business rules below — on top. A hand-copy of
|
|
48
|
+
a sibling module is the drift `hedgehog verify`'s lint step then has to
|
|
49
|
+
catch.
|
|
41
50
|
|
|
42
51
|
## Core Responsibilities
|
|
43
52
|
|
|
@@ -98,24 +107,29 @@ command and tags for each.
|
|
|
98
107
|
`complete` — no need to re-derive that by hand. Cross-module FK
|
|
99
108
|
targets should already have their own schema landed (the packet's
|
|
100
109
|
dependencies guarantee this); check before writing the FK column.
|
|
101
|
-
2. Build exactly one layer, matching the packet's ALLOWED SCOPE
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
`packages/contracts`),
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
2. Build exactly one layer, matching the packet's ALLOWED SCOPE: run its
|
|
111
|
+
generator, then author this entity's delta. Run typecheck, lint, and
|
|
112
|
+
test yourself as a sanity check before reporting back — necessary, not
|
|
113
|
+
sufficient. If this layer also has to create the package it lands in
|
|
114
|
+
(the first module through `contract` creates `packages/contracts`), the
|
|
115
|
+
shell files its generator lands sit outside the packet's ALLOWED SCOPE
|
|
116
|
+
and `hedgehog verify` will leave them uncommitted — stop and say so
|
|
117
|
+
before building, so the scope can be widened for this one task
|
|
108
118
|
(`hedgehog-loop`, "First arrival in a package"). Don't build against a
|
|
109
|
-
scope you already know won't commit your work.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
scope you already know won't commit your work. If the layer wires a new
|
|
120
|
+
or newly-linked package into the workspace, run `pnpm install` and
|
|
121
|
+
`pnpm nx sync` yourself (`hedgehog-loop`, "Scaffolding a layer") and
|
|
122
|
+
name the shared files that changed (typically `pnpm-lock.yaml`, root
|
|
123
|
+
`tsconfig.json`) in your report — the orchestrating session commits
|
|
124
|
+
them separately, since you report but never commit (next step).
|
|
113
125
|
3. **Report the work as done; do not commit it yourself.** Per the build
|
|
114
126
|
graph's design, an agent reporting success never moves a task — only
|
|
115
127
|
`hedgehog verify <task-id>`'s passing exit code does. It checks your
|
|
116
128
|
changes against the packet's ALLOWED SCOPE, re-runs the real
|
|
117
129
|
verification command, and on a pass writes the commit (the packet's
|
|
118
|
-
exact Conventional Commit message) itself.
|
|
130
|
+
exact Conventional Commit message) itself. Any shared workspace files
|
|
131
|
+
you flagged in step 2 are a separate commit the orchestrating session
|
|
132
|
+
makes before dispatching `hedgehog verify`, not something you commit.
|
|
119
133
|
4. One layer at a time — never start the next layer before
|
|
120
134
|
`hedgehog verify` reports the current one `complete`.
|
|
121
135
|
5. Once `hedgehog verify` reports the `controller` layer (and any bundled
|
|
@@ -32,9 +32,20 @@ before the next starts.
|
|
|
32
32
|
Use `nx-run-tasks` (build/lint/test/typecheck), `nx-workspace` (inspecting
|
|
33
33
|
project/target config), `nx-generate` (scaffolding a new library/app), and
|
|
34
34
|
`link-workspace-packages` (wiring a new package into a consumer) as
|
|
35
|
-
needed.
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
needed.
|
|
36
|
+
|
|
37
|
+
**Both layers you own start from their generator in `tools/generators/`** —
|
|
38
|
+
`hook` lands `packages/hooks`'s shell, `nx.tags`, the query hook set, and
|
|
39
|
+
its barrel wiring; `screen` lands the route, the screen component wired to
|
|
40
|
+
that hook, and its test file. The claimed packet's LAYER SHAPE section
|
|
41
|
+
prints the exact command for the layer you're on; `hedgehog-loop`'s
|
|
42
|
+
"Scaffolding a layer" section owns the full flag contract and the
|
|
43
|
+
workspace wiring a new package needs. Generate first, then author this
|
|
44
|
+
entity's delta on top. The `screen` generator is skeleton-only by
|
|
45
|
+
design — placeholders for the list, filter shell, empty state, and form,
|
|
46
|
+
with layout, information hierarchy, and interaction pattern left to
|
|
47
|
+
`ux-planner`'s rationale and your build, so no two modules' screens come
|
|
48
|
+
out identical before anyone decided they should.
|
|
38
49
|
|
|
39
50
|
If the screen step calls for animation or motion — entrances, sequencing,
|
|
40
51
|
scroll-driven effects, drag, SVG/morph effects — use GSAP, loading the
|
|
@@ -93,18 +104,24 @@ don't reach for a second one.
|
|
|
93
104
|
no such confirmation, stop — you're being asked to build Phase B
|
|
94
105
|
early.
|
|
95
106
|
2. Build the hook against the contract client, matching the packet's
|
|
96
|
-
ALLOWED SCOPE
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
building, so the scope can be
|
|
102
|
-
(`hedgehog-loop`, "First arrival in a
|
|
103
|
-
|
|
107
|
+
ALLOWED SCOPE: run its generator, then author this entity's delta. Run
|
|
108
|
+
typecheck, lint, and test yourself as a sanity check before reporting
|
|
109
|
+
back — necessary, not sufficient. On the first module through this
|
|
110
|
+
layer, the hook also creates `packages/hooks`, and that package's shell
|
|
111
|
+
sits outside the packet's ALLOWED SCOPE — `hedgehog verify` would leave
|
|
112
|
+
it uncommitted. Stop and say so before building, so the scope can be
|
|
113
|
+
widened for this one task (`hedgehog-loop`, "First arrival in a
|
|
114
|
+
package"); run `pnpm install` and `pnpm nx sync` yourself
|
|
115
|
+
(`hedgehog-loop`, "Scaffolding a layer") to wire the new package into
|
|
116
|
+
the workspace, and name the shared files that changed (typically
|
|
117
|
+
`pnpm-lock.yaml`, root `tsconfig.json`) in your report — the
|
|
118
|
+
orchestrating session commits them separately (next step).
|
|
104
119
|
3. **Report the work as done; do not commit it yourself.** Only
|
|
105
120
|
`hedgehog verify <task-id>`'s passing exit code moves the task to
|
|
106
121
|
`complete` and writes the commit (the packet's exact Conventional
|
|
107
|
-
Commit message).
|
|
122
|
+
Commit message). Any shared workspace files you flagged in step 2 are a
|
|
123
|
+
separate commit the orchestrating session makes before dispatching
|
|
124
|
+
`hedgehog verify`, not something you commit.
|
|
108
125
|
4. Build the screen consuming the hook the same way — packet, build,
|
|
109
126
|
report, `hedgehog verify`.
|
|
110
127
|
5. One layer at a time — `hook` fully `complete` before the `screen`
|
package/src/db/next.mjs
CHANGED
|
@@ -241,14 +241,50 @@ export function taskStatusLine(task) {
|
|
|
241
241
|
// — printing it in the packet turns that failure into a pre-flight fact
|
|
242
242
|
// instead of something `nx lint` teaches the agent after the fact. Keyed
|
|
243
243
|
// by layer id, not module, since the shape is the same for every module.
|
|
244
|
+
//
|
|
245
|
+
// `scaffold` is the same kind of fact one step earlier: the generator in
|
|
246
|
+
// `tools/generators/` every layer starts from (hedgehog-loop, "Scaffolding
|
|
247
|
+
// a layer", which owns the flag contract these lines instantiate). It sits
|
|
248
|
+
// here rather than in RELEVANT RULES because that section is per-intent —
|
|
249
|
+
// identical across every layer of an intent, by plan.mjs's requirement
|
|
250
|
+
// linkage — while the command differs per layer, which is exactly what
|
|
251
|
+
// LAYER SHAPE already keys on.
|
|
244
252
|
const FULL_STACK_APP_LAYER_TAGS = {
|
|
245
|
-
schema: {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
253
|
+
schema: {
|
|
254
|
+
tags: ['scope:db', 'type:adapter'],
|
|
255
|
+
dependsOnTags: [],
|
|
256
|
+
scaffold: "nx g ./tools/generators:schema --module={module} --fields='<name:type,...>'",
|
|
257
|
+
},
|
|
258
|
+
contract: {
|
|
259
|
+
tags: ['scope:contracts', 'type:contract'],
|
|
260
|
+
dependsOnTags: ['type:adapter', 'type:util'],
|
|
261
|
+
scaffold: "nx g ./tools/generators:contract --module={module} --fields='<the schema layer's list>'",
|
|
262
|
+
},
|
|
263
|
+
repository: {
|
|
264
|
+
tags: ['scope:{module}', 'type:adapter'],
|
|
265
|
+
dependsOnTags: ['type:adapter', 'type:contract', 'type:util'],
|
|
266
|
+
scaffold: 'nx g ./tools/generators:repository --module={module}',
|
|
267
|
+
},
|
|
268
|
+
service: {
|
|
269
|
+
tags: ['scope:{module}', 'type:service'],
|
|
270
|
+
dependsOnTags: ['type:adapter', 'type:contract', 'type:util'],
|
|
271
|
+
scaffold: 'nx g ./tools/generators:service --module={module}',
|
|
272
|
+
},
|
|
273
|
+
controller: {
|
|
274
|
+
tags: ['scope:api'],
|
|
275
|
+
dependsOnTags: ['type:adapter', 'type:service', 'type:contract', 'type:util'],
|
|
276
|
+
scaffold: "nx g ./tools/generators:controller --module={module} --fields='<the schema layer's list>'",
|
|
277
|
+
},
|
|
278
|
+
hook: {
|
|
279
|
+
tags: ['scope:hooks', 'type:hook'],
|
|
280
|
+
dependsOnTags: ['type:contract', 'type:util'],
|
|
281
|
+
scaffold: 'nx g ./tools/generators:hook --module={module} [--toggleField=<boolField>]',
|
|
282
|
+
},
|
|
283
|
+
screen: {
|
|
284
|
+
tags: ['scope:web'],
|
|
285
|
+
dependsOnTags: ['scope:contracts', 'scope:hooks', 'scope:shared', 'type:util'],
|
|
286
|
+
scaffold: 'nx g ./tools/generators:screen --module={module} (skeleton only — UX is yours)',
|
|
287
|
+
},
|
|
252
288
|
};
|
|
253
289
|
|
|
254
290
|
// A LAYER SHAPE section for full-stack-app tasks only (`coreId ===
|
|
@@ -267,10 +303,12 @@ function layerShapeLines(task, coreId) {
|
|
|
267
303
|
} else {
|
|
268
304
|
lines.push(` may depend on tags: ${shape.dependsOnTags.join(', ')}`);
|
|
269
305
|
}
|
|
306
|
+
lines.push(` scaffold from: ${shape.scaffold.replace('{module}', task.module)}`);
|
|
270
307
|
lines.push(
|
|
271
308
|
" Confirm against packages/config/eslint-base.js's depConstraints before",
|
|
272
309
|
" writing an import — a mismatch here is a pre-flight fact, not a lint",
|
|
273
|
-
' failure to discover later.',
|
|
310
|
+
' failure to discover later. Start from the generator and author only',
|
|
311
|
+
" this entity's delta on top of what it lands.",
|
|
274
312
|
);
|
|
275
313
|
return lines;
|
|
276
314
|
}
|
|
@@ -4,8 +4,25 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"nx": {
|
|
6
6
|
"targets": {
|
|
7
|
+
"generate-feature-modules": {
|
|
8
|
+
"executor": "nx:run-commands",
|
|
9
|
+
"cache": true,
|
|
10
|
+
"inputs": [
|
|
11
|
+
"{projectRoot}/src/app/*/*.module.ts"
|
|
12
|
+
],
|
|
13
|
+
"outputs": [
|
|
14
|
+
"{projectRoot}/src/app/feature-modules.ts"
|
|
15
|
+
],
|
|
16
|
+
"options": {
|
|
17
|
+
"command": "node tools/generate-feature-modules.cjs",
|
|
18
|
+
"cwd": "{workspaceRoot}"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
7
21
|
"build": {
|
|
8
22
|
"executor": "nx:run-commands",
|
|
23
|
+
"dependsOn": [
|
|
24
|
+
"generate-feature-modules"
|
|
25
|
+
],
|
|
9
26
|
"options": {
|
|
10
27
|
"command": "webpack-cli build",
|
|
11
28
|
"env": {
|
|
@@ -91,7 +108,8 @@
|
|
|
91
108
|
"tslib": "^2.3.0",
|
|
92
109
|
"nestjs-pino": "^4.6.1",
|
|
93
110
|
"pino-http": "^11.0.0",
|
|
94
|
-
"config": "workspace:*"
|
|
111
|
+
"config": "workspace:*",
|
|
112
|
+
"db": "workspace:*"
|
|
95
113
|
},
|
|
96
114
|
"devDependencies": {
|
|
97
115
|
"@nestjs/testing": "^11.0.0"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Test } from '@nestjs/testing';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { AppModule } from './app.module';
|
|
4
|
+
|
|
5
|
+
describe('AppModule', () => {
|
|
6
|
+
it('compiles with the health check controller wired', async () => {
|
|
7
|
+
const moduleRef = await Test.createTestingModule({
|
|
8
|
+
imports: [AppModule],
|
|
9
|
+
}).compile();
|
|
10
|
+
|
|
11
|
+
expect(moduleRef).toBeDefined();
|
|
12
|
+
|
|
13
|
+
await moduleRef.close();
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { Module } from '@nestjs/common';
|
|
2
2
|
import { LoggerModule } from 'nestjs-pino';
|
|
3
3
|
import { HealthController } from './health.controller';
|
|
4
|
+
// Generated by tools/generate-feature-modules.cjs (see that file's header)
|
|
5
|
+
// from every apps/api/src/app/*/*.module.ts on disk — never hand-edited,
|
|
6
|
+
// so no domain module's controller task ever edits this file or
|
|
7
|
+
// app.module.ts itself to register.
|
|
8
|
+
import { featureModules } from './feature-modules';
|
|
4
9
|
|
|
5
10
|
@Module({
|
|
6
|
-
imports: [LoggerModule.forRoot()],
|
|
11
|
+
imports: [LoggerModule.forRoot(), ...featureModules],
|
|
7
12
|
controllers: [HealthController],
|
|
8
13
|
providers: [],
|
|
9
14
|
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// GENERATED FILE — do not hand-edit. Regenerated by
|
|
2
|
+
// tools/generate-feature-modules.cjs (the generate-feature-modules Nx
|
|
3
|
+
// target, which test and build depend on) from every
|
|
4
|
+
// apps/api/src/app/*/*.module.ts file on disk. A module directory's
|
|
5
|
+
// controller layer only ever creates its own *.module.ts inside its own
|
|
6
|
+
// directory — never edits this file.
|
|
7
|
+
|
|
8
|
+
export const featureModules = [];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./out-tsc/vitest",
|
|
5
|
+
"types": [
|
|
6
|
+
"vitest/globals",
|
|
7
|
+
"vitest/importMeta",
|
|
8
|
+
"vite/client",
|
|
9
|
+
"node",
|
|
10
|
+
"vitest"
|
|
11
|
+
],
|
|
12
|
+
"experimentalDecorators": true,
|
|
13
|
+
"emitDecoratorMetadata": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"vite.config.ts",
|
|
18
|
+
"vite.config.mts",
|
|
19
|
+
"vitest.config.ts",
|
|
20
|
+
"vitest.config.mts",
|
|
21
|
+
"src/**/*.test.ts",
|
|
22
|
+
"src/**/*.spec.ts",
|
|
23
|
+
"src/**/*.test.tsx",
|
|
24
|
+
"src/**/*.spec.tsx",
|
|
25
|
+
"src/**/*.test.js",
|
|
26
|
+
"src/**/*.spec.js",
|
|
27
|
+
"src/**/*.test.jsx",
|
|
28
|
+
"src/**/*.spec.jsx",
|
|
29
|
+
"src/**/*.d.ts"
|
|
30
|
+
],
|
|
31
|
+
"references": [
|
|
32
|
+
{
|
|
33
|
+
"path": "./tsconfig.app.json"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig(() => ({
|
|
4
|
+
root: __dirname,
|
|
5
|
+
cacheDir: '../../node_modules/.vite/apps/api',
|
|
6
|
+
test: {
|
|
7
|
+
name: 'api',
|
|
8
|
+
watch: false,
|
|
9
|
+
globals: true,
|
|
10
|
+
environment: 'node',
|
|
11
|
+
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
12
|
+
reporters: ['default'],
|
|
13
|
+
coverage: {
|
|
14
|
+
reportsDirectory: './test-output/vitest/coverage',
|
|
15
|
+
provider: 'v8' as const,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { ThemeToggle } from './theme-toggle';
|
|
5
|
+
|
|
6
|
+
describe('ThemeToggle', () => {
|
|
7
|
+
it('renders a toggle button and flips the document theme class on click', async () => {
|
|
8
|
+
const user = userEvent.setup();
|
|
9
|
+
render(<ThemeToggle />);
|
|
10
|
+
|
|
11
|
+
const toggle = screen.getByRole('button', { name: /toggle theme/i });
|
|
12
|
+
expect(toggle).toBeInTheDocument();
|
|
13
|
+
|
|
14
|
+
await user.click(toggle);
|
|
15
|
+
expect(document.documentElement.classList.contains('dark')).toBe(true);
|
|
16
|
+
|
|
17
|
+
await user.click(toggle);
|
|
18
|
+
expect(document.documentElement.classList.contains('dark')).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/vitest';
|
|
@@ -41,8 +41,11 @@
|
|
|
41
41
|
"node_modules",
|
|
42
42
|
"jest.config.ts",
|
|
43
43
|
"jest.config.cts",
|
|
44
|
+
"src/test-setup.ts",
|
|
44
45
|
"src/**/*.spec.ts",
|
|
45
46
|
"src/**/*.test.ts",
|
|
47
|
+
"src/**/*.spec.tsx",
|
|
48
|
+
"src/**/*.test.tsx",
|
|
46
49
|
".next",
|
|
47
50
|
"eslint.config.js",
|
|
48
51
|
"eslint.config.cjs",
|
|
@@ -51,6 +54,9 @@
|
|
|
51
54
|
"references": [
|
|
52
55
|
{
|
|
53
56
|
"path": "../../packages/config"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"path": "./tsconfig.spec.json"
|
|
54
60
|
}
|
|
55
61
|
]
|
|
56
62
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./out-tsc/vitest",
|
|
5
|
+
"jsx": "preserve",
|
|
6
|
+
"types": [
|
|
7
|
+
"vitest/globals",
|
|
8
|
+
"vitest/importMeta",
|
|
9
|
+
"vite/client",
|
|
10
|
+
"node",
|
|
11
|
+
"vitest",
|
|
12
|
+
"@testing-library/jest-dom"
|
|
13
|
+
],
|
|
14
|
+
"forceConsistentCasingInFileNames": true
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"vite.config.ts",
|
|
18
|
+
"vite.config.mts",
|
|
19
|
+
"vitest.config.ts",
|
|
20
|
+
"vitest.config.mts",
|
|
21
|
+
"src/test-setup.ts",
|
|
22
|
+
"src/**/*.test.ts",
|
|
23
|
+
"src/**/*.spec.ts",
|
|
24
|
+
"src/**/*.test.tsx",
|
|
25
|
+
"src/**/*.spec.tsx",
|
|
26
|
+
"src/**/*.test.js",
|
|
27
|
+
"src/**/*.spec.js",
|
|
28
|
+
"src/**/*.test.jsx",
|
|
29
|
+
"src/**/*.spec.jsx",
|
|
30
|
+
"src/**/*.d.ts"
|
|
31
|
+
],
|
|
32
|
+
"references": [
|
|
33
|
+
{
|
|
34
|
+
"path": "../../packages/config"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
export default defineConfig(() => ({
|
|
6
|
+
root: __dirname,
|
|
7
|
+
cacheDir: '../../node_modules/.vite/apps/web',
|
|
8
|
+
plugins: [react()],
|
|
9
|
+
resolve: {
|
|
10
|
+
alias: {
|
|
11
|
+
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
test: {
|
|
15
|
+
name: 'web',
|
|
16
|
+
watch: false,
|
|
17
|
+
globals: true,
|
|
18
|
+
environment: 'jsdom',
|
|
19
|
+
setupFiles: ['./src/test-setup.ts'],
|
|
20
|
+
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
21
|
+
reporters: ['default'],
|
|
22
|
+
coverage: {
|
|
23
|
+
reportsDirectory: './test-output/vitest/coverage',
|
|
24
|
+
provider: 'v8' as const,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
@@ -29,12 +29,16 @@
|
|
|
29
29
|
"@swc/cli": "~0.8.1",
|
|
30
30
|
"@swc/core": "~1.15.5",
|
|
31
31
|
"@swc/helpers": "~0.5.18",
|
|
32
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
33
|
+
"@testing-library/react": "^16.3.2",
|
|
34
|
+
"@testing-library/user-event": "^14.6.4",
|
|
32
35
|
"@types/node": "^22.0.0",
|
|
33
36
|
"@types/pg": "^8.20.0",
|
|
34
37
|
"@types/react": "^19.0.0",
|
|
35
38
|
"@types/react-dom": "^19.0.0",
|
|
36
39
|
"@typescript-eslint/eslint-plugin": "^8.65.0",
|
|
37
40
|
"@typescript-eslint/parser": "^8.65.0",
|
|
41
|
+
"@vitejs/plugin-react": "^6.0.5",
|
|
38
42
|
"@vitest/coverage-v8": "~4.1.0",
|
|
39
43
|
"drizzle-kit": "^0.31.10",
|
|
40
44
|
"drizzle-zod": "^0.8.3",
|
|
@@ -45,6 +49,7 @@
|
|
|
45
49
|
"eslint-plugin-playwright": "^1.6.2",
|
|
46
50
|
"eslint-plugin-react": "^7.35.0",
|
|
47
51
|
"eslint-plugin-react-hooks": "5.0.0",
|
|
52
|
+
"jsdom": "^30.0.1",
|
|
48
53
|
"lefthook": "^2.1.10",
|
|
49
54
|
"nx": "23.1.0",
|
|
50
55
|
"prettier": "~3.6.2",
|
|
@@ -74,6 +79,9 @@
|
|
|
74
79
|
"zod": "^4.4.3"
|
|
75
80
|
},
|
|
76
81
|
"pnpm": {
|
|
82
|
+
"onlyBuiltDependencies": [
|
|
83
|
+
"lefthook"
|
|
84
|
+
],
|
|
77
85
|
"overrides": {
|
|
78
86
|
"esbuild": "0.25.12",
|
|
79
87
|
"axios": "^1.18.0",
|