@jimhoyd/urlcode 0.5.5 → 0.5.8

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 (136) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +16 -1
  2. package/.claude/skills/urlcode-operations/SKILL.md +5 -1
  3. package/README.md +24 -14
  4. package/dist/BUILD-MANIFEST.json +55 -54
  5. package/dist/agents-guide.js +5 -2
  6. package/dist/assets.js +2 -2
  7. package/dist/authoring.js +3 -58
  8. package/dist/aws.js +21 -4
  9. package/dist/body-schema.js +2 -3
  10. package/dist/capabilities.js +1 -1
  11. package/dist/capability-query.js +3 -3
  12. package/dist/catalog.js +1 -1
  13. package/dist/cli.js +42 -15
  14. package/dist/cloudflare.js +26 -1
  15. package/dist/config.js +1 -1
  16. package/dist/context.js +6 -6
  17. package/dist/egress.js +1 -1
  18. package/dist/examples.js +2 -2
  19. package/dist/explain.js +4 -6
  20. package/dist/extension-artifacts.js +8 -8
  21. package/dist/extension-transport.js +6 -4
  22. package/dist/function-worker.js +5 -2
  23. package/dist/functions.js +1 -1
  24. package/dist/guest-api.js +22 -15
  25. package/dist/http-policy.js +1 -3
  26. package/dist/http-response.js +61 -10
  27. package/dist/init-with.js +8 -8
  28. package/dist/interchange.js +3 -2
  29. package/dist/logging.js +4 -4
  30. package/dist/match.js +1 -1
  31. package/dist/mcp-authoring.js +1 -2
  32. package/dist/mcp.js +2 -2
  33. package/dist/object-guards.js +17 -0
  34. package/dist/pattern-guard.js +33 -3
  35. package/dist/policies/agents.js +1 -1
  36. package/dist/policies/cache.js +16 -4
  37. package/dist/policies/compression.js +1 -1
  38. package/dist/policies/security.js +1 -1
  39. package/dist/policies/throttle.js +1 -1
  40. package/dist/policies.js +1 -1
  41. package/dist/policy.js +3 -3
  42. package/dist/prerender.js +1 -2
  43. package/dist/project-dependencies.js +8 -8
  44. package/dist/proxy.js +1 -1
  45. package/dist/readiness.js +16 -17
  46. package/dist/review.js +2 -2
  47. package/dist/route-diff.js +4 -4
  48. package/dist/router.js +1 -1
  49. package/dist/runtime.js +4 -1
  50. package/dist/scaffold.js +1 -1
  51. package/dist/schema-query.js +1 -1
  52. package/dist/server.js +49 -7
  53. package/dist/signals.js +2 -2
  54. package/dist/site.js +2 -2
  55. package/dist/trusted-functions.js +1 -1
  56. package/dist/types/assets.d.ts +3 -2
  57. package/dist/types/authoring.d.ts +3 -6
  58. package/dist/types/body-schema.d.ts +2 -1
  59. package/dist/types/catalog.d.ts +2 -1
  60. package/dist/types/config.d.ts +0 -8
  61. package/dist/types/context.d.ts +3 -1
  62. package/dist/types/egress.d.ts +1 -1
  63. package/dist/types/explain.d.ts +4 -4
  64. package/dist/types/extension-artifacts.d.ts +4 -13
  65. package/dist/types/extension-transport.d.ts +6 -4
  66. package/dist/types/functions.d.ts +1 -1
  67. package/dist/types/http-response.d.ts +7 -3
  68. package/dist/types/init-with.d.ts +3 -9
  69. package/dist/types/logging.d.ts +4 -3
  70. package/dist/types/match.d.ts +2 -1
  71. package/dist/types/object-guards.d.ts +7 -0
  72. package/dist/types/pattern-guard.d.ts +0 -1
  73. package/dist/types/policies/cache.d.ts +4 -3
  74. package/dist/types/policies/compression.d.ts +1 -1
  75. package/dist/types/policies/throttle.d.ts +1 -1
  76. package/dist/types/policy.d.ts +4 -3
  77. package/dist/types/project-dependencies.d.ts +1 -40
  78. package/dist/types/proxy.d.ts +2 -1
  79. package/dist/types/readiness.d.ts +12 -16
  80. package/dist/types/review.d.ts +0 -2
  81. package/dist/types/route-diff.d.ts +4 -3
  82. package/dist/types/router.d.ts +1 -1
  83. package/dist/types/server.d.ts +22 -0
  84. package/dist/types/signals.d.ts +3 -2
  85. package/dist/types/site.d.ts +1 -2
  86. package/dist/types/trusted-functions.d.ts +1 -1
  87. package/dist/types/types.d.ts +17 -11
  88. package/dist/types/vercel.d.ts +1 -0
  89. package/dist/types/verify-deployment.d.ts +1 -1
  90. package/dist/types.js +19 -12
  91. package/dist/vercel.js +12 -3
  92. package/dist/verify-deployment.js +29 -11
  93. package/docs/AI-AUTHORING.md +10 -1
  94. package/docs/FUNCTION-SECURITY.md +4 -0
  95. package/docs/README.md +2 -2
  96. package/docs/TOOLING.md +27 -1
  97. package/examples/assets/Makefile +1 -1
  98. package/examples/assets/example.yaml +3 -3
  99. package/examples/aws/example.yaml +3 -3
  100. package/examples/cloudflare/example.yaml +3 -3
  101. package/examples/compliance/README.md +1 -1
  102. package/examples/compliance/example.yaml +1 -1
  103. package/examples/compliance/rules.mjs +2 -2
  104. package/examples/conditions/example.yaml +3 -3
  105. package/examples/cookbook/README.md +4 -4
  106. package/examples/cookbook/example.yaml +3 -3
  107. package/examples/coverage-waiver/example.yaml +3 -3
  108. package/examples/egress/example.yaml +2 -2
  109. package/examples/extensions/example.yaml +1 -1
  110. package/examples/lifecycle/example.yaml +2 -2
  111. package/examples/not-found/README.md +2 -2
  112. package/examples/not-found/example.yaml +3 -3
  113. package/examples/prerender/README.md +4 -4
  114. package/examples/prerender/example.yaml +2 -2
  115. package/examples/provider-conformance/example.yaml +2 -2
  116. package/examples/shared-blocks/example.yaml +3 -3
  117. package/examples/vercel/example.yaml +3 -3
  118. package/llms-full.txt +49 -18
  119. package/llms.txt +9 -2
  120. package/package.json +14 -13
  121. package/skills/urlcode/SKILL.md +7 -7
  122. package/starters/default/.github/workflows/urlcode.yml +2 -1
  123. package/starters/default/AGENTS.md +3 -3
  124. package/starters/default/Makefile +1 -1
  125. package/starters/default/README.md +15 -13
  126. package/starters/default/tests/requests.json +0 -50
  127. package/starters/default/urlcode.yaml +1 -4
  128. package/dist/scripts/operational-drills.js +0 -33
  129. package/starters/default/functions/hello.mjs +0 -3
  130. package/starters/default/middleware/headers.mjs +0 -6
  131. package/starters/default/routes/functions.yaml +0 -20
  132. package/starters/default/routes/marketing/links.yaml +0 -7
  133. package/starters/page/README.md +0 -14
  134. package/starters/page/public/index.html +0 -12
  135. package/starters/page/tests/requests.json +0 -17
  136. package/starters/page/urlcode.yaml +0 -6
package/dist/vercel.js CHANGED
@@ -12,6 +12,17 @@ import { assert, HttpError } from './errors.js';
12
12
 
13
13
  const platformOrigins = ['VERCEL_PROJECT_PRODUCTION_URL','VERCEL_URL','VERCEL_BRANCH_URL'];
14
14
 
15
+ // `x-forwarded-for` is documented as overwritten (not appended to) by
16
+ // Vercel's edge, but only when Vercel itself is the client-facing proxy; a
17
+ // project's own proxy in front of Vercel can still set it before Vercel ever
18
+ // sees the request. `x-vercel-forwarded-for` is Vercel's own copy of the same
19
+ // value and is the header Vercel's docs say to prefer for exactly that reason
20
+ // (https://vercel.com/docs/headers/request-headers#x-vercel-forwarded-for).
21
+ // Exported so the adapter's client-IP resolution can be verified directly.
22
+ export function forwardedClient(headers , headerCounts ) {
23
+ return headerCounts['x-vercel-forwarded-for'] === 1 ? (headers.get('x-vercel-forwarded-for') ?? '').split(',')[0]?.trim() || undefined : undefined;
24
+ }
25
+
15
26
  function readBody(req , limit ) {
16
27
  if (req.headers['content-length'] && Number(req.headers['content-length']) > limit) return Promise.reject(new HttpError(413,'Request body too large'));
17
28
  return new Promise((resolve,reject) => {
@@ -49,9 +60,7 @@ export function createVercelHandler({ project = process.cwd(), origin, environme
49
60
  }
50
61
  const limit = Math.min(maxBodyBytes, runtime.requestLimit(target) ?? maxBodyBytes);
51
62
  const body = await readBody(req,limit);
52
- // The platform terminates TLS and sets the forwarded header itself, so
53
- // its leftmost entry is the client; the socket peer is the platform.
54
- const forwarded = headerCounts['x-forwarded-for'] === 1 ? (headers.get('x-forwarded-for') ?? '').split(',')[0]?.trim() : undefined;
63
+ const forwarded = forwardedClient(headers, headerCounts);
55
64
  const publicOrigin = resolveOrigin(origin,environment,platformOrigins) ?? 'http://localhost';
56
65
  const result = await runtime.handle({ target, method, headers, headerCounts, body,
57
66
  origin: publicOrigin, client: forwarded || req.socket?.remoteAddress });
@@ -3,6 +3,7 @@ import { request, Agent } from 'node:http';
3
3
  import { request as secureRequest, Agent as SecureAgent } from 'node:https';
4
4
  import { randomBytes } from 'node:crypto';
5
5
  import { assert } from './errors.js';
6
+ import { isRecord } from './object-guards.js';
6
7
  import { createRuntime } from './runtime.js';
7
8
 
8
9
  import { benchmarkTarget, hit, readFixtures, runFixtures, isStepsFixture, probeAgent } from './readiness.js';
@@ -26,7 +27,7 @@ import { lists as bundledAgents } from '../data/agents/index.js';
26
27
  // snapshot would; it says nothing about the host, the proxy or the network.
27
28
 
28
29
 
29
-
30
+
30
31
 
31
32
 
32
33
 
@@ -46,32 +47,49 @@ const MAX_REQUESTS = 10000;
46
47
  const BODY_LIMIT = 1048576;
47
48
  const SNIPPET = 200;
48
49
  const siteTypes = { 'site.robots': 'text/plain', 'site.sitemap': 'application/xml', 'site.securityTxt': 'text/plain', 'site.llms': 'text/plain', 'site.notFound': 'text/html' };
49
- const isRecord = (value ) => value !== null && typeof value === 'object' && !Array.isArray(value);
50
50
  const snippet = (body ) => body.length > SNIPPET ? `${body.length} bytes: ${body.subarray(0, SNIPPET).toString('utf8')}` : body.toString('utf8');
51
51
  const tlsCode = /CERT|TLS|SSL|SELF_SIGNED/;
52
52
 
53
53
  // One request, one answer. Bodies are read up to 1 MiB and never logged
54
54
  // beyond the snippet a failing assertion carries. Redirects are not followed
55
55
  // and a transport error is reported as text, never thrown.
56
+ //
57
+ // `timeout` on a Node request/response is an *idle* timer: it only fires
58
+ // once a socket has gone quiet, so a deployment that drips one byte just
59
+ // under that interval keeps resetting it and can hold this probe open
60
+ // indefinitely. A single wall-clock deadline covering the whole request
61
+ // (connect through body) closes that gap. Past `BODY_LIMIT` the response is
62
+ // destroyed rather than left to keep streaming into a discard loop: this
63
+ // probe only ever needs a bounded snippet, never the rest of an oversized
64
+ // or endless body.
56
65
  function probe(target , { path, method = 'GET', headers = {} } , agent , timeoutMs ) {
57
66
  return new Promise(resolve => {
58
- const fail = (error ) => {
67
+ let settled = false;
68
+ const finish = (answer ) => { if (settled) return; settled = true; clearTimeout(deadline); resolve(answer); };
69
+ const fail = (error ) => {
59
70
  const code = isRecord(error) && typeof error.code === 'string' ? error.code : error instanceof Error ? error.message : 'transport';
60
- resolve({ status: 0, headers: {}, body: Buffer.alloc(0), error: code });
71
+ finish({ status: 0, headers: {}, body: Buffer.alloc(0), error: code });
61
72
  };
62
73
  let req ;
74
+ const deadline = setTimeout(() => { req?.destroy(new Error('timeout')); }, timeoutMs);
63
75
  try {
64
76
  const options = { host: target.hostname, port: target.port, path, method, agent, timeout: timeoutMs,
65
77
  headers: { host: target.hostname, 'user-agent': probeAgent, 'accept-encoding': 'identity', ...headers } };
66
78
  req = (target.protocol === 'https:' ? secureRequest : request)(options, (res ) => {
79
+ const out = {};
80
+ for (const [key, value] of Object.entries(res.headers)) if (value !== undefined) out[key] = Array.isArray(value) ? value.join(', ') : value;
67
81
  const chunks = []; let size = 0;
68
- res.on('data', (chunk ) => { if (size < BODY_LIMIT) chunks.push(chunk); size += chunk.length; });
69
- res.on('error', fail);
70
- res.on('end', () => {
71
- const out = {};
72
- for (const [key, value] of Object.entries(res.headers)) if (value !== undefined) out[key] = Array.isArray(value) ? value.join(', ') : value;
73
- resolve({ status: res.statusCode ?? 0, headers: out, body: Buffer.concat(chunks) });
82
+ const done = () => finish({ status: res.statusCode ?? 0, headers: out, body: Buffer.concat(chunks) });
83
+ res.on('data', (chunk ) => {
84
+ if (size < BODY_LIMIT) chunks.push(chunk);
85
+ size += chunk.length;
86
+ if (size > BODY_LIMIT) res.destroy();
74
87
  });
88
+ res.on('error', fail);
89
+ res.on('end', done);
90
+ // A body cut off by the BODY_LIMIT destroy() above ends here, not on
91
+ // 'end'; the snippet already collected is still a valid answer.
92
+ res.on('close', () => { if (!settled) done(); });
75
93
  });
76
94
  req.on('error', fail); req.on('timeout', () => req?.destroy(new Error('timeout'))); req.end();
77
95
  } catch (error) { req?.destroy(); fail(error); }
@@ -148,7 +166,7 @@ export async function verifyDeployment(project , { target, origin, expect
148
166
  const parse = (answer ) => { try { const json = JSON.parse(answer.body.toString('utf8')); return isRecord(json) ? json : undefined; } catch { return undefined; } };
149
167
  const healthJson = parse(health);
150
168
  check(health.status === 200 && healthJson?.status === 'ok' && typeof healthJson.version === 'string' && typeof healthJson.routes === 'number',
151
- { check: 'probes', severity: 'high', message: '/_urlcode/health must answer 200 with {status:"ok",version,routes}', expected: '200', observed: `${health.status} ${snippet(health.body)}` });
169
+ { check: 'probes', severity: 'high', message: '/_urlcode/health must answer 200 with {status:"ok",version,routes}; start the target with --health-details (or --metrics) if it currently answers {status} only', expected: '200', observed: `${health.status} ${snippet(health.body)}` });
152
170
  const ready = await send({ path: '/_urlcode/ready' }), readyJson = parse(ready);
153
171
  check(ready.status === 200 && readyJson?.status === 'ok', { check: 'probes', severity: 'high', message: '/_urlcode/ready must answer 200 (the deployment reports itself degraded or is not URLCode)', expected: '200', observed: `${ready.status} ${snippet(ready.body)}` });
154
172
  if (typeof readyJson?.version === 'string') version.observed = readyJson.version;
@@ -63,6 +63,11 @@ The root [llms.txt](../llms.txt) is a compact discovery index; the generated
63
63
  reading order for agents that want complete context in one fetch. It is a convenience,
64
64
  not a runtime protocol or a guarantee that AI clients automatically consume it.
65
65
  The generated reference is checked against the schema in `npm run verify`.
66
+ [URLCode AI](https://urlcode.ai/) is the optional hosted companion for shared
67
+ skills and LLM tooling. It complements the project-local MCP server; see
68
+ [hosted AI MCP setup](TOOLING.md#optional-hosted-ai-mcp) for its authenticated
69
+ remote connection details. Its machine-readable entry point is
70
+ [`https://urlcode.ai/llms.txt`](https://urlcode.ai/llms.txt).
66
71
 
67
72
  Follow [organization and readability practices](BEST-PRACTICES.md): preserve local
68
73
  conventions, use clear names, keep middleware focused and avoid needless layers.
@@ -138,7 +143,7 @@ urlcode benchmark --project ./my-links --requests 100 --concurrency 2
138
143
  ```
139
144
 
140
145
  Use the intentional actual count, not always 2. Runtime checkout users can replace
141
- `urlcode` with `node src/cli.ts`. Template users can use the equivalent npm scripts.
146
+ `urlcode` with `node packages/core/src/cli.ts`. Template users can use the equivalent npm scripts.
142
147
  External bindings require an already reviewed policy; add `--policy` where needed.
143
148
  The benchmark operates locally; it is not a load test of an external deployment.
144
149
 
@@ -432,6 +437,10 @@ the operator, never by tool arguments; `--allow-authoring` on the operator's
432
437
  command line adds project-confined route, recipe, scaffold and runner tools.
433
438
  `urlcode init` writes `.mcp.json` so Claude Code and Codex register the read-only
434
439
  server for the project ([registering the server](TOOLING.md#registering-the-server)).
440
+ The optional hosted URLCode AI MCP is a separate authenticated connection for
441
+ shared skills and LLM tools; it does not replace the local project server. Its
442
+ endpoint and credential-handling requirements are in
443
+ [hosted AI MCP setup](TOOLING.md#optional-hosted-ai-mcp).
435
444
  Inspection is not activation/deployment readiness: real grants, asset snapshots
436
445
  and service availability still need normal runtime checks. Provider conformance replay is local evidence; only
437
446
  explicit live [deployment observations](PROVIDER-VERIFICATION.md) test ingress.
@@ -82,6 +82,10 @@ into it; only the default for routes that declare neither option has changed.
82
82
  - A fresh guest heap/module state per invocation prevents state crossing requests.
83
83
  - 32 MiB guest heap, 512 KiB stack, source/input/output/header limits, bounded
84
84
  concurrency, guest interruption and an independent worker termination deadline.
85
+ - The guest's result is recorded where guest code cannot rewrite it, and the
86
+ host checks its shape before trusting it. A result that states a body length
87
+ for any method other than HEAD is invalid and answers 502; the runtime frames
88
+ the response by the bytes it sends ([responses](HTTP.md#responses)).
85
89
  - External bindings are denied by default. Project YAML cannot self-authorize.
86
90
  Operator grants are exact-name, route-scoped and pinned to configuration/source.
87
91
 
package/docs/README.md CHANGED
@@ -8,7 +8,7 @@ Use documentation from the same pinned revision as your runtime.
8
8
  | Understand what URLCode does | [Framework](FRAMEWORK.md) |
9
9
  | Build my first project | [Installation](INSTALL.md), then [YAML guide](YAML-GUIDE.md) |
10
10
  | Build a site with UI, accounts and admin | [Composing a site](COMPOSING-A-SITE.md) |
11
- | Have an AI author a project | [AI authoring](AI-AUTHORING.md), [agent index](../llms.txt) |
11
+ | Have an AI author a project | [AI authoring](AI-AUTHORING.md), [agent index](../llms.txt), [hosted agent guide](https://urlcode.ai/llms.txt) |
12
12
  | Deploy and operate a project | [Operations](OPERATIONS.md) |
13
13
  | Contribute to URLCode | [Contributing](../CONTRIBUTING.md), [local development](LOCAL-DEVELOPMENT.md) |
14
14
 
@@ -26,7 +26,7 @@ The [specification](SPECIFICATION.md) owns implemented semantics; the
26
26
  | Load authoring/operations rules into an agent | [Authoring skill](../.claude/skills/urlcode-authoring/SKILL.md), [operations skill](../.claude/skills/urlcode-operations/SKILL.md), [how they are distributed](AI-AUTHORING.md#agent-skills) |
27
27
  | Understand exact behavior | [Specification](SPECIFICATION.md), [routing](ROUTING.md), [HTTP](HTTP.md) |
28
28
  | Run examples | [Executable cookbook](../examples/cookbook/README.md), [prerender recipe](../examples/prerender/README.md), [small starter](STARTERS.md) |
29
- | Let an AI build routes | [The framework](FRAMEWORK.md), [AI authoring guide](AI-AUTHORING.md), [llms.txt](../llms.txt), [SDK and read-only MCP](TOOLING.md) |
29
+ | Let an AI build routes | [The framework](FRAMEWORK.md), [AI authoring guide](AI-AUTHORING.md), [llms.txt](../llms.txt), [hosted agent guide](https://urlcode.ai/llms.txt), [SDK and read-only MCP](TOOLING.md) |
30
30
  | Reuse code around routes | [Middleware](MIDDLEWARE.md), [middleware examples](MIDDLEWARE-EXAMPLES.md) |
31
31
  | Handle secrets and decide what to sandbox | [Function security](FUNCTION-SECURITY.md) |
32
32
  | Author guest functions in TypeScript | [Build-time guest transpilation](TYPESCRIPT-AUTHORING.md) |
package/docs/TOOLING.md CHANGED
@@ -330,7 +330,7 @@ choices added by hand, never by `init` or by an agent.
330
330
 
331
331
  - **Claude Code** reads `.mcp.json` in the project directory as a project-scoped
332
332
  server and asks for approval on first use. A project that pins the runtime in its
333
- `package.json` (`--template redirects`, `--with`, `--manifest`) gets
333
+ `package.json` (`--with`, `--manifest`) gets
334
334
  `"command": "npx"` with `--no --package @jimhoyd/urlcode urlcode mcp ...`, which runs the
335
335
  installed copy and never fetches (do not use a bare `npx urlcode`: that names an unrelated
336
336
  registry package). A project without one keeps the bare `urlcode` command for a global
@@ -354,6 +354,32 @@ The generated `AGENTS.md` and the packaged skill tell agents to prefer
354
354
  `get_manifest` when the server is registered and to fall back to the matching
355
355
  CLI commands otherwise.
356
356
 
357
+ ## Optional hosted AI MCP
358
+
359
+ The local `urlcode mcp` server remains the project-aware URLCode integration:
360
+ it reads the selected checkout, validates its configuration and never needs a
361
+ network credential. Do not replace its generated `.mcp.json` entry with a
362
+ hosted service.
363
+
364
+ [URLCode AI](https://urlcode.ai/) is a separate, opt-in hosted service for
365
+ shared skills and LLM-assisted work. A client that supports authenticated HTTP
366
+ MCP can add it as a second server with these connection details:
367
+
368
+ - URL: `https://mcp.urlcode.ai/mcp`
369
+ - request header: `Authorization: Bearer <URLCODE_AI_TOKEN>`
370
+
371
+ Store `URLCODE_AI_TOKEN` in the MCP client's secret or environment-variable
372
+ facility. Do not put a literal bearer token in `.mcp.json`, `urlcode.yaml`, a
373
+ checked-in client configuration, or a shell history. Each client has its own
374
+ remote-server configuration syntax, so configure that endpoint explicitly in
375
+ the client rather than asking `urlcode init` to generate it.
376
+
377
+ The hosted tools are not a proxy for this local server: they do not receive the
378
+ project root and do not replace local `get_context`, validation, manifest,
379
+ extension-artifact or authoring tools. Keep the local server registered for
380
+ framework- and project-specific work; add the hosted server only where its
381
+ shared skill catalog or LLM tools are useful.
382
+
357
383
  ## Authoring mode
358
384
 
359
385
  `urlcode mcp --allow-authoring --project DIR` adds six tools to the twenty-two read
@@ -1,6 +1,6 @@
1
1
  .DEFAULT_GOAL := help
2
2
 
3
- # Install the runtime separately, or override: URLCODE='node /path/to/urlcode/src/cli.js'
3
+ # Install the runtime separately, or override: URLCODE='node /path/to/urlcode/dist/cli.js'
4
4
  URLCODE ?= urlcode
5
5
  HOST ?= 127.0.0.1
6
6
  PORT ?= 3000
@@ -9,9 +9,9 @@ files: [urlcode.yaml, functions/hello.mjs, public/about.html, public/guide.txt,
9
9
  tests:
10
10
  fixtures: tests/requests.json
11
11
  commands:
12
- - node src/cli.ts validate --local --project examples/assets
13
- - node src/cli.ts test --project examples/assets
14
- - node src/cli.ts audit --project examples/assets --expect-routes 5
12
+ - node packages/core/src/cli.ts validate --local --project examples/assets
13
+ - node packages/core/src/cli.ts test --project examples/assets
14
+ - node packages/core/src/cli.ts audit --project examples/assets --expect-routes 5
15
15
  behavior:
16
16
  - native page, static and download routes serve snapshotted files with explicit caching
17
17
  - one trusted function shows the two kinds of route side by side
@@ -12,9 +12,9 @@ files: [urlcode.yaml, handler.mjs, template.yaml, public/index.html, public/note
12
12
  tests:
13
13
  fixtures: tests/requests.json
14
14
  commands:
15
- - node src/cli.ts validate --local --project examples/aws
16
- - node src/cli.ts test --project examples/aws
17
- - node src/cli.ts audit --project examples/aws --expect-routes 5
15
+ - node packages/core/src/cli.ts validate --local --project examples/aws
16
+ - node packages/core/src/cli.ts test --project examples/aws
17
+ - node packages/core/src/cli.ts audit --project examples/aws --expect-routes 5
18
18
  behavior:
19
19
  - every route is native, so the project activates on Lambda without the sandbox
20
20
  - handler.mjs wraps the project with createLambdaHandler; deployment itself is unverified evidence
@@ -12,8 +12,8 @@ files: [urlcode.yaml, wrangler.toml, tests/requests.json, README.md]
12
12
  tests:
13
13
  fixtures: tests/requests.json
14
14
  commands:
15
- - node src/cli.ts validate --local --project examples/cloudflare
16
- - node src/cli.ts test --project examples/cloudflare
17
- - node src/cli.ts audit --project examples/cloudflare --expect-routes 4
15
+ - node packages/core/src/cli.ts validate --local --project examples/cloudflare
16
+ - node packages/core/src/cli.ts test --project examples/cloudflare
17
+ - node packages/core/src/cli.ts audit --project examples/cloudflare --expect-routes 4
18
18
  behavior:
19
19
  - only capabilities the Worker lowering supports appear, so the build succeeds on every target
@@ -10,7 +10,7 @@ Run it against the cookbook from the runtime checkout. The module must be an
10
10
  absolute path outside the audited project:
11
11
 
12
12
  ```sh
13
- node src/cli.ts audit --project examples/cookbook \
13
+ node packages/core/src/cli.ts audit --project examples/cookbook \
14
14
  --compliance baseline --compliance-rules "$PWD/examples/compliance/rules.mjs" --compliance-warn
15
15
  ```
16
16
 
@@ -6,6 +6,6 @@ runnable: false
6
6
  files: [rules.mjs, README.md]
7
7
  tests:
8
8
  commands:
9
- - node src/cli.ts audit --project examples/cookbook --compliance baseline --compliance-rules "$PWD/examples/compliance/rules.mjs" --compliance-warn
9
+ - node packages/core/src/cli.ts audit --project examples/cookbook --compliance baseline --compliance-rules "$PWD/examples/compliance/rules.mjs" --compliance-warn
10
10
  behavior:
11
11
  - three custom rules run alongside the built-in profile; one built-in notice is disabled and one severity raised
@@ -1,7 +1,7 @@
1
1
  // Operator compliance rules for `urlcode audit --compliance-rules`.
2
- // Runnable against the cookbook from the runtime checkout:
2
+ // Runnable against the cookbook from the runtime checkout (after `npm run build`):
3
3
  //
4
- // node src/cli.js audit --project examples/cookbook \
4
+ // node dist/cli.js audit --project examples/cookbook \
5
5
  // --compliance baseline --compliance-rules "$PWD/examples/compliance/rules.mjs" --compliance-warn
6
6
  //
7
7
  // The file must sit outside the audited project (it is trusted host code, the
@@ -12,8 +12,8 @@ files: [urlcode.yaml, tests/requests.json, README.md]
12
12
  tests:
13
13
  fixtures: tests/requests.json
14
14
  commands:
15
- - node src/cli.ts validate --local --project examples/conditions --origin https://conditions.example.test
16
- - node src/cli.ts test --project examples/conditions --origin https://conditions.example.test
17
- - node src/cli.ts audit --project examples/conditions --expect-routes 4 --origin https://conditions.example.test
15
+ - node packages/core/src/cli.ts validate --local --project examples/conditions --origin https://conditions.example.test
16
+ - node packages/core/src/cli.ts test --project examples/conditions --origin https://conditions.example.test
17
+ - node packages/core/src/cli.ts audit --project examples/conditions --expect-routes 4 --origin https://conditions.example.test
18
18
  behavior:
19
19
  - cases are provably disjoint and every branch stays no-store; no branch is authentication
@@ -11,10 +11,10 @@ external services are required. Redirects target example.com; tests never follow
11
11
  From the runtime checkout:
12
12
 
13
13
  ```sh
14
- node src/cli.ts validate --project examples/cookbook
15
- node src/cli.ts test --project examples/cookbook
16
- node src/cli.ts audit --project examples/cookbook --expect-routes 40
17
- node src/cli.ts dev --project examples/cookbook
14
+ node packages/core/src/cli.ts validate --project examples/cookbook
15
+ node packages/core/src/cli.ts test --project examples/cookbook
16
+ node packages/core/src/cli.ts audit --project examples/cookbook --expect-routes 40
17
+ node packages/core/src/cli.ts dev --project examples/cookbook
18
18
  ```
19
19
 
20
20
  The [YAML guide](../../docs/YAML-GUIDE.md) explains the recipes and binding policy.
@@ -9,9 +9,9 @@ files: [urlcode.yaml, routes/code.yaml, routes/redirects.yaml, routes/responses.
9
9
  tests:
10
10
  fixtures: tests/requests.json
11
11
  commands:
12
- - node src/cli.ts validate --local --project examples/cookbook
13
- - node src/cli.ts test --project examples/cookbook
14
- - node src/cli.ts audit --project examples/cookbook --expect-routes 40
12
+ - node packages/core/src/cli.ts validate --local --project examples/cookbook
13
+ - node packages/core/src/cli.ts test --project examples/cookbook
14
+ - node packages/core/src/cli.ts audit --project examples/cookbook --expect-routes 40
15
15
  behavior:
16
16
  - urlcode examples search finds single routes here through the generated route-index.json
17
17
  - fifteen routes exercise the fourteen middleware modules that the middleware recipe copies
@@ -9,8 +9,8 @@ files: [urlcode.yaml, functions/notes.mjs, tests/requests.json, README.md]
9
9
  tests:
10
10
  fixtures: tests/requests.json
11
11
  commands:
12
- - node src/cli.ts validate --local --project examples/coverage-waiver
13
- - node src/cli.ts test --project examples/coverage-waiver
14
- - node src/cli.ts audit --project examples/coverage-waiver --expect-routes 1
12
+ - node packages/core/src/cli.ts validate --local --project examples/coverage-waiver
13
+ - node packages/core/src/cli.ts test --project examples/coverage-waiver
14
+ - node packages/core/src/cli.ts audit --project examples/coverage-waiver --expect-routes 1
15
15
  behavior:
16
16
  - POST /notes is waived from coverage with a reason; GET is asserted by a fixture; the waiver is listed by audit, not hidden
@@ -13,7 +13,7 @@ grants:
13
13
  files: [urlcode.yaml, README.md]
14
14
  tests:
15
15
  commands:
16
- - node src/cli.ts permissions --project examples/egress
17
- - node src/cli.ts validate --local --project examples/egress --policy /operator/egress-policy.json
16
+ - node packages/core/src/cli.ts permissions --project examples/egress
17
+ - node packages/core/src/cli.ts validate --local --project examples/egress --policy /operator/egress-policy.json
18
18
  behavior:
19
19
  - without the policy the project refuses to activate; nothing here reaches the network during authoring inspection
@@ -16,6 +16,6 @@ grants:
16
16
  files: [urlcode.yaml, README.md]
17
17
  tests:
18
18
  commands:
19
- - node src/cli.ts validate --local --project examples/extensions --host-file /operator/host.mjs --origin https://extensions.example.test
19
+ - node packages/core/src/cli.ts validate --local --project examples/extensions --host-file /operator/host.mjs --origin https://extensions.example.test
20
20
  behavior:
21
21
  - "/account uses auth: {role: member}; routes and audit show the expanded policies.extensions.auth form"
@@ -12,8 +12,8 @@ files: [urlcode.yaml, functions/notes.mjs, tests/requests.json, README.md]
12
12
  tests:
13
13
  fixtures: tests/requests.json
14
14
  commands:
15
- - node src/cli.ts test --project examples/lifecycle
16
- - node src/cli.ts audit --project examples/lifecycle --expect-routes 2
15
+ - node packages/core/src/cli.ts test --project examples/lifecycle
16
+ - node packages/core/src/cli.ts audit --project examples/lifecycle --expect-routes 2
17
17
  behavior:
18
18
  - a note created by POST /notes is readable, replaceable and still present after the runtime restarts on the same data directory
19
19
  - the id and Location the create response returns are captured and substituted into later steps as {{id}} and {{where}}
@@ -5,6 +5,6 @@ route, with status 404 and `text/html`. See
5
5
  [site conventions](../../docs/SITE.md#notfound--404html).
6
6
 
7
7
  ```sh
8
- node src/cli.ts test --project examples/not-found
9
- node src/cli.ts build --target static --project examples/not-found # writes 404.html
8
+ node packages/core/src/cli.ts test --project examples/not-found
9
+ node packages/core/src/cli.ts build --target static --project examples/not-found # writes 404.html
10
10
  ```
@@ -9,9 +9,9 @@ files: [urlcode.yaml, public/index.html, public/404.html, tests/requests.json, R
9
9
  tests:
10
10
  fixtures: tests/requests.json
11
11
  commands:
12
- - node src/cli.ts validate --local --project examples/not-found
13
- - node src/cli.ts test --project examples/not-found
14
- - node src/cli.ts audit --project examples/not-found --expect-routes 2
12
+ - node packages/core/src/cli.ts validate --local --project examples/not-found
13
+ - node packages/core/src/cli.ts test --project examples/not-found
14
+ - node packages/core/src/cli.ts audit --project examples/not-found --expect-routes 2
15
15
  behavior:
16
16
  - an unmatched GET or HEAD is answered with the page and status 404
17
17
  - other methods keep the plain-text 404
@@ -14,11 +14,11 @@ runs no code at all.
14
14
  From the runtime checkout:
15
15
 
16
16
  ```sh
17
- node src/cli.ts dev --project examples/prerender # the dynamic source, live
18
- node src/cli.ts test --project examples/prerender # 7 fixtures
17
+ node packages/core/src/cli.ts dev --project examples/prerender # the dynamic source, live
18
+ node packages/core/src/cli.ts test --project examples/prerender # 7 fixtures
19
19
  node examples/prerender/prerender.mjs examples/prerender /absolute/out
20
- node src/cli.ts test --project /absolute/out # fixtures the build wrote
21
- node src/cli.ts audit --project /absolute/out --expect-routes 3
20
+ node packages/core/src/cli.ts test --project /absolute/out # fixtures the build wrote
21
+ node packages/core/src/cli.ts audit --project /absolute/out --expect-routes 3
22
22
  ```
23
23
 
24
24
  The same three URLs answer identically before and after. The difference is what
@@ -9,8 +9,8 @@ files: [urlcode.yaml, functions/page.mjs, middleware/template.mjs, prerender.mjs
9
9
  tests:
10
10
  fixtures: tests/requests.json
11
11
  commands:
12
- - node src/cli.ts test --project examples/prerender
12
+ - node packages/core/src/cli.ts test --project examples/prerender
13
13
  - node examples/prerender/prerender.mjs examples/prerender /absolute/out
14
- - node src/cli.ts audit --project /absolute/out --expect-routes 3
14
+ - node packages/core/src/cli.ts audit --project /absolute/out --expect-routes 3
15
15
  behavior:
16
16
  - the rendered project serves the same bytes from native page routes with no sandbox on the request path
@@ -8,7 +8,7 @@ routes: 5
8
8
  files: [urlcode.yaml, README.md]
9
9
  tests:
10
10
  commands:
11
- - node src/cli.ts validate --local --project examples/provider-conformance
12
- - node src/cli.ts verify-provider --target self-hosted --origin https://owned-fixture.example
11
+ - node packages/core/src/cli.ts validate --local --project examples/provider-conformance
12
+ - node packages/core/src/cli.ts verify-provider --target self-hosted --origin https://owned-fixture.example
13
13
  behavior:
14
14
  - no functions, bindings, assets or policies, so every target activates it; never mix it into a real project
@@ -10,8 +10,8 @@ files: [urlcode.yaml, tests/requests.json, README.md]
10
10
  tests:
11
11
  fixtures: tests/requests.json
12
12
  commands:
13
- - node src/cli.ts validate --local --project examples/shared-blocks
14
- - node src/cli.ts test --project examples/shared-blocks
15
- - node src/cli.ts audit --project examples/shared-blocks --expect-routes 4
13
+ - node packages/core/src/cli.ts validate --local --project examples/shared-blocks
14
+ - node packages/core/src/cli.ts test --project examples/shared-blocks
15
+ - node packages/core/src/cli.ts audit --project examples/shared-blocks --expect-routes 4
16
16
  behavior:
17
17
  - a route's own request or response key replaces the shared block whole; nothing is deep merged
@@ -12,8 +12,8 @@ files: [urlcode.yaml, api/index.js, vercel.json, public/index.html, public/notes
12
12
  tests:
13
13
  fixtures: tests/requests.json
14
14
  commands:
15
- - node src/cli.ts validate --local --project examples/vercel
16
- - node src/cli.ts test --project examples/vercel
17
- - node src/cli.ts audit --project examples/vercel --expect-routes 5
15
+ - node packages/core/src/cli.ts validate --local --project examples/vercel
16
+ - node packages/core/src/cli.ts test --project examples/vercel
17
+ - node packages/core/src/cli.ts audit --project examples/vercel --expect-routes 5
18
18
  behavior:
19
19
  - every route is native, so the project activates on Vercel without the sandbox