@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.
Files changed (82) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +10 -0
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/CONTRIBUTING.md +36 -0
  4. package/README.md +20 -15
  5. package/ROADMAP.md +25 -16
  6. package/dist/BUILD-MANIFEST.json +20 -19
  7. package/dist/authoring.js +15 -1
  8. package/dist/capability-query.js +0 -1
  9. package/dist/catalog.js +0 -1
  10. package/dist/cli.js +24 -7
  11. package/dist/config.js +1 -1
  12. package/dist/explain.js +1 -1
  13. package/dist/extensions.js +78 -1
  14. package/dist/http-response.js +1 -1
  15. package/dist/index.js +1 -0
  16. package/dist/init-with.js +36 -11
  17. package/dist/manifest.js +1 -1
  18. package/dist/mcp-authoring.js +2 -2
  19. package/dist/mcp.js +1 -1
  20. package/dist/policies/cache.js +2 -2
  21. package/dist/policy.js +16 -0
  22. package/dist/project-dependencies.js +305 -0
  23. package/dist/readiness.js +5 -1
  24. package/dist/runtime.js +1 -1
  25. package/dist/tooling.js +2 -1
  26. package/dist/trusted-functions.js +4 -5
  27. package/dist/types/authoring.d.ts +9 -1
  28. package/dist/types/capability-query.d.ts +0 -1
  29. package/dist/types/catalog.d.ts +0 -4
  30. package/dist/types/config.d.ts +1 -9
  31. package/dist/types/explain.d.ts +0 -1
  32. package/dist/types/extensions.d.ts +58 -0
  33. package/dist/types/http-response.d.ts +0 -1
  34. package/dist/types/index.d.ts +1 -0
  35. package/dist/types/init-with.d.ts +7 -13
  36. package/dist/types/manifest.d.ts +0 -1
  37. package/dist/types/project-dependencies.d.ts +78 -0
  38. package/dist/types/readiness.d.ts +3 -0
  39. package/dist/types/tooling.d.ts +1 -0
  40. package/dist/types/trusted-functions.d.ts +1 -4
  41. package/docs/AI-AUTHORING.md +10 -5
  42. package/docs/AWS.md +9 -0
  43. package/docs/CI-FOLLOWUP-2026-09-19.md +1 -1
  44. package/docs/CODEBASE-AUDIT-2026-09-20.md +6 -0
  45. package/docs/COMPOSING-A-SITE.md +287 -0
  46. package/docs/CONTAINER-PROMOTION.md +74 -0
  47. package/docs/DEVELOPMENT-PIPELINE.md +242 -119
  48. package/docs/EXTENSIONS.md +88 -93
  49. package/docs/FRAMEWORK.md +45 -30
  50. package/docs/FUNCTION-SECURITY.md +5 -8
  51. package/docs/INSTALL.md +13 -8
  52. package/docs/MIDDLEWARE.md +10 -4
  53. package/docs/OPEN-DECISIONS.md +64 -99
  54. package/docs/READINESS.md +8 -4
  55. package/docs/README.md +18 -13
  56. package/docs/RELEASE-0.4.1.md +73 -0
  57. package/docs/RELEASE-0.4.2.md +30 -0
  58. package/docs/RELEASE-READINESS.md +40 -11
  59. package/docs/RELEASE-SECURITY.md +33 -14
  60. package/docs/SPECIFICATION.md +5 -1
  61. package/docs/SPIKE-CORE-LAYERING.md +1 -1
  62. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +9 -13
  63. package/docs/STARTERS.md +17 -5
  64. package/docs/TOOLING.md +7 -5
  65. package/docs/VERCEL.md +10 -2
  66. package/docs/VERSION-ALIGNMENT.md +50 -8
  67. package/docs/archive/2026-09-19/ROADMAP.md +1 -0
  68. package/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md +1 -0
  69. package/docs/{SPIKE-LAMBDA-COMPILE.md → archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md} +168 -12
  70. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +2 -0
  71. package/docs/archive/2026-09-20/OPEN-DECISIONS-COMPLETED.md +116 -0
  72. package/docs/archive/README.md +2 -0
  73. package/docs/yaml/functions.md +10 -2
  74. package/docs/yaml/middleware.md +5 -3
  75. package/examples/cookbook/middleware/envelope.mjs +4 -2
  76. package/llms-full.txt +458 -143
  77. package/llms.txt +2 -1
  78. package/package.json +8 -5
  79. package/packaging/claude-plugin/.claude-plugin/plugin.json +1 -1
  80. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +10 -0
  81. package/recipes/middleware/middleware/envelope.mjs +4 -2
  82. 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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Claude plugins published from the URLCode repository.",
9
- "version": "0.4.0-alpha.3"
9
+ "version": "0.4.2"
10
10
  },
11
11
  "plugins": [
12
12
  {
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 reach them only through operator grants
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.0-alpha.2` (alpha) on top of the `0.3.0` release, Apache-2.0 |
57
- | [urlcode-ui](packages/ui) (in this repository) | Shared presentation: escaped templates, shadcn/ui partials, themes, translations | `0.1.0-alpha.5` on npm, alpha: review pending |
58
- | [urlcode-auth](packages/auth) (in this repository) | Accounts: password, passkeys, OIDC, email codes, TOTP, sessions, roles, account page | `0.1.0-alpha.3` on npm, alpha: review pending |
59
- | [urlcode-admin](packages/admin) (in this repository) | Administration: users, sessions, roles, audit, approvals, cases, impersonation | `0.1.0-alpha.3` on npm, alpha: review pending |
60
-
61
- Every version in this table is the one published to npm under the `alpha`
62
- dist-tag, and they are released together in the order core → ui → auth →
63
- admin. The authoritative cross-repository register, including each
64
- package's declared peer ranges and the rules that keep them consistent, is
65
- [docs/VERSION-ALIGNMENT.md](docs/VERSION-ALIGNMENT.md).
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.0-alpha.2` is the current release of the extension contract and the
107
- agent tooling, on top of the `0.3.0` self-hosted release. `0.4.0-alpha.1`
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
- npm install --global @jimhoyd/urlcode # or: brew tap jimhoyd-com/urlcode && brew install urlcode
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 at `db375bf` provides declarative routing, responses, assets, policies,
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, UI and middleware are separate optional packages. Auth/admin already
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
- Monorepo work is starting now. Move middleware in as a separate package before
24
- folding it into core; coordinate the guidance cleanup with that migration.
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
- [Issue 168](https://github.com/jimhoyd-com/urlcode/issues/168) tracks checking
30
- schema-invalid documentation examples beyond the existing prose checks.
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. Live
44
- Google/Apple/SES checks remain explicitly deferred.
45
- 4. **After the monorepo move, consolidate middleware into core.** Move the
46
- existing middleware package first, preserving behavior.
47
- [issue 172](https://github.com/jimhoyd-com/urlcode/issues/172) tracks the later consolidation
48
- and consumer migration; package retirement is not part of the initial move. Static targets continue rejecting
49
- request-time middleware.
50
- 5. **Choose expansion from evidence.** Collections, a business application suite,
51
- and per-route Lambda compilation are proposals,
52
- not available features. Decide scope before implementing them.
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
 
@@ -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": "ec5240e8686c124f4601229b45f89ce112d9eeb9d35e3059fe55e93056c45209",
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": "ba148b27c6a60f8c895f13f26468b16c18ae0a5cddea26b602965c8efbba90ae",
16
- "dist/catalog.js": "ab1c01c9296cc277d2bd4fd682aeb053656a7d9f91c61974f4b78feea0211de8",
17
- "dist/cli.js": "ff3e583bd43044bdc06122e33c6137f400f677967ebff1ab49900b517682531c",
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": "0cd82b4c407848f9c66e804a6dd4bf9a8a8503264e51584456578073e0f01921",
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": "dff1ed9a1bd818906ea867f4fb6d4601397943f9bdc4fceccf1809a365f95168",
35
- "dist/extensions.js": "0506ec6289fa0582399d8c6d27727ffeb629bd442264248c921c63cc5b46ff01",
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": "5fc3bb9eb932a3fd2fbc72c361416a5f1439b8bf45b2be37baca1c6446abf40c",
43
- "dist/index.js": "7e9f6ec8a125e2dfd4230b2ecd17ac513c0153d778a57bbcee7a1a9cd9681f81",
44
- "dist/init-with.js": "8371f568867ebdc6ab8e9aa762234ae2aa015865f46aa901254528e20d69b791",
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": "aa450ff88d44b184ed1ad3331d1f81005a844a9f971e9e12dfa039952839bc75",
48
+ "dist/manifest.js": "83eed0d59621039ef364cf6e7b54c5bd47e0d8cf0a5abe7b07344bfc1db56ed8",
49
49
  "dist/match.js": "53ebcc2cda529a8d07fb83f8cb69a1446036ffc1641af3c4bd45a55851bded28",
50
- "dist/mcp-authoring.js": "4987b0ba08999309e07aea1b8bb31f4600cec22d79998e6982e56f765070587a",
51
- "dist/mcp.js": "fa44f5f9e0bc4ca9790d1d1eb461147673c1539cf1704a4daae9bad30e4120eb",
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": "03f79b91694bb78a98de4473384e5110e1ae1c3c83d46f929495d03af04d9da1",
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": "33c527008fafe4af18243ba668d069517c20659150f9f1d1a5a1d076e8724f89",
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": "90b54bc0cf60dd155d05789a1aad767bd67483d5fa6064ae8b1143f8cf9e06b2",
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": "0470dcab0774d2da3336e9ce76a4853757bf96081e52bd741f6b44c1bdd4161f",
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": "7409db98422325cd4181d073d645ea57debca0e54cb151123474b85f4d4ed7dc",
78
- "dist/trusted-functions.js": "befd256848be786f97e372c6af0e05ac08a0c93baed38bf2470ea49bc00dd3e4",
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
- export async function initProject(destination ) {
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
  }
@@ -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.0-alpha.3 — local/self-hosted runtime
31
- urlcode init <directory> [--with auth,admin] # --with: layered site from installed @jimhoyd/urlcode-<name> packages
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
- if (values.with === undefined) { await initProject(arg); print({ event:'created' }); break; }
272
- {
273
- const created = await initProjectWith(arg, parseWithNames(values.with));
274
- 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` });
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
- export function normalizeRoute(pattern , route ) {
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
- export function routeState(route ,now ) {return route.enabled===false?'disabled':route.expiresAt&&now>=route.expiresAt?'expired':'active';}
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];
@@ -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)&&registration.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??{};
@@ -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
- export const forbiddenHeaders = new Set(['connection','keep-alive','transfer-encoding','content-length','upgrade','trailer','proxy-authenticate','proxy-authorization','te']);
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
@@ -42,4 +42,5 @@ export {scaffoldProject} from './scaffold.js';
42
42
 
43
43
  export {initProject, addRedirect} from './authoring.js';
44
44
  export {initProjectWith} from './init-with.js';
45
+ export {collectDependencySet,renderPackageManifest,installSteps} from './project-dependencies.js';
45
46