@noego/app 0.0.1

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/AGENTS.md ADDED
@@ -0,0 +1,457 @@
1
+ Objective
2
+
3
+ - Implement a production-ready build pipeline driven by a simple CLI:
4
+ - app build --server index.ts --page ui/index.html
5
+ - Preserve the current runtime model:
6
+ - Single Node process serves API + SSR UI
7
+ - Browser loads a page that hydrates a client bundle
8
+ - Eliminate runtime compilation of Svelte; precompile SSR/CSR
9
+ - Keep server-side dynamic discovery/loading (controllers, middleware, OpenAPI, SQL) working without rewriting application code or public
10
+ framework interfaces
11
+ - Make paths predictable and configurable; handle relative/absolute dirs correctly
12
+
13
+ Entry Points
14
+
15
+ - Server entry: `noblelaw/index.ts` (spawns/hosts Express + @noego/dinner API + @noego/forge SSR UI)
16
+ - Page entry: `noblelaw/ui/index.html` (loads `noblelaw/ui/client.ts` to hydrate in browser)
17
+
18
+ Repository Roots and Path Referencing
19
+
20
+ - Unless explicitly prefixed, relative paths in this document refer to the application project root (`--root`), which in our environment is `noblelaw` at `/Users/shavauhngabay/dev/noblelaw`.
21
+ - Source checkouts for frameworks/tools:
22
+ - Forge: `/Users/shavauhngabay/dev/ego/forge`
23
+ - Dinner: `/Users/shavauhngabay/dev/noego/dinner`
24
+ - Sqlstack: `/Users/shavauhngabay/dev/ego/sqlstack`
25
+ - App (this CLI): `/Users/shavauhngabay/dev/app`
26
+ - Example dist outputs in this doc are under the application project: `noblelaw/dist/...`.
27
+
28
+ Current Architecture
29
+
30
+ - API
31
+ - @noego/dinner (source: `/Users/shavauhngabay/dev/noego/dinner`) loads OpenAPI (default noblelaw uses `noblelaw/server/stitch.yaml`), dynamically wires controllers (`noblelaw/server/controller/**`) and middleware (`noblelaw/middleware/**`) based on configured base dirs
32
+ - Paths are resolved with process.cwd() and are runtime file-based; both controllers base and middleware base are configurable
33
+ - UI SSR
34
+ - @noego/forge/server (source: `/Users/shavauhngabay/dev/ego/forge`) uses options from `noblelaw/ui/options.ts`
35
+ - In dev, Vite middleware compiles .svelte on demand
36
+ - In prod, must be precompiled (no loaders)
37
+ - Client
38
+ - Hydration bootstrap at `noblelaw/ui/client.ts`, page template `noblelaw/ui/index.html`
39
+ - DB
40
+ - Repositories (server/repo/**) use sqlstack decorators (source: `/Users/shavauhngabay/dev/ego/sqlstack`)
41
+ - sqlstack reads .sql from filesystem at runtime, adjacent to transpiled repo JS
42
+ - Tests/CI
43
+ - Jest + Python runner; not directly relevant to build targets, but artifacts must be runnable in CI
44
+ - Config files
45
+ - `noblelaw/tsconfig.json`, `noblelaw/vite.config.js`, `noblelaw/proper.json`, `noblelaw/server/stitch.yaml`, `noblelaw/ui/stitch.yaml`
46
+
47
+ Constraints and Implications
48
+
49
+ - sqlstack requires .sql files living next to the repository implementation at runtime; bundling server code breaks this unless the layout and
50
+ files are preserved
51
+ - Dinner and Forge rely on runtime files and directories (OpenAPI YAML, controller paths, middleware paths). If paths are relative to
52
+ process.cwd(), starting from a different CWD breaks them; if they use __dirname, relocating without mirroring structure breaks them
53
+ - The exact locations of controllers, middleware, and UI components are user-configurable in the app’s own options; the build must not assume fixed paths
54
+ - Therefore:
55
+ - Prefer transpile-only for server-side code (no bundling), preserving directory layout
56
+ - Copy all server-side assets needed at runtime: .sql, .yaml, and any file paths referenced by code
57
+ - Fully bundle client (CSR) and precompile SSR
58
+ - Replace only the dev-only Svelte/Vite compilation-on-request with precompiled SSR modules + manifests
59
+
60
+ CLI Contract
61
+
62
+ - Command: app build --server index.ts --page ui/index.html
63
+ - Options (all optional; default values assume noblelaw layout)
64
+ - --root . project root
65
+ - --out dist output directory root
66
+ - --server-root . where to resolve server entry from (default --root)
67
+ - --ui-root ui where to resolve the page and client entry (default dirname of --page)
68
+ - --controllers server/controller runtime path for controllers (copied, layout preserved) — can be any path
69
+ - --middleware middleware runtime middleware path (copied, layout preserved) — can be any path
70
+ - --openapi server/stitch.yaml server OpenAPI entry (copied)
71
+ - --ui-openapi ui/stitch.yaml UI OpenAPI entry for Forge (copied into UI build context or resolved via manifest)
72
+ - --sql-glob 'server/repo/**/*.sql' glob(s) to copy alongside transpiled server code
73
+ - --assets 'ui/resources/**' static assets to copy/public
74
+ - --client-exclude 'server/**' glob(s) to exclude from the client bundle (can be repeated)
75
+ - --mode production
76
+ - Relative paths resolve from --root unless starting with / (absolute)
77
+ - App must normalize all paths, make them absolute internally, and record these in manifests used at runtime
78
+
79
+ Build Requirements
80
+
81
+ - Client (CSR)
82
+ - Bundle/minify ui/client.ts via Vite/Rollup
83
+ - Output hashed assets and a client manifest (for preload and hydration tags)
84
+ - Honor client exclusions: replace imports matching `--client-exclude` patterns with stub modules that throw on access (ensures server-only code never ships)
85
+ - UI SSR
86
+ - Compile Svelte views/layouts to SSR modules ahead-of-time
87
+ - Produce an SSR bundle or set of modules and an SSR manifest mapping routes/views/layouts to module file paths
88
+ - No Node loader or compile-on-request in prod
89
+ - Allow user-supplied Vite config to extend/override defaults (see Configuration Overrides)
90
+ - Server
91
+ - Transpile-only (TS → ESM) for:
92
+ - index.ts, server/**, middleware/**
93
+ - Preserve folder structure under dist so runtime discovery continues to work
94
+ - Copy assets required at runtime:
95
+ - All .sql adjacent to repos
96
+ - OpenAPI YAML (server/stitch.yaml, referenced server/openapi/**)
97
+ - Optional UI OpenAPI YAML (ui/stitch.yaml) if Forge SSR reads it at runtime
98
+ - Do not inline or bundle away filesystem references expected by dinner/sqlstack/forge
99
+ - The resulting server entry must be able to:
100
+ - Serve static client assets from dist/client
101
+ - Import/call the precompiled SSR handler using the SSR manifest
102
+ - Initialize dinner with OpenAPI YAML in dist/server/...
103
+ - HTML template
104
+ - Use ui/index.html as the SSR template in prod
105
+ - Inject correct script/style tags and preload links using the client manifest
106
+
107
+ Configuration Overrides
108
+
109
+ - Principle
110
+ - Provide sane defaults; allow projects to extend or replace them. The application retains ultimate control over build configuration.
111
+
112
+ - Vite (Client CSR)
113
+ - Default: App loads the project’s own `vite.config.{js,ts}` (`configFile: true`) and applies minimal, required overrides:
114
+ - `build.outDir = dist/client`, `build.manifest = true`, `rollupOptions.input = <resolved --page>`.
115
+ - Injects the client-exclude plugin when `--client-exclude` is provided.
116
+ - Extensibility:
117
+ - `--client-vite-config <path>`: use an alternate Vite config file for the client build.
118
+ - `--client-vite-override <json>`: deep-merge object applied after loading the config file but before enforcing invariants; last‑write wins except for required invariants.
119
+ - Required invariants always win: App will always set `outDir`, `manifest`, and `input` unless `--allow-override-required` is passed (not recommended).
120
+
121
+ - Vite (SSR precompile)
122
+ - Default: reuse project Vite config with `build.ssr = true`, `outDir = dist/ui/ssr`, and `preserveModules` to keep module paths predictable.
123
+ - Extensibility:
124
+ - `--ssr-vite-config <path>`: alternate config file for SSR build.
125
+ - `--ssr-vite-override <json>`: deep-merge overrides (e.g., additional plugins, resolve.alias). Required invariants are enforced similarly (ssr, outDir, preserveModules input set derived from OpenAPI routes).
126
+
127
+ - Forge Options Wrapper
128
+ - App generates `dist/server/ui/options.js` by importing compiled options and deep‑merging minimal production overrides:
129
+ - `development=false`, `viteOptions.root=<dist root>`, `component_dir='ui/ssr'`, `build_dir='client'`, updated `renderer` and `open_api_path`.
130
+ - User retains control: all original options (including custom renderer or assets) are preserved unless they conflict with production invariants. Projects may opt out (future flag) to supply their own production options module.
131
+
132
+ - TypeScript/tsc
133
+ - App uses the project’s local TypeScript with the project’s `tsconfig.json`. Projects may customize compiler settings freely. App only changes `outDir` via CLI and performs a post‑emit ESM import extension fixup.
134
+
135
+ YAML Configuration (Preferred)
136
+
137
+ - Principle
138
+ - While every option is available via CLI, the preferred way to configure App is a YAML config file committed to the repo. This provides a single, reviewable source of truth. CLI flags remain available for ad‑hoc overrides.
139
+
140
+ - Location and discovery
141
+ - Default filenames searched at the application root (`noblelaw`): `app.yaml`, `app.yml`, `app.config.yaml`, `app.config.yml`, or `app.config.json`.
142
+ - A future `--config <path>` CLI flag may explicitly point to a YAML file. Paths inside the YAML resolve relative to the YAML file itself unless absolute.
143
+
144
+ - Schema (high‑level)
145
+ - Server
146
+ - `entry`: e.g., `noblelaw/index.ts`
147
+ - `rootDir`: e.g., `noblelaw`
148
+ - `controllers`: e.g., `noblelaw/server/controller`
149
+ - `middleware`: e.g., `noblelaw/middleware`
150
+ - `openapi`: e.g., `noblelaw/server/stitch.yaml`
151
+ - `sqlGlobs`: list of globs, e.g., `['noblelaw/server/repo/**/*.sql']`
152
+ - UI
153
+ - `page`: e.g., `noblelaw/ui/index.html`
154
+ - `options`: e.g., `noblelaw/ui/options.ts` (Forge server options entry)
155
+ - `rootDir`: e.g., `noblelaw/ui`
156
+ - `openapi`: e.g., `noblelaw/ui/stitch.yaml`
157
+ - `assets`: list of globs, e.g., `['noblelaw/ui/resources/**']`
158
+ - `clientExclude`: list of globs for CSR exclusions, e.g., `['noblelaw/server/**', 'noblelaw/middleware/**']`
159
+ - Dev
160
+ - `watch`: boolean
161
+ - `watchPaths`: list of globs (preferred)
162
+ - `splitServe`: boolean
163
+ - `frontendCmd`: e.g., `vite`
164
+ - Vite (extensible)
165
+ - `client`:
166
+ - `configFile`: path to a Vite config for CSR (JSON or JS)
167
+ - `override`: object or path to a JSON file with deep‑merge overrides
168
+ - `ssr`:
169
+ - `configFile`: path to a Vite config for SSR build
170
+ - `override`: object or path to a JSON file with deep‑merge overrides
171
+ - Output
172
+ - `outDir`: e.g., `noblelaw/dist`
173
+
174
+ - Referencing JSON files
175
+ - Any `override` field under `vite.client` or `vite.ssr` may be either an inline object or a string path to a JSON file. App will load JSON and deep‑merge it into the Vite config (after the file’s own `configFile` is applied, before enforcing invariants like `outDir`).
176
+ - This pattern can be extended to other future sections where JSON is a convenient format.
177
+
178
+ - Example: `noblelaw/app.yaml`
179
+ - server:
180
+ entry: noblelaw/index.ts
181
+ rootDir: noblelaw
182
+ controllers: noblelaw/server/controller
183
+ middleware: noblelaw/middleware
184
+ openapi: noblelaw/server/stitch.yaml
185
+ sqlGlobs:
186
+ - noblelaw/server/repo/**/*.sql
187
+ - ui:
188
+ page: noblelaw/ui/index.html
189
+ options: noblelaw/ui/options.ts
190
+ rootDir: noblelaw/ui
191
+ openapi: noblelaw/ui/stitch.yaml
192
+ assets:
193
+ - noblelaw/ui/resources/**
194
+ - noblelaw/ui/styles/**
195
+ clientExclude:
196
+ - noblelaw/server/**
197
+ - noblelaw/middleware/**
198
+ - dev:
199
+ watch: true
200
+ watchPaths:
201
+ - noblelaw/server/**/*.ts
202
+ - noblelaw/ui/openapi/**/*.yaml
203
+ splitServe: true
204
+ frontendCmd: vite
205
+ - vite:
206
+ client:
207
+ configFile: noblelaw/vite.config.js
208
+ override: noblelaw/vite.client.override.json
209
+ ssr:
210
+ configFile: noblelaw/vite.config.js
211
+ override:
212
+ build:
213
+ ssr: true
214
+ rollupOptions:
215
+ preserveModules: true
216
+ - outDir: noblelaw/dist
217
+
218
+ - Precedence and merging
219
+ - Defaults → YAML → CLI: App starts from sane defaults, applies YAML config, then applies CLI flags as the final layer. Required invariants for production builds still apply (e.g., outDir, manifest, SSR flags), unless explicitly documented otherwise.
220
+
221
+ OpenAPI‑Driven Discovery (Build/Serve Optimization)
222
+
223
+ - Rationale
224
+ - The OpenAPI specs (server and UI stitch YAML) already declare every API route, controller reference, middleware reference, and UI view/layout. App can parse these ahead of time to optimize build and reduce required options.
225
+
226
+ - What App derives
227
+ - Server OpenAPI (`noblelaw/server/stitch.yaml`):
228
+ - All controller identifiers from `x-controller` across routes.
229
+ - All middleware identifiers from `x-middleware`.
230
+ - UI OpenAPI (`noblelaw/ui/stitch.yaml`):
231
+ - All pages, layouts, and component paths that need SSR precompilation.
232
+
233
+ - How we use it
234
+ - SSR entry set: feed the exact list of Svelte components (views/layouts) into the SSR Vite build, avoiding filesystem globs.
235
+ - Validation: after transpile/copy, verify all referenced controllers/middleware resolve under `noblelaw/dist` and fail fast with actionable errors.
236
+ - Manifest: emit a debug manifest including discovered controllers, middleware, and SSR components for troubleshooting.
237
+
238
+ - Option minimization (sane defaults with override)
239
+ - If `--controllers` or `--middleware` are omitted, App attempts to infer base directories by computing the longest common directory prefix from discovered identifiers. Example: if `x-controller` uses `controllers/*.controller`, infer `noblelaw/server/controller`.
240
+ - If inference fails or is ambiguous, App falls back to NobleLaw defaults and warns. Users retain full control by setting flags explicitly.
241
+ - `--ui-openapi` remains required for SSR discovery unless the project embeds its location in code; App defaults to `noblelaw/ui/stitch.yaml`.
242
+
243
+ - Serve improvements
244
+ - During `app serve`, App may parse OpenAPI once at startup to log missing controllers/middleware early, while still delegating runtime wiring to Dinner/Forge.
245
+ - Optional strict mode (future): `--strict-openapi` to fail serve/build when unresolved references are detected.
246
+
247
+ Directory and Path Semantics
248
+
249
+ - Input resolution
250
+ - Treat CLI values as relative to --root unless absolute
251
+ - --page determines --ui-root when not provided (dirname of --page)
252
+ - Output layout (example)
253
+ - `noblelaw/dist/server/**` transpiled JS, mirrored structure of `noblelaw/server/**` and `noblelaw/middleware/**`, plus copied `.sql` and `noblelaw/server/openapi/**`, `noblelaw/server/stitch.yaml`
254
+ - `noblelaw/dist/client/**` bundled CSR assets + `manifest.json`
255
+ - `noblelaw/dist/ssr/**` SSR bundle/modules + `ssr-manifest.json` (or embed SSR output under `noblelaw/dist/server/ssr/**`)
256
+ - Runtime path resolution
257
+ - All server code that used process.cwd() should continue to work if server is launched with cwd at dist root OR the dist structure mirrors
258
+ source-tree paths
259
+ - App must ensure:
260
+ - Dinner’s `openapi_path` points to a real YAML under `noblelaw/dist/server/...`
261
+ - Forge’s `open_api_path` resolves correctly under `noblelaw/dist` (either copy UI YAML or generate a stitched snapshot consumed by SSR)
262
+ - sqlstack finds `.sql` files next to the transpiled repo JS under `noblelaw/dist/server/repo/**`
263
+ - If any path cannot be made to resolve from cwd reliably, App provides a tiny generated bootstrap wrapper that rewrites those path
264
+ strings to __dirname-based equivalents at build time (no change to public interfaces)
265
+
266
+ Path Customization and Framework Mapping
267
+
268
+ - Dinner (API)
269
+ - controllers_base_path: absolute base directory for resolving x-controller identifiers. The identifiers in OpenAPI (e.g., controllers/user.controller) are paths relative to this base, without extensions.
270
+ - middleware_path: absolute base directory for resolving x-middleware identifiers. If omitted, Dinner falls back to controllers_base_path, then process.cwd().
271
+ - Resolution behavior: Dinner computes module paths with path.resolve(process.cwd(), path.join(base, id)). This is why setting process.cwd() correctly at runtime matters.
272
+ - Implication: You can place controllers and middleware anywhere; keep x-controller and x-middleware stable, and change only the base paths.
273
+
274
+ - Forge (SSR/UI)
275
+ - component_dir: directory containing Svelte components (layouts, views), resolved relative to viteOptions.root.
276
+ - build_dir: directory for client build output served statically in production.
277
+ - renderer: HTML template path for SSR; in production, Forge reads the file contents at startup.
278
+ - open_api_path: path to the UI OpenAPI (stitch) YAML used to build the manifest; resolved relative to viteOptions.root.
279
+ - middleware_path: optional base for UI middleware; resolved similar to Dinner.
280
+ - Assets: a map of mountPath -> [directories], served from viteOptions.root in production.
281
+ - Dev vs prod: in dev Vite compiles on demand; in prod, ProdComponentLoader loads precompiled SSR modules from component_dir.
282
+
283
+ - Sqlstack (DB)
284
+ - Decorators load .sql files from the filesystem at runtime next to the compiled repo JS. Dialect variants are supported (e.g., find.sql, find.sqlite.sql).
285
+ - Root directory for SQL resolution is inferred from decorator location or explicitly passed via @Query(). No bundling or inlining — files must exist at runtime.
286
+
287
+ - App CLI flags → runtime mapping
288
+ - --controllers: the source directory to mirror into dist for runtime controller discovery. Must correspond to the app’s controllers_base_path at runtime. Defaults to server/controller for NobleLaw, but any path is valid.
289
+ - --middleware: the source directory to mirror into dist for runtime middleware discovery. Must correspond to the app’s middleware_path (or controllers_base_path fallback). Defaults to middleware for NobleLaw.
290
+ - --openapi / --ui-openapi: OpenAPI stitch YAML locations for API and UI. App copies these into dist/server and dist/ui respectively.
291
+ - --sql-glob: one or more glob patterns for SQL files to copy next to compiled JS outputs. Use this to support custom repo locations.
292
+ - --page / --ui-root: determines client entry and build root used by Vite; paths are normalized relative to --root unless absolute.
293
+ - All paths accept non-default, project-specific layouts — App normalizes to absolute and preserves structure under dist.
294
+
295
+ How App Preserves Flexibility
296
+
297
+ - CWD bootstrap: The generated dist/server/index.js sets process.chdir(distRoot) before importing the compiled entry. This makes Dinner and Forge path resolution (which are CWD-relative) behave identically to dev.
298
+ - Layout mirroring: App transpiles without bundling and copies the resulting JS tree so relative relationships are preserved (e.g., server/controller/**, server/repo/**). Middleware is mirrored under dist/middleware or as configured.
299
+ - Options wrapper (Forge): App wraps ui/options.js in dist to set viteOptions.root to the dist root and points component_dir to precompiled SSR (ui/ssr), build_dir to client, and updates renderer/open_api_path to dist paths. This preserves Forge’s public API while switching from dev loaders to precompiled modules.
300
+ - Asset copying: OpenAPI YAML trees and SQL globs are copied verbatim so runtime file-based discovery continues to work.
301
+
302
+ ESM Import Extensions (Server JS)
303
+
304
+ - Problem
305
+ - Node’s native ESM loader requires explicit file extensions for relative imports (e.g., `import x from './foo.js'`).
306
+ - TypeScript source commonly uses extensionless relative imports (preferred) and directory imports (e.g., `import './repo'`). These compile verbatim and will fail at runtime without a loader.
307
+
308
+ - Policy
309
+ - Source TS: extensionless relative imports are allowed and preferred by default. Projects may opt to include `.js` in TS if they want; App supports both styles.
310
+ - No dev loaders in production: we will not rely on ts-node/tsx or custom Node loaders to fix extensions at runtime.
311
+
312
+ - Build-time Fixup
313
+ - After TSC emits ESM, App rewrites import specifiers in emitted `.js` files to be Node‑compatible:
314
+ - Relative specifiers without extensions: `./foo` → `./foo.js` when `foo.js` exists in dist.
315
+ - Directory specifiers: `./repo` → `./repo/index.js` when `repo/index.js` exists in dist.
316
+ - Explicit TS extensions: `./foo.ts` → `./foo.js`.
317
+ - Already‑extended (`.js`, `.mjs`, `.cjs`, `.json`) are left unchanged.
318
+ - Non‑relative (bare) specifiers and built‑ins are never modified.
319
+ - Dynamic imports: simple string literals (e.g., `import('./foo')`) are fixed the same as static. Template or computed specifiers are not rewritten; authors should ensure those resolve to valid `.js` at runtime.
320
+
321
+ - Opt‑out / Control
322
+ - If a project prefers writing `.js` extensions in TS or already manages extensions, App’s fixup is conservative and will not double‑append.
323
+ - Future flag (planned): allow disabling or customizing rewrite rules if a repository has bespoke needs.
324
+
325
+ - Why this approach
326
+ - Keeps server build transpile‑only (no bundling) while producing ESM that runs under Node without loaders.
327
+ - Preserves directory layout so Dinner/Forge/Sqlstack file discovery still works.
328
+
329
+ Client Bundle Exclusions
330
+
331
+ - Goal
332
+ - Prevent accidental inclusion of server-only code in the browser bundle while allowing projects to keep shared TypeScript without extensions.
333
+
334
+ - CLI
335
+ - `--client-exclude <glob>`: may be specified multiple times. Paths are resolved relative to `--root` unless absolute. Example defaults for NobleLaw: `server/**`, `middleware/**`.
336
+
337
+ - Behavior
338
+ - During the Vite/Rollup client build, App installs a plugin that intercepts resolved module IDs. If the absolute file path matches any exclusion pattern, the loader returns a virtual stub module instead of the real source.
339
+ - The stub exports proxies that throw when accessed at runtime. This avoids leaking server implementation into the client and makes violations visible during testing.
340
+
341
+ - Stub semantics
342
+ - Static import: `import x from 'excluded'` → the module evaluates, but any property access throws a clear error: “Excluded from client bundle: <path>”.
343
+ - Named imports: accessing any named export throws the same error.
344
+ - Dynamic import with a literal path is also stubbed; computed/templated specifiers are not guaranteed to be caught and should be avoided for server-only modules.
345
+
346
+ - Notes
347
+ - The stub design preserves tree-shaking; unused excluded imports can be eliminated.
348
+ - This mechanism complements, not replaces, SSR/Prod separation in Forge. It specifically guards the CSR client build.
349
+
350
+ Non‑Default Layout Examples
351
+
352
+ - Controllers under src/api/controllers and middleware under app/mw:
353
+ - app build --controllers src/api/controllers --middleware app/mw
354
+ - Dinner options should set controllers_base_path and middleware_path accordingly; App will mirror these into dist preserving structure.
355
+ - Repositories under backend/data/repos with SQL under backend/data/sql:
356
+ - app build --sql-glob 'backend/data/**/*.sql'
357
+ - Sqlstack will find .sql files next to compiled JS in dist as long as the relative adjacency is preserved.
358
+ - OpenAPI files under config/api/stitch.yaml and config/ui/stitch.yaml:
359
+ - app build --openapi config/api/stitch.yaml --ui-openapi config/ui/stitch.yaml
360
+ - App copies them into dist/server and dist/ui respectively; runtime code reads the copies via CWD-relative paths.
361
+
362
+ Development vs Production
363
+
364
+ - Dev
365
+ - Keep current dev flow (Vite HMR, dynamic load) unchanged
366
+ - app serve runs the uncompiled project:
367
+ - Uses the project’s TypeScript directly via `tsx` (preferred) or `ts-node` if available
368
+ - Injects Forge dev loader automatically when present (`--loader @noego/forge/loader`) so SSR behaves like today
369
+ - Runs with `NODE_ENV=development` and cwd at `--root`
370
+ - Optional file watching and auto‑restart:
371
+ - `--watch` enables restart on server‑side changes
372
+ - `--watch-path <glob>` adds additional watch patterns (repeatable). Preferred: use globs to cover directories rather than listing many files.
373
+ - Defaults watch: controllers, middleware, server OpenAPI (and openapi/**), UI OpenAPI (and openapi/**), and SQL globs
374
+ - UI Svelte/TS/CSS/HTML under `noblelaw/ui/**` are excluded from restart and continue to reload via Vite HMR
375
+ - Optional split processes in watch mode:
376
+ - `--split-serve` starts the frontend dev server (vite) as a separate process so API restarts don’t disrupt HMR
377
+ - `--frontend-cmd vite` selects the frontend runner (currently only vite supported)
378
+ - Note: projects integrating Forge’s dev Vite server inside Express may need to disable that integration to avoid duplicate Vite instances when using split-serve
379
+ - Prod
380
+ - No tsx loaders or custom Node loaders
381
+ - No Svelte compilation on request
382
+ - No OpenAPI generation on request beyond reading YAML
383
+ - No sqlstack SQL inlining; actual .sql files must exist at runtime
384
+
385
+ Allowed Framework Changes (non-breaking)
386
+
387
+ - Forge
388
+ - Add a production SSR build target that emits SSR modules and an SSR manifest
389
+ - Add an option to read UI OpenAPI from a pre-stitched JSON artifact (optional optimization) but keep YAML support by default
390
+ - Ensure serverOnlyStub plugin is dev-only
391
+ - Dinner
392
+ - Allow openapi_path to be YAML or a pre-stitched JSON; retain current API
393
+ - No change to controller/middleware builder/caller interfaces
394
+ - Sqlstack
395
+ - No API changes; optionally expose a pluggable resolver to support alternate asset locations (not required if we copy files)
396
+ - Document environment variable to set default dialect if needed (e.g., SQLITE)
397
+
398
+ Functional Behavior Required After Build
399
+
400
+ - Server starts from dist/server/index.js (or equivalent)
401
+ - Serves API routes per OpenAPI; controllers/middleware load dynamically from dist paths
402
+ - Serves UI routes SSR using precompiled SSR modules and SSR manifest
403
+ - Serves static client assets from dist/client
404
+ - Browser navigation
405
+ - Initial request returns SSR HTML, includes preload links from client manifest, hydrates successfully
406
+ - Client-side navigation fetches prebuilt code-split chunks; no server-side Svelte compilation
407
+ - SQL execution
408
+ - sqlstack reads .sql from dist/server/repo/** with expected naming (findUser.sql, findUser.sqlite.sql, etc.)
409
+ - OpenAPI
410
+ - YAML files are present under dist/server/openapi/** and are correctly referenced
411
+
412
+ Acceptance Criteria
413
+
414
+ - Running app build --server index.ts --page ui/index.html produces:
415
+ - `noblelaw/dist/server` transpiled ESM with mirrored structure and copied `.sql`, OpenAPI YAML
416
+ - `noblelaw/dist/client` minified assets + client manifest
417
+ - `noblelaw/dist/ssr` precompiled SSR output + SSR manifest (or nested under `noblelaw/dist/server/ssr`)
418
+ - Starting node dist/server/index.js:
419
+ - API endpoints function unchanged
420
+ - GET / returns SSR HTML; hydration works
421
+ - Navigating CSR-only routes loads hashed chunks without 500s or on-demand compilation
422
+ - No dependency on tsx, custom Node loaders, or dev-only Vite middlewares in production
423
+ - No changes to public interfaces of Forge, Dinner, or Sqlstack; only configuration/tooling/paths adjusted
424
+ - CI can run npm ci && npm run build && node dist/server/index.js and tests target the running server successfully
425
+
426
+ Implementation Notes
427
+
428
+ - Prefer multi-build with Vite/Rollup:
429
+ - CSR client: entry ui/client.ts (page ui/index.html), emits client manifest
430
+ - SSR: static SSR entry generated by Forge from OpenAPI UI routes; emits SSR manifest
431
+ - Server: transpile-only with esbuild/tsc; copy assets (.sql, .yaml)
432
+ - Path handling policy
433
+ - Record both source and output absolute paths inside a build manifest for troubleshooting
434
+ - If the app is started from outside dist, require cwd to be dist (documented), or generate a wrapper that sets process.chdir(__dirname +
435
+ '/../') at startup
436
+ - Defaults for noblelaw
437
+ - --root .
438
+ - --server index.ts
439
+ - --page ui/index.html
440
+ - --controllers server/controller (customizable)
441
+ - --middleware middleware (customizable)
442
+ - --openapi server/stitch.yaml
443
+ - --ui-openapi ui/stitch.yaml
444
+ - --sql-glob 'server/repo/**/*.sql'
445
+ - --client-exclude 'server/**,middleware/**'
446
+ - --out dist
447
+
448
+
449
+
450
+ Framework Reference
451
+ `/Users/shavauhngabay/dev/ego/forge`
452
+ `/Users/shavauhngabay/dev/noego/dinner`
453
+ `/Users/shavauhngabay/dev/ego/sqlstack`
454
+
455
+
456
+ Project Reference
457
+ `/Users/shavauhngabay/dev/noblelaw`
package/bin/app.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import('../src/cli.js').catch((error) => {
3
+ console.error('[app] failed to start CLI:', error);
4
+ process.exitCode = 1;
5
+ });
package/docs/design.md ADDED
@@ -0,0 +1,107 @@
1
+ # App Build CLI – Design Notes
2
+
3
+ ## Goals
4
+ - Provide reproducible `app build` and `app serve` commands for NobleLaw-style projects.
5
+ - Preserve the runtime contract of Dinner/Forge/Sqlstack by keeping file-based discovery working after build.
6
+ - Precompile Svelte (SSR + CSR) so production servers no longer rely on loader-based compilation.
7
+ - Ensure all runtime paths remain predictable by normalising to `--root`, copying required assets, and generating bootstrap/manifests.
8
+ - Prefer declarative configuration (`app.yaml`) with CLI overrides for ad-hoc tweaks.
9
+
10
+ ## Configuration Resolution
11
+ 1. **Defaults** — baked into App, biased toward NobleLaw layout.
12
+ 2. **YAML config** — auto-discovered (`app.yaml`, `app.yml`, or `app.config.yaml` inside `--root`). Future: explicit `--config`.
13
+ 3. **CLI flags** — highest precedence.
14
+
15
+ All relative paths resolve against the config file’s directory (for YAML) or `--root` (for CLI). The merged configuration is normalised into absolute paths and fan-out structures (`BuildConfig`) used downstream.
16
+
17
+ Key fields:
18
+ - `server`: entry, rootDir, controllers, middleware, openapi, sqlGlobs.
19
+ - `ui`: page, rootDir, openapi, assets, clientExclude globs.
20
+ - `vite`: optional overrides for CSR/SSR builds (config file + JSON override).
21
+ - `dev`: serve-mode toggles (watch, watchPaths, splitServe, frontendCmd).
22
+ - `outDir`: build destination.
23
+
24
+ App records the merged config and important paths in a build manifest for auditing.
25
+
26
+ ## High-Level Pipeline
27
+ 1. **Path Resolution**
28
+ - Parse CLI options, defaulting to the NobleLaw layout.
29
+ - Resolve every user-supplied path relative to `--root` unless already absolute.
30
+ - Produce an internal `BuildConfig` with absolute `root`, `out`, `serverEntry`, `uiRoot`, `openApi`, `sqlGlobs`, etc.
31
+ - Record both source and output paths in a build manifest for troubleshooting.
32
+
33
+ 2. **Workspace Prep**
34
+ - Clean the target `--out` directory.
35
+ - Create a deterministic staging area (`.app/tmp`) for compiler outputs before reshaping into the final layout.
36
+
37
+ 3. **OpenAPI Discovery**
38
+ - Read stitched OpenAPI specs (server + UI) ahead of time.
39
+ - Extract controller + middleware identifiers (server) and view/layout component paths (UI).
40
+ - Use results to:
41
+ - Validate that expected files are emitted into `dist`.
42
+ - Derive SSR input set and infer controller/middleware base dirs when options omitted.
43
+ - Populate debug manifest (list of routes/components).
44
+
45
+ 4. **Server Build (TS ➜ JS)**
46
+ - Invoke the project’s own TypeScript compiler (`tsc`) with `outDir={tmp}/server` so compiler options (decorators, metadata) stay consistent.
47
+ - Copy the resulting JS tree into `dist/server`, flattening `tmp/server/**/*` so controllers end up at `dist/server/controller`, repos at `dist/server/repo`, etc.
48
+ - Copy TS outputs for `ui/**/*` into `dist/server/ui` to satisfy imports like `./ui/frontend`.
49
+ - Copy middleware outputs into `dist/middleware` (Dinner & Forge middleware lookup).
50
+ - Move the compiled entry `index.js` aside and generate a bootstrap `dist/server/index.js` that:
51
+ - Resolves `__dirname`, calls `process.chdir(<dist root>)` so `process.cwd()`-relative paths continue to work.
52
+ - `await import('./index.build.js')` (or similar) to execute the compiled entry module.
53
+ - Rename the original entry to `index.build.js` (keeps sourcemaps intact).
54
+ - Generate `dist/server/ui/options.js` wrapper that imports the compiled options and deep-merges production overrides:
55
+ - `development=false`, `viteOptions.root=<dist root>`
56
+ - `component_dir='ui/ssr'`, `build_dir='client'`
57
+ - `open_api_path` & `renderer` rewritten to dist copies
58
+ - Preserve custom user fields unless they conflict with production invariants.
59
+
60
+ 5. **Asset Copy**
61
+ - Copy OpenAPI YAML (`server/stitch.yaml`, `server/openapi/**`, UI stitch) into mirrored dist paths.
62
+ - Copy SQL files specified by `--sql-glob` so each repo’s `.sql` sits next to its compiled JS sibling.
63
+ - Copy arbitrary asset globs (`--assets`) – defaults include `ui/resources/**`, `database/**`, etc.
64
+ - Verify all controller/middleware identifiers discovered from OpenAPI resolve to files under `dist`.
65
+
66
+ 6. **Client Build (CSR)**
67
+ - Use the project’s Vite config via `vite.build({ configFile, root: uiRoot, build: { outDir: dist/client, manifest: true } })`.
68
+ - Force `build.rollupOptions.input` to the resolved client entry module so Vite emits hashed assets and a manifest without relying on HTML transforms.
69
+ - Collect the generated manifest (supports both `manifest.json` and `.vite/manifest.json`) to drive preload/script injection.
70
+ - Install the client-exclusion plugin that stubs modules matching `clientExclude` globs, preventing server-only sources from landing in the browser bundle.
71
+
72
+ 7. **SSR Compilation**
73
+ - Reuse Vite in SSR mode with:
74
+ - `build: { ssr: true, outDir: dist/ui/ssr, rollupOptions: { preserveModules: true, preserveModulesRoot: uiRoot, input: <set of layout/view components> } }`.
75
+ - Component list comes from OpenAPI-derived routes (layouts + views).
76
+ - Resulting files mirror the original `.svelte` tree but with `.js` extensions, allowing Forge’s `ProdComponentLoader` (`replace('.svelte', '.js')`) to work unchanged.
77
+ - Emit `dist/ssr/manifest.json` describing every route → layout/view module path (relative to dist root) for debugging and possible runtime use.
78
+
79
+ 8. **HTML Template Rewriting**
80
+ - Read `ui/index.html`, strip dev-only tags (`/@vite/client`, direct `ui/client.ts` script).
81
+ - Inject preload + `<script type="module">` tags based on the CSR manifest.
82
+ - Persist the transformed template to `dist/ui/index.html`, which Forge’s `BaseHTMLRender` will load.
83
+
84
+ 9. **Runtime Bootstrap & Manifests**
85
+ - Generate `dist/server/runtime-manifest.json` capturing:
86
+ - Absolute → relative path mapping for key assets (openapi, sql roots, middleware root, client manifest, ssr manifest, template).
87
+ - Build metadata (timestamps, CLI version, input options).
88
+ - Ensure the bootstrap wrapper keeps CWD pinned to the dist root so legacy `process.cwd()` resolution in Dinner/Forge/sqlstack continues to work.
89
+
90
+ 10. **Verification & Dev Mode Hooks**
91
+ - Optionally run a lightweight post-build check (`node --check dist/server/index.build.js`) and ensure expected artifacts exist.
92
+ - Provide `app build --inspect` (future) to print manifest contents.
93
+ - `app serve` launches `tsx` (or `ts-node`) against the original entry with Forge loader, optional watch, and split frontend process per config.
94
+
95
+ ## External Dependencies
96
+ - **TypeScript**: use the project-local compiler via `root/node_modules/typescript`.
97
+ - **Vite** / **@sveltejs/vite-plugin-svelte**: loaded from the target project for both CSR and SSR builds.
98
+ - **@noego/stitch**: consumed through Forge’s parser to discover routes/layouts when creating the SSR entry set.
99
+ - **yaml** / **deepmerge**: parse `app.yaml` and merge overrides.
100
+ - **glob** (project-local) for file discovery.
101
+ - No additional runtime deps for App itself; CLI implemented in ESM JS.
102
+
103
+ ## Open Questions / Follow-ups
104
+ - Whether Forge should load a precomputed SSR manifest vs building on start. Current plan keeps runtime unchanged (Forge still builds at start) but provides manifest for inspection; can revisit once CLI is functional.
105
+ - Potential production mode differences (NODE_ENV). App sets `mode=production` for Vite by default but allows overrides via `--mode`.
106
+ - How to expose build summaries (JSON vs console). Initial version will log human-readable summary and emit `runtime-manifest.json`.
107
+ - Improve developer ergonomics around TypeScript diagnostics (currently streamed verbatim when `tsc` reports errors but emit proceeds).
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@noego/app",
3
+ "version": "0.0.1",
4
+ "description": "Production build tool for Dinner/Forge apps.",
5
+ "type": "module",
6
+ "bin": {
7
+ "app": "./bin/app.js",
8
+ "noego":"./bin/app.js"
9
+ },
10
+ "scripts": {
11
+ "build": "node ./bin/app.js build"
12
+ },
13
+ "engines": {
14
+ "node": ">=20.11"
15
+ },
16
+ "license": "MIT",
17
+ "author": "App Build CLI",
18
+ "dependencies": {
19
+ "deepmerge": "^4.3.1",
20
+ "picomatch": "^2.3.1",
21
+ "yaml": "^2.6.0"
22
+ },
23
+ "devDependencies": {}
24
+ }