@skyf0xx/hedgehog-core-full-stack-app 1.0.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/CLAUDE.core.md +147 -0
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/agents/backend-eng.md +189 -0
- package/agents/front-end-eng.md +176 -0
- package/agents/ux-planner.md +174 -0
- package/hedgehog-core.yaml +20 -0
- package/package.json +41 -0
- package/skills/hedgehog-bootstrap/SKILL.md +324 -0
- package/skills/hedgehog-bootstrap-full-stack-app-core/SKILL.md +411 -0
- package/skills/hedgehog-loop/SKILL.md +643 -0
- package/skills/link-workspace-packages/SKILL.md +41 -0
- package/skills/nx-generate/SKILL.md +165 -0
- package/skills/nx-run-tasks/SKILL.md +52 -0
- package/skills/nx-workspace/SKILL.md +261 -0
- package/skills/nx-workspace/references/AFFECTED.md +27 -0
- package/vendor-skills/GSAP/ATTRIBUTION.md +30 -0
- package/vendor-skills/GSAP/LICENSE +21 -0
- package/vendor-skills/GSAP/gsap-core/SKILL.md +254 -0
- package/vendor-skills/GSAP/gsap-frameworks/SKILL.md +266 -0
- package/vendor-skills/GSAP/gsap-performance/SKILL.md +79 -0
- package/vendor-skills/GSAP/gsap-plugins/SKILL.md +433 -0
- package/vendor-skills/GSAP/gsap-react/SKILL.md +136 -0
- package/vendor-skills/GSAP/gsap-scrolltrigger/SKILL.md +296 -0
- package/vendor-skills/GSAP/gsap-timeline/SKILL.md +107 -0
- package/vendor-skills/GSAP/gsap-utils/SKILL.md +284 -0
- package/vendor-skills/GSAP/llms.txt +39 -0
- package/workspace/.env.example +5 -0
- package/workspace/.github/workflows/phase-gate.yml +17 -0
- package/workspace/.prettierignore +5 -0
- package/workspace/.prettierrc +3 -0
- package/workspace/.vscode/extensions.json +3 -0
- package/workspace/apps/api/eslint.config.mjs +3 -0
- package/workspace/apps/api/package.json +117 -0
- package/workspace/apps/api/src/app/app.module.spec.ts +15 -0
- package/workspace/apps/api/src/app/app.module.ts +15 -0
- package/workspace/apps/api/src/app/feature-modules.ts +8 -0
- package/workspace/apps/api/src/app/health.controller.ts +9 -0
- package/workspace/apps/api/src/assets/.gitkeep +0 -0
- package/workspace/apps/api/src/main.ts +32 -0
- package/workspace/apps/api/tsconfig.app.json +22 -0
- package/workspace/apps/api/tsconfig.json +13 -0
- package/workspace/apps/api/tsconfig.spec.json +36 -0
- package/workspace/apps/api/vitest.config.mts +18 -0
- package/workspace/apps/api/webpack.config.cjs +25 -0
- package/workspace/apps/api-e2e/eslint.config.mjs +3 -0
- package/workspace/apps/api-e2e/package.json +28 -0
- package/workspace/apps/api-e2e/src/api/api.spec.ts +11 -0
- package/workspace/apps/api-e2e/src/support/global-setup.ts +17 -0
- package/workspace/apps/api-e2e/src/support/test-setup.ts +6 -0
- package/workspace/apps/api-e2e/tsconfig.json +10 -0
- package/workspace/apps/api-e2e/tsconfig.spec.json +28 -0
- package/workspace/apps/api-e2e/vitest.config.mts +20 -0
- package/workspace/apps/web/.env.example +12 -0
- package/workspace/apps/web/.prettierrc.js +10 -0
- package/workspace/apps/web/.swcrc +30 -0
- package/workspace/apps/web/components.json +21 -0
- package/workspace/apps/web/eslint.config.mjs +12 -0
- package/workspace/apps/web/index.d.ts +7 -0
- package/workspace/apps/web/next-env.d.ts +7 -0
- package/workspace/apps/web/next.config.js +9 -0
- package/workspace/apps/web/package.json +38 -0
- package/workspace/apps/web/postcss.config.mjs +7 -0
- package/workspace/apps/web/public/.gitkeep +0 -0
- package/workspace/apps/web/public/favicon.ico +0 -0
- package/workspace/apps/web/src/app/global.css +83 -0
- package/workspace/apps/web/src/app/layout.tsx +34 -0
- package/workspace/apps/web/src/app/module-routes.ts +13 -0
- package/workspace/apps/web/src/app/page.tsx +34 -0
- package/workspace/apps/web/src/app/providers.tsx +12 -0
- package/workspace/apps/web/src/components/theme-toggle.spec.tsx +20 -0
- package/workspace/apps/web/src/components/theme-toggle.tsx +32 -0
- package/workspace/apps/web/src/components/ui/button.tsx +60 -0
- package/workspace/apps/web/src/lib/utils.ts +6 -0
- package/workspace/apps/web/src/test-setup.ts +1 -0
- package/workspace/apps/web/tsconfig.json +62 -0
- package/workspace/apps/web/tsconfig.spec.json +37 -0
- package/workspace/apps/web/vitest.config.mts +27 -0
- package/workspace/apps/web-e2e/eslint.config.mjs +12 -0
- package/workspace/apps/web-e2e/package.json +10 -0
- package/workspace/apps/web-e2e/playwright.config.mts +75 -0
- package/workspace/apps/web-e2e/src/example.spec.ts +7 -0
- package/workspace/apps/web-e2e/tsconfig.json +26 -0
- package/workspace/commitlint.config.cjs +3 -0
- package/workspace/core.yaml +68 -0
- package/workspace/docker-compose.yml +14 -0
- package/workspace/eslint.config.mjs +15 -0
- package/workspace/gitignore.template +24 -0
- package/workspace/lefthook.yml +52 -0
- package/workspace/nx.json +90 -0
- package/workspace/package.json +99 -0
- package/workspace/packages/config/eslint-base.js +205 -0
- package/workspace/packages/config/package.json +28 -0
- package/workspace/packages/config/prettier.js +13 -0
- package/workspace/packages/config/src/env.schema.spec.ts +38 -0
- package/workspace/packages/config/src/env.schema.ts +18 -0
- package/workspace/packages/config/src/index.ts +1 -0
- package/workspace/packages/config/tsconfig.json +13 -0
- package/workspace/packages/config/tsconfig.lib.json +27 -0
- package/workspace/packages/config/tsconfig.spec.json +34 -0
- package/workspace/packages/config/vitest.config.mts +18 -0
- package/workspace/packages/db/package.json +27 -0
- package/workspace/packages/db/src/index.ts +2 -0
- package/workspace/packages/db/src/lib/db.spec.ts +48 -0
- package/workspace/packages/db/src/lib/db.ts +54 -0
- package/workspace/packages/db/src/schema/index.ts +4 -0
- package/workspace/packages/db/tsconfig.json +13 -0
- package/workspace/packages/db/tsconfig.lib.json +27 -0
- package/workspace/packages/db/tsconfig.spec.json +34 -0
- package/workspace/packages/db/vitest.config.mts +18 -0
- package/workspace/pnpm-lock.yaml +23719 -0
- package/workspace/pnpm-workspace.yaml +7 -0
- package/workspace/tools/generate-feature-modules.cjs +104 -0
- package/workspace/tools/generate-module-routes.cjs +89 -0
- package/workspace/tools/generators/contract/generator.ts +336 -0
- package/workspace/tools/generators/contract/schema.json +24 -0
- package/workspace/tools/generators/controller/generator.ts +390 -0
- package/workspace/tools/generators/controller/schema.json +24 -0
- package/workspace/tools/generators/fields.ts +160 -0
- package/workspace/tools/generators/generators.json +42 -0
- package/workspace/tools/generators/hook/generator.ts +291 -0
- package/workspace/tools/generators/hook/schema.json +19 -0
- package/workspace/tools/generators/lib-shell.ts +124 -0
- package/workspace/tools/generators/naming.ts +84 -0
- package/workspace/tools/generators/package.json +6 -0
- package/workspace/tools/generators/repository/generator.ts +298 -0
- package/workspace/tools/generators/repository/schema.json +15 -0
- package/workspace/tools/generators/schema/generator.ts +169 -0
- package/workspace/tools/generators/schema/schema.json +20 -0
- package/workspace/tools/generators/screen/generator.ts +218 -0
- package/workspace/tools/generators/screen/schema.json +15 -0
- package/workspace/tools/generators/service/generator.ts +270 -0
- package/workspace/tools/generators/service/schema.json +19 -0
- package/workspace/tools/phase-gate.cjs +77 -0
- package/workspace/tsconfig.base.json +21 -0
- package/workspace/tsconfig.json +25 -0
- package/workspace/vitest.workspace.ts +4 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hedgehog-bootstrap-full-stack-app-core
|
|
3
|
+
description: Use once, at the start of a new Hedgehog project on the full-stack-app core, before hedgehog-bootstrap's add-on steps, to land the full-stack-app core workspace (Nx, packages/config, packages/db, apps/api, apps/web, and every enforcement file) and verify it's green. Runs as the first move of the `bootstrap` agent, which `planner` invokes automatically after Confirm & Lock. No per-project decisions, no add-on awareness.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Hedgehog Bootstrap — full-stack-app Core
|
|
7
|
+
|
|
8
|
+
Lands the always-on core of a Hedgehog project — the same four pieces on
|
|
9
|
+
every project, regardless of size or which add-ons are on — by copying a
|
|
10
|
+
pre-built, pre-verified workspace (this package's `workspace/`) rather
|
|
11
|
+
than generating it live: Nx workspace + enforcement
|
|
12
|
+
config, `packages/db`, `apps/api`, `apps/web`. These pieces are
|
|
13
|
+
deterministic — the same commands produce the same output on every
|
|
14
|
+
project — so the output is committed once, upstream, and copied here
|
|
15
|
+
instead of re-derived by an agent on every run. `hedgehog-bootstrap`
|
|
16
|
+
calls this skill first, unconditionally, then continues with its own
|
|
17
|
+
add-on steps (Auth, Queue, Mobile) — those genuinely vary per project
|
|
18
|
+
and stay live.
|
|
19
|
+
|
|
20
|
+
This skill has no per-project decisions to make: no Add-ons dependency,
|
|
21
|
+
no Add-ons check, nothing to ask. Core is identical on every Hedgehog
|
|
22
|
+
project.
|
|
23
|
+
|
|
24
|
+
## What lands
|
|
25
|
+
|
|
26
|
+
Everything under this package's `workspace/`, copied to the repo root:
|
|
27
|
+
|
|
28
|
+
- Root: `nx.json`, `pnpm-workspace.yaml`, `package.json` (with the
|
|
29
|
+
`pnpm.overrides.esbuild` pin, `packageManager` field, and a `dev`
|
|
30
|
+
script — `docker compose up -d && nx run-many --target=serve,dev
|
|
31
|
+
--projects=api,web --parallel` — so a fresh clone has one command that
|
|
32
|
+
brings up Postgres and both apps together), `eslint.config.mjs`,
|
|
33
|
+
`docker-compose.yml` (Postgres only — Redis joins later, only if the
|
|
34
|
+
Queue add-on turns on), `.env.example`
|
|
35
|
+
(`DATABASE_URL`/`NODE_ENV`/`WEB_ORIGIN`, copied to `.env` in step 4),
|
|
36
|
+
`lefthook.yml`, `commitlint.config.cjs`,
|
|
37
|
+
`tools/phase-gate.cjs`, `.github/workflows/phase-gate.yml`,
|
|
38
|
+
`tsconfig.base.json`, `pnpm-lock.yaml`, and `core.yaml` — the shipped
|
|
39
|
+
layer sequence `hedgehog plan`/`verify`/`next` read for this project.
|
|
40
|
+
This root `core.yaml` is a different file from `.hedgehog/core.yaml`,
|
|
41
|
+
which only exists on an authored core (see `hedgehog-core-design`) —
|
|
42
|
+
the two never coexist on the same project.
|
|
43
|
+
- `packages/config/` — `eslint-base.js`, `prettier.js` (no
|
|
44
|
+
`prettier-plugin-tailwindcss` — that's `apps/web`'s own config, already
|
|
45
|
+
wired), `env.schema.ts` (core fields only: `DATABASE_URL`, `NODE_ENV`,
|
|
46
|
+
and `WEB_ORIGIN` — the origin `apps/api` enables CORS for, defaulted to
|
|
47
|
+
`apps/web`'s dev origin). Tagged `type:util`.
|
|
48
|
+
- `packages/db/` — Drizzle client/connection wired to `loadEnv()`, no
|
|
49
|
+
domain schema. `src/index.ts` re-exports `src/schema/index.ts`, an
|
|
50
|
+
empty barrel-of-barrels a module's `schema` layer adds one re-export
|
|
51
|
+
line to (in that layer's scope — see `core.yaml`). Tagged `scope:db`,
|
|
52
|
+
`type:adapter`.
|
|
53
|
+
- `apps/api/` — Nest shell, `nestjs-pino` wired, CORS enabled for
|
|
54
|
+
`WEB_ORIGIN`, health check only, no domain controllers. Vitest wired
|
|
55
|
+
(`vitest.config.mts`, `tsconfig.spec.json` with
|
|
56
|
+
`experimentalDecorators`/`emitDecoratorMetadata` for `Test.createTestingModule`),
|
|
57
|
+
with a smoke test (`app.module.spec.ts`) instantiating `AppModule`.
|
|
58
|
+
`apps/api/src/app/feature-modules.ts` is a generated barrel — see
|
|
59
|
+
**The controller barrel** below — that `AppModule` imports and spreads
|
|
60
|
+
into its `imports`, so no domain module's controller layer ever edits
|
|
61
|
+
`app.module.ts`. Depends on `packages/db` (the controller layer imports
|
|
62
|
+
it by construction). `apps/api-e2e` already converted to Vitest with an
|
|
63
|
+
explicit `e2e` target. Tagged `scope:api`.
|
|
64
|
+
- `apps/web/` — `.env.example` (`NEXT_PUBLIC_API_BASE_URL`, copied to
|
|
65
|
+
`apps/web/.env.local` in step 4 — Next loads env files from the app
|
|
66
|
+
directory, so the root `.env` never reaches it; the value carries
|
|
67
|
+
`apps/api`'s `/api` global prefix), Next shell, Tailwind v4 (PostCSS
|
|
68
|
+
plugin, no `tailwind.config.js`), hand-built ShadCN base (`components.json`,
|
|
69
|
+
`cn()` util, CSS variable theme, light/dark toggle via an inline
|
|
70
|
+
pre-hydration script + a client-side `ThemeToggle`), TanStack Query
|
|
71
|
+
provider at the root layout, `prettier-plugin-tailwindcss` scoped to
|
|
72
|
+
its own `.prettierrc.js`. Vitest wired for jsdom (`vitest.config.mts`
|
|
73
|
+
with the `@vitejs/plugin-react` plugin and the `@/*` -> `./src/*` alias
|
|
74
|
+
apps/web/tsconfig.json already declares, `tsconfig.spec.json`,
|
|
75
|
+
`src/test-setup.ts` loading `@testing-library/jest-dom`'s matchers),
|
|
76
|
+
with a smoke test (`theme-toggle.spec.tsx`) rendering and clicking
|
|
77
|
+
`ThemeToggle` via Testing Library. Tagged `scope:web`. `apps/web-e2e`
|
|
78
|
+
(Playwright, scaffolded automatically by `@nx/next:app`) gets its own
|
|
79
|
+
`e2e` target by default — no rename needed, unlike `apps/api-e2e`.
|
|
80
|
+
- The full `@nx/enforce-module-boundaries` `depConstraints` list for
|
|
81
|
+
exactly these tags, matching the project shape `core.yaml`'s layer
|
|
82
|
+
sequence actually produces — plus the `no-restricted-imports` rules that
|
|
83
|
+
carry port discipline, which a tag constraint can't express because a
|
|
84
|
+
module's port interface and its Drizzle adapter share one lib
|
|
85
|
+
(`libs/<module>/repository`). `enforceBuildableLibDependency` is off:
|
|
86
|
+
`packages/contracts` and `packages/hooks` are source-only by design, and
|
|
87
|
+
it fails `apps/web` for importing either. `scope:auth`/`scope:worker`/
|
|
88
|
+
`scope:mobile` entries are added live by the matching add-on step in
|
|
89
|
+
`hedgehog-bootstrap`, only if that add-on turns on.
|
|
90
|
+
- The `@nx/js/typescript` plugin registration (`typecheck` target) with
|
|
91
|
+
`composite`/`declaration` already set on every `tsconfig.lib.json` /
|
|
92
|
+
`tsconfig.app.json` / `tsconfig.spec.json` this core touches.
|
|
93
|
+
|
|
94
|
+
`node_modules` is not part of the copy — `pnpm install` regenerates it
|
|
95
|
+
from the committed `pnpm-lock.yaml`, which is a fast resolve against a
|
|
96
|
+
locked graph, not a fresh solve.
|
|
97
|
+
|
|
98
|
+
## The controller barrel
|
|
99
|
+
|
|
100
|
+
`apps/api/src/app/app.module.ts` never takes a per-module edit.
|
|
101
|
+
`core.yaml`'s controller layer scope is `apps/api/src/app/{module}/**` —
|
|
102
|
+
module-disjoint by construction, so two modules' controller tasks never
|
|
103
|
+
touch the same file — but `app.module.ts` itself sits outside every
|
|
104
|
+
module's scope, and `src/db/core.mjs`'s `validateCore` rejects a
|
|
105
|
+
non-exclusive layer whose scope omits `{module}` on a module-axis core,
|
|
106
|
+
so the controller layer's scope can't be widened to include it either. A
|
|
107
|
+
file two concurrent module builds both hand-edited would also be
|
|
108
|
+
invisible to the scheduler's conflict check (`src/db/conflict.mjs` only
|
|
109
|
+
compares each task's own declared scope globs), so even a single shared
|
|
110
|
+
line to append to would race undetected.
|
|
111
|
+
|
|
112
|
+
`apps/api/src/app/feature-modules.ts` solves this by never being
|
|
113
|
+
hand-edited at all: `tools/generate-feature-modules.cjs` globs
|
|
114
|
+
`apps/api/src/app/*/*.module.ts` and writes it as a generated barrel — a
|
|
115
|
+
literal `import { XModule } from './x/x.module'` per domain module found,
|
|
116
|
+
plus an exported `featureModules` array — every time the
|
|
117
|
+
`generate-feature-modules` Nx target runs. `AppModule` imports that one
|
|
118
|
+
generated file and spreads `featureModules` into its own `imports`. A
|
|
119
|
+
module's controller layer only ever creates its own `{module}.module.ts`
|
|
120
|
+
inside its own `apps/api/src/app/{module}/` directory — always in scope,
|
|
121
|
+
never colliding with any other module's controller task. In the shipped
|
|
122
|
+
core, no domain module exists yet, so the glob finds nothing and
|
|
123
|
+
`feature-modules.ts` exports an empty array.
|
|
124
|
+
|
|
125
|
+
`generate-feature-modules` is wired as an explicit Nx target on `apps/api`
|
|
126
|
+
(`apps/api/package.json`'s `nx.targets`), cached, with `build` declaring
|
|
127
|
+
it as a `dependsOn`; `test` and `typecheck` pick it up the same way
|
|
128
|
+
through `nx.json`'s `targetDefaults` (a project with no matching target,
|
|
129
|
+
such as `db` or `web`, silently skips a missing `dependsOn` entry rather
|
|
130
|
+
than failing). Static imports rather than a runtime directory scan:
|
|
131
|
+
`apps/api` builds through `NxAppWebpackPlugin`, which bundles by
|
|
132
|
+
statically walking `main.ts`'s import graph — a file never reached by a
|
|
133
|
+
static `import`/`require` is dropped from the bundle entirely, so a
|
|
134
|
+
runtime `fs.readdirSync` scan for sibling files would find nothing in the
|
|
135
|
+
built output even though the same scan works when Vitest runs the same
|
|
136
|
+
source directly. Generating literal imports keeps both paths identical.
|
|
137
|
+
|
|
138
|
+
## The route index
|
|
139
|
+
|
|
140
|
+
`apps/web/src/app/page.tsx` is the same category of file on the web side,
|
|
141
|
+
and gets the same answer. The screen layer's scope is
|
|
142
|
+
`apps/web/src/app/{module}/**`, module-disjoint and — by the same
|
|
143
|
+
`validateCore` rule — unable to be widened to reach one level up, so the
|
|
144
|
+
root page is outside every layer's scope. Left to itself it can never
|
|
145
|
+
learn about the modules built beneath it, and a finished build ships a
|
|
146
|
+
landing page with no route into the app it just built.
|
|
147
|
+
|
|
148
|
+
`tools/generate-module-routes.cjs` globs `apps/web/src/app/*/page.tsx`
|
|
149
|
+
and writes `apps/web/src/app/module-routes.ts` — an exported
|
|
150
|
+
`moduleRoutes` array of `{ href, label }`, one per route directory found,
|
|
151
|
+
skipping Next.js route groups (`(group)`) and `_`/`.`-prefixed
|
|
152
|
+
directories, which are not routes. The root page imports that one
|
|
153
|
+
generated file and renders a link per entry, or a short "no modules yet"
|
|
154
|
+
line when the array is empty, which is what the shipped core has. A
|
|
155
|
+
module's screen layer only ever creates its own `page.tsx` inside its own
|
|
156
|
+
`apps/web/src/app/{module}/` directory — always in scope — and nothing
|
|
157
|
+
ever hand-edits the root page or the generated file.
|
|
158
|
+
|
|
159
|
+
`generate-module-routes` is wired as an explicit Nx target on `apps/web`
|
|
160
|
+
(`apps/web/package.json`'s `nx.targets`), cached, with `build`, `dev`,
|
|
161
|
+
`test` and `typecheck` picking it up through `nx.json`'s `targetDefaults`
|
|
162
|
+
the same way its API sibling does.
|
|
163
|
+
|
|
164
|
+
## Steps
|
|
165
|
+
|
|
166
|
+
### 1. Confirm this hasn't already run
|
|
167
|
+
|
|
168
|
+
Check for `nx.json` at the repo root, or a prior
|
|
169
|
+
`feat(config): workspace + shared config` commit
|
|
170
|
+
(`git log --grep="^feat(config): workspace"`). Either means core already
|
|
171
|
+
landed — stop, don't re-copy. If something about the landed core seems
|
|
172
|
+
wrong, that's a Correction Protocol case (`hedgehog-loop`), not a
|
|
173
|
+
re-copy: patch the specific file at its source.
|
|
174
|
+
|
|
175
|
+
### 2. Confirm Docker is available
|
|
176
|
+
|
|
177
|
+
`docker --version`. Core's `docker-compose.yml` (Postgres) needs it
|
|
178
|
+
immediately after landing, on every host OS — macOS, Windows, Linux
|
|
179
|
+
alike. No Docker: stop and point to installing Docker Desktop
|
|
180
|
+
(macOS/Windows) or Docker Engine (Linux) rather than falling back to a
|
|
181
|
+
natively-installed Postgres. See **Local infra: Docker, always** below.
|
|
182
|
+
|
|
183
|
+
### 3. Land this package's `workspace/`
|
|
184
|
+
|
|
185
|
+
`hedgehog init --ts-full-stack-app` copies this package's `workspace/`
|
|
186
|
+
to the repo root at install time, the same way it copies the engine's
|
|
187
|
+
`src/agents` to this host's own agents directory — check whether the
|
|
188
|
+
core files are already present (same check as step 1) before copying
|
|
189
|
+
again. On a project that ran plain `init` (no core flag) and only
|
|
190
|
+
reaches `full-stack-app` because `planner` picked it at Phase 0, this
|
|
191
|
+
hasn't happened yet: copy `workspace/`'s contents to the repo root now.
|
|
192
|
+
Also merge this core's `CLAUDE.md` section into root `CLAUDE.md`
|
|
193
|
+
(this package's `CLAUDE.core.md` fills the shell's `{{CORE_SECTION}}`
|
|
194
|
+
placeholder left unfilled by a deferred install) — skip this if the
|
|
195
|
+
section is already filled. Either way, by the end of this step every
|
|
196
|
+
file listed in "What lands" above should be on disk.
|
|
197
|
+
|
|
198
|
+
### 4. Install and start local infra
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
pnpm install
|
|
202
|
+
cp .env.example .env
|
|
203
|
+
cp apps/web/.env.example apps/web/.env.local
|
|
204
|
+
docker compose up -d
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Two env files, because the two apps load env from different places. The
|
|
208
|
+
root `.env` is `apps/api`'s: gitignored and never shipped, with
|
|
209
|
+
`.env.example` as the committed template (`DATABASE_URL` matching
|
|
210
|
+
`docker-compose.yml`'s Postgres credentials, `NODE_ENV=development`,
|
|
211
|
+
`WEB_ORIGIN`). Skipping that copy means `packages/config`'s `loadEnv()`
|
|
212
|
+
fails its Zod check the moment `apps/api` boots (`DATABASE_URL` missing) —
|
|
213
|
+
a confusing crash to debug live instead of one line here.
|
|
214
|
+
|
|
215
|
+
`apps/web/.env.local` is Next's, loaded from the app directory rather than
|
|
216
|
+
the workspace root, so nothing in the root file reaches the browser.
|
|
217
|
+
Skipping that copy is worse than a crash: `NEXT_PUBLIC_API_BASE_URL` comes
|
|
218
|
+
back `undefined`, the ts-rest client builds a relative URL, and every hook
|
|
219
|
+
404s against Next's own dev server rather than the api — a routing bug in
|
|
220
|
+
appearance, an env bug in fact.
|
|
221
|
+
|
|
222
|
+
`pnpm install` resolves against the committed `pnpm-lock.yaml` — this
|
|
223
|
+
should be fast and produce no lockfile changes. A lockfile diff here
|
|
224
|
+
means the shipped `pnpm-lock.yaml` doesn't match `package.json` — that's
|
|
225
|
+
a packaging bug in this package's `workspace/`, not something to patch
|
|
226
|
+
locally (see **If verification fails**, below).
|
|
227
|
+
|
|
228
|
+
### 5. Verify
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
npx nx run-many -t typecheck,lint,test
|
|
232
|
+
npx nx format:write
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Both must be clean: 0 errors, 0 warnings from the first command; no
|
|
236
|
+
diff produced by the second. This is the one live check that replaces
|
|
237
|
+
four separate live generate-and-verify passes — core isn't proven
|
|
238
|
+
correct by trusting the copy, it's proven correct by actually running
|
|
239
|
+
the same gate every other step in this discipline runs.
|
|
240
|
+
|
|
241
|
+
The commit gate is already active by this point: `lefthook.yml` came
|
|
242
|
+
with the copy, and lefthook's own postinstall ran `lefthook install -f`
|
|
243
|
+
during step 4's `pnpm install`. Run `pnpm dlx lefthook install` only if
|
|
244
|
+
`.git/hooks/pre-commit` is somehow absent.
|
|
245
|
+
|
|
246
|
+
Confirm the gate is real rather than assuming it, with:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
hedgehog status
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Its `COMMIT GATE` line reads `active` only when the hooks exist, were
|
|
253
|
+
generated with `lefthook.yml`'s `assert_lefthook_installed: true`, and
|
|
254
|
+
the lefthook binary actually resolves. Anything else is a gate that
|
|
255
|
+
isn't enforcing, and the line names the repair. Two failure modes it
|
|
256
|
+
catches that nothing else does: hooks generated before that flag
|
|
257
|
+
existed (they fail open — regenerate with `pnpm dlx lefthook install`),
|
|
258
|
+
and a `node_modules` that has since been cleared (`pnpm install`).
|
|
259
|
+
|
|
260
|
+
Also check the hook resolves to the project's pinned local lefthook
|
|
261
|
+
(`lefthook version` during a commit, or that `.git/hooks/` resolves
|
|
262
|
+
into `node_modules/`) rather than a global Homebrew-installed shadow.
|
|
263
|
+
|
|
264
|
+
### 6. Commit
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
feat(config): workspace + shared config
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
One commit for all of core, landed as a verified copy. That commit
|
|
271
|
+
existing is the record that core landed — `bootstrap` checks for it via
|
|
272
|
+
the commit log, not a checklist line.
|
|
273
|
+
|
|
274
|
+
## Known issues baked into the full-stack-app core
|
|
275
|
+
|
|
276
|
+
These are already fixed in the committed tree — listed here so anyone
|
|
277
|
+
regenerating `workspace/` (see `scripts/regenerate-full-stack-app-core.sh`
|
|
278
|
+
in this package) knows why the tree looks the way it does, and doesn't
|
|
279
|
+
reintroduce the bug by "cleaning up" what looks like an unnecessary pin
|
|
280
|
+
or directive.
|
|
281
|
+
|
|
282
|
+
- **`tsconfig.base.json` uses `esnext`/`bundler` module resolution, not
|
|
283
|
+
`nodenext`.** Turbopack (Next 16's dev bundler) can't resolve a
|
|
284
|
+
`nodenext`-style `.js`-extension relative import specifier (`export *
|
|
285
|
+
from './tasks/index.js'` where only `index.ts` exists) across a pnpm
|
|
286
|
+
workspace-package boundary — `apps/web` importing a runtime value
|
|
287
|
+
(not just a type) from `packages/hooks`, `packages/contracts`, or
|
|
288
|
+
`packages/db` 500s with `Module not found`. `bundler` is TypeScript's
|
|
289
|
+
mode for exactly this case and is Next's own tsconfig default; nothing
|
|
290
|
+
in this stack runs raw `tsc` output directly with Node (`apps/api`
|
|
291
|
+
builds via `webpack-cli`, `apps/web` via Next/Turbopack, tests via
|
|
292
|
+
Vitest), so `nodenext`'s stricter runtime-accurate extension
|
|
293
|
+
enforcement isn't load-bearing anywhere here. Every relative import
|
|
294
|
+
specifier across the template is extensionless accordingly — a
|
|
295
|
+
`schema`/`contract`/`hook` layer adding a new barrel re-export must
|
|
296
|
+
match (`export * from './tasks/index'`, not `'./tasks/index.js'`).
|
|
297
|
+
- **`packages/db/src/schema/index.ts` needs `export {};`.** It's an
|
|
298
|
+
empty barrel-of-barrels (see the schema layer's scope in `core.yaml`)
|
|
299
|
+
until the first module's schema layer adds a line to it, but a
|
|
300
|
+
comment-only file with zero import/export statements compiles as an
|
|
301
|
+
ambient script, not a module, under `isolatedModules` +
|
|
302
|
+
`declaration`/`composite`. `packages/db/src/index.ts`'s `export *
|
|
303
|
+
from './schema/index'` fails `db:typecheck` (`TS2306: File ... is not
|
|
304
|
+
a module`) without it.
|
|
305
|
+
- **`apps/web/package.json` needs `"nx": { "tags": ["scope:web"] }`.**
|
|
306
|
+
`packages/config/eslint-base.js`'s `depConstraints` already defines a
|
|
307
|
+
`sourceTag: 'scope:web'` rule, but `@nx/next:app` doesn't tag the
|
|
308
|
+
project it generates — an untagged project is barred from depending on
|
|
309
|
+
any tagged library at all under `@nx/enforce-module-boundaries`, so
|
|
310
|
+
`apps/web` fails lint on its first import from any workspace package
|
|
311
|
+
without this.
|
|
312
|
+
- **`apps/web-e2e/tsconfig.json` needs an explicit `"types": ["node"]`.**
|
|
313
|
+
`@nx/next:app`'s generated Playwright config (`playwright.config.mts`)
|
|
314
|
+
uses `process.env` and `import.meta.dirname`, but the generator's
|
|
315
|
+
`tsconfig.json` for `apps/web-e2e` has no `types` array — unlike
|
|
316
|
+
`apps/api-e2e`'s Vitest config, which explicitly lists `node` among
|
|
317
|
+
its types. Without it, `nx run web-e2e:typecheck` fails with
|
|
318
|
+
`TS2591`/`TS2339` on both. Add `"types": ["node"]` to
|
|
319
|
+
`compilerOptions` there.
|
|
320
|
+
- **`prettier-plugin-tailwindcss` must stay pinned to `^0.7.4`, not
|
|
321
|
+
`^0.8.x`.** `0.8.0+` made `parser.preprocess` async, but Prettier core
|
|
322
|
+
calls it synchronously — crashes with `TypeError: e.charAt is not a
|
|
323
|
+
function` on every `.ts`/`.tsx` file, independent of Tailwind config,
|
|
324
|
+
module format, or Nx/pnpm. `0.7.4` fully supports Tailwind v4's
|
|
325
|
+
`tailwindStylesheet` option, so nothing is lost by staying on it.
|
|
326
|
+
Re-check this pin when bumping the plugin.
|
|
327
|
+
- **Tailwind v4 needs `tailwindStylesheet` set explicitly in
|
|
328
|
+
`apps/web/.prettierrc.js`.** There's no `tailwind.config.js` for the
|
|
329
|
+
plugin to autodetect under v4 — point it at
|
|
330
|
+
`./src/app/global.css` (the file with `@import 'tailwindcss'`),
|
|
331
|
+
resolved relative to the `.prettierrc.js` file itself.
|
|
332
|
+
- **Any component that imports Radix's `Slot` (the `asChild` pattern —
|
|
333
|
+
e.g. `components/ui/button.tsx`) needs `'use client'` as its first
|
|
334
|
+
line, even if a given render path never actually sets `asChild`.**
|
|
335
|
+
`Slot` calls `React.createContext` at module scope, which crashes
|
|
336
|
+
Server Component builds (`TypeError: e.createContext is not a
|
|
337
|
+
function`) purely on import. Verify with a clean `.next`/Nx cache
|
|
338
|
+
(`rm -rf apps/web/.next .nx/cache`) — a warm cache can mask this.
|
|
339
|
+
- **`apps/web/package.json` needs `"type": "module"`, same reasoning as
|
|
340
|
+
the root `package.json`** — `apps/web/.prettierrc.js`
|
|
341
|
+
is ESM (`export default`), and without a matching `"type": "module"`
|
|
342
|
+
Node emits a `MODULE_TYPELESS_PACKAGE_JSON` warning on every prettier
|
|
343
|
+
invocation touching that directory.
|
|
344
|
+
- **`apps/api`'s dev port defaults to 3333, not 3000.** `apps/web`'s
|
|
345
|
+
`next dev` also defaults to port 3000, and the root `dev` script runs
|
|
346
|
+
both side by side (`nx run-many --target=serve,dev
|
|
347
|
+
--projects=api,web`). If `apps/api/src/main.ts` ever falls back to
|
|
348
|
+
`process.env.PORT || 3000`, whichever process binds the port second
|
|
349
|
+
either crashes or is silently unreachable, and every API call from the
|
|
350
|
+
web client 404s against Next's own dev server instead (Next has no
|
|
351
|
+
matching route, so it serves its catch-all 404 page rather than a
|
|
352
|
+
connection error — easy to mistake for a routing bug in the api
|
|
353
|
+
itself). Keep `apps/api`'s fallback at `3333` and don't let it drift
|
|
354
|
+
back to matching Next's default.
|
|
355
|
+
- **`apps/web/vitest.config.mts` needs `environment: 'jsdom'`, the
|
|
356
|
+
`@vitejs/plugin-react` plugin, and the `@/*` -> `./src/*` alias resolved
|
|
357
|
+
explicitly.** `@nx/next:app --unitTestRunner=vitest` generates a
|
|
358
|
+
Node-flavored config with no JSX plugin and no alias resolution — wrong
|
|
359
|
+
for a React app, and Vitest doesn't read `tsconfig.json`'s `paths` on
|
|
360
|
+
its own. `apps/web/src/test-setup.ts` (loading
|
|
361
|
+
`@testing-library/jest-dom/vitest`) is wired as the config's
|
|
362
|
+
`setupFiles` entry so `toBeInTheDocument()` and friends resolve in every
|
|
363
|
+
spec without a per-file import.
|
|
364
|
+
- **No `NODE_ENV=production` build-target override needed** (Nx
|
|
365
|
+
23.1.0, Next 16.1.7). Targets are inferred from
|
|
366
|
+
`package.json`/`next.config.js` via the `@nx/next` plugin, with no
|
|
367
|
+
per-app `project.json` to override. If a future Nx/Next bump ever
|
|
368
|
+
produces a dev-mode production build crash
|
|
369
|
+
(`web:build` succeeds but crashes prerendering `/_global-error` with a
|
|
370
|
+
React-internals error), set `NODE_ENV=production` explicitly on
|
|
371
|
+
`apps/web`'s `build` target as the fix.
|
|
372
|
+
|
|
373
|
+
## If verification fails
|
|
374
|
+
|
|
375
|
+
A clean copy of `workspace/` that fails typecheck/lint/test or needs a
|
|
376
|
+
lockfile change means the shipped template itself is broken — not a
|
|
377
|
+
per-project problem to hand-patch around. Stop and report exactly what
|
|
378
|
+
failed (which target, which error). Fixing this means updating
|
|
379
|
+
`workspace/` at its source (via `scripts/regenerate-full-stack-app-core.sh`
|
|
380
|
+
in this package) and shipping a new package version — never patch a
|
|
381
|
+
consuming project's copy to route around a broken template and call core
|
|
382
|
+
done.
|
|
383
|
+
|
|
384
|
+
## Local infra: Docker, always
|
|
385
|
+
|
|
386
|
+
Postgres runs through the `docker-compose.yml` this step lands, on
|
|
387
|
+
every host OS, regardless of which add-ons a project turns on later.
|
|
388
|
+
This isn't a convenience default — it's what makes "clone the repo, run
|
|
389
|
+
the stack" mechanically true regardless of who's building. Don't offer
|
|
390
|
+
a native-Postgres alternative even if a contributor already has one
|
|
391
|
+
running locally for another project. Redis joins the same file only if
|
|
392
|
+
the Queue add-on turns on later, in `hedgehog-bootstrap`'s own step —
|
|
393
|
+
not here.
|
|
394
|
+
|
|
395
|
+
## Constraints
|
|
396
|
+
|
|
397
|
+
- Run once per project, always as `hedgehog-bootstrap`'s first move —
|
|
398
|
+
never invoked on its own by a user.
|
|
399
|
+
- No add-on awareness. If a check here ever seems to need
|
|
400
|
+
`.hedgehog/addons.yaml`, that check belongs in `hedgehog-bootstrap`
|
|
401
|
+
instead — this skill's whole point is being identical across every
|
|
402
|
+
project.
|
|
403
|
+
- Don't hand-edit any file this step lands to work around a verification
|
|
404
|
+
failure. Fix `workspace/` at the source instead (see **If
|
|
405
|
+
verification fails**).
|
|
406
|
+
- Don't add domain schema, contracts, or any `libs/<module>/*` content —
|
|
407
|
+
that's Phase A, after every Bootstrap box (core and whichever add-ons
|
|
408
|
+
are on) is checked.
|
|
409
|
+
- Local Postgres always runs through the copied `docker-compose.yml`, on
|
|
410
|
+
every host OS. Never substitute a natively-installed Postgres, even to
|
|
411
|
+
match a contributor's existing local setup.
|