create-astram-app 0.1.1 → 0.1.3
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/dist/index.js +32 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/template/CLAUDE.md +51 -0
- package/template/README.md +43 -44
- package/template/_gitignore +6 -0
- package/template/frontend/README.md +16 -0
- package/template/server/eslint.config.js +95 -0
- package/template/server/package.json +46 -0
- package/template/server/scripts/dev.mjs +51 -0
- package/template/server/scripts/sync-astram-guide.mjs +63 -0
- package/template/package.json +0 -37
- /package/template/{_env.example → server/_env.example} +0 -0
- /package/template/{src → server/src}/app.module.ts +0 -0
- /package/template/{src → server/src}/features/_gitkeep +0 -0
- /package/template/{src → server/src}/health/demo.controller.ts +0 -0
- /package/template/{src → server/src}/health/health.controller.ts +0 -0
- /package/template/{src → server/src}/main.ts +0 -0
- /package/template/{test → server/test}/enforcement.e2e.spec.ts +0 -0
- /package/template/{tsconfig.build.json → server/tsconfig.build.json} +0 -0
- /package/template/{tsconfig.json → server/tsconfig.json} +0 -0
- /package/template/{vitest.config.ts → server/vitest.config.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Reference skeleton: `examples/example-backend/` in the astram-framework repo.
|
|
22
22
|
*/
|
|
23
|
-
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
24
24
|
import { dirname, join, resolve } from 'node:path';
|
|
25
25
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
26
26
|
/** Token substituted with the app name at copy time (in package.json name, README title, …). */
|
|
@@ -152,30 +152,47 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
152
152
|
if (report.skipped.length > 0) {
|
|
153
153
|
console.log(` ${report.skipped.length} existing file(s) left untouched (never-clobber): ${report.skipped.join(', ')}`);
|
|
154
154
|
}
|
|
155
|
-
console.log('\nWhat you got:');
|
|
156
|
-
console.log(' •
|
|
157
|
-
console.log(' default (ADR-0002) unless you classify it
|
|
158
|
-
console.log(' •
|
|
159
|
-
console.log('
|
|
160
|
-
console.log('
|
|
161
|
-
console.log('
|
|
155
|
+
console.log('\nWhat you got (a monorepo):');
|
|
156
|
+
console.log(' • server/ — a running, framework-ENFORCED NestJS API. Every route is denied');
|
|
157
|
+
console.log(' by default (ADR-0002) unless you classify it @Public()/@Classified().');
|
|
158
|
+
console.log(' • frontend/ — an empty space for your UI (add any framework: Vite/React, etc.).');
|
|
159
|
+
console.log(' • The framework installed in server/ as immutable dependencies (@astram/core,');
|
|
160
|
+
console.log(' @astram/guard, @astram/engine, @astram/sdk) — you never edit it; upgrade via');
|
|
161
|
+
console.log(' `npm update`.');
|
|
162
|
+
console.log(' • server/src/features/ — your editable API space. Add a feature by writing a');
|
|
163
|
+
console.log(' Nest module/controller there and registering it in server/src/app.module.ts.');
|
|
162
164
|
console.log(' • .astram/guide/ — your own, upgrade-safe project-rules space (ADR-0025).');
|
|
163
165
|
console.log('\n (A turnkey login/session boilerplate ships in a later framework release;');
|
|
164
166
|
console.log(' today the app gives you correct-by-default ENFORCEMENT out of the box.)');
|
|
165
167
|
console.log('\nNext steps:');
|
|
166
|
-
console.log(` cd ${appName}`);
|
|
168
|
+
console.log(` cd ${appName}/server`);
|
|
167
169
|
console.log(' npm install');
|
|
168
|
-
console.log(' npm run dev # boots the
|
|
170
|
+
console.log(' npm run dev # boots the API; GET /health → 200, unclassified → 403');
|
|
169
171
|
console.log('');
|
|
170
172
|
return 0;
|
|
171
173
|
}
|
|
172
174
|
// Run ONLY when this module is the process entry point (i.e. executed as the
|
|
173
175
|
// CLI), NOT when imported by a test. Keyed on the actual entry script rather
|
|
174
|
-
// than NODE_ENV
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
// than NODE_ENV (so a consumer whose shell exports NODE_ENV=test still gets a
|
|
177
|
+
// working CLI). CRITICAL: `npx`/npm run the package through a `bin` SYMLINK, so
|
|
178
|
+
// `process.argv[1]` is the symlink path, not this real dist file — we must
|
|
179
|
+
// resolve it (realpath) before comparing, or `import.meta.url` (the real file)
|
|
180
|
+
// never matches and the CLI silently no-ops. When imported by a test, argv[1]
|
|
181
|
+
// is the test runner, which resolves to a different real file → correctly skips.
|
|
182
|
+
function isInvokedAsCli() {
|
|
183
|
+
const entry = process.argv[1];
|
|
184
|
+
if (entry === undefined)
|
|
185
|
+
return false;
|
|
186
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
187
|
+
try {
|
|
188
|
+
return realpathSync(entry) === realpathSync(thisFile);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// If either path can't be realpath'd, fall back to a direct compare.
|
|
192
|
+
return pathToFileURL(entry).href === import.meta.url;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (isInvokedAsCli()) {
|
|
179
196
|
process.exitCode = main();
|
|
180
197
|
}
|
|
181
198
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-astram-app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Scaffolder for a new Astram app — emits a running, framework-enforced consumer project (empty src/features/ + deny-by-default enforcement) that installs the immutable @astram/core, @astram/guard, and @astram/sdk framework.",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# __APP_NAME__ — AI guide (entry)
|
|
2
|
+
|
|
3
|
+
This is the entry point for AI assistants (Claude Code) and teammates. It ties together
|
|
4
|
+
the **two-layer guide** (ADR-0025): the framework's rules (the floor) + this app's own
|
|
5
|
+
rules (on top). Read both before doing any work.
|
|
6
|
+
|
|
7
|
+
## 1. Framework rules — the FLOOR (framework-owned, refreshed on upgrade)
|
|
8
|
+
|
|
9
|
+
The Astram framework ships its hard rules + capabilities in the installed
|
|
10
|
+
`@astram/guide` package. They are synced into `.claude/rules/` (do NOT edit those
|
|
11
|
+
copies — they are regenerated from the framework package on every install/upgrade):
|
|
12
|
+
|
|
13
|
+
- **`.claude/rules/astram-framework-rules.md`** — the non-negotiable rules: never edit
|
|
14
|
+
the framework, all data through the RecordEngine pipe, classify every route,
|
|
15
|
+
**STOP-and-flag a gap** instead of working around it, fail closed, and — after any
|
|
16
|
+
framework upgrade — **re-read these rules first** (Rule 0).
|
|
17
|
+
- **`.claude/rules/astram-framework-capabilities.md`** — what the framework provides
|
|
18
|
+
this version (check here before building; if it's not there, it's a gap → flag it).
|
|
19
|
+
|
|
20
|
+
@./.claude/rules/astram-framework-rules.md
|
|
21
|
+
|
|
22
|
+
> These are a FLOOR: this app's own rules (below) may ADD to or TIGHTEN them, but never
|
|
23
|
+
> weaken them. The never-touch-core / never-bypass-enforcement rules are absolute.
|
|
24
|
+
|
|
25
|
+
## 2. Your project's rules — the project layer (yours, never touched by an upgrade)
|
|
26
|
+
|
|
27
|
+
Your project-specific rules, conventions, and the framework-gap log live in
|
|
28
|
+
**`.astram/guide/`** (git-tracked, developer-owned — a framework upgrade never touches
|
|
29
|
+
it):
|
|
30
|
+
|
|
31
|
+
- **`.astram/guide/README.md`** — what this layer is + how it relates to the framework.
|
|
32
|
+
- **`.astram/guide/gaps.md`** — the framework capability gaps you've flagged (create it
|
|
33
|
+
the first time you hit one — the gap-flag loop).
|
|
34
|
+
|
|
35
|
+
Add your own rules as markdown files in `.astram/guide/`.
|
|
36
|
+
|
|
37
|
+
## 3. Keeping the framework rules current (the upgrade flow)
|
|
38
|
+
|
|
39
|
+
The framework rules in `.claude/rules/` are a **generated mirror** of the installed
|
|
40
|
+
`@astram/guide` package. They refresh automatically:
|
|
41
|
+
|
|
42
|
+
- `server/`'s `postinstall` runs `sync-astram-guide` on every `npm install` / `npm
|
|
43
|
+
update`, copying the current framework rules into `.claude/rules/`.
|
|
44
|
+
- To refresh manually: `cd server && npm run guide:sync`.
|
|
45
|
+
- The synced framework files + `.claude/rules/.astram-guide-version` are **gitignored**
|
|
46
|
+
(they belong to the framework version, not your app). Your `.astram/guide/` layer is
|
|
47
|
+
git-tracked and yours.
|
|
48
|
+
|
|
49
|
+
**On a framework upgrade:** run `npm update` (or `npm install`) in `server/` — the
|
|
50
|
+
framework rules refresh to the new version, your `.astram/guide/` rules are untouched,
|
|
51
|
+
then re-read the refreshed `.claude/rules/astram-framework-rules.md` (Rule 0).
|
package/template/README.md
CHANGED
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
# __APP_NAME__
|
|
2
2
|
|
|
3
|
-
An **Astram consumer app**, scaffolded with `create-astram-app`.
|
|
4
|
-
|
|
3
|
+
An **Astram consumer app**, scaffolded with `create-astram-app`. A monorepo with a
|
|
4
|
+
`server/` (the API) and a `frontend/` (your UI). The API depends on the published,
|
|
5
|
+
immutable framework packages ([`@astram/core`](https://www.npmjs.com/package/@astram/core),
|
|
5
6
|
[`@astram/guard`](https://www.npmjs.com/package/@astram/guard),
|
|
7
|
+
[`@astram/engine`](https://www.npmjs.com/package/@astram/engine),
|
|
6
8
|
[`@astram/sdk`](https://www.npmjs.com/package/@astram/sdk)) and enforces
|
|
7
|
-
**deny-by-default** from the very first boot. You build features; the framework
|
|
8
|
-
|
|
9
|
+
**deny-by-default** from the very first boot. You build features; the framework lives
|
|
10
|
+
read-only in `server/node_modules` and upgrades via `npm update` — you never edit it.
|
|
9
11
|
|
|
10
|
-
##
|
|
12
|
+
## Layout
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
__APP_NAME__/
|
|
16
|
+
server/ the API (NestJS + the Astram framework enforcement)
|
|
17
|
+
frontend/ your UI (empty — add Vite/React/Next/… and call the API)
|
|
18
|
+
.astram/ your project-owned, upgrade-safe AI/dev guide (ADR-0025)
|
|
19
|
+
docs/ your own docs (the scaffolder never clobbers this)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Run the API
|
|
11
23
|
|
|
12
24
|
```bash
|
|
25
|
+
cd server
|
|
13
26
|
npm install
|
|
14
|
-
npm run dev # boots the
|
|
27
|
+
npm run dev # boots the API (tsx watch) on http://localhost:3000
|
|
15
28
|
```
|
|
16
29
|
|
|
17
30
|
Then:
|
|
18
31
|
|
|
19
32
|
```bash
|
|
20
|
-
curl -i localhost:3000/health
|
|
21
|
-
curl -i localhost:3000/demo/public
|
|
33
|
+
curl -i localhost:3000/health # 200 — @Public health probe
|
|
34
|
+
curl -i localhost:3000/demo/public # 200 — explicit @Public() opt-out
|
|
22
35
|
curl -i localhost:3000/demo/unclassified # 403 — DENIED (no classification)
|
|
23
36
|
```
|
|
24
37
|
|
|
@@ -26,12 +39,13 @@ That last 403 is the whole point: **every route is denied unless you explicitly
|
|
|
26
39
|
classify it.** Forgetting to declare a route locks the door instead of silently
|
|
27
40
|
exposing it.
|
|
28
41
|
|
|
29
|
-
Other scripts: `npm run build` (→ `dist/`), `npm start` (run the build),
|
|
30
|
-
`npm test
|
|
42
|
+
Other `server/` scripts: `npm run build` (→ `dist/`), `npm start` (run the build),
|
|
43
|
+
`npm test`, `npm run lint`.
|
|
31
44
|
|
|
32
|
-
## How
|
|
45
|
+
## How the API wires the framework
|
|
33
46
|
|
|
34
|
-
`src/app.module.ts` imports the ONE registration seam — no local security
|
|
47
|
+
`server/src/app.module.ts` imports the ONE registration seam — no local security
|
|
48
|
+
wiring:
|
|
35
49
|
|
|
36
50
|
```ts
|
|
37
51
|
import { ShieldModule } from '@astram/guard';
|
|
@@ -45,45 +59,30 @@ export class AppModule {}
|
|
|
45
59
|
|
|
46
60
|
`ShieldModule.forRoot()` binds the global deny-by-default guard (ADR-0002), the
|
|
47
61
|
boot-pool assertion (ADR-0018), and the Core decision-authority token. You never
|
|
48
|
-
re-implement enforcement — you register it once and add your controllers.
|
|
62
|
+
re-implement enforcement — you register it once and add your controllers. Enable the
|
|
63
|
+
full auth surface (login/MFA/refresh/logout) with `ShieldModule.forRoot({ auth: true,
|
|
64
|
+
credentialResolver, decisionAuthority })`.
|
|
49
65
|
|
|
50
66
|
## Add a feature (the framework way)
|
|
51
67
|
|
|
52
|
-
1. Create `src/features/<your-feature>/` with a Nest module + controller(s).
|
|
53
|
-
2. Register it in `src/app.module.ts` (add the module to `imports`, or its
|
|
68
|
+
1. Create `server/src/features/<your-feature>/` with a Nest module + controller(s).
|
|
69
|
+
2. Register it in `server/src/app.module.ts` (add the module to `imports`, or its
|
|
54
70
|
controller to `controllers`).
|
|
55
|
-
3. **Classify every route:** `@Public()` for open routes, `@Classified(
|
|
56
|
-
guarded ones. Anything unclassified is denied (403) by design.
|
|
57
|
-
4.
|
|
71
|
+
3. **Classify every route:** `@Public()` for open routes, `@Classified({ typeKey,
|
|
72
|
+
action })` for guarded ones. Anything unclassified is denied (403) by design.
|
|
73
|
+
4. **Data access goes through the framework's RecordEngine** — never a raw DB pool.
|
|
74
|
+
The `server/eslint.config.js` wall rule fails the build on off-road DB access.
|
|
75
|
+
5. Add a test (see `server/test/` for the pattern).
|
|
58
76
|
|
|
59
|
-
`src/features/` starts empty — it's your space.
|
|
77
|
+
`server/src/features/` starts empty — it's your space.
|
|
60
78
|
|
|
61
|
-
##
|
|
79
|
+
## The frontend
|
|
62
80
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
later framework release — when it lands, you'll pick it up via `npm update` without
|
|
67
|
-
touching your `src/`.
|
|
68
|
-
|
|
69
|
-
## Layout
|
|
70
|
-
|
|
71
|
-
```
|
|
72
|
-
src/
|
|
73
|
-
main.ts HTTP bootstrap (thin) — loadAppConfig from @astram/guard
|
|
74
|
-
app.module.ts declares enforcement via ShieldModule.forRoot()
|
|
75
|
-
health/
|
|
76
|
-
health.controller.ts /health /ready /live (all @Public)
|
|
77
|
-
demo.controller.ts /demo/public (@Public) + /demo/unclassified (denied)
|
|
78
|
-
features/ EMPTY — your editable space
|
|
79
|
-
test/
|
|
80
|
-
enforcement.e2e.spec.ts boots the app; proves 200 health + 403 unclassified
|
|
81
|
-
.astram/guide/ YOUR project guide (upgrade-safe; never touched)
|
|
82
|
-
docs/ your own docs (the scaffolder never clobbers this)
|
|
83
|
-
```
|
|
81
|
+
`frontend/` is intentionally empty — Astram is a **backend security kernel**, so it
|
|
82
|
+
does not impose a UI framework. Add whatever you like and call the API. See
|
|
83
|
+
`frontend/README.md` for the auth endpoints your UI will consume.
|
|
84
84
|
|
|
85
85
|
## The `.astram/guide/` directory
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`.astram/guide/README.md`.
|
|
87
|
+
Your **project-owned** AI/dev guide layer (ADR-0025) — framework upgrades never touch
|
|
88
|
+
it. Put your project's own rules and context there. See `.astram/guide/README.md`.
|
package/template/_gitignore
CHANGED
|
@@ -3,3 +3,9 @@ dist/
|
|
|
3
3
|
.env
|
|
4
4
|
*.log
|
|
5
5
|
.DS_Store
|
|
6
|
+
|
|
7
|
+
# The framework rules synced from @astram/guide (ADR-0025) — a GENERATED mirror of
|
|
8
|
+
# the installed framework version, refreshed on every install/upgrade. Not yours to
|
|
9
|
+
# edit or commit. (Your OWN rules live in .astram/guide/, which IS git-tracked.)
|
|
10
|
+
.claude/rules/astram-framework-*.md
|
|
11
|
+
.claude/rules/.astram-guide-version
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frontend
|
|
2
|
+
|
|
3
|
+
Your UI lives here. This folder is intentionally empty — the Astram framework is a
|
|
4
|
+
**backend security kernel**, so it does not impose a frontend framework. Add whatever
|
|
5
|
+
you like (Vite + React, Next.js, SvelteKit, a mobile app's web build, …) and point it
|
|
6
|
+
at the API in `../server` (default `http://localhost:3000`).
|
|
7
|
+
|
|
8
|
+
The API's auth surface is ready to consume:
|
|
9
|
+
|
|
10
|
+
- `POST /auth/login` `{ identityHint, password }` → `{ session, tokens: { accessToken, refreshToken } }`
|
|
11
|
+
- `POST /auth/refresh` `{ refreshToken }` → new tokens
|
|
12
|
+
- `POST /auth/logout` `{ sessionId }` → 204
|
|
13
|
+
- Protected routes: send `Authorization: Bearer <accessToken>`. A `@Classified` route
|
|
14
|
+
is enforced by the framework — no token → 401, denied by policy → 403.
|
|
15
|
+
|
|
16
|
+
See `../server/src/features/` for the API features your UI will call.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// ESLint flat config for an Astram consumer app.
|
|
2
|
+
//
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
// THE PHYSICAL WALL — backstop (do not remove).
|
|
5
|
+
//
|
|
6
|
+
// The Astram paved road is the ONE way features touch data: inject the framework's
|
|
7
|
+
// RecordEngine (from @astram/engine) and go through the pipe (permission gate +
|
|
8
|
+
// audit are mandatory, cross-tenant isolation is enforced). A feature must NEVER
|
|
9
|
+
// reach the database directly — a raw mariadb/typeorm pool, a DataSource, a
|
|
10
|
+
// QueryRunner, or a repository `.save()`/`.query()` skips the security layer
|
|
11
|
+
// entirely (this is exactly how astram-org's ORS got bypassed 165 times).
|
|
12
|
+
//
|
|
13
|
+
// The PRIMARY wall is structural: the framework never hands feature scope a pool
|
|
14
|
+
// (EngineModule seals it). THIS lint rule is the BACKSTOP: it fails the build if a
|
|
15
|
+
// file under `src/features/**` imports a raw DB symbol or calls a raw DB method, so
|
|
16
|
+
// an off-road attempt is a red build, not a silent success.
|
|
17
|
+
//
|
|
18
|
+
// If you hit this rule because the pipe can't do what you need: STOP. Do NOT work
|
|
19
|
+
// around it. Record the gap in `.astram/guide/gaps.md` and raise it with the
|
|
20
|
+
// framework — the capability must be added to @astram/engine (the gap-flag loop).
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
import js from '@eslint/js';
|
|
24
|
+
import tseslint from 'typescript-eslint';
|
|
25
|
+
|
|
26
|
+
/** Raw DB packages a feature must never import directly. */
|
|
27
|
+
const FORBIDDEN_DB_IMPORTS = [
|
|
28
|
+
{ name: 'mariadb', message: 'Wall: features never open a DB pool — use the RecordEngine (@astram/engine).' },
|
|
29
|
+
{ name: 'mysql', message: 'Wall: features never open a DB driver — use the RecordEngine (@astram/engine).' },
|
|
30
|
+
{ name: 'mysql2', message: 'Wall: features never open a DB driver — use the RecordEngine (@astram/engine).' },
|
|
31
|
+
{ name: 'typeorm', message: 'Wall: no ORM in features — use the RecordEngine (@astram/engine).' },
|
|
32
|
+
{ name: '@nestjs/typeorm', message: 'Wall: no ORM in features — use the RecordEngine (@astram/engine).' },
|
|
33
|
+
{ name: 'pg', message: 'Wall: features never open a DB driver — use the RecordEngine (@astram/engine).' },
|
|
34
|
+
{ name: 'knex', message: 'Wall: no query builder in features — use the RecordEngine (@astram/engine).' },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Named imports that hand a feature raw DB access even from an ALLOWED package.
|
|
39
|
+
* `SHIELD_DB_POOL` is exported from `@astram/guard` (a package a feature legitimately
|
|
40
|
+
* imports for other reasons), so it must be blocked by NAME on that path — the engine
|
|
41
|
+
* owns the pool; feature scope must never import the pool token.
|
|
42
|
+
*/
|
|
43
|
+
const FORBIDDEN_DB_NAMED_FROM = [
|
|
44
|
+
{
|
|
45
|
+
name: '@astram/guard',
|
|
46
|
+
importNames: ['SHIELD_DB_POOL'],
|
|
47
|
+
message: 'Wall: the DB pool is sealed inside the engine — features use the RecordEngine (@astram/engine).',
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export default tseslint.config(
|
|
52
|
+
js.configs.recommended,
|
|
53
|
+
...tseslint.configs.recommended,
|
|
54
|
+
{
|
|
55
|
+
ignores: ['dist/**', 'node_modules/**'],
|
|
56
|
+
},
|
|
57
|
+
// ── The wall: scoped to feature code ──────────────────────────────────────
|
|
58
|
+
{
|
|
59
|
+
files: ['src/features/**/*.ts'],
|
|
60
|
+
rules: {
|
|
61
|
+
'no-restricted-imports': [
|
|
62
|
+
'error',
|
|
63
|
+
{
|
|
64
|
+
paths: [...FORBIDDEN_DB_IMPORTS, ...FORBIDDEN_DB_NAMED_FROM],
|
|
65
|
+
patterns: [
|
|
66
|
+
{
|
|
67
|
+
group: ['**/adapters/mariadb-*', '@astram/engine/dist/adapters/*'],
|
|
68
|
+
message:
|
|
69
|
+
'Wall: the engine adapters (pool/uow/audit) are framework-internal — features use the RecordEngine facade only.',
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
// Block raw query-runner / repository calls even if a pool were obtained.
|
|
75
|
+
'no-restricted-syntax': [
|
|
76
|
+
'error',
|
|
77
|
+
{
|
|
78
|
+
selector:
|
|
79
|
+
"CallExpression[callee.property.name='createQueryRunner']",
|
|
80
|
+
message: 'Wall: no QueryRunner in features — use the RecordEngine (@astram/engine).',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
selector:
|
|
84
|
+
"CallExpression[callee.property.name='getRepository']",
|
|
85
|
+
message: 'Wall: no repository access in features — use the RecordEngine (@astram/engine).',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
selector:
|
|
89
|
+
"CallExpression[callee.property.name='createPool']",
|
|
90
|
+
message: 'Wall: features never create a DB pool — use the RecordEngine (@astram/engine).',
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__APP_NAME__",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "The server (API) half of an Astram consumer app. Depends on the immutable framework (@astram/core, @astram/guard, @astram/engine, @astram/sdk) and declares enforcement via ShieldModule.forRoot(). Features go in src/features/; the UI lives in ../frontend.",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"main": "dist/main.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"prebuild": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
10
|
+
"build": "tsc -p tsconfig.build.json",
|
|
11
|
+
"start": "node --env-file-if-exists=.env dist/main.js",
|
|
12
|
+
"dev": "node scripts/dev.mjs",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
15
|
+
"guide:sync": "node scripts/sync-astram-guide.mjs",
|
|
16
|
+
"postinstall": "node scripts/sync-astram-guide.mjs"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@astram/core": "^1.0.0",
|
|
20
|
+
"@astram/engine": "^0.1.0",
|
|
21
|
+
"@astram/guard": "^0.3.0",
|
|
22
|
+
"@astram/guide": "^0.1.0",
|
|
23
|
+
"@astram/identity": "^0.1.0",
|
|
24
|
+
"@astram/migrate": "^0.1.0",
|
|
25
|
+
"@astram/sdk": "^0.2.0",
|
|
26
|
+
"@nestjs/common": "^11.0.0",
|
|
27
|
+
"@nestjs/core": "^11.0.0",
|
|
28
|
+
"@nestjs/platform-express": "^11.0.0",
|
|
29
|
+
"reflect-metadata": "^0.2.2",
|
|
30
|
+
"rxjs": "^7.8.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@eslint/js": "^9.0.0",
|
|
34
|
+
"@nestjs/testing": "^11.0.0",
|
|
35
|
+
"@types/express": "^5.0.0",
|
|
36
|
+
"@types/node": "^22.0.0",
|
|
37
|
+
"@types/supertest": "^6.0.2",
|
|
38
|
+
"eslint": "^9.0.0",
|
|
39
|
+
"supertest": "^7.0.0",
|
|
40
|
+
"tsx": "^4.19.2",
|
|
41
|
+
"typescript": "^5.7.0",
|
|
42
|
+
"typescript-eslint": "^8.0.0",
|
|
43
|
+
"unplugin-swc": "^1.5.1",
|
|
44
|
+
"vitest": "^2.1.8"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev runner — TypeScript watch + auto-restart, with CORRECT NestJS decorator
|
|
3
|
+
* metadata.
|
|
4
|
+
*
|
|
5
|
+
* WHY not `tsx watch`: tsx uses esbuild, which does NOT emit `emitDecoratorMetadata`.
|
|
6
|
+
* NestJS constructor injection (`constructor(private x: Service)`) depends on that
|
|
7
|
+
* metadata, so under tsx a controller's injected service is `undefined` at runtime
|
|
8
|
+
* (a silent 500 on the first call). `tsc` DOES emit the metadata — so this runner
|
|
9
|
+
* compiles with `tsc --watch` (→ dist/) and runs `node --watch dist/main.js`, giving
|
|
10
|
+
* hot-reload AND correct DI. Same compiler as the production build, no extra deps.
|
|
11
|
+
*
|
|
12
|
+
* `.env` is loaded by the node child via --env-file-if-exists.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { spawn } from 'node:child_process';
|
|
16
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
17
|
+
|
|
18
|
+
mkdirSync('dist', { recursive: true });
|
|
19
|
+
|
|
20
|
+
// 1) tsc in watch mode → dist/ (emits decorator metadata; the whole point).
|
|
21
|
+
const tsc = spawn(
|
|
22
|
+
process.execPath,
|
|
23
|
+
[new URL('../node_modules/typescript/bin/tsc', import.meta.url).pathname, '-w', '-p', 'tsconfig.build.json', '--preserveWatchOutput'],
|
|
24
|
+
{ stdio: 'inherit' },
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// 2) Wait for the first emit, then run node --watch on the built entry.
|
|
28
|
+
let started = false;
|
|
29
|
+
const startNode = () => {
|
|
30
|
+
if (started) return;
|
|
31
|
+
if (!existsSync('dist/main.js')) {
|
|
32
|
+
setTimeout(startNode, 300);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
started = true;
|
|
36
|
+
const node = spawn(
|
|
37
|
+
process.execPath,
|
|
38
|
+
['--env-file-if-exists=.env', '--watch', 'dist/main.js'],
|
|
39
|
+
{ stdio: 'inherit' },
|
|
40
|
+
);
|
|
41
|
+
node.on('exit', (code) => {
|
|
42
|
+
tsc.kill();
|
|
43
|
+
process.exit(code ?? 0);
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
setTimeout(startNode, 800);
|
|
47
|
+
|
|
48
|
+
process.on('SIGINT', () => {
|
|
49
|
+
tsc.kill();
|
|
50
|
+
process.exit(0);
|
|
51
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sync-astram-guide — copy the framework's guide docs into `.claude/rules/` so
|
|
3
|
+
* Claude Code reads the CURRENT framework rules.
|
|
4
|
+
*
|
|
5
|
+
* WHY (ADR-0025, the two-layer AI guide): the framework ships its hard rules +
|
|
6
|
+
* capabilities in the installed `@astram/guide` package (in node_modules). Claude
|
|
7
|
+
* Code reliably reads rules from `.claude/rules/` (a documented, symlink-aware
|
|
8
|
+
* location) — but NOT from an @import that points into node_modules. So this script
|
|
9
|
+
* bridges the two: it copies the framework docs from the installed package into the
|
|
10
|
+
* app-root `.claude/rules/`, where the app's CLAUDE.md references them.
|
|
11
|
+
*
|
|
12
|
+
* It runs on `npm install` AND `npm update` (wired as a postinstall in
|
|
13
|
+
* server/package.json), so a FRAMEWORK UPGRADE automatically refreshes the rules —
|
|
14
|
+
* the whole point of the upgrade-safe design. The copied files are GITIGNORED (they
|
|
15
|
+
* are a generated mirror of the framework's version); the developer-owned
|
|
16
|
+
* `.astram/guide/` layer is separate, git-tracked, and NEVER touched by this script.
|
|
17
|
+
*
|
|
18
|
+
* The app root is the monorepo root (one level up from server/), because CLAUDE.md +
|
|
19
|
+
* .claude/ live at the app root and Claude walks UP the tree from wherever you work.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { cpSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { dirname, join, resolve } from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
import { createRequire } from 'node:module';
|
|
26
|
+
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
const here = dirname(fileURLToPath(import.meta.url)); // server/scripts
|
|
29
|
+
const appRoot = resolve(here, '..', '..'); // monorepo root
|
|
30
|
+
const rulesDir = join(appRoot, '.claude', 'rules');
|
|
31
|
+
|
|
32
|
+
function main() {
|
|
33
|
+
let guide;
|
|
34
|
+
try {
|
|
35
|
+
guide = require('@astram/guide');
|
|
36
|
+
} catch {
|
|
37
|
+
// The framework guide package isn't installed (e.g. a partial install). Skip
|
|
38
|
+
// silently — this is a best-effort refresh, never a hard failure of the install.
|
|
39
|
+
process.stdout.write('[sync-astram-guide] @astram/guide not found — skipped.\n');
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
mkdirSync(rulesDir, { recursive: true });
|
|
44
|
+
// Copy the framework docs into .claude/rules/ (overwrite — they are the framework's
|
|
45
|
+
// to own; on upgrade the newer versions replace the older ones cleanly).
|
|
46
|
+
cpSync(guide.FRAMEWORK_RULES_PATH, join(rulesDir, 'astram-framework-rules.md'));
|
|
47
|
+
cpSync(guide.CAPABILITIES_PATH, join(rulesDir, 'astram-framework-capabilities.md'));
|
|
48
|
+
|
|
49
|
+
// Stamp the synced version so a human/AI can see which framework version's rules
|
|
50
|
+
// are present (and notice when an upgrade changed them).
|
|
51
|
+
writeFileSync(
|
|
52
|
+
join(rulesDir, '.astram-guide-version'),
|
|
53
|
+
`${guide.version}\n`,
|
|
54
|
+
'utf8',
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
process.stdout.write(
|
|
58
|
+
`[sync-astram-guide] framework rules synced (v${guide.version}) → .claude/rules/\n`,
|
|
59
|
+
);
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
process.exitCode = main();
|
package/template/package.json
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "__APP_NAME__",
|
|
3
|
-
"version": "0.0.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"description": "An Astram consumer app. Depends on the published, immutable framework (@astram/core, @astram/guard, @astram/sdk) and declares enforcement via ShieldModule.forRoot(). Features go in src/features/.",
|
|
6
|
-
"type": "commonjs",
|
|
7
|
-
"main": "dist/main.js",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"prebuild": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
10
|
-
"build": "tsc -p tsconfig.build.json",
|
|
11
|
-
"start": "node dist/main.js",
|
|
12
|
-
"dev": "tsx watch src/main.ts",
|
|
13
|
-
"test": "vitest run",
|
|
14
|
-
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\""
|
|
15
|
-
},
|
|
16
|
-
"dependencies": {
|
|
17
|
-
"@astram/core": "^0.1.0",
|
|
18
|
-
"@astram/guard": "^0.1.1",
|
|
19
|
-
"@astram/sdk": "^0.1.0",
|
|
20
|
-
"@nestjs/common": "^11.0.0",
|
|
21
|
-
"@nestjs/core": "^11.0.0",
|
|
22
|
-
"@nestjs/platform-express": "^11.0.0",
|
|
23
|
-
"reflect-metadata": "^0.2.2",
|
|
24
|
-
"rxjs": "^7.8.1"
|
|
25
|
-
},
|
|
26
|
-
"devDependencies": {
|
|
27
|
-
"@nestjs/testing": "^11.0.0",
|
|
28
|
-
"@types/express": "^5.0.0",
|
|
29
|
-
"@types/node": "^22.0.0",
|
|
30
|
-
"@types/supertest": "^6.0.2",
|
|
31
|
-
"supertest": "^7.0.0",
|
|
32
|
-
"tsx": "^4.19.2",
|
|
33
|
-
"typescript": "^5.7.0",
|
|
34
|
-
"unplugin-swc": "^1.5.1",
|
|
35
|
-
"vitest": "^2.1.8"
|
|
36
|
-
}
|
|
37
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|