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 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(' • A running, framework-ENFORCED NestJS app — every route is denied by');
157
- console.log(' default (ADR-0002) unless you classify it with @Public()/@Classified().');
158
- console.log(' • The framework installed as immutable dependencies (@astram/core,');
159
- console.log(' @astram/guard, @astram/sdk) — you never edit it; upgrade via `npm update`.');
160
- console.log(' • An empty src/features/ — your editable space. Add a feature by writing a');
161
- console.log(' Nest module/controller there and registering it in src/app.module.ts.');
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 app; GET /health → 200, unclassified → 403');
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, so a consumer whose shell happens to export NODE_ENV=test
175
- // still gets a working `npx create-astram-app` (avoids a silent no-op).
176
- const invokedAsCli = process.argv[1] !== undefined &&
177
- import.meta.url === pathToFileURL(process.argv[1]).href;
178
- if (invokedAsCli) {
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":"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"}
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.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).
@@ -1,24 +1,37 @@
1
1
  # __APP_NAME__
2
2
 
3
- An **Astram consumer app**, scaffolded with `create-astram-app`. It depends on the
4
- published, immutable framework packages ([`@astram/core`](https://www.npmjs.com/package/@astram/core),
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
- lives read-only in `node_modules` and upgrades via `npm update` — you never edit it.
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
- ## Run it
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 app (tsx watch) on http://localhost:3000
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 # 200 — @Public health probe
21
- curl -i localhost:3000/demo/public # 200 — explicit @Public() opt-out
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` (the enforcement e2e), `npm run lint`.
42
+ Other `server/` scripts: `npm run build` (→ `dist/`), `npm start` (run the build),
43
+ `npm test`, `npm run lint`.
31
44
 
32
- ## How it wires the framework
45
+ ## How the API wires the framework
33
46
 
34
- `src/app.module.ts` imports the ONE registration seam — no local security wiring:
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(...)` for
56
- guarded ones. Anything unclassified is denied (403) by design.
57
- 4. Add a test (see `test/enforcement.e2e.spec.ts` for the pattern).
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
- ## What's here today (and what's next)
79
+ ## The frontend
62
80
 
63
- Today you get correct-by-default **enforcement** out of the box: the framework's
64
- guard protects every route from the first boot. A turnkey **login / session**
65
- boilerplate (wired through the same `ShieldModule.forRoot()` seam) arrives in a
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
- This is your **project-owned** AI/dev guide layer (ADR-0025) — framework upgrades
88
- never touch it. Put your project's own rules and context there. See
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`.
@@ -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();
@@ -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