@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/llms-full.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- Generated by scripts/build-llms-full.ts (npm run docs:llms). Do not edit; edit the source documents. -->
|
|
2
|
-
<!-- Consolidated URLCode authoring reference:
|
|
2
|
+
<!-- Consolidated URLCode authoring reference: 16 documents, about 52,118 tokens (estimate: characters / 4). -->
|
|
3
3
|
|
|
4
4
|
# URLCode
|
|
5
5
|
|
|
@@ -36,7 +36,8 @@ Agents that explicitly want the complete consolidated reference in one fetch sho
|
|
|
36
36
|
12. [Conditional routing](#conditional-routing)
|
|
37
37
|
13. [Bounded proxy and webhook transport](#bounded-proxy-and-webhook-transport)
|
|
38
38
|
14. [Operator-installed extensions](#operator-installed-extensions)
|
|
39
|
-
15. [
|
|
39
|
+
15. [Composing a site from ui, auth and admin](#composing-a-site-from-ui-auth-and-admin)
|
|
40
|
+
16. [Function execution: trusted by default, sandboxed opt-in](#function-execution-trusted-by-default-sandboxed-opt-in)
|
|
40
41
|
|
|
41
42
|
---
|
|
42
43
|
|
|
@@ -51,26 +52,26 @@ claim here is implemented in the linked repository; nothing is roadmap.
|
|
|
51
52
|
|
|
52
53
|
### Four packages, one project shape
|
|
53
54
|
|
|
54
|
-
| Package |
|
|
55
|
+
| Package | Source | What it adds | How a project declares it |
|
|
55
56
|
|---|---|---|---|
|
|
56
|
-
| `@jimhoyd/urlcode` |
|
|
57
|
-
| `@jimhoyd/urlcode-ui` | [
|
|
58
|
-
| `@jimhoyd/urlcode-auth` | [
|
|
59
|
-
| `@jimhoyd/urlcode-admin` | [
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
endorsement: the source is complete, but independent review, deployment
|
|
66
|
-
evidence and an accessibility assessment are still pending
|
|
57
|
+
| `@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"` |
|
|
58
|
+
| `@jimhoyd/urlcode-ui` | [`packages/ui`](https://github.com/jimhoyd-com/urlcode/blob/main/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 |
|
|
59
|
+
| `@jimhoyd/urlcode-auth` | [`packages/auth`](https://github.com/jimhoyd-com/urlcode/blob/main/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 |
|
|
60
|
+
| `@jimhoyd/urlcode-admin` | [`packages/admin`](https://github.com/jimhoyd-com/urlcode/blob/main/packages/admin) | Administration: users, sessions, roles, audit, registration approval, two-person cases, support impersonation, health | `extensions.admin` plus an `/admin/*` mount |
|
|
61
|
+
|
|
62
|
+
All four are Apache-2.0. The `0.4.1` release line aligns their stable versions;
|
|
63
|
+
check `npm run release:status` for publication progress. A stable npm channel is
|
|
64
|
+
not an independent assessment: review, deployment evidence and an accessibility
|
|
65
|
+
assessment are still pending
|
|
67
66
|
([issue 58](https://github.com/jimhoyd-com/urlcode/issues/58)). Their status
|
|
68
|
-
files say exactly what is built: [auth](https://github.com/jimhoyd-com/urlcode
|
|
69
|
-
[admin](https://github.com/jimhoyd-com/urlcode
|
|
70
|
-
[ui](https://github.com/jimhoyd-com/urlcode
|
|
71
|
-
|
|
72
|
-
in
|
|
73
|
-
|
|
67
|
+
files say exactly what is built: [auth](https://github.com/jimhoyd-com/urlcode/blob/main/packages/auth/IMPLEMENTATION-STATUS.md),
|
|
68
|
+
[admin](https://github.com/jimhoyd-com/urlcode/blob/main/packages/admin/IMPLEMENTATION-STATUS.md),
|
|
69
|
+
[ui](https://github.com/jimhoyd-com/urlcode/blob/main/packages/ui/IMPLEMENTATION-STATUS.md).
|
|
70
|
+
The current version of each package is its own manifest, and the peer ranges it
|
|
71
|
+
declares are in that manifest too; do not read a version number out of this
|
|
72
|
+
page. How versions, channels and release tags line up is recorded in
|
|
73
|
+
[package and channel alignment](https://github.com/jimhoyd-com/urlcode/blob/main/docs/VERSION-ALIGNMENT.md), and `npm run
|
|
74
|
+
release:status` reports the live registry and tag state.
|
|
74
75
|
|
|
75
76
|
### The ladder
|
|
76
77
|
|
|
@@ -87,8 +88,10 @@ Each rung's YAML is valid on every rung above it.
|
|
|
87
88
|
3. **Functions and middleware.** `function` routes and ordered `middleware`
|
|
88
89
|
in JavaScript, trusted and in-process by default; a route declaring
|
|
89
90
|
`sandbox: true` runs isolated instead (QuickJS inside WebAssembly, fresh
|
|
90
|
-
heap per call, no Node, filesystem or network).
|
|
91
|
-
only
|
|
91
|
+
heap per call, no Node, filesystem or network). The `env`/`secrets` the
|
|
92
|
+
runtime injects into a function come only from an operator grant pinned to
|
|
93
|
+
the project revision; the grant governs that injected context, not the
|
|
94
|
+
ambient Node environment trusted in-process code can reach on its own.
|
|
92
95
|
4. **Accounts.** The `auth` extension: sign-in, registration, MFA, account
|
|
93
96
|
page and protected routes. The operator installs it in a host file outside
|
|
94
97
|
the project; YAML only declares the mount and configuration.
|
|
@@ -105,17 +108,19 @@ was removed from core. A `urlcode-dynamic-link` package owned them the same way
|
|
|
105
108
|
package occupies this rung today.
|
|
106
109
|
|
|
107
110
|
Rungs 1 to 3 need only the core package. Rungs 4 to 6 need the extension
|
|
108
|
-
packages
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
packages installed from npm and a Node host with a
|
|
112
|
+
patched SQLite build; see each package's README ([auth](https://github.com/jimhoyd-com/urlcode/blob/main/packages/auth/README.md),
|
|
113
|
+
[admin](https://github.com/jimhoyd-com/urlcode/blob/main/packages/admin/README.md), [ui](https://github.com/jimhoyd-com/urlcode/blob/main/packages/ui/README.md)) for the
|
|
114
|
+
exact requirement.
|
|
111
115
|
|
|
112
116
|
### The composition contract
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
After the aligned `0.4.1` packages are published, an extended project starts
|
|
119
|
+
with the packages and one command:
|
|
115
120
|
|
|
116
121
|
```sh
|
|
117
|
-
npm install @jimhoyd/urlcode @jimhoyd/urlcode-ui @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin
|
|
118
|
-
urlcode init my-site --with auth,admin
|
|
122
|
+
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
|
|
123
|
+
urlcode init my-site --with ui,auth,admin
|
|
119
124
|
```
|
|
120
125
|
|
|
121
126
|
Installing from npm is the normal path; `scripts/pack-sources.mjs` still builds
|
|
@@ -184,7 +189,7 @@ See each package README for its complete kit registration example.
|
|
|
184
189
|
urlcode serve --project /absolute/site --host-file /absolute/operator/host.mjs --origin https://site.example
|
|
185
190
|
```
|
|
186
191
|
|
|
187
|
-
`urlcode init <dir> --with auth,admin` writes this layout in one step: it
|
|
192
|
+
`urlcode init <dir> --with ui,auth,admin` writes this layout in one step: it
|
|
188
193
|
resolves each installed `@jimhoyd/urlcode-<name>` from the current directory,
|
|
189
194
|
calls its `scaffold` export and merges the fragments into `app/urlcode.yaml`,
|
|
190
195
|
one `host.mjs` and one `README.md`, refusing before writing anything when a
|
|
@@ -195,6 +200,17 @@ administrator from JSON on stdin. `inspectExtensionRevision(project)` prints
|
|
|
195
200
|
the SHA-256 that `projectSha256` must carry; changing extension YAML, policies
|
|
196
201
|
or mounts changes the revision and needs an explicit operator reapproval.
|
|
197
202
|
|
|
203
|
+
The presentation tooling composes the same way, by naming packages rather than
|
|
204
|
+
depending on them. `urlcode-ui` is the kit alone until
|
|
205
|
+
`--extensions @jimhoyd/urlcode-auth,@jimhoyd/urlcode-admin` names the packages
|
|
206
|
+
that ship the other namespaces: each is resolved from `--project` with Node
|
|
207
|
+
package resolution and imported for the namespace it exports, so `list`,
|
|
208
|
+
`doctor`, `eject`, `preview` and `copy --missing` cover the `auth/*` and
|
|
209
|
+
`admin/*` templates and copy the host registers, and a project override of an
|
|
210
|
+
extension template is checked against the shipped view model. A package that is
|
|
211
|
+
not installed is skipped. `urlcode init --with` writes the commands with the
|
|
212
|
+
flag already set; `@jimhoyd/urlcode-ui` still depends on neither peer.
|
|
213
|
+
|
|
198
214
|
### Rules an agent must follow
|
|
199
215
|
|
|
200
216
|
These are the facts that keep generated projects valid. The full matrix is in
|
|
@@ -239,9 +255,9 @@ These are the facts that keep generated projects valid. The full matrix is in
|
|
|
239
255
|
| Need | Read |
|
|
240
256
|
|---|---|
|
|
241
257
|
| Write or change routes | [YAML guide](https://github.com/jimhoyd-com/urlcode/blob/main/docs/YAML-GUIDE.md), [field reference](https://github.com/jimhoyd-com/urlcode/blob/main/docs/YAML-REFERENCE.md), [cookbook](https://github.com/jimhoyd-com/urlcode/blob/main/examples/cookbook/README.md) |
|
|
242
|
-
| Add accounts | [auth README](https://github.com/jimhoyd-com/urlcode
|
|
243
|
-
| Add administration | [admin README](https://github.com/jimhoyd-com/urlcode
|
|
244
|
-
| Restyle every page | [ui README](https://github.com/jimhoyd-com/urlcode
|
|
258
|
+
| Add accounts | [auth README](https://github.com/jimhoyd-com/urlcode/blob/main/packages/auth/README.md), [auth security](https://github.com/jimhoyd-com/urlcode/blob/main/packages/auth/SECURITY.md) |
|
|
259
|
+
| Add administration | [admin README](https://github.com/jimhoyd-com/urlcode/blob/main/packages/admin/README.md) |
|
|
260
|
+
| Restyle every page | [ui README](https://github.com/jimhoyd-com/urlcode/blob/main/packages/ui/README.md), [ui contract](https://github.com/jimhoyd-com/urlcode/blob/main/packages/ui/CONTRACT.md) |
|
|
245
261
|
| Write an extension | [extensions](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md), [extension model review](https://github.com/jimhoyd-com/urlcode/blob/main/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md) |
|
|
246
262
|
| Run it | [operations](https://github.com/jimhoyd-com/urlcode/blob/main/docs/OPERATIONS.md), [install](https://github.com/jimhoyd-com/urlcode/blob/main/docs/INSTALL.md), [deployment checks](https://github.com/jimhoyd-com/urlcode/blob/main/docs/DEPLOYMENT-CHECKS.md) |
|
|
247
263
|
|
|
@@ -276,7 +292,11 @@ fields or bypass target limits or operator grants. See [the design principle](ht
|
|
|
276
292
|
6. [Readiness](https://github.com/jimhoyd-com/urlcode/blob/main/docs/READINESS.md), [capacity](https://github.com/jimhoyd-com/urlcode/blob/main/docs/CAPACITY.md), [DDoS/recovery](https://github.com/jimhoyd-com/urlcode/blob/main/docs/RESILIENCE.md).
|
|
277
293
|
7. [The framework](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FRAMEWORK.md) for accounts, administration and presentation:
|
|
278
294
|
`extensions.<name>` blocks and `extension` mounts are the only YAML those
|
|
279
|
-
packages need
|
|
295
|
+
packages need. [Composing a site](https://github.com/jimhoyd-com/urlcode/blob/main/docs/COMPOSING-A-SITE.md) is the map of what a
|
|
296
|
+
consumer may then change: the `config` each package accepts, the
|
|
297
|
+
presentation overrides under `ui/`, the project functions its lifecycle
|
|
298
|
+
hooks call, and when a requirement instead needs a new extension in
|
|
299
|
+
TypeScript.
|
|
280
300
|
|
|
281
301
|
The root [llms.txt](https://github.com/jimhoyd-com/urlcode/blob/main/llms.txt) is a compact discovery index; the generated
|
|
282
302
|
[llms-full.txt](https://github.com/jimhoyd-com/urlcode/blob/main/llms-full.txt) concatenates the authoring documents above in
|
|
@@ -520,9 +540,10 @@ either `true` or `false`) or `sandbox: true` is enough to silence it.
|
|
|
520
540
|
|
|
521
541
|
The same judgment call applies to a project-level lifecycle hook an
|
|
522
542
|
extension invokes (`onSignUp`, `beforeRegister` and the like) — it is
|
|
523
|
-
first-party project code with the same trusted-by-default rule
|
|
524
|
-
`
|
|
525
|
-
|
|
543
|
+
first-party project code with the same trusted-by-default rule as a
|
|
544
|
+
`function`/`middleware` route. Extension hook contract v1 is trusted-only;
|
|
545
|
+
`sandbox: true` is rejected rather than silently ignored. See
|
|
546
|
+
[EXTENSIONS.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md#project-level-lifecycle-hooks).
|
|
526
547
|
|
|
527
548
|
Guest TypeScript needs `build-typescript --project SOURCE --out NEW_DIRECTORY`
|
|
528
549
|
before serving. Only the emitted `.js`/`.mjs` executes, in QuickJS for a
|
|
@@ -541,7 +562,7 @@ origin grants pinned to the project revision. These are self-hosted features;
|
|
|
541
562
|
providers refuse them. Signals are bounded best effort with drops, no retries
|
|
542
563
|
or persistence. Never turn a user request into an implicit network grant.
|
|
543
564
|
|
|
544
|
-
Before using a feature, ask `urlcode capabilities <name>` for its constraints, grants and target support and `urlcode schema <path>` for only that YAML fragment (MCP: `get_capability`, `get_schema`), instead of guessing.
|
|
565
|
+
Before using a feature, ask `urlcode capabilities <name>` for its constraints, grants and target support and `urlcode schema <path>` for only that YAML fragment (MCP: `get_capability`, `get_schema`), instead of guessing. For an installed extension, use `urlcode extensions --host-file ... --json` or MCP `get_extensions`; its hook contracts include the accepted names, purpose and input/output schemas. Prefer extension configuration and UI copy/templates/theme/CSS, then a declared project hook, and only then a new extension or fork.
|
|
545
566
|
The [tooling SDK and stdio MCP](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md) inspect, validate, explain and preview
|
|
546
567
|
without guest execution, environment reads or writes. Run `urlcode explain /route`
|
|
547
568
|
to check effective methods, policies and cache outcome, and `urlcode manifest`
|
|
@@ -1307,7 +1328,11 @@ entry may likewise be a string, normalized to `{source: <string>}`. Only the
|
|
|
1307
1328
|
long form exists after loading, so `routes`, `audit`, `explain`, revision hashes
|
|
1308
1329
|
and the field reference describe the expansion.
|
|
1309
1330
|
|
|
1310
|
-
ES modules only
|
|
1331
|
+
ES modules only. `.mjs` is loaded as ESM in both modes. A `sandbox: true` route
|
|
1332
|
+
also reads `.js` as ESM independently of Node package settings, but a trusted
|
|
1333
|
+
route imports `.js` through Node's own loader, so an ESM `.js` module needs the
|
|
1334
|
+
nearest `package.json` to declare `"type": "module"`; under `"type": "commonjs"`
|
|
1335
|
+
it fails activation. Prefer `.mjs` for modules that must work in either mode.
|
|
1311
1336
|
[Build-time TypeScript authoring](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TYPESCRIPT-AUTHORING.md) can produce these
|
|
1312
1337
|
JavaScript modules in a separate output project; serving does not transpile them.
|
|
1313
1338
|
The build never imports application code into Node, uses fixed compiler settings,
|
|
@@ -1744,12 +1769,18 @@ Every middleware and the function share the same request and context. Validated
|
|
|
1744
1769
|
with a fresh `context.state` object for this chain. State never survives the
|
|
1745
1770
|
request. Header edits are visible downstream; editing the request does not
|
|
1746
1771
|
reroute it or change already validated inputs. Request bodies are single-use:
|
|
1747
|
-
reading one consumes it for downstream code.
|
|
1748
|
-
API
|
|
1772
|
+
reading one consumes it for downstream code. On a `sandbox: true` route there is
|
|
1773
|
+
no `clone()` or streaming API at all, so pass parsed data through
|
|
1774
|
+
`context.state`; a trusted route receives Node's own `Request`/`Response` and so
|
|
1775
|
+
does have `clone()`, but passing parsed data through `context.state` keeps the
|
|
1776
|
+
chain portable between the two modes.
|
|
1749
1777
|
|
|
1750
1778
|
Function responses support the existing text/JSON guest API. To transform their
|
|
1751
|
-
body, read it and return a new `Response`.
|
|
1752
|
-
download bodies are opaque and cannot be read
|
|
1779
|
+
body, read it and return a new `Response`. On a `sandbox: true` route, native
|
|
1780
|
+
redirect/respond/page/static/download bodies are opaque and cannot be read
|
|
1781
|
+
through `text()` or `json()`; a trusted chain receives them as an ordinary
|
|
1782
|
+
`Response` and can read them, so wrapping `respond: {text: hello}` and returning
|
|
1783
|
+
`HELLO` works there and fails in the guest.
|
|
1753
1784
|
Returning the same native response preserves original bytes, including binary
|
|
1754
1785
|
files, ranges and HEAD lengths. You may add headers, but cannot change its
|
|
1755
1786
|
original status or existing native headers while preserving that body. To replace
|
|
@@ -2672,29 +2703,18 @@ Node adapter conformance is not a live-provider deployment claim.
|
|
|
2672
2703
|
|
|
2673
2704
|
### Project-level lifecycle hooks
|
|
2674
2705
|
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
**The pattern.** An extension with lifecycle events an author judges worth
|
|
2689
|
-
exposing should let the project name its own function in the extension's own
|
|
2690
|
-
`config`, using the same source shape `function`/`middleware` routes already
|
|
2691
|
-
use (a string path, or `{source, export, args}` — `schemas/urlcode.schema.json`),
|
|
2692
|
-
and add its own `sandbox` boolean next to it (below). The extension's own
|
|
2693
|
-
`activate()` reads that config, and its own runtime dispatch — not a new core
|
|
2694
|
-
primitive, an ordinary call the extension package makes with the request
|
|
2695
|
-
context it already has — invokes the named function at the lifecycle point
|
|
2696
|
-
it defines, with a typed input and a typed verdict the extension's own
|
|
2697
|
-
schema documents. For example, an auth-style extension might declare:
|
|
2706
|
+
Extensions expose project customization points through the core hook primitive.
|
|
2707
|
+
Each registration publishes `hooks`, a machine-readable list containing the
|
|
2708
|
+
hook name, whether it is a value-transforming `filter` or side-effect `action`,
|
|
2709
|
+
its description and its input/output JSON Schemas. The extension embeds
|
|
2710
|
+
`extensionHooksSchema(contracts)` in its configuration schema and calls
|
|
2711
|
+
`loadExtensionHooks(config.hooks, contracts, context)` during activation.
|
|
2712
|
+
Core then enforces the common source/export shape, project-root confinement,
|
|
2713
|
+
known names, eager module/export validation, input/output schemas and reload
|
|
2714
|
+
cache busting. Hook entry bytes participate in the project revision, so editing
|
|
2715
|
+
a hook invalidates the operator's extension pin.
|
|
2716
|
+
|
|
2717
|
+
Projects select those declared hooks in the extension's own configuration:
|
|
2698
2718
|
|
|
2699
2719
|
```yaml
|
|
2700
2720
|
extensions:
|
|
@@ -2710,76 +2730,51 @@ extensions:
|
|
|
2710
2730
|
```
|
|
2711
2731
|
|
|
2712
2732
|
with `beforeRegister` called before an account is created, given a typed
|
|
2713
|
-
`{email,
|
|
2733
|
+
`{email, profile?}` input and returning a typed verdict (`{allow: true}`
|
|
2714
2734
|
or `{allow: false, reason}`), and `onSignUp` called after, for side effects
|
|
2715
|
-
such as provisioning a workspace.
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
project code
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
unchanged file URL makes a second activation in the same process keep
|
|
2754
|
-
serving the hook code that was on disk at the first one
|
|
2755
|
-
(jimhoyd-com/urlcode#198). Only the hook's **entry** module is refreshed
|
|
2756
|
-
this way — modules the hook itself imports stay on Node's module cache,
|
|
2757
|
-
the same limitation the trusted route path has, so a change to a hook's
|
|
2758
|
-
own dependency still needs a process restart.
|
|
2759
|
-
- **Sandboxed (`sandbox: true`).** `@jimhoyd/urlcode/sandbox` exports
|
|
2760
|
-
`SandboxPool`, the same QuickJS/worker-thread engine that backs a
|
|
2761
|
-
sandboxed `function`/`middleware` route — the identical module-allowlist
|
|
2762
|
-
walk, memory/stack limits, two-layer deadline enforcement, `maxBytes` and
|
|
2763
|
-
response-shape validation, with no separate or weaker engine for
|
|
2764
|
-
extensions. It takes an explicit list of `{source, export}` entries
|
|
2765
|
-
(resolve a hook's `source` string with the re-exported `functionFile()`,
|
|
2766
|
-
the same resolution/validation a native route's `source` gets) instead of
|
|
2767
|
-
anything route/YAML-shaped, and `execute({entry, chain}, request, context)`
|
|
2768
|
-
in place of a `FunctionRoute`. There is no "trusted" mode exported
|
|
2769
|
-
alongside it — `SandboxPool` is only ever the isolated path; see
|
|
2770
|
-
[FUNCTION-SECURITY.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FUNCTION-SECURITY.md) and
|
|
2771
|
-
[TYPESCRIPT.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TYPESCRIPT.md) for the full contract.
|
|
2772
|
-
|
|
2773
|
-
An extension honoring a hook's `sandbox: true` is expected to actually
|
|
2774
|
-
isolate that invocation through `SandboxPool` now that the primitive exists
|
|
2775
|
-
(or document plainly that it does not yet, rather than accepting the field
|
|
2776
|
-
and silently running it trusted) — say which, in the extension's own docs,
|
|
2777
|
-
so an author reading them is not misled about what opt-in exists.
|
|
2735
|
+
such as provisioning a workspace. Hook names and lifecycle timing remain the
|
|
2736
|
+
extension's domain, while their declaration, loading and discovery are shared.
|
|
2737
|
+
|
|
2738
|
+
Hooks are first-party project code and run trusted in-process by default, with
|
|
2739
|
+
full Node access, like trusted `function` and `middleware` routes. Contract v1
|
|
2740
|
+
does not define an arbitrary-value sandbox hook protocol. A hook reference with
|
|
2741
|
+
`sandbox: true` is rejected during activation rather than silently run trusted.
|
|
2742
|
+
Only the entry module is refreshed during reactivation; its imported dependencies
|
|
2743
|
+
remain in Node's module cache until restart.
|
|
2744
|
+
|
|
2745
|
+
The UI extension exposes `transformView`, a synchronous filter called before a
|
|
2746
|
+
named kit template renders. It receives `{template, view}` and returns the view
|
|
2747
|
+
model to render. Use copy, templates, theme and CSS for ordinary presentation
|
|
2748
|
+
changes; use this hook for project-specific computed view data that those
|
|
2749
|
+
declarative layers cannot express.
|
|
2750
|
+
|
|
2751
|
+
### Building an extension
|
|
2752
|
+
|
|
2753
|
+
An extension package should export a registration factory and, when it supports
|
|
2754
|
+
`urlcode init --with`, a side-effect-free `scaffold` function. The registration:
|
|
2755
|
+
|
|
2756
|
+
1. Declares its logical name, contract version, supported targets, exact project
|
|
2757
|
+
revision pin and strict configuration/policy schemas.
|
|
2758
|
+
2. Publishes every project hook through `hooks` and reuses
|
|
2759
|
+
`extensionHooksSchema` plus `loadExtensionHooks`; it does not implement its
|
|
2760
|
+
own path resolver or dynamic-import cache.
|
|
2761
|
+
3. Activates all configuration, files, services and hooks before serving a
|
|
2762
|
+
request. Invalid or stale configuration fails activation.
|
|
2763
|
+
4. Returns `handle` for mounts and optionally `authorize`/`middleware` for route
|
|
2764
|
+
policies. It closes resources it owns.
|
|
2765
|
+
5. Keeps credentials, storage and provider setup in the operator host. Project
|
|
2766
|
+
YAML contains logical configuration and project-relative hook references.
|
|
2767
|
+
|
|
2768
|
+
Consumers install the package, declare its YAML block and mounts/policies, and
|
|
2769
|
+
register it in `host.mjs`. They modify it through declared configuration,
|
|
2770
|
+
presentation layers and hooks. A fork is reserved for changing behavior the
|
|
2771
|
+
extension has not exposed; that is evidence for a new declarative field or hook.
|
|
2772
|
+
See [Composing a site](https://github.com/jimhoyd-com/urlcode/blob/main/docs/COMPOSING-A-SITE.md) for the complete ui/auth/admin example.
|
|
2778
2773
|
|
|
2779
2774
|
### Discovering schemas
|
|
2780
2775
|
|
|
2781
2776
|
Each registration carries the JSON Schemas that validate its `config` block and
|
|
2782
|
-
its per-route policy requirements. `urlcode extensions` prints them together with
|
|
2777
|
+
its per-route policy requirements, plus its hook contracts. `urlcode extensions` prints them together with
|
|
2783
2778
|
the project's own declarations so an author can see what a mount accepts:
|
|
2784
2779
|
|
|
2785
2780
|
```sh
|
|
@@ -2788,6 +2783,7 @@ urlcode extensions --project ./site --host-file /absolute/operator/host.mjs [--j
|
|
|
2788
2783
|
|
|
2789
2784
|
For every registration in the host file it reports the name, contract version,
|
|
2790
2785
|
targets, credential headers, configuration schema, policy schema (if any),
|
|
2786
|
+
declared hook names, kinds, descriptions and input/output schemas,
|
|
2791
2787
|
whether the project declares it, whether its `projectSha256` matches the current
|
|
2792
2788
|
revision, the routes that mount it and the routes whose policies require it.
|
|
2793
2789
|
Declared names the host does not register are listed as unregistered. The command
|
|
@@ -2825,16 +2821,15 @@ boundary, not a JavaScript sandbox or an independent security review.
|
|
|
2825
2821
|
|
|
2826
2822
|
### Scaffolding with `init --with`
|
|
2827
2823
|
|
|
2828
|
-
`urlcode init <directory> --with auth,admin` produces the layered site the
|
|
2824
|
+
`urlcode init <directory> --with ui,auth,admin` produces the layered site the
|
|
2829
2825
|
[framework page](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FRAMEWORK.md#the-composition-contract) describes in one
|
|
2830
2826
|
command: the starter under `<directory>/app/`, one `host.mjs`, one `README.md`,
|
|
2831
2827
|
and each extension's own operator files. Core never bundles or imports the
|
|
2832
2828
|
extension packages at build time; at run time it resolves
|
|
2833
2829
|
`@jimhoyd/urlcode-<name>` for each name with Node's package resolution from
|
|
2834
|
-
the invoking directory
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
the package and calls its
|
|
2830
|
+
the invoking directory. Install a compatible package set there, as shown in
|
|
2831
|
+
[the framework guide](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FRAMEWORK.md#the-composition-contract), before using
|
|
2832
|
+
`--with ui,auth`; UI must activate before auth. Core imports the package and calls its
|
|
2838
2833
|
`scaffold` export with this request:
|
|
2839
2834
|
|
|
2840
2835
|
```ts
|
|
@@ -2890,6 +2885,37 @@ Assembly rules, in `--with` order:
|
|
|
2890
2885
|
review the project and pin it explicitly; the host is generated to require
|
|
2891
2886
|
the pin, never to compute it.
|
|
2892
2887
|
|
|
2888
|
+
#### Recorded versions
|
|
2889
|
+
|
|
2890
|
+
`init --with` also writes `<directory>/package.json`: a private manifest that
|
|
2891
|
+
pins, at exactly the version that was resolved, the running runtime, every
|
|
2892
|
+
extension named in `--with`, and every package those extensions declare in
|
|
2893
|
+
`peerDependencies` (so `@jimhoyd/urlcode-ui` is pinned for an `auth,admin` site
|
|
2894
|
+
although nobody named it). Before anything is written, the whole set is checked
|
|
2895
|
+
against every declared peer range; an incompatible combination or a missing
|
|
2896
|
+
required peer refuses and names it, leaving no directory behind.
|
|
2897
|
+
|
|
2898
|
+
Nothing is installed. The generated site has no `node_modules` and no
|
|
2899
|
+
`package-lock.json` until you run `npm install` in it yourself, which the
|
|
2900
|
+
command and the generated README both state as the next step. Reproducibility
|
|
2901
|
+
comes from that install, not from generation.
|
|
2902
|
+
|
|
2903
|
+
- `--no-manifest` generates the site without a `package.json`, for a site whose
|
|
2904
|
+
dependencies are managed elsewhere. Plain `urlcode init` is unchanged and
|
|
2905
|
+
still writes no manifest; add `--manifest` to pin the runtime for a
|
|
2906
|
+
route-only project too.
|
|
2907
|
+
- `--pin <package>=<specifier>` records a specifier instead of the resolved
|
|
2908
|
+
version, for local tarball or offline development
|
|
2909
|
+
(`--pin @jimhoyd/urlcode-auth=file:/abs/urlcode-auth-0.1.0-alpha.6.tgz`). A
|
|
2910
|
+
package installed from a local path or tarball is detected from npm's own
|
|
2911
|
+
install record and pinned by that path without any flag; the README says so,
|
|
2912
|
+
because such a pin only reproduces where that path exists.
|
|
2913
|
+
|
|
2914
|
+
There is no upgrade command. Moving a generated project to newer versions today
|
|
2915
|
+
means editing its `package.json` and re-running `npm install` yourself; nothing
|
|
2916
|
+
in this runtime selects a newer tested set, shows the change, or updates a
|
|
2917
|
+
lockfile for you.
|
|
2918
|
+
|
|
2893
2919
|
Serving the result is the usual explicit host binding:
|
|
2894
2920
|
|
|
2895
2921
|
```sh
|
|
@@ -2898,6 +2924,298 @@ urlcode validate --project app --host-file "$PWD/host.mjs" --origin https://site
|
|
|
2898
2924
|
|
|
2899
2925
|
---
|
|
2900
2926
|
|
|
2927
|
+
# Composing a site from ui, auth and admin
|
|
2928
|
+
|
|
2929
|
+
Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/COMPOSING-A-SITE.md
|
|
2930
|
+
|
|
2931
|
+
One command produces a site that already has accounts, an administration
|
|
2932
|
+
console and a presentation kit wired together:
|
|
2933
|
+
|
|
2934
|
+
```sh
|
|
2935
|
+
npm install @jimhoyd/urlcode @jimhoyd/urlcode-ui @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin
|
|
2936
|
+
urlcode init site --with ui,auth,admin
|
|
2937
|
+
```
|
|
2938
|
+
|
|
2939
|
+
This page is the map of what you may then change, and with which tool. It
|
|
2940
|
+
covers three different activities that are easy to confuse:
|
|
2941
|
+
|
|
2942
|
+
| You want to | Use | Code? |
|
|
2943
|
+
|---|---|---|
|
|
2944
|
+
| Change what an extension is configured to do | the `config` block in `app/urlcode.yaml` | none |
|
|
2945
|
+
| Change how its screens look or read | files under `<site>/ui/` | none (templates are data) |
|
|
2946
|
+
| Run your own logic at one of its lifecycle points | a project function named from that `config` | first-party project JavaScript |
|
|
2947
|
+
| Add a capability none of the three provides | a new extension package | TypeScript against the runtime contract |
|
|
2948
|
+
|
|
2949
|
+
Work down that list, not up. The [declarative-first
|
|
2950
|
+
principle](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PROJECT-DIRECTION.md#design-principle-declarative-first) applies
|
|
2951
|
+
here as much as it does to routes: reach for the next row only when the one
|
|
2952
|
+
above it cannot express the requirement.
|
|
2953
|
+
|
|
2954
|
+
### What `--with ui,auth,admin` generates
|
|
2955
|
+
|
|
2956
|
+
- `site/app/` — the route project: `urlcode.yaml` with an `extensions` block
|
|
2957
|
+
per package, and `routes/extensions.yaml` holding `/assets/ui/*`,
|
|
2958
|
+
`/account/*`, `/private` and `/admin/*`.
|
|
2959
|
+
- `site/host.mjs` — the operator host module, the one place that holds code.
|
|
2960
|
+
It builds the kit with `createUiExtension`, passes the returned object into
|
|
2961
|
+
`authExtension` and `adminExtension`, and lists `ui.registration` first.
|
|
2962
|
+
- `site/ui/` — `copy/`, `templates/` and `extra.css`, the project's
|
|
2963
|
+
presentation overrides, beside the host and **outside** `app/`.
|
|
2964
|
+
- `site/operator-service.mjs`, `site/data/` — auth's operator service and its
|
|
2965
|
+
private key material, mode `0600`.
|
|
2966
|
+
- `site/README.md` — the merged next steps, environment table and the project
|
|
2967
|
+
revision to review and pin.
|
|
2968
|
+
|
|
2969
|
+
Nothing about that wiring is manual any more. The generated host registers
|
|
2970
|
+
`authCatalogue` as a copy source and both `authUiTemplates` and
|
|
2971
|
+
`adminUiTemplates` as template namespaces, because auth and admin render only
|
|
2972
|
+
through the kit and refuse to activate without their own templates present.
|
|
2973
|
+
|
|
2974
|
+
#### Supported combinations
|
|
2975
|
+
|
|
2976
|
+
`--with` order is the activation order, and the kit must be active before
|
|
2977
|
+
anything that renders through it.
|
|
2978
|
+
|
|
2979
|
+
| `--with` | Result |
|
|
2980
|
+
|---|---|
|
|
2981
|
+
| `ui` | Kit only; the host wires no peer catalogue or templates. |
|
|
2982
|
+
| `ui,auth` | Accounts on `/account/*`, rendered through the kit. |
|
|
2983
|
+
| `ui,auth,admin` | The full composition above. |
|
|
2984
|
+
| `auth` or `auth,admin` | Refused: the scaffold names the missing `ui`. |
|
|
2985
|
+
| `auth,admin,ui` | Refused: `ui` must come before the extensions it renders. |
|
|
2986
|
+
| `admin` without `auth` | Refused: admin reuses auth's service, CSRF key and revision. |
|
|
2987
|
+
|
|
2988
|
+
Every refusal happens before anything is written, and leaves no directory
|
|
2989
|
+
behind. There is no auth-without-ui or admin-without-ui configuration in this
|
|
2990
|
+
revision: the UI primitive fallback was retired, so the kit is the only render
|
|
2991
|
+
path (see [OPEN-DECISIONS.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/OPEN-DECISIONS.md)).
|
|
2992
|
+
|
|
2993
|
+
### Declarative configuration
|
|
2994
|
+
|
|
2995
|
+
Each package owns one `extensions.<name>` block. The block itself is core
|
|
2996
|
+
schema (`version` plus `config`); what may go inside `config` is the
|
|
2997
|
+
package's own JSON Schema, which you can print rather than guess:
|
|
2998
|
+
|
|
2999
|
+
```sh
|
|
3000
|
+
urlcode extensions --project ./site/app --host-file "$PWD/site/host.mjs" --json
|
|
3001
|
+
```
|
|
3002
|
+
|
|
3003
|
+
The same report is the MCP tool `get_extensions`, and it is the authoritative
|
|
3004
|
+
answer for both the configuration schema and the per-route policy schema. The
|
|
3005
|
+
generated site starts from something like this:
|
|
3006
|
+
|
|
3007
|
+
```yaml
|
|
3008
|
+
version: "1"
|
|
3009
|
+
extensions:
|
|
3010
|
+
ui:
|
|
3011
|
+
version: "1"
|
|
3012
|
+
config:
|
|
3013
|
+
theme:
|
|
3014
|
+
name: My Site
|
|
3015
|
+
languages: [en]
|
|
3016
|
+
copy: ui/copy
|
|
3017
|
+
templates: ui/templates
|
|
3018
|
+
stylesheet: ui/extra.css
|
|
3019
|
+
auth:
|
|
3020
|
+
version: "1"
|
|
3021
|
+
config:
|
|
3022
|
+
registration: "off"
|
|
3023
|
+
admin:
|
|
3024
|
+
version: "1"
|
|
3025
|
+
config: {}
|
|
3026
|
+
```
|
|
3027
|
+
|
|
3028
|
+
Routes mount an extension, and policies require one:
|
|
3029
|
+
|
|
3030
|
+
```yaml
|
|
3031
|
+
routes:
|
|
3032
|
+
/assets/ui/*:
|
|
3033
|
+
extension: ui
|
|
3034
|
+
methods: [GET, HEAD]
|
|
3035
|
+
/account/*:
|
|
3036
|
+
extension: auth
|
|
3037
|
+
methods: [GET, HEAD, POST]
|
|
3038
|
+
/admin/*:
|
|
3039
|
+
extension: admin
|
|
3040
|
+
methods: [GET, HEAD, POST]
|
|
3041
|
+
/private:
|
|
3042
|
+
respond:
|
|
3043
|
+
text: Signed in
|
|
3044
|
+
policies:
|
|
3045
|
+
extensions:
|
|
3046
|
+
auth: {}
|
|
3047
|
+
```
|
|
3048
|
+
|
|
3049
|
+
See [EXTENSIONS.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md) for the `auth` route short form, extension
|
|
3050
|
+
middleware, and the host-file trust boundary.
|
|
3051
|
+
|
|
3052
|
+
### Presentation overrides
|
|
3053
|
+
|
|
3054
|
+
The `ui` config's `copy`, `templates` and `stylesheet` paths point at the
|
|
3055
|
+
project's own directories. Nothing here forks a package.
|
|
3056
|
+
|
|
3057
|
+
| Override | File | Effect |
|
|
3058
|
+
|---|---|---|
|
|
3059
|
+
| Wording and translation | `ui/copy/<locale>.json` | Replaces catalogue ids, including ids the auth and admin packages own. Listed in `languages`. |
|
|
3060
|
+
| A whole screen | `ui/templates/<name>.html` | Shadows a kit or extension template of that name, for example `ui/templates/auth/sign-in.html` or `ui/templates/admin/dashboard.html`. |
|
|
3061
|
+
| Styling | `ui/extra.css` | Appended after the kit stylesheet; `{file, replace: true}` replaces it instead. |
|
|
3062
|
+
| Colours, logo, favicon, radius, font | the `theme` block | Declarative; no file needed. |
|
|
3063
|
+
|
|
3064
|
+
A template is data in the kit's own language. It cannot add a script, change
|
|
3065
|
+
what a form validates, or change what a page sends in headers — so an override
|
|
3066
|
+
cannot weaken the screen it restyles. Stylesheets containing `@import`,
|
|
3067
|
+
`script`, `javascript:` or `expression(` are refused.
|
|
3068
|
+
|
|
3069
|
+
Names, coverage and what the runtime will actually load:
|
|
3070
|
+
|
|
3071
|
+
```sh
|
|
3072
|
+
npx urlcode-ui list --project ./site --extensions @jimhoyd/urlcode-auth,@jimhoyd/urlcode-admin
|
|
3073
|
+
npx urlcode-ui doctor --project ./site --extensions @jimhoyd/urlcode-auth,@jimhoyd/urlcode-admin --copy ui/copy --templates ui/templates --stylesheet ui/extra.css
|
|
3074
|
+
npx urlcode-ui eject auth/sign-in --out ./site/ui/templates --project ./site --extensions @jimhoyd/urlcode-auth
|
|
3075
|
+
npx urlcode-ui copy --missing fr --project ./site --extensions @jimhoyd/urlcode-auth --copy ui/copy --languages en,fr
|
|
3076
|
+
```
|
|
3077
|
+
|
|
3078
|
+
`eject` copies the shipped source so an override starts from what ships and
|
|
3079
|
+
never overwrites an existing file. `ui/` lives outside `app/`, so editing copy
|
|
3080
|
+
or templates does **not** change the project revision and does not require
|
|
3081
|
+
re-pinning `PROJECT_SHA256`.
|
|
3082
|
+
|
|
3083
|
+
**Name the packages that ship the other namespaces.** `urlcode-ui` is this
|
|
3084
|
+
kit alone until `--extensions` names them. Each package is resolved from
|
|
3085
|
+
`--project` with Node package resolution and imported for the namespace it
|
|
3086
|
+
exports; one that is not installed there is skipped with a note, so the
|
|
3087
|
+
command still runs. The site's `host.mjs` is never read: it builds services
|
|
3088
|
+
and reads secrets at its top level, and a read-only `list` or `doctor` must
|
|
3089
|
+
not run it. With the packages named:
|
|
3090
|
+
|
|
3091
|
+
- `list` shows the `auth/*` and `admin/*` names beside the kit's own, each
|
|
3092
|
+
with its origin, and `eject auth/sign-in` copies the shipped source.
|
|
3093
|
+
- `doctor` reports an `expected` view model for an extension template, so its
|
|
3094
|
+
`behind` flag tells you when an override of one has fallen behind what
|
|
3095
|
+
ships. Its `extensions` field names the namespaces the report covers, so a
|
|
3096
|
+
report built without a peer is visible as such.
|
|
3097
|
+
- `preview auth/sign-in` renders the extension's own sample view model.
|
|
3098
|
+
- `copy --missing` skeletons cover the auth ids the account screens use.
|
|
3099
|
+
Admin-owned `adminUi.*` ids are deliberately not offered: admin composes
|
|
3100
|
+
its catalogue onto the kit's presentation rather than registering it there,
|
|
3101
|
+
and those translations do not currently reach the console
|
|
3102
|
+
([#227](https://github.com/jimhoyd-com/urlcode/issues/227)).
|
|
3103
|
+
|
|
3104
|
+
`urlcode init <directory> --with ui,auth,admin` writes these commands into the
|
|
3105
|
+
generated README with the flag already set. `@jimhoyd/urlcode-ui` depends on
|
|
3106
|
+
neither peer; the operator names them.
|
|
3107
|
+
|
|
3108
|
+
Overrides of extension templates and of extension-owned catalogue ids reach
|
|
3109
|
+
the rendered screens, which is what the regression test below asserts.
|
|
3110
|
+
|
|
3111
|
+
### Project functions: lifecycle hooks
|
|
3112
|
+
|
|
3113
|
+
A hook is your own function, named from the extension's `config`, that the
|
|
3114
|
+
extension calls at a lifecycle point it defines. It uses the same source shape
|
|
3115
|
+
a `function` route uses — a bare path, or `{source, export}` — resolved
|
|
3116
|
+
relative to the route project.
|
|
3117
|
+
|
|
3118
|
+
```yaml
|
|
3119
|
+
extensions:
|
|
3120
|
+
auth:
|
|
3121
|
+
version: "1"
|
|
3122
|
+
config:
|
|
3123
|
+
registration: open
|
|
3124
|
+
hooks:
|
|
3125
|
+
beforeRegister:
|
|
3126
|
+
source: ./hooks/registration-rule.mjs
|
|
3127
|
+
export: default
|
|
3128
|
+
onSignUp: ./hooks/on-signup.mjs
|
|
3129
|
+
```
|
|
3130
|
+
|
|
3131
|
+
`hooks` remains each package's own config, but core supplies the reference
|
|
3132
|
+
schema, trusted loader and machine-readable hook contract. `get_extensions`
|
|
3133
|
+
reports those contracts, so an agent can discover accepted names, purpose and
|
|
3134
|
+
input/output shapes without guessing from prose.
|
|
3135
|
+
|
|
3136
|
+
Hooks are first-party project code and run **trusted and in-process**, the
|
|
3137
|
+
same default `function` and `middleware` routes have
|
|
3138
|
+
([FUNCTION-SECURITY.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/FUNCTION-SECURITY.md)). Extension hook contract v1 is
|
|
3139
|
+
trusted-only; `sandbox: true` is rejected loudly at activation.
|
|
3140
|
+
|
|
3141
|
+
#### `@jimhoyd/urlcode-ui`
|
|
3142
|
+
|
|
3143
|
+
| Hook | Input | Returns | Called |
|
|
3144
|
+
|---|---|---|---|
|
|
3145
|
+
| `transformView` | `{template, view}` | the view object to render | Synchronously before each public `ui.kit.render()` or `ui.kit.page()` call. Use it only when theme, copy, templates and CSS cannot express the change. |
|
|
3146
|
+
|
|
3147
|
+
For example, `transformView: ./hooks/ui-view.mjs` can add project-computed
|
|
3148
|
+
navigation or labels to an auth/admin view without editing either package.
|
|
3149
|
+
|
|
3150
|
+
#### `@jimhoyd/urlcode-auth`
|
|
3151
|
+
|
|
3152
|
+
| Hook | Input | Returns | Called |
|
|
3153
|
+
|---|---|---|---|
|
|
3154
|
+
| `beforeRegister` | `{email, profile?}` | `{allow: boolean, reason?}` | Before an account is created, on `POST /account/register` and on `POST /account/signup/begin`. |
|
|
3155
|
+
| `onSignUp` | `{accountId, email}` | ignored | After a genuinely new account is created — on `/account/register`, and on `/account/signup/complete` only when that completion created an account rather than signing an existing one in. |
|
|
3156
|
+
| `onDelete` | `{accountId, email}` | ignored | After the account owner's own deletion is scheduled. Not on an administrator-initiated deletion, and not on the background purge when the grace period ends. |
|
|
3157
|
+
|
|
3158
|
+
#### `@jimhoyd/urlcode-admin`
|
|
3159
|
+
|
|
3160
|
+
| Hook | Input | Returns | Called |
|
|
3161
|
+
|---|---|---|---|
|
|
3162
|
+
| `beforeRoleChange` | `{accountId, currentRoles, requestedRoles, actorId, reason}` | `{allow: boolean, reason?}` | Before roles are applied, after the administrator's permission check. A veto means the auth service is never asked. |
|
|
3163
|
+
| `onRegistrationApproved` | `{requestId, accountId, email, actorId, reason}` | ignored | After a registration request is approved. |
|
|
3164
|
+
| `onAccountStatusChanged` | `{accountId, status, actorId, reason}` | ignored | After an account is locked or unlocked. |
|
|
3165
|
+
|
|
3166
|
+
#### Verdicts and failure
|
|
3167
|
+
|
|
3168
|
+
- **A veto is explicit.** A pre-action hook allows only by returning
|
|
3169
|
+
`allow: true`. `allow: false`, or no verdict at all, rejects the operation
|
|
3170
|
+
with `403` and the hook's own `reason`, or a generic message when it gave
|
|
3171
|
+
none. Nothing is written. A hook that *throws* has not returned a verdict:
|
|
3172
|
+
the operation is still refused, but as a generic `500`, so return a verdict
|
|
3173
|
+
rather than throwing when you mean to deny.
|
|
3174
|
+
- **Broken hooks fail at activation, not at the first request.** A missing
|
|
3175
|
+
module, a source path escaping the project, an export that is not a
|
|
3176
|
+
function, or `sandbox: true` all throw while the extension activates, naming
|
|
3177
|
+
the hook. The site does not start.
|
|
3178
|
+
- **A post-action hook cannot undo anything.** `onSignUp`,
|
|
3179
|
+
`onDelete`, `onRegistrationApproved` and `onAccountStatusChanged` run after
|
|
3180
|
+
the operation has committed. Throwing from one replaces the success response
|
|
3181
|
+
with a `500` while the account, approval or status change stands. There is
|
|
3182
|
+
no retry and no rollback. Keep them non-throwing: catch your own errors and
|
|
3183
|
+
queue the work instead of failing the request.
|
|
3184
|
+
- **A hook's message is not a channel to the browser.** Only a pre-action
|
|
3185
|
+
`reason` is shown. An uncaught error surfaces as a generic failure.
|
|
3186
|
+
- **An edited hook needs a restart.** Activation re-imports the hook's entry
|
|
3187
|
+
module, so a reload picks up an edit to that file — but modules it imports
|
|
3188
|
+
stay on Node's module cache, exactly as for trusted route functions.
|
|
3189
|
+
|
|
3190
|
+
### TypeScript: implementing a new extension
|
|
3191
|
+
|
|
3192
|
+
Only write an extension when a capability is genuinely absent — not to
|
|
3193
|
+
customize one of the three above. An extension is an operator-installed
|
|
3194
|
+
package whose host object core activates; it is named in `host.mjs`, never
|
|
3195
|
+
in YAML. The contract, the activation inputs, `ExtensionActivation.root`,
|
|
3196
|
+
credential headers and the `projectSha256` pin are in
|
|
3197
|
+
[EXTENSIONS.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md) and [TYPESCRIPT.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TYPESCRIPT.md); the
|
|
3198
|
+
`scaffold` export that makes a package work with `init --with` is in
|
|
3199
|
+
[EXTENSIONS.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md#scaffolding-with-init---with).
|
|
3200
|
+
|
|
3201
|
+
If its screens should be themeable the same way auth's and admin's are, it
|
|
3202
|
+
also exports a template namespace (and, if it ships English wording, a
|
|
3203
|
+
catalogue) for a host to pass to `createUiExtension`. That is what makes
|
|
3204
|
+
`ui/templates/<yourname>/<screen>.html` work in a consumer project without a
|
|
3205
|
+
fork.
|
|
3206
|
+
|
|
3207
|
+
### What this page does not claim
|
|
3208
|
+
|
|
3209
|
+
The composition, the refusals and the override path are exercised by
|
|
3210
|
+
`test/workspace-scaffold.integration.ts`, which runs `init --with` against the
|
|
3211
|
+
built packages, drops a template and a copy catalogue into the generated
|
|
3212
|
+
`ui/` directory and asserts both reach a rendered auth screen and a rendered
|
|
3213
|
+
admin screen. That runs in-process against the generated host: no HTTP
|
|
3214
|
+
listener, TLS proxy, browser or deployed site is exercised, and no published
|
|
3215
|
+
npm tarball is checked against this checkout.
|
|
3216
|
+
|
|
3217
|
+
---
|
|
3218
|
+
|
|
2901
3219
|
# Function execution: trusted by default, sandboxed opt-in
|
|
2902
3220
|
|
|
2903
3221
|
Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/FUNCTION-SECURITY.md
|
|
@@ -2995,14 +3313,11 @@ machinery either way.
|
|
|
2995
3313
|
|
|
2996
3314
|
This engine — worker spawning, the module-allowlist walk, the two-layer
|
|
2997
3315
|
deadline, `maxBytes` and response-shape validation — is one implementation
|
|
2998
|
-
shared by route dispatch and by `@jimhoyd/urlcode/sandbox`'s `SandboxPool
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
`sandbox: true`
|
|
3002
|
-
|
|
3003
|
-
through that entry point; there is no separate, weaker sandbox for extensions
|
|
3004
|
-
to reach for, and no "trusted" mode exported there — trusted execution needs
|
|
3005
|
-
no primitive at all (docs/SPIKE-DEFAULT-TRUST-MODEL.md).
|
|
3316
|
+
shared by route dispatch and by `@jimhoyd/urlcode/sandbox`'s `SandboxPool`.
|
|
3317
|
+
That public HTTP-shaped primitive remains available to extension authors, but
|
|
3318
|
+
project extension hooks use arbitrary typed values and contract v1 runs them
|
|
3319
|
+
trusted in-process; it rejects `sandbox: true` rather than claiming HTTP sandbox
|
|
3320
|
+
semantics apply to them. See [extensions](https://github.com/jimhoyd-com/urlcode/blob/main/docs/EXTENSIONS.md#project-level-lifecycle-hooks).
|
|
3006
3321
|
|
|
3007
3322
|
### What the trusted default can and can't do
|
|
3008
3323
|
|