@jimhoyd/urlcode 0.4.0-alpha.3 → 0.4.2
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/.claude/skills/urlcode-authoring/SKILL.md +10 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/CONTRIBUTING.md +36 -0
- package/README.md +20 -15
- package/ROADMAP.md +25 -16
- package/dist/BUILD-MANIFEST.json +20 -19
- package/dist/authoring.js +15 -1
- package/dist/capability-query.js +0 -1
- package/dist/catalog.js +0 -1
- package/dist/cli.js +24 -7
- package/dist/config.js +1 -1
- package/dist/explain.js +1 -1
- package/dist/extensions.js +78 -1
- package/dist/http-response.js +1 -1
- package/dist/index.js +1 -0
- package/dist/init-with.js +36 -11
- package/dist/manifest.js +1 -1
- package/dist/mcp-authoring.js +2 -2
- package/dist/mcp.js +1 -1
- package/dist/policies/cache.js +2 -2
- package/dist/policy.js +16 -0
- package/dist/project-dependencies.js +305 -0
- package/dist/readiness.js +5 -1
- package/dist/runtime.js +1 -1
- package/dist/tooling.js +2 -1
- package/dist/trusted-functions.js +4 -5
- package/dist/types/authoring.d.ts +9 -1
- package/dist/types/capability-query.d.ts +0 -1
- package/dist/types/catalog.d.ts +0 -4
- package/dist/types/config.d.ts +1 -9
- package/dist/types/explain.d.ts +0 -1
- package/dist/types/extensions.d.ts +58 -0
- package/dist/types/http-response.d.ts +0 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/init-with.d.ts +7 -13
- package/dist/types/manifest.d.ts +0 -1
- package/dist/types/project-dependencies.d.ts +78 -0
- package/dist/types/readiness.d.ts +3 -0
- package/dist/types/tooling.d.ts +1 -0
- package/dist/types/trusted-functions.d.ts +1 -4
- package/docs/AI-AUTHORING.md +10 -5
- package/docs/AWS.md +9 -0
- package/docs/CI-FOLLOWUP-2026-09-19.md +1 -1
- package/docs/CODEBASE-AUDIT-2026-09-20.md +6 -0
- package/docs/COMPOSING-A-SITE.md +287 -0
- package/docs/CONTAINER-PROMOTION.md +74 -0
- package/docs/DEVELOPMENT-PIPELINE.md +242 -119
- package/docs/EXTENSIONS.md +88 -93
- package/docs/FRAMEWORK.md +45 -30
- package/docs/FUNCTION-SECURITY.md +5 -8
- package/docs/INSTALL.md +13 -8
- package/docs/MIDDLEWARE.md +10 -4
- package/docs/OPEN-DECISIONS.md +64 -99
- package/docs/READINESS.md +8 -4
- package/docs/README.md +18 -13
- package/docs/RELEASE-0.4.1.md +73 -0
- package/docs/RELEASE-0.4.2.md +30 -0
- package/docs/RELEASE-READINESS.md +40 -11
- package/docs/RELEASE-SECURITY.md +33 -14
- package/docs/SPECIFICATION.md +5 -1
- package/docs/SPIKE-CORE-LAYERING.md +1 -1
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +9 -13
- package/docs/STARTERS.md +17 -5
- package/docs/TOOLING.md +7 -5
- package/docs/VERCEL.md +10 -2
- package/docs/VERSION-ALIGNMENT.md +50 -8
- package/docs/archive/2026-09-19/ROADMAP.md +1 -0
- package/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md +1 -0
- package/docs/{SPIKE-LAMBDA-COMPILE.md → archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md} +168 -12
- package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +2 -0
- package/docs/archive/2026-09-20/OPEN-DECISIONS-COMPLETED.md +116 -0
- package/docs/archive/README.md +2 -0
- package/docs/yaml/functions.md +10 -2
- package/docs/yaml/middleware.md +5 -3
- package/examples/cookbook/middleware/envelope.mjs +4 -2
- package/llms-full.txt +458 -143
- package/llms.txt +2 -1
- package/package.json +8 -5
- package/packaging/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +10 -0
- package/recipes/middleware/middleware/envelope.mjs +4 -2
- package/skills/urlcode/SKILL.md +8 -1
package/docs/EXTENSIONS.md
CHANGED
|
@@ -183,29 +183,18 @@ Node adapter conformance is not a live-provider deployment claim.
|
|
|
183
183
|
|
|
184
184
|
## Project-level lifecycle hooks
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
**The pattern.** An extension with lifecycle events an author judges worth
|
|
200
|
-
exposing should let the project name its own function in the extension's own
|
|
201
|
-
`config`, using the same source shape `function`/`middleware` routes already
|
|
202
|
-
use (a string path, or `{source, export, args}` — `schemas/urlcode.schema.json`),
|
|
203
|
-
and add its own `sandbox` boolean next to it (below). The extension's own
|
|
204
|
-
`activate()` reads that config, and its own runtime dispatch — not a new core
|
|
205
|
-
primitive, an ordinary call the extension package makes with the request
|
|
206
|
-
context it already has — invokes the named function at the lifecycle point
|
|
207
|
-
it defines, with a typed input and a typed verdict the extension's own
|
|
208
|
-
schema documents. For example, an auth-style extension might declare:
|
|
186
|
+
Extensions expose project customization points through the core hook primitive.
|
|
187
|
+
Each registration publishes `hooks`, a machine-readable list containing the
|
|
188
|
+
hook name, whether it is a value-transforming `filter` or side-effect `action`,
|
|
189
|
+
its description and its input/output JSON Schemas. The extension embeds
|
|
190
|
+
`extensionHooksSchema(contracts)` in its configuration schema and calls
|
|
191
|
+
`loadExtensionHooks(config.hooks, contracts, context)` during activation.
|
|
192
|
+
Core then enforces the common source/export shape, project-root confinement,
|
|
193
|
+
known names, eager module/export validation, input/output schemas and reload
|
|
194
|
+
cache busting. Hook entry bytes participate in the project revision, so editing
|
|
195
|
+
a hook invalidates the operator's extension pin.
|
|
196
|
+
|
|
197
|
+
Projects select those declared hooks in the extension's own configuration:
|
|
209
198
|
|
|
210
199
|
```yaml
|
|
211
200
|
extensions:
|
|
@@ -221,76 +210,51 @@ extensions:
|
|
|
221
210
|
```
|
|
222
211
|
|
|
223
212
|
with `beforeRegister` called before an account is created, given a typed
|
|
224
|
-
`{email,
|
|
213
|
+
`{email, profile?}` input and returning a typed verdict (`{allow: true}`
|
|
225
214
|
or `{allow: false, reason}`), and `onSignUp` called after, for side effects
|
|
226
|
-
such as provisioning a workspace.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
project code
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
unchanged file URL makes a second activation in the same process keep
|
|
265
|
-
serving the hook code that was on disk at the first one
|
|
266
|
-
(jimhoyd-com/urlcode#198). Only the hook's **entry** module is refreshed
|
|
267
|
-
this way — modules the hook itself imports stay on Node's module cache,
|
|
268
|
-
the same limitation the trusted route path has, so a change to a hook's
|
|
269
|
-
own dependency still needs a process restart.
|
|
270
|
-
- **Sandboxed (`sandbox: true`).** `@jimhoyd/urlcode/sandbox` exports
|
|
271
|
-
`SandboxPool`, the same QuickJS/worker-thread engine that backs a
|
|
272
|
-
sandboxed `function`/`middleware` route — the identical module-allowlist
|
|
273
|
-
walk, memory/stack limits, two-layer deadline enforcement, `maxBytes` and
|
|
274
|
-
response-shape validation, with no separate or weaker engine for
|
|
275
|
-
extensions. It takes an explicit list of `{source, export}` entries
|
|
276
|
-
(resolve a hook's `source` string with the re-exported `functionFile()`,
|
|
277
|
-
the same resolution/validation a native route's `source` gets) instead of
|
|
278
|
-
anything route/YAML-shaped, and `execute({entry, chain}, request, context)`
|
|
279
|
-
in place of a `FunctionRoute`. There is no "trusted" mode exported
|
|
280
|
-
alongside it — `SandboxPool` is only ever the isolated path; see
|
|
281
|
-
[FUNCTION-SECURITY.md](FUNCTION-SECURITY.md) and
|
|
282
|
-
[TYPESCRIPT.md](TYPESCRIPT.md) for the full contract.
|
|
283
|
-
|
|
284
|
-
An extension honoring a hook's `sandbox: true` is expected to actually
|
|
285
|
-
isolate that invocation through `SandboxPool` now that the primitive exists
|
|
286
|
-
(or document plainly that it does not yet, rather than accepting the field
|
|
287
|
-
and silently running it trusted) — say which, in the extension's own docs,
|
|
288
|
-
so an author reading them is not misled about what opt-in exists.
|
|
215
|
+
such as provisioning a workspace. Hook names and lifecycle timing remain the
|
|
216
|
+
extension's domain, while their declaration, loading and discovery are shared.
|
|
217
|
+
|
|
218
|
+
Hooks are first-party project code and run trusted in-process by default, with
|
|
219
|
+
full Node access, like trusted `function` and `middleware` routes. Contract v1
|
|
220
|
+
does not define an arbitrary-value sandbox hook protocol. A hook reference with
|
|
221
|
+
`sandbox: true` is rejected during activation rather than silently run trusted.
|
|
222
|
+
Only the entry module is refreshed during reactivation; its imported dependencies
|
|
223
|
+
remain in Node's module cache until restart.
|
|
224
|
+
|
|
225
|
+
The UI extension exposes `transformView`, a synchronous filter called before a
|
|
226
|
+
named kit template renders. It receives `{template, view}` and returns the view
|
|
227
|
+
model to render. Use copy, templates, theme and CSS for ordinary presentation
|
|
228
|
+
changes; use this hook for project-specific computed view data that those
|
|
229
|
+
declarative layers cannot express.
|
|
230
|
+
|
|
231
|
+
## Building an extension
|
|
232
|
+
|
|
233
|
+
An extension package should export a registration factory and, when it supports
|
|
234
|
+
`urlcode init --with`, a side-effect-free `scaffold` function. The registration:
|
|
235
|
+
|
|
236
|
+
1. Declares its logical name, contract version, supported targets, exact project
|
|
237
|
+
revision pin and strict configuration/policy schemas.
|
|
238
|
+
2. Publishes every project hook through `hooks` and reuses
|
|
239
|
+
`extensionHooksSchema` plus `loadExtensionHooks`; it does not implement its
|
|
240
|
+
own path resolver or dynamic-import cache.
|
|
241
|
+
3. Activates all configuration, files, services and hooks before serving a
|
|
242
|
+
request. Invalid or stale configuration fails activation.
|
|
243
|
+
4. Returns `handle` for mounts and optionally `authorize`/`middleware` for route
|
|
244
|
+
policies. It closes resources it owns.
|
|
245
|
+
5. Keeps credentials, storage and provider setup in the operator host. Project
|
|
246
|
+
YAML contains logical configuration and project-relative hook references.
|
|
247
|
+
|
|
248
|
+
Consumers install the package, declare its YAML block and mounts/policies, and
|
|
249
|
+
register it in `host.mjs`. They modify it through declared configuration,
|
|
250
|
+
presentation layers and hooks. A fork is reserved for changing behavior the
|
|
251
|
+
extension has not exposed; that is evidence for a new declarative field or hook.
|
|
252
|
+
See [Composing a site](COMPOSING-A-SITE.md) for the complete ui/auth/admin example.
|
|
289
253
|
|
|
290
254
|
## Discovering schemas
|
|
291
255
|
|
|
292
256
|
Each registration carries the JSON Schemas that validate its `config` block and
|
|
293
|
-
its per-route policy requirements. `urlcode extensions` prints them together with
|
|
257
|
+
its per-route policy requirements, plus its hook contracts. `urlcode extensions` prints them together with
|
|
294
258
|
the project's own declarations so an author can see what a mount accepts:
|
|
295
259
|
|
|
296
260
|
```sh
|
|
@@ -299,6 +263,7 @@ urlcode extensions --project ./site --host-file /absolute/operator/host.mjs [--j
|
|
|
299
263
|
|
|
300
264
|
For every registration in the host file it reports the name, contract version,
|
|
301
265
|
targets, credential headers, configuration schema, policy schema (if any),
|
|
266
|
+
declared hook names, kinds, descriptions and input/output schemas,
|
|
302
267
|
whether the project declares it, whether its `projectSha256` matches the current
|
|
303
268
|
revision, the routes that mount it and the routes whose policies require it.
|
|
304
269
|
Declared names the host does not register are listed as unregistered. The command
|
|
@@ -336,16 +301,15 @@ boundary, not a JavaScript sandbox or an independent security review.
|
|
|
336
301
|
|
|
337
302
|
## Scaffolding with `init --with`
|
|
338
303
|
|
|
339
|
-
`urlcode init <directory> --with auth,admin` produces the layered site the
|
|
304
|
+
`urlcode init <directory> --with ui,auth,admin` produces the layered site the
|
|
340
305
|
[framework page](FRAMEWORK.md#the-composition-contract) describes in one
|
|
341
306
|
command: the starter under `<directory>/app/`, one `host.mjs`, one `README.md`,
|
|
342
307
|
and each extension's own operator files. Core never bundles or imports the
|
|
343
308
|
extension packages at build time; at run time it resolves
|
|
344
309
|
`@jimhoyd/urlcode-<name>` for each name with Node's package resolution from
|
|
345
|
-
the invoking directory
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
the package and calls its
|
|
310
|
+
the invoking directory. Install a compatible package set there, as shown in
|
|
311
|
+
[the framework guide](FRAMEWORK.md#the-composition-contract), before using
|
|
312
|
+
`--with ui,auth`; UI must activate before auth. Core imports the package and calls its
|
|
349
313
|
`scaffold` export with this request:
|
|
350
314
|
|
|
351
315
|
```ts
|
|
@@ -401,6 +365,37 @@ Assembly rules, in `--with` order:
|
|
|
401
365
|
review the project and pin it explicitly; the host is generated to require
|
|
402
366
|
the pin, never to compute it.
|
|
403
367
|
|
|
368
|
+
### Recorded versions
|
|
369
|
+
|
|
370
|
+
`init --with` also writes `<directory>/package.json`: a private manifest that
|
|
371
|
+
pins, at exactly the version that was resolved, the running runtime, every
|
|
372
|
+
extension named in `--with`, and every package those extensions declare in
|
|
373
|
+
`peerDependencies` (so `@jimhoyd/urlcode-ui` is pinned for an `auth,admin` site
|
|
374
|
+
although nobody named it). Before anything is written, the whole set is checked
|
|
375
|
+
against every declared peer range; an incompatible combination or a missing
|
|
376
|
+
required peer refuses and names it, leaving no directory behind.
|
|
377
|
+
|
|
378
|
+
Nothing is installed. The generated site has no `node_modules` and no
|
|
379
|
+
`package-lock.json` until you run `npm install` in it yourself, which the
|
|
380
|
+
command and the generated README both state as the next step. Reproducibility
|
|
381
|
+
comes from that install, not from generation.
|
|
382
|
+
|
|
383
|
+
- `--no-manifest` generates the site without a `package.json`, for a site whose
|
|
384
|
+
dependencies are managed elsewhere. Plain `urlcode init` is unchanged and
|
|
385
|
+
still writes no manifest; add `--manifest` to pin the runtime for a
|
|
386
|
+
route-only project too.
|
|
387
|
+
- `--pin <package>=<specifier>` records a specifier instead of the resolved
|
|
388
|
+
version, for local tarball or offline development
|
|
389
|
+
(`--pin @jimhoyd/urlcode-auth=file:/abs/urlcode-auth-0.1.0-alpha.6.tgz`). A
|
|
390
|
+
package installed from a local path or tarball is detected from npm's own
|
|
391
|
+
install record and pinned by that path without any flag; the README says so,
|
|
392
|
+
because such a pin only reproduces where that path exists.
|
|
393
|
+
|
|
394
|
+
There is no upgrade command. Moving a generated project to newer versions today
|
|
395
|
+
means editing its `package.json` and re-running `npm install` yourself; nothing
|
|
396
|
+
in this runtime selects a newer tested set, shows the change, or updates a
|
|
397
|
+
lockfile for you.
|
|
398
|
+
|
|
404
399
|
Serving the result is the usual explicit host binding:
|
|
405
400
|
|
|
406
401
|
```sh
|
package/docs/FRAMEWORK.md
CHANGED
|
@@ -7,26 +7,26 @@ claim here is implemented in the linked repository; nothing is roadmap.
|
|
|
7
7
|
|
|
8
8
|
## Four packages, one project shape
|
|
9
9
|
|
|
10
|
-
| Package |
|
|
10
|
+
| Package | Source | What it adds | How a project declares it |
|
|
11
11
|
|---|---|---|---|
|
|
12
|
-
| `@jimhoyd/urlcode` |
|
|
13
|
-
| `@jimhoyd/urlcode-ui` | [
|
|
14
|
-
| `@jimhoyd/urlcode-auth` | [
|
|
15
|
-
| `@jimhoyd/urlcode-admin` | [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
endorsement: the source is complete, but independent review, deployment
|
|
22
|
-
evidence and an accessibility assessment are still pending
|
|
12
|
+
| `@jimhoyd/urlcode` | this repository | The runtime: YAML routes, functions and middleware (trusted by default, `sandbox: true` opt-in), pages and assets, policies, site conventions, CLI, provider adapters, the extension contract | `urlcode.yaml` with `version: "1"` |
|
|
13
|
+
| `@jimhoyd/urlcode-ui` | [`packages/ui`](../packages/ui) | Shared presentation: escaped templates, shadcn/ui partials, one stylesheet with light and dark, themes, translations, the `ui` extension that serves the kit's assets | `extensions.ui` plus an asset mount route |
|
|
14
|
+
| `@jimhoyd/urlcode-auth` | [`packages/auth`](../packages/auth) | Accounts: password, passkeys, OpenID Connect, email codes, TOTP, recovery, sessions, roles, registration modes, account page, operator CLI | `extensions.auth` plus an `/account/*` mount and `policies.extensions.auth` on protected routes |
|
|
15
|
+
| `@jimhoyd/urlcode-admin` | [`packages/admin`](../packages/admin) | Administration: users, sessions, roles, audit, registration approval, two-person cases, support impersonation, health | `extensions.admin` plus an `/admin/*` mount |
|
|
16
|
+
|
|
17
|
+
All four are Apache-2.0. The `0.4.1` release line aligns their stable versions;
|
|
18
|
+
check `npm run release:status` for publication progress. A stable npm channel is
|
|
19
|
+
not an independent assessment: review, deployment evidence and an accessibility
|
|
20
|
+
assessment are still pending
|
|
23
21
|
([issue 58](https://github.com/jimhoyd-com/urlcode/issues/58)). Their status
|
|
24
|
-
files say exactly what is built: [auth](
|
|
25
|
-
[admin](
|
|
26
|
-
[ui](
|
|
27
|
-
|
|
28
|
-
in
|
|
29
|
-
|
|
22
|
+
files say exactly what is built: [auth](../packages/auth/IMPLEMENTATION-STATUS.md),
|
|
23
|
+
[admin](../packages/admin/IMPLEMENTATION-STATUS.md),
|
|
24
|
+
[ui](../packages/ui/IMPLEMENTATION-STATUS.md).
|
|
25
|
+
The current version of each package is its own manifest, and the peer ranges it
|
|
26
|
+
declares are in that manifest too; do not read a version number out of this
|
|
27
|
+
page. How versions, channels and release tags line up is recorded in
|
|
28
|
+
[package and channel alignment](VERSION-ALIGNMENT.md), and `npm run
|
|
29
|
+
release:status` reports the live registry and tag state.
|
|
30
30
|
|
|
31
31
|
## The ladder
|
|
32
32
|
|
|
@@ -43,8 +43,10 @@ Each rung's YAML is valid on every rung above it.
|
|
|
43
43
|
3. **Functions and middleware.** `function` routes and ordered `middleware`
|
|
44
44
|
in JavaScript, trusted and in-process by default; a route declaring
|
|
45
45
|
`sandbox: true` runs isolated instead (QuickJS inside WebAssembly, fresh
|
|
46
|
-
heap per call, no Node, filesystem or network).
|
|
47
|
-
only
|
|
46
|
+
heap per call, no Node, filesystem or network). The `env`/`secrets` the
|
|
47
|
+
runtime injects into a function come only from an operator grant pinned to
|
|
48
|
+
the project revision; the grant governs that injected context, not the
|
|
49
|
+
ambient Node environment trusted in-process code can reach on its own.
|
|
48
50
|
4. **Accounts.** The `auth` extension: sign-in, registration, MFA, account
|
|
49
51
|
page and protected routes. The operator installs it in a host file outside
|
|
50
52
|
the project; YAML only declares the mount and configuration.
|
|
@@ -61,17 +63,19 @@ was removed from core. A `urlcode-dynamic-link` package owned them the same way
|
|
|
61
63
|
package occupies this rung today.
|
|
62
64
|
|
|
63
65
|
Rungs 1 to 3 need only the core package. Rungs 4 to 6 need the extension
|
|
64
|
-
packages
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
packages installed from npm and a Node host with a
|
|
67
|
+
patched SQLite build; see each package's README ([auth](../packages/auth/README.md),
|
|
68
|
+
[admin](../packages/admin/README.md), [ui](../packages/ui/README.md)) for the
|
|
69
|
+
exact requirement.
|
|
67
70
|
|
|
68
71
|
## The composition contract
|
|
69
72
|
|
|
70
|
-
|
|
73
|
+
After the aligned `0.4.1` packages are published, an extended project starts
|
|
74
|
+
with the packages and one command:
|
|
71
75
|
|
|
72
76
|
```sh
|
|
73
|
-
npm install @jimhoyd/urlcode @jimhoyd/urlcode-ui @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin
|
|
74
|
-
urlcode init my-site --with auth,admin
|
|
77
|
+
npm install --save-exact @jimhoyd/urlcode@0.4.1 @jimhoyd/urlcode-ui@0.4.1 @jimhoyd/urlcode-auth@0.4.1 @jimhoyd/urlcode-admin@0.4.1
|
|
78
|
+
urlcode init my-site --with ui,auth,admin
|
|
75
79
|
```
|
|
76
80
|
|
|
77
81
|
Installing from npm is the normal path; `scripts/pack-sources.mjs` still builds
|
|
@@ -140,7 +144,7 @@ See each package README for its complete kit registration example.
|
|
|
140
144
|
urlcode serve --project /absolute/site --host-file /absolute/operator/host.mjs --origin https://site.example
|
|
141
145
|
```
|
|
142
146
|
|
|
143
|
-
`urlcode init <dir> --with auth,admin` writes this layout in one step: it
|
|
147
|
+
`urlcode init <dir> --with ui,auth,admin` writes this layout in one step: it
|
|
144
148
|
resolves each installed `@jimhoyd/urlcode-<name>` from the current directory,
|
|
145
149
|
calls its `scaffold` export and merges the fragments into `app/urlcode.yaml`,
|
|
146
150
|
one `host.mjs` and one `README.md`, refusing before writing anything when a
|
|
@@ -151,6 +155,17 @@ administrator from JSON on stdin. `inspectExtensionRevision(project)` prints
|
|
|
151
155
|
the SHA-256 that `projectSha256` must carry; changing extension YAML, policies
|
|
152
156
|
or mounts changes the revision and needs an explicit operator reapproval.
|
|
153
157
|
|
|
158
|
+
The presentation tooling composes the same way, by naming packages rather than
|
|
159
|
+
depending on them. `urlcode-ui` is the kit alone until
|
|
160
|
+
`--extensions @jimhoyd/urlcode-auth,@jimhoyd/urlcode-admin` names the packages
|
|
161
|
+
that ship the other namespaces: each is resolved from `--project` with Node
|
|
162
|
+
package resolution and imported for the namespace it exports, so `list`,
|
|
163
|
+
`doctor`, `eject`, `preview` and `copy --missing` cover the `auth/*` and
|
|
164
|
+
`admin/*` templates and copy the host registers, and a project override of an
|
|
165
|
+
extension template is checked against the shipped view model. A package that is
|
|
166
|
+
not installed is skipped. `urlcode init --with` writes the commands with the
|
|
167
|
+
flag already set; `@jimhoyd/urlcode-ui` still depends on neither peer.
|
|
168
|
+
|
|
154
169
|
## Rules an agent must follow
|
|
155
170
|
|
|
156
171
|
These are the facts that keep generated projects valid. The full matrix is in
|
|
@@ -195,8 +210,8 @@ These are the facts that keep generated projects valid. The full matrix is in
|
|
|
195
210
|
| Need | Read |
|
|
196
211
|
|---|---|
|
|
197
212
|
| Write or change routes | [YAML guide](YAML-GUIDE.md), [field reference](YAML-REFERENCE.md), [cookbook](../examples/cookbook/README.md) |
|
|
198
|
-
| Add accounts | [auth README](
|
|
199
|
-
| Add administration | [admin README](
|
|
200
|
-
| Restyle every page | [ui README](
|
|
213
|
+
| Add accounts | [auth README](../packages/auth/README.md), [auth security](../packages/auth/SECURITY.md) |
|
|
214
|
+
| Add administration | [admin README](../packages/admin/README.md) |
|
|
215
|
+
| Restyle every page | [ui README](../packages/ui/README.md), [ui contract](../packages/ui/CONTRACT.md) |
|
|
201
216
|
| Write an extension | [extensions](EXTENSIONS.md), [extension model review](archive/2026-09-19/SPIKE-EXTENSION-MODEL.md) |
|
|
202
217
|
| Run it | [operations](OPERATIONS.md), [install](INSTALL.md), [deployment checks](DEPLOYMENT-CHECKS.md) |
|
|
@@ -93,14 +93,11 @@ machinery either way.
|
|
|
93
93
|
|
|
94
94
|
This engine — worker spawning, the module-allowlist walk, the two-layer
|
|
95
95
|
deadline, `maxBytes` and response-shape validation — is one implementation
|
|
96
|
-
shared by route dispatch and by `@jimhoyd/urlcode/sandbox`'s `SandboxPool
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`sandbox: true`
|
|
100
|
-
|
|
101
|
-
through that entry point; there is no separate, weaker sandbox for extensions
|
|
102
|
-
to reach for, and no "trusted" mode exported there — trusted execution needs
|
|
103
|
-
no primitive at all (docs/SPIKE-DEFAULT-TRUST-MODEL.md).
|
|
96
|
+
shared by route dispatch and by `@jimhoyd/urlcode/sandbox`'s `SandboxPool`.
|
|
97
|
+
That public HTTP-shaped primitive remains available to extension authors, but
|
|
98
|
+
project extension hooks use arbitrary typed values and contract v1 runs them
|
|
99
|
+
trusted in-process; it rejects `sandbox: true` rather than claiming HTTP sandbox
|
|
100
|
+
semantics apply to them. See [extensions](EXTENSIONS.md#project-level-lifecycle-hooks).
|
|
104
101
|
|
|
105
102
|
## What the trusted default can and can't do
|
|
106
103
|
|
package/docs/INSTALL.md
CHANGED
|
@@ -8,8 +8,13 @@ so the installed `urlcode` command runs `dist/cli.js` and needs no build tool.
|
|
|
8
8
|
|
|
9
9
|
## npm
|
|
10
10
|
|
|
11
|
+
The stable release target is `0.4.1`. Use these commands after publication;
|
|
12
|
+
`npm run release:status` in a checkout reports live registry availability. An
|
|
13
|
+
unversioned npm install selects the current `latest` channel, while `@alpha`
|
|
14
|
+
explicitly selects the separate prerelease channel.
|
|
15
|
+
|
|
11
16
|
```sh
|
|
12
|
-
npm install --global @jimhoyd/urlcode
|
|
17
|
+
npm install --global @jimhoyd/urlcode@0.4.1
|
|
13
18
|
urlcode --help
|
|
14
19
|
```
|
|
15
20
|
|
|
@@ -19,12 +24,12 @@ Which dependency list it belongs in depends on how the project uses URLCode:
|
|
|
19
24
|
```sh
|
|
20
25
|
# Using URLCode as a tool: validate, test and build in CI, never imported by
|
|
21
26
|
# the code that serves requests.
|
|
22
|
-
npm install --save-dev @jimhoyd/urlcode
|
|
27
|
+
npm install --save-dev --save-exact @jimhoyd/urlcode@0.4.1
|
|
23
28
|
npx urlcode validate
|
|
24
29
|
|
|
25
30
|
# Embedding the runtime (see TYPESCRIPT.md): the application imports
|
|
26
31
|
# @jimhoyd/urlcode at startup, so it must survive `npm ci --omit=dev`.
|
|
27
|
-
npm install --save @jimhoyd/urlcode
|
|
32
|
+
npm install --save --save-exact @jimhoyd/urlcode@0.4.1
|
|
28
33
|
```
|
|
29
34
|
|
|
30
35
|
A devDependency is absent from a production install, so an application that
|
|
@@ -65,7 +70,7 @@ It downloads the release tarball, verifies its SHA-256 against the release's
|
|
|
65
70
|
`SHA256SUMS`, and installs with npm. Options:
|
|
66
71
|
|
|
67
72
|
```sh
|
|
68
|
-
curl -fsSL .../install.sh | sh -s -- --version 0.
|
|
73
|
+
curl -fsSL .../install.sh | sh -s -- --version 0.4.1 --prefix "$HOME/.local"
|
|
69
74
|
```
|
|
70
75
|
|
|
71
76
|
`--prefix` avoids needing privileges for a global npm directory; add
|
|
@@ -76,12 +81,12 @@ for that moment: to inspect first, download it, read it, then run it.
|
|
|
76
81
|
|
|
77
82
|
No image is published yet: the release job's GHCR step is gated behind the
|
|
78
83
|
`PUBLISH_CONTAINER` repository variable and has not run, so there is nothing at
|
|
79
|
-
`ghcr.io/jimhoyd-com/urlcode` to pull.
|
|
84
|
+
`ghcr.io/jimhoyd-com/urlcode` to pull. After the release tag exists, build it from that checkout:
|
|
80
85
|
|
|
81
86
|
```sh
|
|
82
|
-
git clone --branch v0.
|
|
83
|
-
docker build -t urlcode:0.
|
|
84
|
-
docker run --rm -p 127.0.0.1:3000:3000 -v "$PWD:/project:ro" urlcode:0.
|
|
87
|
+
git clone --branch v0.4.1 https://github.com/jimhoyd-com/urlcode.git
|
|
88
|
+
docker build -t urlcode:0.4.1 urlcode
|
|
89
|
+
docker run --rm -p 127.0.0.1:3000:3000 -v "$PWD:/project:ro" urlcode:0.4.1 \
|
|
85
90
|
serve --project /project --host 0.0.0.0
|
|
86
91
|
```
|
|
87
92
|
|
package/docs/MIDDLEWARE.md
CHANGED
|
@@ -48,12 +48,18 @@ Every middleware and the function share the same request and context. Validated
|
|
|
48
48
|
with a fresh `context.state` object for this chain. State never survives the
|
|
49
49
|
request. Header edits are visible downstream; editing the request does not
|
|
50
50
|
reroute it or change already validated inputs. Request bodies are single-use:
|
|
51
|
-
reading one consumes it for downstream code.
|
|
52
|
-
API
|
|
51
|
+
reading one consumes it for downstream code. On a `sandbox: true` route there is
|
|
52
|
+
no `clone()` or streaming API at all, so pass parsed data through
|
|
53
|
+
`context.state`; a trusted route receives Node's own `Request`/`Response` and so
|
|
54
|
+
does have `clone()`, but passing parsed data through `context.state` keeps the
|
|
55
|
+
chain portable between the two modes.
|
|
53
56
|
|
|
54
57
|
Function responses support the existing text/JSON guest API. To transform their
|
|
55
|
-
body, read it and return a new `Response`.
|
|
56
|
-
download bodies are opaque and cannot be read
|
|
58
|
+
body, read it and return a new `Response`. On a `sandbox: true` route, native
|
|
59
|
+
redirect/respond/page/static/download bodies are opaque and cannot be read
|
|
60
|
+
through `text()` or `json()`; a trusted chain receives them as an ordinary
|
|
61
|
+
`Response` and can read them, so wrapping `respond: {text: hello}` and returning
|
|
62
|
+
`HELLO` works there and fails in the guest.
|
|
57
63
|
Returning the same native response preserves original bytes, including binary
|
|
58
64
|
files, ranges and HEAD lengths. You may add headers, but cannot change its
|
|
59
65
|
original status or existing native headers while preserving that body. To replace
|