create-astram-app 0.1.3 → 0.3.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 (88) hide show
  1. package/dist/add-frontend.js +107 -0
  2. package/dist/add-frontend.js.map +1 -0
  3. package/dist/index.js +122 -25
  4. package/dist/index.js.map +1 -1
  5. package/package.json +3 -2
  6. package/template/frontend/FRONTEND.md +92 -0
  7. package/template/frontend/_env.example +6 -0
  8. package/template/frontend/_gitignore +9 -0
  9. package/template/frontend/eslint.config.js +71 -0
  10. package/template/frontend/index.html +13 -0
  11. package/template/frontend/package.json +52 -0
  12. package/template/frontend/public/astram-logo-full.png +0 -0
  13. package/template/frontend/public/astram-logo-white.png +0 -0
  14. package/template/frontend/public/favicon.png +0 -0
  15. package/template/frontend/src/App.jsx +191 -0
  16. package/template/frontend/src/auth/AuthContext.jsx +343 -0
  17. package/template/frontend/src/components/ConfirmDialog.jsx +92 -0
  18. package/template/frontend/src/components/EntityFormModal.jsx +154 -0
  19. package/template/frontend/src/components/EntityList.jsx +480 -0
  20. package/template/frontend/src/components/PageHeader.jsx +54 -0
  21. package/template/frontend/src/components/access/CollapsibleGroup.jsx +34 -0
  22. package/template/frontend/src/components/ui/alert-dialog.tsx +175 -0
  23. package/template/frontend/src/components/ui/alert.tsx +83 -0
  24. package/template/frontend/src/components/ui/avatar.tsx +51 -0
  25. package/template/frontend/src/components/ui/badge.tsx +56 -0
  26. package/template/frontend/src/components/ui/button.tsx +67 -0
  27. package/template/frontend/src/components/ui/card.tsx +92 -0
  28. package/template/frontend/src/components/ui/checkbox.tsx +30 -0
  29. package/template/frontend/src/components/ui/date-field.tsx +47 -0
  30. package/template/frontend/src/components/ui/dialog.tsx +214 -0
  31. package/template/frontend/src/components/ui/dropdown-menu.tsx +255 -0
  32. package/template/frontend/src/components/ui/input-otp.tsx +81 -0
  33. package/template/frontend/src/components/ui/input.tsx +21 -0
  34. package/template/frontend/src/components/ui/label.tsx +46 -0
  35. package/template/frontend/src/components/ui/searchable-select.tsx +459 -0
  36. package/template/frontend/src/components/ui/select.tsx +199 -0
  37. package/template/frontend/src/components/ui/separator.tsx +26 -0
  38. package/template/frontend/src/components/ui/sheet.tsx +139 -0
  39. package/template/frontend/src/components/ui/skeleton.tsx +13 -0
  40. package/template/frontend/src/components/ui/sonner.tsx +23 -0
  41. package/template/frontend/src/components/ui/table.tsx +181 -0
  42. package/template/frontend/src/components/ui/tooltip.tsx +59 -0
  43. package/template/frontend/src/config/branding.js +80 -0
  44. package/template/frontend/src/hooks/useMobile.tsx +21 -0
  45. package/template/frontend/src/index.css +265 -0
  46. package/template/frontend/src/lib/utils.js +7 -0
  47. package/template/frontend/src/main.jsx +27 -0
  48. package/template/frontend/src/pages/Dashboard.jsx +127 -0
  49. package/template/frontend/src/pages/ForgotPassword.jsx +104 -0
  50. package/template/frontend/src/pages/Login.jsx +393 -0
  51. package/template/frontend/src/pages/ResetPassword.jsx +159 -0
  52. package/template/frontend/src/pages/access/AccessForm.jsx +416 -0
  53. package/template/frontend/src/pages/access/AccessPage.jsx +282 -0
  54. package/template/frontend/src/pages/access/RoleActionsMenu.jsx +42 -0
  55. package/template/frontend/src/pages/account/AccountLayout.jsx +229 -0
  56. package/template/frontend/src/pages/account/ChangePasswordPage.jsx +235 -0
  57. package/template/frontend/src/pages/account/ProfilePage.jsx +122 -0
  58. package/template/frontend/src/pages/account/ReenrollPage.jsx +98 -0
  59. package/template/frontend/src/pages/account/TwoFactorPage.jsx +833 -0
  60. package/template/frontend/src/pages/audit/AuditPage.jsx +268 -0
  61. package/template/frontend/src/pages/companies/CompaniesPage.jsx +232 -0
  62. package/template/frontend/src/pages/companies/CompanyForm.jsx +127 -0
  63. package/template/frontend/src/pages/cross-tenant/GrantForm.jsx +337 -0
  64. package/template/frontend/src/pages/cross-tenant/GrantsPage.jsx +237 -0
  65. package/template/frontend/src/pages/cross-tenant/InboxPage.jsx +179 -0
  66. package/template/frontend/src/pages/roles/RoleForm.jsx +99 -0
  67. package/template/frontend/src/pages/roles/RolesPage.jsx +207 -0
  68. package/template/frontend/src/pages/users/AssignmentsEditor.jsx +193 -0
  69. package/template/frontend/src/pages/users/MembershipCard.jsx +303 -0
  70. package/template/frontend/src/pages/users/PostingRow.jsx +264 -0
  71. package/template/frontend/src/pages/users/UserForm.jsx +167 -0
  72. package/template/frontend/src/pages/users/UsersPage.jsx +232 -0
  73. package/template/frontend/src/platform/httpClient.js +125 -0
  74. package/template/frontend/src/platform/returnPathStore.js +43 -0
  75. package/template/frontend/src/platform/sessionStore.js +29 -0
  76. package/template/frontend/src/platform/shieldTransport.js +172 -0
  77. package/template/frontend/src/platform/shieldTransport.test.mjs +245 -0
  78. package/template/frontend/src/shell/AppShell.jsx +237 -0
  79. package/template/frontend/src/shell/Sidebar.jsx +257 -0
  80. package/template/frontend/src/shell/nav.js +103 -0
  81. package/template/frontend/src/theme/ThemeProvider.jsx +14 -0
  82. package/template/frontend/src/theme/ThemeToggle.jsx +44 -0
  83. package/template/frontend/tsconfig.json +22 -0
  84. package/template/frontend/vite.config.js +30 -0
  85. package/template/server/_env.example +27 -15
  86. package/template/server/package.json +7 -4
  87. package/template/server/src/app.module.ts +28 -2
  88. package/template/server/src/health/demo.controller.ts +23 -12
@@ -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}`);
@@ -155,7 +240,13 @@ export function main(argv = process.argv.slice(2)) {
155
240
  console.log('\nWhat you got (a monorepo):');
156
241
  console.log(' • server/ — a running, framework-ENFORCED NestJS API. Every route is denied');
157
242
  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.).');
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
+ }
159
250
  console.log(' • The framework installed in server/ as immutable dependencies (@astram/core,');
160
251
  console.log(' @astram/guard, @astram/engine, @astram/sdk) — you never edit it; upgrade via');
161
252
  console.log(' `npm update`.');
@@ -168,6 +259,12 @@ export function main(argv = process.argv.slice(2)) {
168
259
  console.log(` cd ${appName}/server`);
169
260
  console.log(' npm install');
170
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
+ }
171
268
  console.log('');
172
269
  return 0;
173
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.3",
3
+ "version": "0.3.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,92 @@
1
+ # __APP_NAME__ frontend
2
+
3
+ This is the Astram framework's **identity frontend** — login, session, users, roles,
4
+ companies, access, audit — scaffolded straight into your app. It is a real,
5
+ production-shaped Vite/React UI (shadcn/ui + Tailwind v4), not a placeholder.
6
+
7
+ ## What this is
8
+
9
+ The files under `frontend/` are **yours** — you own and edit them like any other
10
+ part of your app. The framework does not manage or overwrite them once they're
11
+ here.
12
+
13
+ The actual auth/session/API **logic** does NOT live in these files — it comes from
14
+ two npm packages:
15
+
16
+ - **`@astram/frontend-core`** — auth flows, session handling, HTTP client wiring.
17
+ - **`@astram/sdk`** — typed calls against the framework API (users, roles,
18
+ companies, access, audit).
19
+
20
+ That split is what makes this model work: the **view** (these files: pages,
21
+ components, routing, styling) is scaffolded once and is fully yours to change;
22
+ the **logic** (auth correctness, new API surface, bug fixes) ships as ordinary
23
+ npm package updates you pull in with `npm update`, without touching a single file
24
+ in this folder.
25
+
26
+ ## Run
27
+
28
+ ```bash
29
+ npm install
30
+ npm run dev
31
+ ```
32
+
33
+ - Opens the identity UI on **http://localhost:5173**.
34
+ - Log in at **`/login`**.
35
+ - The Vite dev server proxies **`/api`** to your backend on **`http://localhost:3000`**
36
+ (see `vite.config.js`) — the browser calls same-origin `/api/auth/login`, and the
37
+ proxy rewrites it to `/auth/login` on the server. Override the target with the
38
+ `API_URL` env var if your backend runs elsewhere.
39
+
40
+ Make sure the framework backend (`../server`) is running first — this UI has
41
+ nothing to talk to otherwise.
42
+
43
+ ## Re-skin
44
+
45
+ You don't need to touch component code to make this look like your product:
46
+
47
+ - **Name / logo** — copy `.env.example` to `.env` and set:
48
+ - `VITE_APP_NAME` — shown in the header/title instead of the default.
49
+ - `VITE_APP_LOGO` — path/URL to your logo (light theme).
50
+ - `VITE_APP_LOGO_DARK` — path/URL to your logo (dark theme).
51
+ - `VITE_APP_TAGLINE` / `VITE_APP_SUB_TAGLINE` — login-screen copy.
52
+ - **Theme color** — edit the `--primary` / `--primary-foreground` oklch pair in
53
+ `src/index.css`: the `:root` block controls light mode, the `.dark` block
54
+ controls dark mode. Every primary button, focus ring, and accent in the UI
55
+ derives from this one token pair — change it once, it propagates everywhere.
56
+
57
+ Both of these are **additive, config-only** customizations — they don't modify
58
+ any scaffolded file, so a future framework/template update never conflicts with
59
+ your branding.
60
+
61
+ ## Updates / no-clobber model
62
+
63
+ Two different things update on two different schedules, and it's important to
64
+ know which is which:
65
+
66
+ 1. **Logic (`@astram/frontend-core`, `@astram/sdk`)** updates like any other npm
67
+ dependency — `npm update` pulls in bug fixes and new API methods for free. You
68
+ never edit these packages directly (they live in `node_modules`).
69
+
70
+ 2. **View files (everything under `frontend/src/`, `frontend/index.html`, etc.)**
71
+ are yours. They were copied in ONCE, by either `create-astram-app --with-frontend`
72
+ at scaffold time or by `npx astram-add-frontend` afterwards. Nothing in the
73
+ framework reaches back into this folder to overwrite your edits — this file, a
74
+ page you've re-themed, a component you've customized, all survive indefinitely.
75
+
76
+ 3. **Re-running `npx astram-add-frontend` is safe, any time.** It's additive and
77
+ idempotent: it only writes files that are still missing (e.g. the published
78
+ template gained a new page or component since you last ran it). Any file that
79
+ already exists in your `frontend/` — including one you've heavily edited — is
80
+ left completely untouched. It will never overwrite your work.
81
+
82
+ 4. **Adopting an improved framework screen is a manual, deliberate step**, not an
83
+ automatic one — by design, so you're never surprised by a silent overwrite. If
84
+ a newer `create-astram-app` release improves a page you're using unmodified
85
+ (e.g. a better `Login.jsx`), diff your version against the current template
86
+ (published inside the `create-astram-app` npm package, under `template/frontend/`)
87
+ and cherry-pick the parts you want.
88
+
89
+ The practical effect: keep your branding in `.env` + the `--primary` token instead
90
+ of hand-editing component internals wherever you can — it keeps your view files
91
+ close to the upstream template, which makes adopting future improvements a small
92
+ diff instead of a merge conflict.
@@ -0,0 +1,6 @@
1
+ # __APP_NAME__ frontend — copy to .env; all optional (astram defaults apply).
2
+ # VITE_APP_NAME=My App
3
+ # VITE_APP_LOGO=/my-logo.png
4
+ # VITE_APP_LOGO_DARK=/my-logo-dark.png
5
+ # VITE_APP_TAGLINE=...
6
+ # VITE_APP_SUB_TAGLINE=...
@@ -0,0 +1,9 @@
1
+ node_modules/
2
+ dist/
3
+ *.log
4
+ .DS_Store
5
+
6
+ # Local env files (real values). `.env.example` is committed as the template.
7
+ .env
8
+ .env.*
9
+ !.env.example
@@ -0,0 +1,71 @@
1
+ // Frontend ESLint flat config (ESLint 9) for an Astram consumer app.
2
+ //
3
+ // React + hooks + fast-refresh rules over the JS/JSX source. The `lint` script
4
+ // (`eslint "src/**/*.{js,jsx}"`) resolves this file. The Vite build is the
5
+ // correctness gate; this catches unused vars, hook-dependency mistakes, and
6
+ // fast-refresh foot-guns. Uses the framework's `_`-prefixed-unused convention.
7
+ import js from '@eslint/js';
8
+ import globals from 'globals';
9
+ import react from 'eslint-plugin-react';
10
+ import reactHooks from 'eslint-plugin-react-hooks';
11
+ import reactRefresh from 'eslint-plugin-react-refresh';
12
+
13
+ export default [
14
+ {
15
+ ignores: ['node_modules/**', 'dist/**', 'coverage/**', 'eslint.config.js'],
16
+ },
17
+
18
+ js.configs.recommended,
19
+
20
+ {
21
+ files: ['src/**/*.{js,jsx}'],
22
+ languageOptions: {
23
+ ecmaVersion: 2022,
24
+ sourceType: 'module',
25
+ globals: { ...globals.browser },
26
+ parserOptions: {
27
+ ecmaFeatures: { jsx: true },
28
+ },
29
+ },
30
+ settings: {
31
+ react: { version: 'detect' },
32
+ },
33
+ plugins: {
34
+ react,
35
+ 'react-hooks': reactHooks,
36
+ 'react-refresh': reactRefresh,
37
+ },
38
+ rules: {
39
+ ...react.configs.flat.recommended.rules,
40
+ ...react.configs.flat['jsx-runtime'].rules, // React 18 automatic JSX runtime — no `React` import needed.
41
+ ...reactHooks.configs.flat.recommended.rules,
42
+
43
+ // Allow `_`-prefixed intentionally-unused args/vars (shared convention).
44
+ 'no-unused-vars': [
45
+ 'error',
46
+ { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
47
+ ],
48
+
49
+ // Fast-refresh: warn (not error) when a module exports non-components
50
+ // alongside components — common + harmless in shadcn ui files.
51
+ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
52
+
53
+ // Project convention: PropTypes are not used (no `prop-types` dep).
54
+ 'react/prop-types': 'off',
55
+
56
+ // react-hooks 7.x ships new, very aggressive rules that flag idiomatic
57
+ // patterns (a mount-guard `setState` in an effect, a fetch-on-mount
58
+ // `load()` effect, the lazy-ref-init `if (!ref.current) ref.current = …`
59
+ // one-time init, etc.). These were REVIEWED case-by-case (task #138): every
60
+ // current hit is a known-good idiom, not a bug — promoting them to error
61
+ // would force contorting clean code, so they stay ADVISORY (warn): visible,
62
+ // non-blocking. The genuinely load-bearing hooks rules below stay errors.
63
+ // If a future hit is an actual bug, fix that site; don't relax the warn.
64
+ 'react-hooks/set-state-in-effect': 'warn',
65
+ 'react-hooks/refs': 'warn',
66
+ // These two catch real bugs — keep them blocking.
67
+ 'react-hooks/exhaustive-deps': 'error',
68
+ 'react-hooks/rules-of-hooks': 'error',
69
+ },
70
+ },
71
+ ];
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en" suppressHydrationWarning>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
+ <link rel="icon" type="image/png" href="/favicon.png" />
7
+ <title>__APP_NAME__</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "The frontend (UI) half of an Astram consumer app — a professional web UI (login / auth / identity management) over the framework auth API. Built on shadcn/ui + Tailwind v4; shared auth/http logic comes from @astram/frontend-core and @astram/sdk. The API (../server) lives at http://localhost:3000 by default.",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "vite build",
10
+ "preview": "vite preview",
11
+ "lint": "eslint \"src/**/*.{js,jsx}\"",
12
+ "test": "node --test src/**/*.test.mjs"
13
+ },
14
+ "dependencies": {
15
+ "@astram/frontend-core": "^0.2.0",
16
+ "@astram/sdk": "^0.2.0",
17
+ "@radix-ui/react-alert-dialog": "^1.1.19",
18
+ "@radix-ui/react-avatar": "^1.1.10",
19
+ "@radix-ui/react-checkbox": "^1.1.4",
20
+ "@radix-ui/react-dialog": "^1.1.15",
21
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
22
+ "@radix-ui/react-label": "^2.1.7",
23
+ "@radix-ui/react-select": "^2.3.3",
24
+ "@radix-ui/react-separator": "^1.1.7",
25
+ "@radix-ui/react-slot": "^1.2.3",
26
+ "@radix-ui/react-tooltip": "^1.2.8",
27
+ "class-variance-authority": "^0.7.1",
28
+ "clsx": "^2.1.1",
29
+ "input-otp": "^1.4.2",
30
+ "lucide-react": "^0.460.0",
31
+ "next-themes": "^0.4.6",
32
+ "qrcode": "^1.5.4",
33
+ "react": "^18.3.1",
34
+ "react-dom": "^18.3.1",
35
+ "react-router-dom": "^6.26.0",
36
+ "sonner": "^2.0.7",
37
+ "tailwind-merge": "^3.3.1"
38
+ },
39
+ "devDependencies": {
40
+ "@eslint/js": "^9.39.5",
41
+ "@tailwindcss/vite": "^4.1.14",
42
+ "@vitejs/plugin-react": "^4.3.0",
43
+ "eslint": "^9.39.5",
44
+ "eslint-plugin-react": "^7.37.5",
45
+ "eslint-plugin-react-hooks": "^7.1.1",
46
+ "eslint-plugin-react-refresh": "^0.5.3",
47
+ "globals": "^17.7.0",
48
+ "tailwindcss": "^4.1.14",
49
+ "tw-animate-css": "^1.4.0",
50
+ "vite": "^5.4.0"
51
+ }
52
+ }