@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 +457 -0
- package/bin/app.js +5 -0
- package/docs/design.md +107 -0
- package/package.json +24 -0
- package/src/args.js +180 -0
- package/src/build/bootstrap.js +43 -0
- package/src/build/client-modules.js +9 -0
- package/src/build/client.js +206 -0
- package/src/build/context.js +16 -0
- package/src/build/fix-imports.js +99 -0
- package/src/build/helpers.js +29 -0
- package/src/build/html.js +83 -0
- package/src/build/openapi.js +249 -0
- package/src/build/plugins/client-exclude.js +90 -0
- package/src/build/runtime-manifest.js +64 -0
- package/src/build/server.js +294 -0
- package/src/build/ssr.js +257 -0
- package/src/build/ui-common.js +188 -0
- package/src/build/vite.js +45 -0
- package/src/cli.js +72 -0
- package/src/commands/build.js +59 -0
- package/src/commands/preview.js +33 -0
- package/src/commands/serve.js +213 -0
- package/src/config.js +584 -0
- package/src/logger.js +16 -0
- package/src/utils/command.js +23 -0
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
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
|
+
}
|