create-astram-app 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/add-frontend.js +107 -0
  2. package/dist/add-frontend.js.map +1 -0
  3. package/dist/index.js +132 -33
  4. package/dist/index.js.map +1 -1
  5. package/package.json +3 -2
  6. package/template/CLAUDE.md +51 -0
  7. package/template/README.md +43 -44
  8. package/template/_gitignore +6 -0
  9. package/template/frontend/FRONTEND.md +92 -0
  10. package/template/frontend/README.md +16 -0
  11. package/template/frontend/_env.example +6 -0
  12. package/template/frontend/_gitignore +9 -0
  13. package/template/frontend/eslint.config.js +71 -0
  14. package/template/frontend/index.html +13 -0
  15. package/template/frontend/package.json +52 -0
  16. package/template/frontend/public/astram-logo-full.png +0 -0
  17. package/template/frontend/public/astram-logo-white.png +0 -0
  18. package/template/frontend/public/favicon.png +0 -0
  19. package/template/frontend/src/App.jsx +167 -0
  20. package/template/frontend/src/auth/AuthContext.jsx +338 -0
  21. package/template/frontend/src/components/ConfirmDialog.jsx +92 -0
  22. package/template/frontend/src/components/EntityFormModal.jsx +154 -0
  23. package/template/frontend/src/components/EntityList.jsx +480 -0
  24. package/template/frontend/src/components/PageHeader.jsx +54 -0
  25. package/template/frontend/src/components/access/CollapsibleGroup.jsx +34 -0
  26. package/template/frontend/src/components/ui/alert-dialog.tsx +175 -0
  27. package/template/frontend/src/components/ui/alert.tsx +83 -0
  28. package/template/frontend/src/components/ui/avatar.tsx +51 -0
  29. package/template/frontend/src/components/ui/badge.tsx +56 -0
  30. package/template/frontend/src/components/ui/button.tsx +67 -0
  31. package/template/frontend/src/components/ui/card.tsx +92 -0
  32. package/template/frontend/src/components/ui/checkbox.tsx +30 -0
  33. package/template/frontend/src/components/ui/date-field.tsx +47 -0
  34. package/template/frontend/src/components/ui/dialog.tsx +214 -0
  35. package/template/frontend/src/components/ui/dropdown-menu.tsx +255 -0
  36. package/template/frontend/src/components/ui/input-otp.tsx +81 -0
  37. package/template/frontend/src/components/ui/input.tsx +21 -0
  38. package/template/frontend/src/components/ui/label.tsx +46 -0
  39. package/template/frontend/src/components/ui/searchable-select.tsx +459 -0
  40. package/template/frontend/src/components/ui/select.tsx +199 -0
  41. package/template/frontend/src/components/ui/separator.tsx +26 -0
  42. package/template/frontend/src/components/ui/sheet.tsx +139 -0
  43. package/template/frontend/src/components/ui/skeleton.tsx +13 -0
  44. package/template/frontend/src/components/ui/sonner.tsx +23 -0
  45. package/template/frontend/src/components/ui/table.tsx +181 -0
  46. package/template/frontend/src/components/ui/tooltip.tsx +59 -0
  47. package/template/frontend/src/config/branding.js +80 -0
  48. package/template/frontend/src/hooks/useMobile.tsx +21 -0
  49. package/template/frontend/src/index.css +265 -0
  50. package/template/frontend/src/lib/utils.js +7 -0
  51. package/template/frontend/src/main.jsx +27 -0
  52. package/template/frontend/src/pages/Dashboard.jsx +127 -0
  53. package/template/frontend/src/pages/ForgotPassword.jsx +104 -0
  54. package/template/frontend/src/pages/Login.jsx +393 -0
  55. package/template/frontend/src/pages/ResetPassword.jsx +159 -0
  56. package/template/frontend/src/pages/access/AccessForm.jsx +416 -0
  57. package/template/frontend/src/pages/access/AccessPage.jsx +282 -0
  58. package/template/frontend/src/pages/access/RoleActionsMenu.jsx +42 -0
  59. package/template/frontend/src/pages/account/AccountLayout.jsx +229 -0
  60. package/template/frontend/src/pages/account/ChangePasswordPage.jsx +235 -0
  61. package/template/frontend/src/pages/account/ProfilePage.jsx +122 -0
  62. package/template/frontend/src/pages/account/ReenrollPage.jsx +98 -0
  63. package/template/frontend/src/pages/account/TwoFactorPage.jsx +833 -0
  64. package/template/frontend/src/pages/audit/AuditPage.jsx +268 -0
  65. package/template/frontend/src/pages/companies/CompaniesPage.jsx +232 -0
  66. package/template/frontend/src/pages/companies/CompanyForm.jsx +127 -0
  67. package/template/frontend/src/pages/roles/RoleForm.jsx +99 -0
  68. package/template/frontend/src/pages/roles/RolesPage.jsx +207 -0
  69. package/template/frontend/src/pages/users/AssignmentsEditor.jsx +193 -0
  70. package/template/frontend/src/pages/users/MembershipCard.jsx +303 -0
  71. package/template/frontend/src/pages/users/PostingRow.jsx +264 -0
  72. package/template/frontend/src/pages/users/UserForm.jsx +167 -0
  73. package/template/frontend/src/pages/users/UsersPage.jsx +232 -0
  74. package/template/frontend/src/platform/httpClient.js +125 -0
  75. package/template/frontend/src/platform/returnPathStore.js +43 -0
  76. package/template/frontend/src/platform/sessionStore.js +29 -0
  77. package/template/frontend/src/platform/shieldTransport.js +172 -0
  78. package/template/frontend/src/platform/shieldTransport.test.mjs +245 -0
  79. package/template/frontend/src/shell/AppShell.jsx +237 -0
  80. package/template/frontend/src/shell/Sidebar.jsx +257 -0
  81. package/template/frontend/src/shell/nav.js +90 -0
  82. package/template/frontend/src/theme/ThemeProvider.jsx +14 -0
  83. package/template/frontend/src/theme/ThemeToggle.jsx +44 -0
  84. package/template/frontend/tsconfig.json +22 -0
  85. package/template/frontend/vite.config.js +30 -0
  86. package/template/server/_env.example +45 -0
  87. package/template/server/eslint.config.js +95 -0
  88. package/template/server/package.json +49 -0
  89. package/template/server/scripts/dev.mjs +51 -0
  90. package/template/server/scripts/sync-astram-guide.mjs +63 -0
  91. package/template/_env.example +0 -33
  92. package/template/package.json +0 -37
  93. /package/template/{src → server/src}/app.module.ts +0 -0
  94. /package/template/{src → server/src}/features/_gitkeep +0 -0
  95. /package/template/{src → server/src}/health/demo.controller.ts +0 -0
  96. /package/template/{src → server/src}/health/health.controller.ts +0 -0
  97. /package/template/{src → server/src}/main.ts +0 -0
  98. /package/template/{test → server/test}/enforcement.e2e.spec.ts +0 -0
  99. /package/template/{tsconfig.build.json → server/tsconfig.build.json} +0 -0
  100. /package/template/{tsconfig.json → server/tsconfig.json} +0 -0
  101. /package/template/{vitest.config.ts → server/vitest.config.ts} +0 -0
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `astram-add-frontend` — additive frontend installer for an EXISTING Astram app
4
+ * (ADR-0023 §3, Task B8).
5
+ *
6
+ * cd my-existing-app
7
+ * npx astram-add-frontend
8
+ *
9
+ * This is the SECOND delivery path for `template/frontend/` (the Vite/React
10
+ * identity UI) alongside `create-astram-app <name> --with-frontend` (Task B7,
11
+ * which emits it at scaffold time for a BRAND NEW app). `astram-add-frontend`
12
+ * targets an app that was scaffolded earlier WITHOUT `--with-frontend`, or any
13
+ * existing directory a developer wants to bolt the identity UI onto later.
14
+ *
15
+ * Copies `template/frontend/` → `<cwd>/frontend/` using the EXACT same
16
+ * `copyTemplate` skip-if-exists semantics as the main scaffolder (imported from
17
+ * `./index.js`, not re-implemented): existing files are NEVER overwritten, so
18
+ * re-running this bin after the developer has edited `frontend/` is always safe
19
+ * — it only adds files that are still missing (e.g. a newer template picked up
20
+ * a file this app doesn't have yet).
21
+ */
22
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
23
+ import { basename, join, resolve } from 'node:path';
24
+ import { fileURLToPath, pathToFileURL } from 'node:url';
25
+ import { copyTemplate, templateDir } from './index.js';
26
+ /**
27
+ * The app name substituted into `__APP_NAME__` tokens in the copied frontend
28
+ * files (`package.json` name, `<title>`, comments — see `template/frontend/`).
29
+ * Resolved from the CURRENT DIRECTORY'S OWN `package.json` `name` field when
30
+ * present (this is an EXISTING app being extended, not a fresh scaffold), else
31
+ * falls back to the directory's basename.
32
+ */
33
+ export function resolveAppName(cwd) {
34
+ const pkgPath = join(cwd, 'package.json');
35
+ if (existsSync(pkgPath)) {
36
+ try {
37
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
38
+ if (typeof pkg.name === 'string' && pkg.name.trim().length > 0) {
39
+ return pkg.name.trim();
40
+ }
41
+ }
42
+ catch {
43
+ // Malformed/unreadable package.json — fall through to the directory name
44
+ // rather than failing the whole add-frontend run over a cosmetic token.
45
+ }
46
+ }
47
+ return basename(cwd);
48
+ }
49
+ /**
50
+ * Add `frontend/` to the app rooted at `cwd` (default `process.cwd()`). Returns
51
+ * the target dir + copy report. Exported for tests (which pass an explicit
52
+ * `cwd`), mirroring `scaffold()` in `index.ts`.
53
+ */
54
+ export function addFrontend(cwd = process.cwd()) {
55
+ const appName = resolveAppName(cwd);
56
+ const targetDir = resolve(cwd, 'frontend');
57
+ const report = { written: [], skipped: [] };
58
+ copyTemplate(join(templateDir(), 'frontend'), targetDir, appName, report, 'frontend');
59
+ return { targetDir, report };
60
+ }
61
+ /** Entry point. Returns a process exit code (0 ok, non-zero on error). */
62
+ export function main() {
63
+ let result;
64
+ try {
65
+ result = addFrontend(process.cwd());
66
+ }
67
+ catch (err) {
68
+ console.error(`astram-add-frontend: ${err.message}`);
69
+ return 1;
70
+ }
71
+ const { report } = result;
72
+ console.log('\nAdded frontend/ (Vite/React identity UI) to this app.');
73
+ console.log(` ${report.written.length} file(s) written.`);
74
+ if (report.skipped.length > 0) {
75
+ console.log(` ${report.skipped.length} existing file(s) left untouched (never-clobber): ${report.skipped.join(', ')}`);
76
+ }
77
+ console.log('\nNext steps:');
78
+ console.log(' cd frontend');
79
+ console.log(' npm install');
80
+ console.log(' npm run dev # boots the identity UI (login, session)');
81
+ console.log('');
82
+ return 0;
83
+ }
84
+ // Run ONLY when this module is the process entry point (i.e. executed as the
85
+ // CLI), NOT when imported by a test. Identical symlink-safe realpath check to
86
+ // `index.ts`'s `isInvokedAsCli()`: `npx`/npm run a `bin` through a SYMLINK, so
87
+ // `process.argv[1]` is the symlink path, not this real dist file — resolve
88
+ // (realpath) before comparing, or the CLI silently no-ops under npx. When
89
+ // imported by a test, argv[1] is the test runner, resolving to a different
90
+ // real file → correctly skips.
91
+ function isInvokedAsCli() {
92
+ const entry = process.argv[1];
93
+ if (entry === undefined)
94
+ return false;
95
+ const thisFile = fileURLToPath(import.meta.url);
96
+ try {
97
+ return realpathSync(entry) === realpathSync(thisFile);
98
+ }
99
+ catch {
100
+ // If either path can't be realpath'd, fall back to a direct compare.
101
+ return pathToFileURL(entry).href === import.meta.url;
102
+ }
103
+ }
104
+ if (isInvokedAsCli()) {
105
+ process.exitCode = main();
106
+ }
107
+ //# sourceMappingURL=add-frontend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-frontend.js","sourceRoot":"","sources":["../src/add-frontend.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,7 +2,8 @@
2
2
  /**
3
3
  * `create-astram-app` — the Astram app scaffolder (ADR-0023 §3).
4
4
  *
5
- * npx create-astram-app <name>
5
+ * npx create-astram-app <name> # server/ only
6
+ * npx create-astram-app <name> --with-frontend # + frontend/ (identity UI)
6
7
  *
7
8
  * Emits a NEW consumer app directory that depends on the published, immutable
8
9
  * framework packages (`@astram/core`, `@astram/guard`, `@astram/sdk`) via real
@@ -21,12 +22,20 @@
21
22
  * Reference skeleton: `examples/example-backend/` in the astram-framework repo.
22
23
  */
23
24
  import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
24
- import { dirname, join, resolve } from 'node:path';
25
+ import { basename, dirname, join, resolve } from 'node:path';
25
26
  import { fileURLToPath, pathToFileURL } from 'node:url';
26
27
  /** Token substituted with the app name at copy time (in package.json name, README title, …). */
27
28
  const APP_NAME_TOKEN = '__APP_NAME__';
28
- /** Directory (inside the published package) that holds the emitted app template. */
29
- function templateDir() {
29
+ /**
30
+ * Directory (inside the published package) that holds the emitted app template.
31
+ * Resolved relative to THIS file's own compiled location, NOT the caller's —
32
+ * safe to import from a sibling bin (e.g. `add-frontend.ts`, Task B8) because
33
+ * both compile into the SAME `dist/` directory, so `dist/index.js/../template`
34
+ * and `dist/add-frontend.js/../template` are identical. Exported so sibling
35
+ * bins reuse this exact path-math instead of duplicating (and risking drift
36
+ * from) it.
37
+ */
38
+ export function templateDir() {
30
39
  // dist/index.js → ../template (bundled via the package's `files` field).
31
40
  const here = dirname(fileURLToPath(import.meta.url));
32
41
  return resolve(here, '..', 'template');
@@ -59,15 +68,42 @@ export function validateAppName(name) {
59
68
  return null;
60
69
  }
61
70
  const USAGE = [
62
- 'Usage: create-astram-app <name>',
71
+ 'Usage: create-astram-app <name> [--with-frontend]',
63
72
  '',
64
73
  'Scaffolds a new Astram consumer app that depends on the published framework',
65
74
  '(@astram/core, @astram/guard, @astram/sdk) and enforces deny-by-default from',
66
75
  'the first boot.',
67
76
  '',
77
+ ' --with-frontend Also scaffold frontend/ (Vite/React identity UI).',
78
+ ' Without this flag, only server/ is scaffolded.',
79
+ '',
68
80
  'Example:',
69
81
  ' npx create-astram-app my-app',
82
+ ' npx create-astram-app my-app --with-frontend',
70
83
  ].join('\n');
84
+ /**
85
+ * Copy a SINGLE template file `srcPath` → `destPath` (never a directory),
86
+ * substituting {@link APP_NAME_TOKEN} in text file CONTENTS, and NEVER
87
+ * overwriting a file that already exists at the destination (spec §3.1 —
88
+ * never clobber developer-authored files). `rel` is the path relative to the
89
+ * template root, recorded in `report.written`/`report.skipped`.
90
+ */
91
+ function copyTemplateFile(srcPath, destPath, appName, report, rel) {
92
+ if (existsSync(destPath)) {
93
+ // Never-clobber: leave the developer's file exactly as-is.
94
+ report.skipped.push(rel);
95
+ return;
96
+ }
97
+ const raw = readFileSync(srcPath);
98
+ if (isTextFile(basename(srcPath))) {
99
+ const substituted = raw.toString('utf8').split(APP_NAME_TOKEN).join(appName);
100
+ writeFileSync(destPath, substituted, 'utf8');
101
+ }
102
+ else {
103
+ cpSync(srcPath, destPath);
104
+ }
105
+ report.written.push(rel);
106
+ }
71
107
  /**
72
108
  * Recursively copy `srcDir` → `destDir`, substituting {@link APP_NAME_TOKEN} in
73
109
  * text file CONTENTS, and NEVER overwriting a file that already exists at the
@@ -75,8 +111,14 @@ const USAGE = [
75
111
  * are created as needed; only missing files are written.
76
112
  *
77
113
  * `relBase` accumulates the path relative to the template root for reporting.
114
+ *
115
+ * Exported so sibling bins (e.g. `add-frontend.ts`, Task B8) reuse this EXACT
116
+ * copy semantics — skip-if-exists / never-clobber / __APP_NAME__ substitution —
117
+ * for a single-subtree copy (`template/frontend` → `<cwd>/frontend`) instead of
118
+ * a full app scaffold, without re-implementing (and risking drift from) the
119
+ * never-clobber contract.
78
120
  */
79
- function copyTemplate(srcDir, destDir, appName, report, relBase = '') {
121
+ export function copyTemplate(srcDir, destDir, appName, report, relBase = '') {
80
122
  mkdirSync(destDir, { recursive: true });
81
123
  for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
82
124
  const srcPath = join(srcDir, entry.name);
@@ -90,31 +132,30 @@ function copyTemplate(srcDir, destDir, appName, report, relBase = '') {
90
132
  copyTemplate(srcPath, destPath, appName, report, rel);
91
133
  continue;
92
134
  }
93
- if (existsSync(destPath)) {
94
- // Never-clobber: leave the developer's file exactly as-is.
95
- report.skipped.push(rel);
96
- continue;
97
- }
98
- const raw = readFileSync(srcPath);
99
- if (isTextFile(entry.name)) {
100
- const substituted = raw.toString('utf8').split(APP_NAME_TOKEN).join(appName);
101
- writeFileSync(destPath, substituted, 'utf8');
102
- }
103
- else {
104
- cpSync(srcPath, destPath);
105
- }
106
- report.written.push(rel);
135
+ copyTemplateFile(srcPath, destPath, appName, report, rel);
107
136
  }
108
137
  }
109
138
  /** Text extensions that get token substitution + utf8 handling. */
110
139
  function isTextFile(fileName) {
111
140
  return /\.(json|ts|js|mjs|cjs|md|txt|example|yml|yaml|gitignore)$/i.test(fileName) || fileName.startsWith('_');
112
141
  }
142
+ /**
143
+ * Names of the template's top-level SUBTREES that are copied separately (not
144
+ * via the generic root-level pass below), because each is independently
145
+ * gated: `server/` is always emitted, `frontend/` only with `--with-frontend`.
146
+ */
147
+ const TEMPLATE_SUBTREES = ['server', 'frontend'];
113
148
  /**
114
149
  * Scaffold `appName` into `<cwd>/<appName>`. Returns the copy report + resolved
115
150
  * target dir. Exported for tests (which pass an explicit `cwd`).
151
+ *
152
+ * `withFrontend` gates the `template/frontend/` → `<app>/frontend/` copy — the
153
+ * scaffolded app is server-only by default; `--with-frontend` (CLI) or
154
+ * `withFrontend: true` (programmatic) also emits the Vite/React identity UI.
155
+ * `server/` and the app-root files (`.astram/guide/`, `CLAUDE.md`, `.gitignore`,
156
+ * `README.md`) are unconditional either way — unchanged from prior behavior.
116
157
  */
117
- export function scaffold(appName, cwd = process.cwd()) {
158
+ export function scaffold(appName, cwd = process.cwd(), withFrontend = false) {
118
159
  const targetDir = resolve(cwd, appName);
119
160
  const report = { written: [], skipped: [] };
120
161
  if (existsSync(targetDir)) {
@@ -125,12 +166,56 @@ export function scaffold(appName, cwd = process.cwd()) {
125
166
  // Existing dir is allowed — copyTemplate skips any file that already exists,
126
167
  // so developer-authored docs/, .astram/guide/, README, etc. survive untouched.
127
168
  }
128
- copyTemplate(templateDir(), targetDir, appName, report);
169
+ const template = templateDir();
170
+ // The generic per-directory copyTemplate() calls always mkdirSync their own
171
+ // destDir first; the app root itself needs the same treatment since
172
+ // root-level FILES (e.g. CLAUDE.md) are written directly into targetDir
173
+ // below without going through copyTemplate.
174
+ mkdirSync(targetDir, { recursive: true });
175
+ // App-root files/dirs (.astram/guide/, CLAUDE.md, .gitignore, README.md, …) —
176
+ // everything directly under template/ EXCEPT the independently-gated
177
+ // server/ and frontend/ subtrees, which are copied below. Dispatch by dirent
178
+ // type: a directory recurses via copyTemplate; a plain file (e.g. CLAUDE.md)
179
+ // copies via copyTemplateFile directly — copyTemplate always treats its
180
+ // srcDir arg as a directory, so calling it on a FILE path throws ENOTDIR.
181
+ for (const entry of readdirSync(template, { withFileTypes: true })) {
182
+ if (TEMPLATE_SUBTREES.includes(entry.name))
183
+ continue;
184
+ const srcPath = join(template, entry.name);
185
+ const destName = entry.name.startsWith('_') ? `.${entry.name.slice(1)}` : entry.name;
186
+ const destPath = join(targetDir, destName);
187
+ if (entry.isDirectory()) {
188
+ copyTemplate(srcPath, destPath, appName, report, destName);
189
+ }
190
+ else {
191
+ copyTemplateFile(srcPath, destPath, appName, report, destName);
192
+ }
193
+ }
194
+ // server/ — always scaffolded (unchanged prior behavior).
195
+ copyTemplate(join(template, 'server'), join(targetDir, 'server'), appName, report, 'server');
196
+ // frontend/ — opt-in via --with-frontend (Task B7). Previously this was
197
+ // copied unconditionally as part of the wholesale template/ copy; it is now
198
+ // gated so a plain `create-astram-app <name>` stays server-only.
199
+ if (withFrontend) {
200
+ copyTemplate(join(template, 'frontend'), join(targetDir, 'frontend'), appName, report, 'frontend');
201
+ }
129
202
  return { targetDir, report };
130
203
  }
204
+ /** Long-form boolean flags this CLI recognizes (anywhere in argv). */
205
+ const WITH_FRONTEND_FLAG = '--with-frontend';
206
+ /**
207
+ * The app name is the first argv entry that is NOT a recognized `--flag` —
208
+ * robust to `--with-frontend` appearing before OR after the name
209
+ * (`create-astram-app --with-frontend my-app` and
210
+ * `create-astram-app my-app --with-frontend` both work).
211
+ */
212
+ function parseAppName(argv) {
213
+ return argv.find((arg) => !arg.startsWith('--'));
214
+ }
131
215
  /** Entry point. Returns a process exit code (0 ok, non-zero on error). */
132
216
  export function main(argv = process.argv.slice(2)) {
133
- const name = argv[0];
217
+ const name = parseAppName(argv);
218
+ const withFrontend = argv.includes(WITH_FRONTEND_FLAG);
134
219
  const validationError = validateAppName(name);
135
220
  if (validationError) {
136
221
  console.error(`create-astram-app: ${validationError}\n`);
@@ -140,7 +225,7 @@ export function main(argv = process.argv.slice(2)) {
140
225
  const appName = name.trim();
141
226
  let report;
142
227
  try {
143
- ({ report } = scaffold(appName));
228
+ ({ report } = scaffold(appName, process.cwd(), withFrontend));
144
229
  }
145
230
  catch (err) {
146
231
  console.error(`create-astram-app: ${err.message}`);
@@ -152,20 +237,34 @@ export function main(argv = process.argv.slice(2)) {
152
237
  if (report.skipped.length > 0) {
153
238
  console.log(` ${report.skipped.length} existing file(s) left untouched (never-clobber): ${report.skipped.join(', ')}`);
154
239
  }
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.');
240
+ console.log('\nWhat you got (a monorepo):');
241
+ console.log(' • server/ — a running, framework-ENFORCED NestJS API. Every route is denied');
242
+ console.log(' by default (ADR-0002) unless you classify it @Public()/@Classified().');
243
+ if (withFrontend) {
244
+ console.log(' • frontend/ — a Vite/React identity UI (login, session) wired to the API.');
245
+ }
246
+ else {
247
+ console.log(' • frontend/ — NOT scaffolded (pass --with-frontend to also emit the Vite/React');
248
+ console.log(' identity UI). You can still add any UI framework yourself later.');
249
+ }
250
+ console.log(' • The framework installed in server/ as immutable dependencies (@astram/core,');
251
+ console.log(' @astram/guard, @astram/engine, @astram/sdk) — you never edit it; upgrade via');
252
+ console.log(' `npm update`.');
253
+ console.log(' • server/src/features/ — your editable API space. Add a feature by writing a');
254
+ console.log(' Nest module/controller there and registering it in server/src/app.module.ts.');
162
255
  console.log(' • .astram/guide/ — your own, upgrade-safe project-rules space (ADR-0025).');
163
256
  console.log('\n (A turnkey login/session boilerplate ships in a later framework release;');
164
257
  console.log(' today the app gives you correct-by-default ENFORCEMENT out of the box.)');
165
258
  console.log('\nNext steps:');
166
- console.log(` cd ${appName}`);
259
+ console.log(` cd ${appName}/server`);
167
260
  console.log(' npm install');
168
- console.log(' npm run dev # boots the app; GET /health → 200, unclassified → 403');
261
+ console.log(' npm run dev # boots the API; GET /health → 200, unclassified → 403');
262
+ if (withFrontend) {
263
+ console.log('');
264
+ console.log(` cd ${appName}/frontend`);
265
+ console.log(' npm install');
266
+ console.log(' npm run dev # boots the identity UI (login, session)');
267
+ }
169
268
  console.log('');
170
269
  return 0;
171
270
  }
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,11 +1,12 @@
1
1
  {
2
2
  "name": "create-astram-app",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
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",
7
7
  "bin": {
8
- "create-astram-app": "dist/index.js"
8
+ "create-astram-app": "dist/index.js",
9
+ "astram-add-frontend": "dist/add-frontend.js"
9
10
  },
10
11
  "publishConfig": {
11
12
  "access": "public"
@@ -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