@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
|
@@ -30,6 +30,11 @@ Start with `urlcode context --project <dir> --budget 4000`, then retrieve the
|
|
|
30
30
|
capability, schema fragment, recipe or example relevant to the change. Use the
|
|
31
31
|
read-only MCP equivalents when available. `llms.txt` is the index; read the
|
|
32
32
|
matching task guide from `docs/` when a query needs more explanation.
|
|
33
|
+
When the project has an operator host file, inspect `urlcode extensions
|
|
34
|
+
--project <dir> --host-file <absolute-file> --json` (MCP: `get_extensions`)
|
|
35
|
+
before writing extension configuration or project hooks. The report is the
|
|
36
|
+
machine-readable source for config/policy schemas and hook names, purposes and
|
|
37
|
+
input/output schemas.
|
|
33
38
|
`docs/SPECIFICATION.md` and `schemas/urlcode.schema.json` resolve contract
|
|
34
39
|
questions. Archived plans are historical, not valid YAML guidance.
|
|
35
40
|
|
|
@@ -49,6 +54,11 @@ questions. Archived plans are historical, not valid YAML guidance.
|
|
|
49
54
|
- Create every referenced module, page and asset **before** validating. All
|
|
50
55
|
source paths resolve from the project root. Trusted modules can import Node built-ins and npm packages;
|
|
51
56
|
only `sandbox: true` modules are restricted to the relative snapshotted graph.
|
|
57
|
+
- Customize installed extensions in this order: declarative configuration;
|
|
58
|
+
`urlcode-ui` copy, theme, template and CSS overrides; a hook declared by the
|
|
59
|
+
extension; a new extension only when the installed contract cannot express
|
|
60
|
+
the behavior. Extension hooks run trusted in-process and reject `sandbox:
|
|
61
|
+
true` in contract v1.
|
|
52
62
|
- Write exact response fixtures for success and failure, covering every active
|
|
53
63
|
method, middleware behavior, HEAD, and any range or cache semantics.
|
|
54
64
|
- Follow `docs/BEST-PRACTICES.md` for layout and readability as the project grows.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -79,6 +79,42 @@ A behavior change that a reader depends on is not finished until the matching
|
|
|
79
79
|
page in `docs/` is updated. Put both in the **same** pull request so review can
|
|
80
80
|
see both halves and neither can land alone.
|
|
81
81
|
|
|
82
|
+
### Maintain existing pages first
|
|
83
|
+
|
|
84
|
+
Before creating a page, search the docs and update the existing home for the
|
|
85
|
+
reader's task. A new page needs a distinct purpose and a link from the relevant
|
|
86
|
+
guide or index. Routine task summaries and verification transcripts belong in
|
|
87
|
+
the pull request, not a new permanent report.
|
|
88
|
+
|
|
89
|
+
Keep each kind of information in its authoritative home:
|
|
90
|
+
|
|
91
|
+
| Information | Home |
|
|
92
|
+
|---|---|
|
|
93
|
+
| Implemented behavior and accepted fields | Specification, topic contracts and generated schema reference |
|
|
94
|
+
| How to accomplish a task | The existing guide or executable recipe |
|
|
95
|
+
| Current versions and publication state | Manifests and the commands in [version alignment](docs/VERSION-ALIGNMENT.md) |
|
|
96
|
+
| Actionable bugs and proposed work | GitHub issues; the roadmap links to priorities rather than copying task lists |
|
|
97
|
+
| Unresolved design choices | [Open decisions](docs/OPEN-DECISIONS.md), linked to the relevant issue |
|
|
98
|
+
| Security and operational evidence | Dated, scoped evidence records and the readiness register |
|
|
99
|
+
| Completed or superseded plans | [Archive](docs/archive/README.md), with a link to the current owner of any remaining work |
|
|
100
|
+
|
|
101
|
+
Link to these sources instead of copying changing status or entire explanations.
|
|
102
|
+
Examples and brief task-specific explanations are useful; a second maintained
|
|
103
|
+
version table or backlog is not. Do not treat archiving a plan as closing its
|
|
104
|
+
unperformed security or deployment checks.
|
|
105
|
+
|
|
106
|
+
When a decision is implemented, update the reader-facing guide and remove its
|
|
107
|
+
obsolete next steps. Preserve useful rationale and evidence with an explicit
|
|
108
|
+
status; do not leave historical proposals presenting themselves as current
|
|
109
|
+
instructions. Avoid moving files only for tidiness: existing links and anchors
|
|
110
|
+
are part of the documentation interface.
|
|
111
|
+
|
|
112
|
+
Run `npm run check:docs` after documentation changes. It checks local links,
|
|
113
|
+
retired repository references, guidance claims and generated resources. These
|
|
114
|
+
checks cannot prove prose is current: review the affected facts against code
|
|
115
|
+
and evidence too. Reviewers should ask which page owns the changed information
|
|
116
|
+
and whether this PR introduced a competing explanation.
|
|
117
|
+
|
|
82
118
|
`urlcode-docs` was deleted on 2026-09-19. It held its own copy of most of these
|
|
83
119
|
pages and had drifted from them; the content that was ahead has been brought
|
|
84
120
|
across, and the repository was retired rather than reconciled page by page.
|
package/README.md
CHANGED
|
@@ -32,8 +32,10 @@ compiles it once, and refuses anything a target cannot enforce with the route
|
|
|
32
32
|
named. Functions and middleware run trusted, in-process, with full Node
|
|
33
33
|
access by default; a route opts into an isolated QuickJS/WebAssembly sandbox
|
|
34
34
|
with a fresh heap per call and no Node, filesystem or network by declaring
|
|
35
|
-
`sandbox: true`. Either way, secrets
|
|
36
|
-
pinned to the project revision
|
|
35
|
+
`sandbox: true`. Either way, the `env`/`secrets` the runtime *hands* a route
|
|
36
|
+
come only from operator grants pinned to the project revision; grants govern
|
|
37
|
+
that injected context, not the ambient Node environment a trusted, in-process
|
|
38
|
+
module can reach on its own like any other code in the host.
|
|
37
39
|
|
|
38
40
|
URLCode is not a URL shortener: stored short links are an operator-installed
|
|
39
41
|
extension, not core's job. It is not a
|
|
@@ -53,16 +55,17 @@ an AI agent must follow are in [the framework](docs/FRAMEWORK.md).
|
|
|
53
55
|
|
|
54
56
|
| Package | Adds | Status |
|
|
55
57
|
|---|---|---|
|
|
56
|
-
| [urlcode](https://github.com/jimhoyd-com/urlcode) (this repository) | Runtime, CLI, policies, provider adapters, extension contract | `0.4.
|
|
57
|
-
| [urlcode-ui](packages/ui) (in this repository) | Shared presentation: escaped templates, shadcn/ui partials, themes, translations | `0.1
|
|
58
|
-
| [urlcode-auth](packages/auth) (in this repository) | Accounts: password, passkeys, OIDC, email codes, TOTP, sessions, roles, account page | `0.1
|
|
59
|
-
| [urlcode-admin](packages/admin) (in this repository) | Administration: users, sessions, roles, audit, approvals, cases, impersonation | `0.1
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
| [urlcode](https://github.com/jimhoyd-com/urlcode) (this repository) | Runtime, CLI, policies, provider adapters, extension contract | `0.4.1` stable release target, Apache-2.0 |
|
|
59
|
+
| [urlcode-ui](packages/ui) (in this repository) | Shared presentation: escaped templates, shadcn/ui partials, themes, translations | `0.4.1` stable release target; review pending |
|
|
60
|
+
| [urlcode-auth](packages/auth) (in this repository) | Accounts: password, passkeys, OIDC, email codes, TOTP, sessions, roles, account page | `0.4.1` stable release target; review pending |
|
|
61
|
+
| [urlcode-admin](packages/admin) (in this repository) | Administration: users, sessions, roles, audit, approvals, cases, impersonation | `0.4.1` stable release target; review pending |
|
|
62
|
+
|
|
63
|
+
This release aligns all four packages at `0.4.1`, with publication to npm's
|
|
64
|
+
`latest` channel in the order core → ui → auth → admin. The table describes
|
|
65
|
+
the release target; confirm publication with `npm run release:status` before
|
|
66
|
+
installing it. Matching numbers do not establish a permanent fixed-version
|
|
67
|
+
policy. Peer ranges, channel rules and generated application pins are documented
|
|
68
|
+
in [package and channel alignment](docs/VERSION-ALIGNMENT.md).
|
|
66
69
|
|
|
67
70
|
`urlcode-dynamic-link` and `urlcode-short` were published once as
|
|
68
71
|
`0.1.0-alpha.1` and have since been retired: both were unpublished from npm and
|
|
@@ -103,8 +106,9 @@ it. Cross-repository acceptance is tracked in
|
|
|
103
106
|
|
|
104
107
|
## Status
|
|
105
108
|
|
|
106
|
-
`0.4.
|
|
107
|
-
|
|
109
|
+
The `0.4.1` release line brings core, UI, auth and admin to matching stable
|
|
110
|
+
versions. A stable version selects the npm `latest` channel; it does not close
|
|
111
|
+
the review and deployment evidence gaps below. `0.4.0-alpha.1`
|
|
108
112
|
added the extension contract, capabilities and provider conformance, strict
|
|
109
113
|
redirect interchange, bulk import, recipes and search, TypeScript guest
|
|
110
114
|
authoring, conditions, bounded proxy and signals, and the MCP read and
|
|
@@ -174,7 +178,8 @@ SQLite build requirement, unverified from this repository.
|
|
|
174
178
|
## Try it
|
|
175
179
|
|
|
176
180
|
```sh
|
|
177
|
-
|
|
181
|
+
# After the 0.4.1 release has been published:
|
|
182
|
+
npm install --global @jimhoyd/urlcode@0.4.1
|
|
178
183
|
urlcode init my-urls && cd my-urls
|
|
179
184
|
urlcode dev
|
|
180
185
|
```
|
package/ROADMAP.md
CHANGED
|
@@ -7,12 +7,14 @@ principles; [the specification](docs/SPECIFICATION.md) owns implemented behavior
|
|
|
7
7
|
|
|
8
8
|
## What works now
|
|
9
9
|
|
|
10
|
-
The source
|
|
10
|
+
The current source provides declarative routing, responses, assets, policies,
|
|
11
11
|
conditions, proxy/signals, trusted Node functions and middleware, and opt-in
|
|
12
12
|
`sandbox: true` isolation. Target support differs: use `urlcode capabilities`
|
|
13
13
|
before promising a deployment. Stored short links have no supported package.
|
|
14
14
|
|
|
15
|
-
Auth, admin
|
|
15
|
+
Auth, admin and UI are optional packages, developed in this repository under
|
|
16
|
+
`packages/` and released independently. Middleware is not a package: per-route
|
|
17
|
+
`middleware:` is native to core. Auth/admin already
|
|
16
18
|
render through the shared UI kit when configured. Core includes scaffolding,
|
|
17
19
|
searchable recipes/examples, compact context, schema queries, a semantic manifest,
|
|
18
20
|
and MCP inspection with separately enabled authoring. These are implemented,
|
|
@@ -20,14 +22,19 @@ not future phases. See [the framework](docs/FRAMEWORK.md).
|
|
|
20
22
|
|
|
21
23
|
## Next work
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
Auth, admin and UI are workspace packages here, released from this repository;
|
|
26
|
+
the migration is [done](docs/archive/2026-09-20/OPEN-DECISIONS-COMPLETED.md) and
|
|
27
|
+
its plan is [archived](docs/archive/2026-09-19/SPIKE-MONOREPO.md). The separate
|
|
28
|
+
middleware package was withdrawn rather than migrated
|
|
29
|
+
([the decision](docs/OPEN-DECISIONS.md#accepted-middleware-withdrawn-rather-than-consolidated)).
|
|
30
|
+
Versions and channels are in [version alignment](docs/VERSION-ALIGNMENT.md) and
|
|
31
|
+
`npm run release:status`, not in this page.
|
|
25
32
|
|
|
26
33
|
1. **Make the existing product coherent.** Keep docs, examples, generated LLM
|
|
27
34
|
resources, installed skills and the standalone template consistent with their
|
|
28
35
|
runtime version. Resolve the [open decisions](docs/OPEN-DECISIONS.md).
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
Schema-invalid YAML examples in Markdown are now checked
|
|
37
|
+
(`scripts/check-guidance-claims.ts`; issue 168 is closed).
|
|
31
38
|
[Issue 174](https://github.com/jimhoyd-com/urlcode/issues/174) retains the
|
|
32
39
|
extension-schema retrieval proposal.
|
|
33
40
|
2. **Measure the agent experience.** The benchmark harness and authoring evals
|
|
@@ -40,16 +47,18 @@ folding it into core; coordinate the guidance cleanup with that migration.
|
|
|
40
47
|
independent security review, deployed recovery/soak tests and real provider
|
|
41
48
|
verification remain distinct from source implementation and local tests.
|
|
42
49
|
[Issue 58](https://github.com/jimhoyd-com/urlcode/issues/58) and
|
|
43
|
-
[release readiness](docs/RELEASE-READINESS.md) retain those gates
|
|
44
|
-
Google/Apple/SES checks remain
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
[issue
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
[release readiness](docs/RELEASE-READINESS.md) retain those gates; a stable
|
|
51
|
+
release or green CI does not close them. Live Google/Apple/SES checks remain
|
|
52
|
+
explicitly deferred. [Issue 185](https://github.com/jimhoyd-com/urlcode/issues/185)
|
|
53
|
+
(CI lane measurement and release-train validation) and
|
|
54
|
+
[issue 202](https://github.com/jimhoyd-com/urlcode/issues/202) (Windows auth
|
|
55
|
+
worker startup timeout) are also still open.
|
|
56
|
+
4. **Choose expansion from evidence.** Collections and a business application
|
|
57
|
+
suite are proposals, not available features. Decide scope before implementing
|
|
58
|
+
them. Per-route Lambda compilation is **decided against**: projects using
|
|
59
|
+
`function` or `middleware` deploy as one trusted Node process, and serverless
|
|
60
|
+
targets keep refusing those routes at activation
|
|
61
|
+
([the decision](docs/OPEN-DECISIONS.md#accepted-one-node-deployment-per-project)).
|
|
53
62
|
|
|
54
63
|
## Records and ownership
|
|
55
64
|
|
package/dist/BUILD-MANIFEST.json
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
"dist/agents-guide.js": "0b19909aee3f8a1039c34c43ff58116ec1bc3f1875e3dbc34b3b01655273201c",
|
|
7
7
|
"dist/assets.js": "0833b093d2fe457ce68281110dc07d47914992753607741c13b339d92c76cf5c",
|
|
8
8
|
"dist/authoring-files.js": "ad32814f9ae9c549c6247396f982aa700d13a293cc0a589609eccc68f8965550",
|
|
9
|
-
"dist/authoring.js": "
|
|
9
|
+
"dist/authoring.js": "5fe1e871ff216ef1f7d2f490f74397b81b1de4edfe31cdb6115907296aa1c652",
|
|
10
10
|
"dist/aws.js": "7d8e3a97b4f6dbefbd68da058f05192469d1efcaed5458b284863ebe462e060e",
|
|
11
11
|
"dist/build-cloudflare.js": "af4f49ad45287d7c4105bb84b1592b5b8e76c930f7159505338298f7c5fd5651",
|
|
12
12
|
"dist/build-static.js": "fe01ef4fcc66d83d8be7a787538b55974e84fd3d7c89e26f0a2f1a152762d565",
|
|
13
13
|
"dist/bulk.js": "aac88d422bd9a7a341a421fb4e29ff5580b28d39250b7f1f5315ba20ae92da8a",
|
|
14
14
|
"dist/capabilities.js": "28dba05b3851b8b12b3c1b809af87b732f84d601c737afe9225abe429886e691",
|
|
15
|
-
"dist/capability-query.js": "
|
|
16
|
-
"dist/catalog.js": "
|
|
17
|
-
"dist/cli.js": "
|
|
15
|
+
"dist/capability-query.js": "9f68bc94901451d7b9ecdd3aee8c3376dca7f0b890e31d8205306cf8120dea86",
|
|
16
|
+
"dist/catalog.js": "c378ef9bfd63b940790f6975f7db54557c2459722b65a7c170b97df24184bc52",
|
|
17
|
+
"dist/cli.js": "76d3741fa476ff20c9dae2daff56bc5c51701c7fdd61ac924ed0f3e2809928ca",
|
|
18
18
|
"dist/client-address.js": "9d0d01466aab23124330605c5a0e0f981f87c897858acfad909f546a63df165b",
|
|
19
19
|
"dist/cloudflare.js": "17b43b0a6b6a0ef8a893415c989e4ae3976d97cc966adbf897b785f9adc472e3",
|
|
20
20
|
"dist/compliance-rules/baseline.js": "6296dea6bcb9f9bb80ed6ed5ab2f456e7f74e6de2be5d2d98ca8c983fb4df3ab",
|
|
@@ -24,58 +24,59 @@
|
|
|
24
24
|
"dist/compliance.js": "29a18da3cefea94802ac7d817d1ab4e3674abd5c3ac4ef893cb8b01c6a23fb61",
|
|
25
25
|
"dist/conditions.js": "ff25e97da550a3bdd80af55522045669438aa31a77d2bc83ad602bcefdee0475",
|
|
26
26
|
"dist/config-worker.js": "bc2170d60c35f8d5de227cbfc4c5067dc61d7cbc5b98aed88e68a11797072da5",
|
|
27
|
-
"dist/config.js": "
|
|
27
|
+
"dist/config.js": "8a0fde242b4658c522031a15d89daed25f8b77761f5c4204f295d5e311ac8ff7",
|
|
28
28
|
"dist/context.js": "549a53e4738b9c20a1ec55614027f758026fb5860bf4ac962ce22d7029e48df0",
|
|
29
29
|
"dist/ecosystem-cli.js": "1e71bacd53d6fa8c7857cfcce3c3012ce054771a434b78d1f39e892e32642edd",
|
|
30
30
|
"dist/egress.js": "2ae29fe2cb4590fd2f715abe6817db47fed2f50dd31f37946ecf42eafbb0dd9e",
|
|
31
31
|
"dist/errors.js": "a908e66496afebbdd632a8c159a6e14d23f8f38798a0b4342ca122f2511c6cde",
|
|
32
32
|
"dist/examples.js": "89ae2b764a688fe52acfb9b94d3d262d7742401ebd0ef10c9ebfc76edfa5631c",
|
|
33
33
|
"dist/explain-cli.js": "f0f8f3b5046430d03c6abb629b38651c9c734b03456040735ce9d7e582b2bcd5",
|
|
34
|
-
"dist/explain.js": "
|
|
35
|
-
"dist/extensions.js": "
|
|
34
|
+
"dist/explain.js": "7fb632036cc68280701dd512c74d27d6e0f97b4848e9024b284d7c5393b35eb5",
|
|
35
|
+
"dist/extensions.js": "07ae8888a5944372ee452b58fb0b6de226b0bc6ed73c5d5a198c1788e1a55fd6",
|
|
36
36
|
"dist/function-sources.js": "16fad4abc81c7ee07b6cbcef2d23a9fc50e97a17cb4dde1db53469bbb555c96f",
|
|
37
37
|
"dist/function-worker.js": "35771790ada4e1b36d447d4967a5e6cf0b543f32944e4c904f5fbf8998e824a5",
|
|
38
38
|
"dist/functions.js": "0f09d1bd27eb655ceb5900a4a5e2b760d2028b02cb637a6a89a4eb5940828008",
|
|
39
39
|
"dist/guest-api.js": "49aec9ed72733ad119e63bcfcee18158dcc8ceb951e1823b8947f6407ff24afc",
|
|
40
40
|
"dist/header-validation.js": "465181dbb08ff05f52defdd29fda025c0c64589bf319fa87fa6d3ab4b68216d5",
|
|
41
41
|
"dist/http-policy.js": "4cb187642269078ddf3460a3f1813dc57878a616a6f8132ef8f8d3b4b91ebb56",
|
|
42
|
-
"dist/http-response.js": "
|
|
43
|
-
"dist/index.js": "
|
|
44
|
-
"dist/init-with.js": "
|
|
42
|
+
"dist/http-response.js": "62f5da2e08bfe7ecff07464a49aaf910aa55770308a59118be9372390c2df9a1",
|
|
43
|
+
"dist/index.js": "8d7532ae0a31f9439ebb62e7b01a2748aee00f350262ff55d1226121d206c1ed",
|
|
44
|
+
"dist/init-with.js": "bf43b3aa4b421e9a31c5227e6afdf4956aba70d836ffddde96176a772d79e09b",
|
|
45
45
|
"dist/interchange-cli.js": "35bd70ba8077af5c3e39404ff0e5d28707632090a59a90645141252449fd3f04",
|
|
46
46
|
"dist/interchange.js": "26789420af33344d9a08c00fe6b2708aa71994224df527b3075295be2151611e",
|
|
47
47
|
"dist/logging.js": "8ec57fea26983aa071b05fe15d680d6550b03488101163ed9666da674118cf31",
|
|
48
|
-
"dist/manifest.js": "
|
|
48
|
+
"dist/manifest.js": "83eed0d59621039ef364cf6e7b54c5bd47e0d8cf0a5abe7b07344bfc1db56ed8",
|
|
49
49
|
"dist/match.js": "53ebcc2cda529a8d07fb83f8cb69a1446036ffc1641af3c4bd45a55851bded28",
|
|
50
|
-
"dist/mcp-authoring.js": "
|
|
51
|
-
"dist/mcp.js": "
|
|
50
|
+
"dist/mcp-authoring.js": "1183a56c5decb99ecb67a5da7bd91d1b494bc17a2965051aa63c4a7e291462b5",
|
|
51
|
+
"dist/mcp.js": "4b818de2567ae8fb03adf3e5984931672ee2b469bc7ae9586388552846dae15e",
|
|
52
52
|
"dist/observability.js": "f4b1ab496f051fe3ef2ed36b2b2e469c63a8940700e2df852e6ff2d9cccdd5ae",
|
|
53
53
|
"dist/operator-host.js": "e3dac9d43a83beb775202b4ab9eeeba7ac63cebfc5dcc669be38d825be587bd0",
|
|
54
54
|
"dist/plugins.js": "a61aa933cd9e1b41020b86fae78aa3a7f45f5313124aa11b50cb57898722385e",
|
|
55
55
|
"dist/policies/agents.js": "c1dbc1e90339f472eeeb2372f7f4637f59a5d210797ea680ac8120a02aa20c10",
|
|
56
|
-
"dist/policies/cache.js": "
|
|
56
|
+
"dist/policies/cache.js": "d08184e50b0ecb7331b29e4145aaefacb23b64ddaf3555d579743762f90c1629",
|
|
57
57
|
"dist/policies/compression.js": "800f3ae3b1a4c1fe58c3d9908364ea9d78f655ff50c7f7589f584378e7f194b0",
|
|
58
58
|
"dist/policies/security.js": "2b3bfdf82140156fea4b162adc28ee43ccd85c402fb200680dbb8d8706417420",
|
|
59
59
|
"dist/policies/throttle.js": "bbb0cb1c24cf70d0a1ac32fc0618efb6c44e5b5d42564842f9c585ff7e501603",
|
|
60
60
|
"dist/policies.js": "3750a7032ec7326aa544f4808683ad9eb91d4f2c5afe79904420cd8a355710a9",
|
|
61
|
-
"dist/policy.js": "
|
|
61
|
+
"dist/policy.js": "2da95b3f1f77249b1e9f399a06b88909b68594fdbc958176075e91ff23104a7c",
|
|
62
62
|
"dist/prerender.js": "6210999e848ab86c87f265943960ef63716896da7a40e248221a6a30425c56b8",
|
|
63
|
+
"dist/project-dependencies.js": "b43a8f3125f62d38f0e71237b7813a785ef0613a83fcdec3fa8314f33758a534",
|
|
63
64
|
"dist/project-tests.js": "445eaa139d2f1a2534369cfdeef081ef518de03218a467e7c5c2059caf840270",
|
|
64
65
|
"dist/provider-verification.js": "ffcf9f05e8cb5f71ea46a97e279e8a613fdfdf9435ab02bd57af11e2680a9107",
|
|
65
66
|
"dist/proxy.js": "34107e59ac0dbbea090efa3cf5badf3226c5302ba25e8f8ff9b2b5596ed80e50",
|
|
66
|
-
"dist/readiness.js": "
|
|
67
|
+
"dist/readiness.js": "28b1ade5addbc3c7427fe6a52b93850001904187c0e03c1552cbb77d8fb8572d",
|
|
67
68
|
"dist/recipes.js": "11853f23e63c3c65c5bed03a278cde0349d28c9fa23266c9907a2df318bb5fc3",
|
|
68
69
|
"dist/route-diff.js": "8d71d50d332ffaaed588656bd32aa36763260d1ad87bb62d7ea1c8fb23383969",
|
|
69
70
|
"dist/router.js": "cf831025d5ed6778d1d6a0e541177799794e76766c82084ded81d3e87317ca1e",
|
|
70
|
-
"dist/runtime.js": "
|
|
71
|
+
"dist/runtime.js": "2346d4630d178ee52caa7a94d4289f9b16f4b8ee467070b39fdd05742ed4513f",
|
|
71
72
|
"dist/sandbox.js": "c88c5aa5dd95b4f55f1ba62da7969ad66dbd97c30f72e5b5f2817a2098ac1c65",
|
|
72
73
|
"dist/scaffold.js": "3ef243aceca7ddf2780aa2c8c04ae17edd07682d3a418fd5d65ba00f5c762f14",
|
|
73
74
|
"dist/schema-query.js": "bfd1844acd8d54ac361115191fcfbaf828a67e223b9ec6e9c22f158761af149b",
|
|
74
75
|
"dist/server.js": "d878bc8f0d6a05f8fd25997cace3150e2ff4d44ee9c8f4b5530e29601e164be1",
|
|
75
76
|
"dist/signals.js": "b55e54efc8fb6703e08f2a1e808ec8e011bda67738db8ceadf1b248f695795be",
|
|
76
77
|
"dist/site.js": "c40a06a6f9e2d6d3bea94727783c115fed4309bb0190787f58434eed64cbde84",
|
|
77
|
-
"dist/tooling.js": "
|
|
78
|
-
"dist/trusted-functions.js": "
|
|
78
|
+
"dist/tooling.js": "191d88eda3bf174f7000fa34995c940f3cf7e9ceafeb4b813e16eb0006767055",
|
|
79
|
+
"dist/trusted-functions.js": "f3800c75f45ce90faf85ba6664398ad9031c5a9ac51d5ce42bdcff08a497d48e",
|
|
79
80
|
"dist/types.js": "2fe7664dbb5695ec72114197abde72de4031df9c3b211740eacefa68e9b21844",
|
|
80
81
|
"dist/typescript-authoring.js": "2e98498a15bdbb76f36fbee84afb7c42ca95bbf80a2e6dd4040147f3b1642a29",
|
|
81
82
|
"dist/vercel.js": "a0da20c095d0c7fa8f579ed4dd5951e7a34191f8a72a57c5e782c9eab39f6f08",
|
package/dist/authoring.js
CHANGED
|
@@ -8,9 +8,18 @@ import { renderAgentsGuide, renderMcpConfig, mcpConfigFile } from './agents-guid
|
|
|
8
8
|
import { compileRoutes } from './router.js';
|
|
9
9
|
import { prepareFunctionSnapshot, requestedPermissions } from './policy.js';
|
|
10
10
|
import { assert } from './errors.js';
|
|
11
|
+
import { renderPackageManifest } from './project-dependencies.js';
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export async function initProject(destination , { manifest } = {}) {
|
|
14
23
|
const target = resolve(destination);
|
|
15
24
|
await mkdir(dirname(target), { recursive: true });
|
|
16
25
|
// Reserve destination before copying; never merge into existing user files.
|
|
@@ -30,6 +39,11 @@ export async function initProject(destination ) {
|
|
|
30
39
|
// .mcp.json registers the read-only server for repository-aware agents; the starter carries the same bytes.
|
|
31
40
|
const mcp = await open(join(target,mcpConfigFile), 'wx', 0o644);
|
|
32
41
|
try { await mcp.writeFile(renderMcpConfig('.')); } finally { await mcp.close(); }
|
|
42
|
+
if (manifest) {
|
|
43
|
+
// Exclusive create: the starter ships no package.json, so this never merges into or overwrites one.
|
|
44
|
+
const pkg = await open(join(target,'package.json'), 'wx', 0o644);
|
|
45
|
+
try { await pkg.writeFile(renderPackageManifest(target, manifest)); } finally { await pkg.close(); }
|
|
46
|
+
}
|
|
33
47
|
} catch (error) { await rm(target, { recursive: true, force: true }); throw error; }
|
|
34
48
|
return target;
|
|
35
49
|
}
|
package/dist/capability-query.js
CHANGED
|
@@ -44,7 +44,6 @@ function usage(base ,files ,name )
|
|
|
44
44
|
}
|
|
45
45
|
return result;
|
|
46
46
|
}
|
|
47
|
-
export function capabilityNameList() {return capabilityNames;}
|
|
48
47
|
/** One catalog entry with its schema fragments and bundled usage. No project, credentials or network are read. */
|
|
49
48
|
export function getCapability(name ) {
|
|
50
49
|
if(typeof name!=='string'||!(capabilityNames ).includes(name))throw new ConfigError('Unknown capability; valid names: '+capabilityNames.join(', '));
|
package/dist/catalog.js
CHANGED
|
@@ -36,7 +36,6 @@ async function validate() {
|
|
|
36
36
|
}
|
|
37
37
|
return validator;
|
|
38
38
|
}
|
|
39
|
-
export const metadataFiles={recipe:'recipe.yaml',example:'example.yaml'} ;
|
|
40
39
|
/** Reads and schema-validates one metadata file; the id must equal the directory name, and file lists stay authoring-safe paths. */
|
|
41
40
|
export async function readMetadata(root ,id ,file ) {
|
|
42
41
|
const text=(await readAuthoringFile(root,file,65536)).toString('utf8');
|
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { startServer } from './server.js';
|
|
|
14
14
|
import {scaffoldProject} from './scaffold.js';
|
|
15
15
|
import { initProject, addRedirect } from './authoring.js';
|
|
16
16
|
import { initProjectWith, parseWithNames } from './init-with.js';
|
|
17
|
+
import { collectDependencySet, installSteps, parsePin } from './project-dependencies.js';
|
|
17
18
|
import { runProjectTests } from './project-tests.js';
|
|
18
19
|
import { verifyDeployment, failLevels } from './verify-deployment.js';
|
|
19
20
|
|
|
@@ -27,8 +28,11 @@ import { loadComplianceRules, profileNames as complianceProfiles } from './compl
|
|
|
27
28
|
import { parseRouteSnapshot, diffRoutes, renderRouteDiff } from './route-diff.js';
|
|
28
29
|
import { readFile } from 'node:fs/promises';
|
|
29
30
|
|
|
30
|
-
const usage = `URLCode 0.4.
|
|
31
|
-
urlcode init <directory> [--with auth,admin]
|
|
31
|
+
const usage = `URLCode 0.4.2 — local/self-hosted runtime
|
|
32
|
+
urlcode init <directory> [--with ui,auth,admin] [--manifest|--no-manifest] [--pin @scope/pkg=specifier]
|
|
33
|
+
# --with: layered site from installed @jimhoyd/urlcode-<name> packages, with a package.json pinning them exactly; name ui first
|
|
34
|
+
# --manifest: also pin the runtime for a route-only project; --no-manifest: --with without a package.json
|
|
35
|
+
# --pin: record a local path or tarball instead of the registry version; repeatable. No install is ever run for you.
|
|
32
36
|
urlcode scaffold [--project directory] [--dry-run]
|
|
33
37
|
urlcode validate [--project directory] [--local] [--origin https://links.example] # origin: absolute URLs in site.* files
|
|
34
38
|
urlcode dev [--project directory] [--port 3000] [--host 127.0.0.1]
|
|
@@ -80,6 +84,7 @@ const print = (value ) => process.stdout.write(typeof value ===
|
|
|
80
84
|
const options = {
|
|
81
85
|
json:{ type:'boolean' }, yaml:{ type:'boolean' }, report:{type:'string'}, 'accept-provider-differences':{type:'boolean'},
|
|
82
86
|
project:{ type:'string', default:'.' }, 'host-file':{type:'string'}, with:{type:'string'},
|
|
87
|
+
manifest:{type:'boolean'}, 'no-manifest':{type:'boolean'}, pin:{type:'string', multiple:true},
|
|
83
88
|
port:{ type:'string' }, host:{ type:'string', default:'127.0.0.1' },
|
|
84
89
|
'expect-routes':{type:'string'}, requests:{type:'string'}, concurrency:{type:'string'}, seconds:{type:'string'}, 'max-p95-ms':{type:'string'}, warmup:{type:'string'}, target:{type:'string'},
|
|
85
90
|
workers:{type:'string'}, 'function-timeout-ms':{type:'string'}, 'max-response-bytes':{type:'string'}, 'max-body-bytes':{type:'string'},
|
|
@@ -130,6 +135,7 @@ function formatExtensions(report ) {
|
|
|
130
135
|
if (!report.declared.length) lines.push('Declared: (none)');
|
|
131
136
|
for (const item of report.extensions) lines.push(`Registered: ${item.name} (contract ${item.version}; targets ${item.targets.join(', ') || '(none)'}; ${item.declared ? 'declared' : 'not declared'}; revision ${item.revisionPinned ? 'pinned' : 'NOT pinned'})`,
|
|
132
137
|
` mounts: ${item.mounts.join(', ') || '(none)'}`, ` policy routes: ${item.policyRoutes.join(', ') || '(none)'}`, ` credential headers: ${item.credentialHeaders.join(', ') || '(none)'}`,
|
|
138
|
+
` hooks: ${item.hooks.length ? item.hooks.map(hook => `${String((hook ).name)} (${String((hook ).kind)})`).join(', ') : '(none)'}`,
|
|
133
139
|
` configuration schema: ${JSON.stringify(item.schema)}`, ` policy schema: ${item.policySchema ? JSON.stringify(item.policySchema) : '(none)'}`);
|
|
134
140
|
lines.push(report.note);
|
|
135
141
|
return lines.join('\n') + '\n';
|
|
@@ -149,6 +155,8 @@ try {
|
|
|
149
155
|
if (command !== 'mcp' && command !== 'context') operatorHost = await loadOperatorHost(values['host-file'], values.project);
|
|
150
156
|
}
|
|
151
157
|
if (values.with !== undefined && command !== 'init') throw new ConfigError('--with is only supported by init');
|
|
158
|
+
if ((values.manifest || values['no-manifest'] || values.pin !== undefined) && command !== 'init') throw new ConfigError('--manifest/--no-manifest/--pin are only supported by init');
|
|
159
|
+
if (values.manifest && values['no-manifest']) throw new ConfigError('Use either --manifest or --no-manifest');
|
|
152
160
|
if (values['allow-authoring'] && command !== 'mcp') throw new ConfigError('--allow-authoring is only supported by mcp');
|
|
153
161
|
const hostOptions = { extensions: operatorHost.extensions, plugins: operatorHost.plugins };
|
|
154
162
|
if ((!['import','recipes','recipe','examples','example','bulk-import'].includes(command) && extra.length) || (!['init','add','import','recipes','recipe','examples','example','bulk-import','explain','capabilities','schema'].includes(command) && arg)) throw new ConfigError('Unexpected positional arguments');
|
|
@@ -266,14 +274,23 @@ try {
|
|
|
266
274
|
const loaded = await loadDocument(values.project);
|
|
267
275
|
print(requestedPermissions(loaded,await prepareFunctionSnapshot(loaded))); break;
|
|
268
276
|
}
|
|
269
|
-
case 'init':
|
|
277
|
+
case 'init': {
|
|
270
278
|
if (!arg) throw new ConfigError('Provide a new project directory');
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
279
|
+
// Pins are opt-in for a route-only project (its runtime may be managed elsewhere) and the default for
|
|
280
|
+
// --with, which has just resolved the very packages the generated site depends on.
|
|
281
|
+
const wanted = values.with === undefined ? values.manifest === true : !values['no-manifest'];
|
|
282
|
+
const pins = new Map((values.pin ?? []).map(parsePin));
|
|
283
|
+
if (pins.size && !wanted) throw new ConfigError('--pin needs a manifest; drop --no-manifest or add --manifest');
|
|
284
|
+
if (values.with === undefined) {
|
|
285
|
+
const set = wanted ? await collectDependencySet([], [], { overrides: pins }) : undefined;
|
|
286
|
+
const created = await initProject(arg, { manifest: set });
|
|
287
|
+
print(set ? { event:'created', dependencies:set.pins, nextSteps:installSteps(created, set) } : { event:'created' });
|
|
288
|
+
break;
|
|
275
289
|
}
|
|
290
|
+
const created = await initProjectWith(arg, parseWithNames(values.with), { manifest: wanted, pins });
|
|
291
|
+
print({ event:'created', ...created, review:`Review ${created.project}/urlcode.yaml and pin its revision explicitly (for example PROJECT_SHA256=${created.projectSha256}); re-review after any project change` });
|
|
276
292
|
break;
|
|
293
|
+
}
|
|
277
294
|
case 'validate': {
|
|
278
295
|
const runtime = await createRuntime(values.project, { ...hostOptions, local:values.local, permissions, origin:values.origin });
|
|
279
296
|
print({ event:'valid', routes:runtime.count, version:runtime.version }); await runtime.close(); break;
|
package/dist/config.js
CHANGED
|
@@ -74,7 +74,7 @@ function modulePath(pattern , kind , file
|
|
|
74
74
|
* a route-level `cache` becomes `policies.cache` (refused alongside a direct `policies.cache`).
|
|
75
75
|
* Everything downstream (routes, audit, the compiled table) sees only the long form.
|
|
76
76
|
*/
|
|
77
|
-
|
|
77
|
+
function normalizeRoute(pattern , route ) {
|
|
78
78
|
const authored = route ;
|
|
79
79
|
const needsFunction = typeof authored.function === 'string';
|
|
80
80
|
const needsMiddleware = authored.middleware?.some(entry => typeof entry === 'string') ?? false;
|
package/dist/explain.js
CHANGED
|
@@ -94,7 +94,7 @@ function targetsOf(loaded ,route ,options
|
|
|
94
94
|
}
|
|
95
95
|
return result;
|
|
96
96
|
}
|
|
97
|
-
|
|
97
|
+
function routeState(route ,now ) {return route.enabled===false?'disabled':route.expiresAt&&now>=route.expiresAt?'expired':'active';}
|
|
98
98
|
/** Describe one compiled route. `chain` is the policy chain compiled for it, when the project declares policies. */
|
|
99
99
|
export function explainCompiledRoute(loaded ,route ,chain ,options ={}) {
|
|
100
100
|
const root=loaded.root,declared=loaded.routes[route.pattern];
|
package/dist/extensions.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import Ajv from 'ajv/dist/2020.js';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
2
4
|
import { assert, HttpError } from './errors.js';
|
|
3
|
-
import { loadDocument } from './config.js';
|
|
5
|
+
import { functionFile, loadDocument } from './config.js';
|
|
4
6
|
import { prepareFunctionSnapshot } from './policy.js';
|
|
5
7
|
import { validateHeaderName, validateHeaderValue } from './header-validation.js';
|
|
6
8
|
|
|
@@ -52,9 +54,76 @@ import { validateHeaderName, validateHeaderValue } from './header-validation.js'
|
|
|
52
54
|
* vary on credentials. Everything else under the mount stays no-store.
|
|
53
55
|
*/
|
|
54
56
|
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
/** Machine-readable contract for one project hook an extension exposes. */
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
/** Shared schema for project hook references. Omission means trusted execution. */
|
|
70
|
+
export const extensionHookReferenceSchema={
|
|
71
|
+
oneOf:[
|
|
72
|
+
{type:'string',minLength:1,maxLength:1024},
|
|
73
|
+
{type:'object',additionalProperties:false,required:['source'],properties:{
|
|
74
|
+
source:{type:'string',minLength:1,maxLength:1024},
|
|
75
|
+
export:{type:'string',pattern:'^[A-Za-z_][A-Za-z0-9_]*$'},
|
|
76
|
+
sandbox:{type:'boolean'},
|
|
77
|
+
sandboxReason:{type:'string',minLength:1,maxLength:512},
|
|
78
|
+
}},
|
|
79
|
+
],
|
|
80
|
+
} ;
|
|
81
|
+
/** Builds the strict `config.hooks` schema from an extension's declared hook names. */
|
|
82
|
+
export function extensionHooksSchema(contracts ) {
|
|
83
|
+
return {type:'object',additionalProperties:false,properties:Object.fromEntries(contracts.map(contract=>[contract.name,extensionHookReferenceSchema]))};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Loads project hooks once per activation. Project hooks are trusted first-party
|
|
87
|
+
* code by default, matching function/middleware routes. Sandboxed arbitrary-value
|
|
88
|
+
* hooks are not part of contract v1 and are refused rather than run trusted.
|
|
89
|
+
*/
|
|
90
|
+
export async function loadExtensionHooks (config ,contracts ,context ) {
|
|
91
|
+
const known=new Map(contracts.map(contract=>[contract.name,contract]));
|
|
92
|
+
const hooks =Object.create(null) ;
|
|
93
|
+
if(config===undefined)return hooks ;
|
|
94
|
+
assert(config&&typeof config==='object'&&!Array.isArray(config),'Extension hooks must be an object');
|
|
95
|
+
const epoch=randomUUID();
|
|
96
|
+
for(const [name,raw] of Object.entries(config)){
|
|
97
|
+
const contract=known.get(name);assert(contract,`Unknown extension hook: ${name}`);
|
|
98
|
+
assert(typeof raw==='string'||raw&&typeof raw==='object'&&!Array.isArray(raw),`Invalid extension hook: ${name}`);
|
|
99
|
+
const reference =typeof raw==='string'?{source:raw}:raw ;
|
|
100
|
+
if(reference.sandbox===true)throw new Error(`hook ${name}: sandbox: true is not supported for extension hooks; hooks run trusted by default`);
|
|
101
|
+
const exportName=reference.export??'default';
|
|
102
|
+
let modulePath ;
|
|
103
|
+
try{modulePath=await functionFile(context.root,reference.source);}
|
|
104
|
+
catch(error){throw new Error(`hook ${name}: failed to load module "${reference.source}"`,{cause:error});}
|
|
105
|
+
let module ;
|
|
106
|
+
try{module=await import(pathToFileURL(modulePath).href+'?urlcode-extension-hook-epoch='+epoch) ;}
|
|
107
|
+
catch(error){throw new Error(`hook ${name}: failed to load module "${reference.source}"`,{cause:error});}
|
|
108
|
+
const fn=module[exportName];
|
|
109
|
+
if(typeof fn!=='function')throw new Error(`hook ${name}: export "${exportName}" in "${reference.source}" is not a function`);
|
|
110
|
+
const ajv=new Ajv.default({strict:false,allErrors:false});
|
|
111
|
+
const validateInput=ajv.compile(contract.inputSchema);
|
|
112
|
+
const validateOutput=contract.outputSchema?ajv.compile(contract.outputSchema):undefined;
|
|
113
|
+
hooks[name]=(input ) =>{
|
|
114
|
+
assert(validateInput(input),`Invalid extension hook input: ${name}`);
|
|
115
|
+
const validate=(output ) =>{if(validateOutput)assert(validateOutput(output),`Invalid extension hook output: ${name}`);return output;};
|
|
116
|
+
const output=(fn )(input);
|
|
117
|
+
return output instanceof Promise?output.then(validate):validate(output);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return hooks ;
|
|
121
|
+
}
|
|
55
122
|
|
|
56
123
|
|
|
57
124
|
|
|
125
|
+
|
|
126
|
+
|
|
58
127
|
|
|
59
128
|
|
|
60
129
|
|
|
@@ -105,6 +174,7 @@ import { validateHeaderName, validateHeaderValue } from './header-validation.js'
|
|
|
105
174
|
|
|
106
175
|
|
|
107
176
|
const namePattern=/^[a-z][a-z0-9-]{0,63}$/;
|
|
177
|
+
const hookNamePattern=/^[a-z][A-Za-z0-9]{0,63}$/;
|
|
108
178
|
const cacheHeaders=new Set(['cache-control','cdn-cache-control','vercel-cdn-cache-control','surrogate-control']);
|
|
109
179
|
const segmentPattern=/^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$/;
|
|
110
180
|
export const immutableCacheControl='public, max-age=31536000, immutable';
|
|
@@ -153,6 +223,13 @@ export function prepareExtensions(document ,routes
|
|
|
153
223
|
assert(registration.version==='1'&&typeof registration.activate==='function','Invalid extension version or activation hook');
|
|
154
224
|
assert(Array.isArray(registration.targets)&®istration.targets.every(target=>['node','aws','vercel'].includes(target)),'Extension targets must be node, aws or vercel');
|
|
155
225
|
assert(typeof registration.projectSha256==='string'&&/^[a-f0-9]{64}$/.test(registration.projectSha256),'Extension requires an explicit operator revision pin');
|
|
226
|
+
const hookNames=new Set ();
|
|
227
|
+
for(const hook of registration.hooks??[]){
|
|
228
|
+
assert(hook&&typeof hook==='object'&&hookNamePattern.test(hook.name)&&!hookNames.has(hook.name),'Invalid extension hook contract');
|
|
229
|
+
assert(['filter','action'].includes(hook.kind)&&typeof hook.description==='string'&&hook.description.length>=1&&hook.description.length<=512,'Invalid extension hook contract');
|
|
230
|
+
assert(hook.inputSchema&&typeof hook.inputSchema==='object'&&(!hook.outputSchema||typeof hook.outputSchema==='object'),'Invalid extension hook contract');
|
|
231
|
+
hookNames.add(hook.name);
|
|
232
|
+
}
|
|
156
233
|
provided.set(registration.name,registration);
|
|
157
234
|
}
|
|
158
235
|
const declarations=document.extensions??{};
|
package/dist/http-response.js
CHANGED
|
@@ -16,7 +16,7 @@ import { HttpError } from './errors.js';
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
// Hop-by-hop and runtime-owned headers a handler must never set on the wire.
|
|
19
|
-
|
|
19
|
+
const forbiddenHeaders = new Set(['connection','keep-alive','transfer-encoding','content-length','upgrade','trailer','proxy-authenticate','proxy-authorization','te']);
|
|
20
20
|
|
|
21
21
|
// One place decides what a URLCode response *is*, independent of how a host
|
|
22
22
|
// delivers it. A Node server writes it to a socket; a Lambda returns it as
|
package/dist/index.js
CHANGED