@jimhoyd/urlcode 0.3.0 → 0.4.0-alpha.1
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 +106 -0
- package/.claude/skills/urlcode-operations/SKILL.md +114 -0
- package/.claude-plugin/marketplace.json +18 -0
- package/CONTRIBUTING.md +30 -2
- package/README.md +157 -230
- package/ROADMAP.md +85 -2
- package/SECURITY.md +8 -1
- package/dist/BUILD-MANIFEST.json +62 -32
- package/dist/adapters.js +4 -23
- package/dist/agent-lists.js +1 -1
- package/dist/agents-guide.js +113 -0
- package/dist/authoring-files.js +60 -0
- package/dist/authoring.js +11 -1
- package/dist/aws.js +4 -3
- package/dist/build-cloudflare.js +11 -24
- package/dist/bulk.js +37 -0
- package/dist/capabilities.js +208 -0
- package/dist/capability-query.js +72 -0
- package/dist/catalog.js +105 -0
- package/dist/cli.js +154 -15
- package/dist/client-address.js +1 -1
- package/dist/compliance-rules/baseline.js +8 -8
- package/dist/compliance-rules/privacy.js +3 -3
- package/dist/compliance-rules/strict.js +5 -5
- package/dist/conditions.js +88 -0
- package/dist/config.js +60 -4
- package/dist/context.js +156 -0
- package/dist/ecosystem-cli.js +88 -0
- package/dist/egress.js +98 -0
- package/dist/examples.js +92 -0
- package/dist/explain-cli.js +64 -0
- package/dist/explain.js +132 -0
- package/dist/extensions.js +172 -0
- package/dist/function-sources.js +15 -3
- package/dist/index.js +37 -0
- package/dist/init-with.js +165 -0
- package/dist/interchange-cli.js +42 -0
- package/dist/interchange.js +189 -0
- package/dist/link-cli.js +1 -1
- package/dist/management-policy.js +0 -1
- package/dist/manifest.js +111 -0
- package/dist/match.js +2 -2
- package/dist/mcp-authoring.js +147 -0
- package/dist/mcp.js +97 -0
- package/dist/observability.js +6 -0
- package/dist/operator-host.js +29 -0
- package/dist/plugins.js +12 -0
- package/dist/policies/agents.js +2 -2
- package/dist/policies/compression.js +2 -1
- package/dist/policies/security.js +0 -0
- package/dist/policies.js +1 -1
- package/dist/policy.js +29 -7
- package/dist/prerender.js +100 -41
- package/dist/project-tests.js +3 -3
- package/dist/provider-verification.js +92 -0
- package/dist/proxy.js +44 -0
- package/dist/readiness.js +5 -5
- package/dist/recipes.js +41 -0
- package/dist/route-diff.js +106 -0
- package/dist/router.js +42 -2
- package/dist/runtime.js +95 -14
- package/dist/schema-query.js +62 -0
- package/dist/signals.js +24 -0
- package/dist/site.js +0 -0
- package/dist/tooling.js +96 -0
- package/dist/types/adapters.d.ts +7 -4
- package/dist/types/agent-lists.d.ts +0 -1
- package/dist/types/agents-guide.d.ts +17 -0
- package/dist/types/authoring-files.d.ts +10 -0
- package/dist/types/aws.d.ts +3 -1
- package/dist/types/build-cloudflare.d.ts +1 -0
- package/dist/types/bulk.d.ts +27 -0
- package/dist/types/capabilities.d.ts +55 -0
- package/dist/types/capability-query.d.ts +24 -0
- package/dist/types/catalog.d.ts +65 -0
- package/dist/types/client-address.d.ts +0 -1
- package/dist/types/compliance-rules/baseline.d.ts +1 -9
- package/dist/types/compliance-rules/privacy.d.ts +1 -4
- package/dist/types/compliance-rules/strict.d.ts +0 -5
- package/dist/types/conditions.d.ts +19 -0
- package/dist/types/config.d.ts +20 -2
- package/dist/types/context.d.ts +65 -0
- package/dist/types/ecosystem-cli.d.ts +17 -0
- package/dist/types/egress.d.ts +46 -0
- package/dist/types/examples.d.ts +50 -0
- package/dist/types/explain-cli.d.ts +11 -0
- package/dist/types/explain.d.ts +95 -0
- package/dist/types/extensions.d.ts +122 -0
- package/dist/types/function-sources.d.ts +5 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/init-with.d.ts +30 -0
- package/dist/types/interchange-cli.d.ts +16 -0
- package/dist/types/interchange.d.ts +42 -0
- package/dist/types/link-cli.d.ts +2 -1
- package/dist/types/management-policy.d.ts +0 -1
- package/dist/types/manifest.d.ts +81 -0
- package/dist/types/match.d.ts +1 -0
- package/dist/types/mcp-authoring.d.ts +92 -0
- package/dist/types/mcp.d.ts +12 -0
- package/dist/types/observability.d.ts +2 -0
- package/dist/types/operator-host.d.ts +8 -0
- package/dist/types/plugins.d.ts +2 -0
- package/dist/types/policies/agents.d.ts +0 -2
- package/dist/types/policies/compression.d.ts +2 -0
- package/dist/types/policies/security.d.ts +0 -1
- package/dist/types/policy.d.ts +15 -4
- package/dist/types/project-tests.d.ts +3 -1
- package/dist/types/provider-verification.d.ts +53 -0
- package/dist/types/proxy.d.ts +21 -0
- package/dist/types/readiness.d.ts +1 -1
- package/dist/types/recipes.d.ts +30 -0
- package/dist/types/route-diff.d.ts +27 -0
- package/dist/types/runtime.d.ts +11 -0
- package/dist/types/schema-query.d.ts +12 -0
- package/dist/types/signals.d.ts +25 -0
- package/dist/types/site.d.ts +0 -1
- package/dist/types/tooling.d.ts +115 -0
- package/dist/types/types.d.ts +57 -0
- package/dist/types/typescript-authoring.d.ts +12 -0
- package/dist/types/vercel.d.ts +3 -1
- package/dist/types/verify-deployment.d.ts +47 -0
- package/dist/types.js +21 -2
- package/dist/typescript-authoring.js +104 -0
- package/dist/vercel.js +4 -3
- package/dist/verify-deployment.js +270 -0
- package/docs/AI-AUTHORING.md +130 -8
- package/docs/BULK.md +79 -0
- package/docs/CAPABILITIES.md +179 -0
- package/docs/CAPACITY.md +1 -1
- package/docs/CI.md +142 -0
- package/docs/CONDITIONS.md +74 -0
- package/docs/DEPLOYMENT-CHECKS.md +108 -0
- package/docs/DYNAMIC-LINKS.md +18 -518
- package/docs/EGRESS.md +125 -0
- package/docs/EXTENSIONS.md +226 -0
- package/docs/FRAMEWORK.md +182 -0
- package/docs/INSTALL.md +45 -7
- package/docs/INTERCHANGE.md +134 -0
- package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
- package/docs/MIDDLEWARE.md +2 -0
- package/docs/NEXT-PHASE-PLAN.md +90 -0
- package/docs/NEXT-STEPS.md +415 -0
- package/docs/OBSERVABILITY.md +4 -2
- package/docs/OPERATIONAL-PROOF.md +4 -1
- package/docs/OPERATIONS.md +6 -3
- package/docs/PLUGINS.md +37 -0
- package/docs/POLICIES.md +12 -309
- package/docs/PRERENDER.md +40 -0
- package/docs/PROJECT-DIRECTION.md +42 -0
- package/docs/PROVIDER-VERIFICATION.md +84 -0
- package/docs/READINESS.md +21 -1
- package/docs/README.md +82 -31
- package/docs/RECIPES.md +99 -0
- package/docs/RELEASE-READINESS.md +11 -9
- package/docs/RELEASE-SECURITY.md +27 -4
- package/docs/SECURITY-AUDIT.md +1 -1
- package/docs/SPECIFICATION.md +95 -8
- package/docs/SPIKE-BUSINESS-SUITE.md +1013 -0
- package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
- package/docs/SPIKE-EXTENSIONS.md +1 -0
- package/docs/SPIKE-LAMBDA-COMPILE.md +199 -0
- package/docs/STANDARDS.md +150 -142
- package/docs/STARTERS.md +21 -1
- package/docs/TOOLING.md +291 -0
- package/docs/TYPESCRIPT-AUTHORING.md +67 -0
- package/docs/TYPESCRIPT.md +1 -1
- package/docs/USABILITY-REVIEW.md +123 -0
- package/docs/YAML-GUIDE.md +18 -479
- package/docs/YAML-REFERENCE.md +127 -16
- package/docs/links/cli.md +110 -0
- package/docs/links/limits.md +175 -0
- package/docs/links/management-api.md +80 -0
- package/docs/links/pools.md +75 -0
- package/docs/links/setup.md +135 -0
- package/docs/policies/agents.md +1 -1
- package/docs/policies/contract.md +52 -0
- package/docs/policies/hardened.md +56 -0
- package/docs/policies/interoperability.md +169 -0
- package/docs/policies/operations.md +45 -0
- package/docs/yaml/assets.md +36 -0
- package/docs/yaml/conditions.md +20 -0
- package/docs/yaml/functions.md +160 -0
- package/docs/yaml/links.md +30 -0
- package/docs/yaml/middleware.md +29 -0
- package/docs/yaml/organization.md +74 -0
- package/docs/yaml/policies.md +37 -0
- package/docs/yaml/redirects.md +64 -0
- package/docs/yaml/responses.md +57 -0
- package/docs/yaml/site.md +24 -0
- package/examples/assets/example.yaml +17 -0
- package/examples/aws/example.yaml +20 -0
- package/examples/cloudflare/example.yaml +19 -0
- package/examples/compliance/example.yaml +11 -0
- package/examples/conditions/README.md +12 -0
- package/examples/conditions/example.yaml +19 -0
- package/examples/conditions/tests/requests.json +13 -0
- package/examples/conditions/urlcode.yaml +24 -0
- package/examples/cookbook/README.md +8 -4
- package/examples/cookbook/example.yaml +17 -0
- package/examples/cookbook/functions/catalog.mjs +3 -0
- package/examples/cookbook/functions/fail.mjs +4 -0
- package/examples/cookbook/functions/items.mjs +3 -0
- package/examples/cookbook/functions/profile.mjs +3 -0
- package/examples/cookbook/functions/resource.mjs +3 -0
- package/examples/cookbook/functions/status.mjs +3 -0
- package/examples/cookbook/middleware/auth.mjs +48 -0
- package/examples/cookbook/middleware/body.mjs +15 -0
- package/examples/cookbook/middleware/bucket.mjs +19 -0
- package/examples/cookbook/middleware/cors.mjs +21 -0
- package/examples/cookbook/middleware/debug.mjs +13 -0
- package/examples/cookbook/middleware/envelope.mjs +11 -0
- package/examples/cookbook/middleware/errors.mjs +11 -0
- package/examples/cookbook/middleware/etag.mjs +18 -0
- package/examples/cookbook/middleware/locale.mjs +16 -0
- package/examples/cookbook/middleware/maintenance.mjs +10 -0
- package/examples/cookbook/middleware/methods.mjs +15 -0
- package/examples/cookbook/middleware/negotiate.mjs +20 -0
- package/examples/cookbook/middleware/referer.mjs +12 -0
- package/examples/cookbook/middleware/request-id.mjs +16 -0
- package/examples/cookbook/route-index.json +676 -0
- package/examples/cookbook/routes/middleware.yaml +126 -0
- package/examples/cookbook/tests/requests.json +526 -0
- package/examples/cookbook/urlcode.yaml +1 -0
- package/examples/egress/README.md +22 -0
- package/examples/egress/example.yaml +19 -0
- package/examples/egress/urlcode.yaml +19 -0
- package/examples/extensions/README.md +7 -0
- package/examples/extensions/example.yaml +21 -0
- package/examples/extensions/urlcode.yaml +25 -0
- package/examples/live-links/example.yaml +21 -0
- package/examples/monitoring/example.yaml +8 -0
- package/examples/prerender/example.yaml +16 -0
- package/examples/provider-conformance/README.md +12 -0
- package/examples/provider-conformance/example.yaml +14 -0
- package/examples/provider-conformance/urlcode.yaml +34 -0
- package/examples/tunnel/example.yaml +8 -0
- package/examples/vercel/example.yaml +19 -0
- package/llms-full.txt +2709 -0
- package/llms.txt +48 -19
- package/package.json +29 -7
- package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +106 -0
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +114 -0
- package/recipes/authenticated-json-api/README.md +51 -0
- package/recipes/authenticated-json-api/functions/profile.mjs +5 -0
- package/recipes/authenticated-json-api/recipe.yaml +34 -0
- package/recipes/authenticated-json-api/tests/requests.json +39 -0
- package/recipes/authenticated-json-api/urlcode.yaml +12 -0
- package/recipes/contact-form/README.md +25 -0
- package/recipes/contact-form/functions/contact.mjs +17 -0
- package/recipes/contact-form/recipe.yaml +33 -0
- package/recipes/contact-form/tests/requests.json +47 -0
- package/recipes/contact-form/urlcode.yaml +18 -0
- package/recipes/cors-api/README.md +16 -0
- package/recipes/cors-api/functions/items.mjs +3 -0
- package/recipes/cors-api/middleware/cors.mjs +21 -0
- package/recipes/cors-api/recipe.yaml +26 -0
- package/recipes/cors-api/tests/requests.json +65 -0
- package/recipes/cors-api/urlcode.yaml +12 -0
- package/recipes/health-page/README.md +13 -0
- package/recipes/health-page/recipe.yaml +23 -0
- package/recipes/health-page/tests/requests.json +36 -0
- package/recipes/health-page/urlcode.yaml +19 -0
- package/recipes/json-api/README.md +6 -0
- package/recipes/json-api/functions/echo.mjs +3 -0
- package/recipes/json-api/recipe.yaml +25 -0
- package/recipes/json-api/tests/requests.json +34 -0
- package/recipes/json-api/urlcode.yaml +12 -0
- package/recipes/middleware/README.md +34 -0
- package/recipes/middleware/functions/catalog.mjs +3 -0
- package/recipes/middleware/functions/fail.mjs +4 -0
- package/recipes/middleware/functions/items.mjs +3 -0
- package/recipes/middleware/functions/profile.mjs +3 -0
- package/recipes/middleware/functions/resource.mjs +3 -0
- package/recipes/middleware/functions/status.mjs +3 -0
- package/recipes/middleware/middleware/auth.mjs +48 -0
- package/recipes/middleware/middleware/body.mjs +15 -0
- package/recipes/middleware/middleware/bucket.mjs +19 -0
- package/recipes/middleware/middleware/cors.mjs +21 -0
- package/recipes/middleware/middleware/debug.mjs +13 -0
- package/recipes/middleware/middleware/envelope.mjs +11 -0
- package/recipes/middleware/middleware/errors.mjs +11 -0
- package/recipes/middleware/middleware/etag.mjs +18 -0
- package/recipes/middleware/middleware/locale.mjs +16 -0
- package/recipes/middleware/middleware/maintenance.mjs +10 -0
- package/recipes/middleware/middleware/methods.mjs +15 -0
- package/recipes/middleware/middleware/negotiate.mjs +20 -0
- package/recipes/middleware/middleware/referer.mjs +12 -0
- package/recipes/middleware/middleware/request-id.mjs +16 -0
- package/recipes/middleware/public/guide.txt +1 -0
- package/recipes/middleware/recipe.yaml +50 -0
- package/recipes/middleware/tests/requests.json +528 -0
- package/recipes/middleware/urlcode.yaml +127 -0
- package/recipes/protected-download/README.md +22 -0
- package/recipes/protected-download/files/report.txt +1 -0
- package/recipes/protected-download/recipe.yaml +31 -0
- package/recipes/protected-download/tests/requests.json +32 -0
- package/recipes/protected-download/urlcode.yaml +15 -0
- package/recipes/redirect/README.md +7 -0
- package/recipes/redirect/recipe.yaml +25 -0
- package/recipes/redirect/tests/requests.json +19 -0
- package/recipes/redirect/urlcode.yaml +9 -0
- package/recipes/static-plus-api/README.md +15 -0
- package/recipes/static-plus-api/functions/info.mjs +3 -0
- package/recipes/static-plus-api/public/assets/index.html +3 -0
- package/recipes/static-plus-api/public/assets/site.css +1 -0
- package/recipes/static-plus-api/public/index.html +8 -0
- package/recipes/static-plus-api/recipe.yaml +29 -0
- package/recipes/static-plus-api/tests/requests.json +56 -0
- package/recipes/static-plus-api/urlcode.yaml +23 -0
- package/recipes/typescript/README.md +7 -0
- package/recipes/typescript/functions/hello.ts +5 -0
- package/recipes/typescript/recipe.yaml +23 -0
- package/recipes/typescript/tests/requests.json +18 -0
- package/recipes/typescript/urlcode.yaml +5 -0
- package/recipes/webhook-receiver/README.md +16 -0
- package/recipes/webhook-receiver/functions/receive.mjs +16 -0
- package/recipes/webhook-receiver/recipe.yaml +26 -0
- package/recipes/webhook-receiver/tests/requests.json +59 -0
- package/recipes/webhook-receiver/urlcode.yaml +16 -0
- package/schemas/recipe.schema.json +138 -0
- package/schemas/urlcode.schema.json +656 -80
- package/skills/urlcode/SKILL.md +98 -0
- package/starters/default/.github/workflows/urlcode.yml +23 -0
- package/starters/default/.mcp.json +12 -0
- package/starters/default/AGENTS.md +79 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Redirect interchange and bulk authoring
|
|
2
|
+
|
|
3
|
+
`importRoutes` and `exportRoutes` return a conversion report without writing files,
|
|
4
|
+
activating a runtime, reading bindings, loading function sources or running code.
|
|
5
|
+
The CLI can preview the report before creating a new output file. Failed reports
|
|
6
|
+
never contain a partial document or output; duplicate paths never overwrite a row.
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
import { importRoutes, exportRoutes } from '@jimhoyd/urlcode';
|
|
10
|
+
const report = await importRoutes({
|
|
11
|
+
format: 'csv', source: 'migration.csv',
|
|
12
|
+
text: 'path,url,status\n/old,https://example.test/new,301\n'
|
|
13
|
+
});
|
|
14
|
+
if (!report.ok) throw new Error(JSON.stringify(report.diagnostics));
|
|
15
|
+
// report.output is URLCode project YAML; report.document is the validated project.
|
|
16
|
+
const csv = await exportRoutes({ format: 'csv', document: report.document });
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Each report has `ok`, `lossless`, `routeCount` and `diagnostics`. Diagnostics name
|
|
20
|
+
an input source, physical CSV/text row or JSON/YAML array index where available,
|
|
21
|
+
a path where appropriate, a code, severity and explanation. Destinations and
|
|
22
|
+
credentials are not echoed in validation errors. Output is sorted by literal
|
|
23
|
+
path, independent of input order. Import does not merge an existing project;
|
|
24
|
+
normal project loading rejects collisions across includes when output is added.
|
|
25
|
+
|
|
26
|
+
## CLI usage
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
# Bulk row formats can be inferred from the input extension or named explicitly.
|
|
30
|
+
urlcode import csv migration.csv --out routes.yaml --dry-run
|
|
31
|
+
urlcode import csv migration.csv --out routes.yaml
|
|
32
|
+
urlcode import migration.json --report json --dry-run
|
|
33
|
+
|
|
34
|
+
# Provider migrations require explicit acknowledgment of the reported differences.
|
|
35
|
+
urlcode import netlify _redirects --out imported.yaml --dry-run
|
|
36
|
+
urlcode import netlify _redirects --out imported.yaml --accept-provider-differences
|
|
37
|
+
urlcode export --target netlify --project ./project --out _redirects --accept-provider-differences
|
|
38
|
+
urlcode export --target cloudflare --project ./project --dry-run
|
|
39
|
+
urlcode export --target vercel --project ./project --dry-run
|
|
40
|
+
urlcode export --target netlify-toml --project ./project --dry-run
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
General import syntax is `urlcode import [format] FILE`; `--format` explicitly
|
|
44
|
+
overrides the positional format or extension. Exports use `--target` for a
|
|
45
|
+
provider format, or `--format csv|json|yaml` for row data. `--out FILE` creates a
|
|
46
|
+
new file with mode 0600 and never overwrites one. `--dry-run` validates and
|
|
47
|
+
returns a report without creating the requested output. `--report json` always
|
|
48
|
+
emits the report. Failures exit nonzero and leave no converted output.
|
|
49
|
+
|
|
50
|
+
Reports include source/row diagnostics and the successful candidate output.
|
|
51
|
+
Acknowledged provider conversions always emit a report with their warnings,
|
|
52
|
+
even when `--out` is provided; raw provider output is never silently printed as
|
|
53
|
+
if it were lossless. Project export flattens validated includes, but refuses
|
|
54
|
+
other unsupported project behavior instead of dropping it. For a large input
|
|
55
|
+
that needs a complete sharded project, use [bulk import](BULK.md).
|
|
56
|
+
|
|
57
|
+
## Supported forms
|
|
58
|
+
|
|
59
|
+
| Format | Input/output subset |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `csv` | Header exactly `path,url,status`; optional empty status defaults to 302; quoted fields and escaped quotes |
|
|
62
|
+
| `json` | Array of `{path,url,status?}`; status must be a number |
|
|
63
|
+
| `yaml` | The same row array in strict URLCode YAML syntax; not a full project file |
|
|
64
|
+
| `netlify` | `_redirects`: literal source, absolute HTTP(S) destination, optional status (default 301) |
|
|
65
|
+
| `cloudflare` | Pages `_redirects`: same columns, default 302; 2,000 static rules, 1,000 characters per rule |
|
|
66
|
+
| `vercel` | `vercel.json` containing only `redirects`; literal `source`, absolute `destination`, and either boolean `permanent` (308/307) or `statusCode` |
|
|
67
|
+
| `netlify-toml` | Only `[[redirects]]` blocks with `from`, `to`, optional numeric `status` (default 301); unescaped double-quoted strings |
|
|
68
|
+
|
|
69
|
+
All statuses are restricted to 301, 302, 303, 307 and 308. Literal ASCII paths
|
|
70
|
+
and absolute HTTP(S) destinations are required. The existing schema and semantic
|
|
71
|
+
compiler validate resulting routes. No provider pattern compiler is introduced.
|
|
72
|
+
|
|
73
|
+
The TOML subset deliberately rejects general TOML constructs, build settings,
|
|
74
|
+
escapes, inline comments, nested conditions, force flags and other tables. A full
|
|
75
|
+
`netlify.toml` must be separated into a redirects-only input by the operator. This
|
|
76
|
+
avoids silently discarding build settings or reinterpreting unsupported syntax.
|
|
77
|
+
|
|
78
|
+
Functions, assets, middleware, conditions, parameters, query maps/allowlists,
|
|
79
|
+
headers, policies, includes and any other extra route/project fields cannot be
|
|
80
|
+
exported by this subset. Relative destinations, wildcards, provider placeholder
|
|
81
|
+
syntax, rewrites and duplicate paths fail. Use a runtime adapter for richer
|
|
82
|
+
behavior. CSV/JSON/YAML round-trips are lossless within the declared simple subset.
|
|
83
|
+
|
|
84
|
+
## Provider semantics require explicit acknowledgment
|
|
85
|
+
|
|
86
|
+
Provider conversions fail by default. `acceptProviderDifferences: true` (CLI
|
|
87
|
+
`--accept-provider-differences`) permits a **non-lossless migration candidate**;
|
|
88
|
+
it does not suppress invalid rules, discard unsupported fields or claim exact
|
|
89
|
+
behavior. Every such successful report retains a warning and `lossless: false`.
|
|
90
|
+
|
|
91
|
+
URLCode drops incoming query parameters and defaults to GET/HEAD. Native
|
|
92
|
+
redirect systems can apply to other methods and have different normalization
|
|
93
|
+
and request-query behavior. Netlify automatically forwards queries for common
|
|
94
|
+
redirect statuses and can give existing files precedence. Cloudflare Pages
|
|
95
|
+
redirects override assets, but Pages Functions can bypass `_redirects`.
|
|
96
|
+
Review behavior for requests with queries, non-GET methods, slash/URL normalization
|
|
97
|
+
and conflicting assets/functions before deploying. The acknowledged subset is
|
|
98
|
+
literal GET/HEAD requests without these conflicts. No provider deployment was
|
|
99
|
+
performed as part of interchange unit tests.
|
|
100
|
+
|
|
101
|
+
Provider references checked 2026-09-17:
|
|
102
|
+
|
|
103
|
+
- [Netlify redirect options](https://docs.netlify.com/manage/routing/redirects/redirect-options/)
|
|
104
|
+
- [Netlify TOML configuration](https://docs.netlify.com/build/configure-builds/file-based-configuration/)
|
|
105
|
+
- [Cloudflare Pages redirects](https://developers.cloudflare.com/pages/configuration/redirects/)
|
|
106
|
+
- [Vercel redirect configuration](https://vercel.com/docs/project-configuration/vercel-json)
|
|
107
|
+
|
|
108
|
+
## Resource limits
|
|
109
|
+
|
|
110
|
+
Imports reject more than 32 MiB of input or 100,000 rows. Diagnostic collection
|
|
111
|
+
stops after 100 invalid rows; normal route compilation retains its deadline and
|
|
112
|
+
validation rules. Large output can still exceed the runtime YAML worker's heap
|
|
113
|
+
or deadline: conversion success does not promise activation of a single 100k
|
|
114
|
+
route YAML file. Split large migration inputs into independently reviewed files
|
|
115
|
+
and use explicit `includes`; aggregate project limits continue to apply.
|
|
116
|
+
|
|
117
|
+
Reports also expose bounded classification counts:
|
|
118
|
+
|
|
119
|
+
- `convertedRoutes`: routes actually returned in successful output; zero when
|
|
120
|
+
any error prevents output.
|
|
121
|
+
- `nativeEquivalentRoutes`: returned routes with no conversion warnings; always
|
|
122
|
+
zero for acknowledged provider conversions.
|
|
123
|
+
- `runtimeRequiredRoutes`: route diagnostics identifying behavior outside the
|
|
124
|
+
simple export subset.
|
|
125
|
+
- `unsupportedRows`: known invalid/duplicate input-row diagnostics.
|
|
126
|
+
- `providerDifferenceRoutes`: candidate routes subject to the provider semantic
|
|
127
|
+
warning; these overlap converted routes after acknowledgment.
|
|
128
|
+
- `fullyScanned`: false on parser/global failures or truncated diagnostics. Counts
|
|
129
|
+
then describe only examined input, never an inferred total.
|
|
130
|
+
|
|
131
|
+
These counts are not a partition of arbitrary malformed input. `routeCount`
|
|
132
|
+
retains its original meaning: routes in the candidate table, or rows
|
|
133
|
+
parsed before an early global failure. A failed report contains no candidate
|
|
134
|
+
output even if some rows were convertible.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Middleware examples
|
|
2
|
+
|
|
3
|
+
Fourteen reusable middleware modules covering fifteen patterns (`auth.mjs`
|
|
4
|
+
exports both `bearer` and `basic`), each in
|
|
5
|
+
[`examples/cookbook/middleware`](../examples/cookbook/middleware) with a route in
|
|
6
|
+
[`routes/middleware.yaml`](../examples/cookbook/routes/middleware.yaml) and request
|
|
7
|
+
fixtures in the cookbook tests. The same modules ship as the `middleware`
|
|
8
|
+
[local recipe](RECIPES.md):
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
urlcode recipes add middleware --out ./my-middleware
|
|
12
|
+
urlcode test --project ./my-middleware
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
They cover what Express, Koa, Fastify, Hono, Next.js and edge runtimes usually
|
|
16
|
+
ship as middleware, rewritten for URLCode's [guest API](MIDDLEWARE.md): text/JSON
|
|
17
|
+
`Request`/`Response`, `Headers`, `context.inputs/args/env/secrets/state`, timers
|
|
18
|
+
and nothing else. There is no `crypto`, `URL`, `fetch`, storage or console, and
|
|
19
|
+
`context.state` dies with the request. Patterns that need any of those are listed
|
|
20
|
+
at the end so nobody wastes time porting them.
|
|
21
|
+
|
|
22
|
+
| Pattern | Module | Framework equivalent | Demonstrates |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| Bearer token gate | `auth.mjs` `bearer` | Hono `bearerAuth`, `express-bearer-token` | Early 401, `www-authenticate`, constant-time compare against an `env` binding |
|
|
25
|
+
| Basic authentication | `auth.mjs` `basic` | Hono `basicAuth`, `express-basic-auth` | Hand-written base64 (no `atob`), both checks always evaluated, `state.user` |
|
|
26
|
+
| CORS | `cors.mjs` | Express `cors`, Hono `cors` | `OPTIONS` answered before the handler, origin allowlist, `vary: origin` |
|
|
27
|
+
| Correlation id and timing | `request-id.mjs` | Hono `requestId`/`timing`, Express `response-time` | Validating a caller header, `server-timing`, coexisting with the runtime's own `x-request-id` |
|
|
28
|
+
| Maintenance switch | `maintenance.mjs` | Next.js and Netlify Edge maintenance examples | 503 with `retry-after`, bypass header, flipping behavior from a binding |
|
|
29
|
+
| Error boundary | `errors.mjs` | Koa `onerror`, Express error handlers | Catching a downstream throw, JSON 500 instead of a bare 502 |
|
|
30
|
+
| JSON envelope | `envelope.mjs` | Response transformers | Reading a function body once, passing native bodies through untouched |
|
|
31
|
+
| Content negotiation | `negotiate.mjs` | Express `res.format` | Parsing `accept` with q-values, 406, `vary: accept` |
|
|
32
|
+
| Method override | `methods.mjs` `override` | Express `method-override` | Bounded tunneling through POST, 405 with `allow` |
|
|
33
|
+
| ETag and 304 | `etag.mjs` | Express `etag`, Fastify `@fastify/etag` | FNV-1a weak tag, `if-none-match`, null-body 304 |
|
|
34
|
+
| A/B bucket | `bucket.mjs` | Vercel and Cloudflare A/B examples | Cookie parsing, `set-cookie`, replacing a native redirect |
|
|
35
|
+
| Locale redirect | `locale.mjs` | Next.js i18n middleware | `accept-language` ranking, allowlisted languages, `vary` |
|
|
36
|
+
| Referer allowlist | `referer.mjs` | Hotlink protection rules | Gating a native download without reading it |
|
|
37
|
+
| Body validation | `body.mjs` | `express-validator`, Fastify schemas | Single-use body, 422 error list, handoff through `state` |
|
|
38
|
+
| Debug echo | `debug.mjs` | Request loggers | Inspecting inputs, args and redacted headers when the console is silent |
|
|
39
|
+
|
|
40
|
+
## Reading the modules
|
|
41
|
+
|
|
42
|
+
Every module follows the shape in [middleware](MIDDLEWARE.md): read the request,
|
|
43
|
+
optionally return early, otherwise `await next()` once and return a `Response`.
|
|
44
|
+
Three habits recur and are worth copying:
|
|
45
|
+
|
|
46
|
+
- **Configuration lives in bindings.** Tokens, allowlists and switches are read
|
|
47
|
+
from `context.env`. The cookbook binds literal values so it runs without grants;
|
|
48
|
+
a deployed project uses `{secret: name}` for credentials and an operator grant.
|
|
49
|
+
- **Native bodies stay opaque.** `envelope`, `negotiate` and `etag` only rewrite a
|
|
50
|
+
body when the downstream response is a function response with a readable
|
|
51
|
+
content type. `bucket`, `locale` and `referer` wrap native redirects and
|
|
52
|
+
downloads without touching their bytes; to change the destination they return
|
|
53
|
+
a new `Response` instead.
|
|
54
|
+
- **Chains compose through `state`.** `/fragile` runs `request-id` before
|
|
55
|
+
`errors`, so the fallback JSON carries the correlation id. `/profile` parses
|
|
56
|
+
the body once in middleware and the function reads `context.state.body`.
|
|
57
|
+
|
|
58
|
+
## Limits these examples respect
|
|
59
|
+
|
|
60
|
+
- **No `crypto`.** The auth modules compare a shared token; they cannot verify
|
|
61
|
+
HMAC signatures, JWTs or password hashes. Signed URLs would be a runtime
|
|
62
|
+
feature, not a middleware example.
|
|
63
|
+
- **No cross-request state.** Rate limiting, caching, sessions and CSRF tokens
|
|
64
|
+
need storage the guest does not have. Throttling and cache headers exist as
|
|
65
|
+
native [policies](POLICIES.md) instead.
|
|
66
|
+
- **No logging target.** The guest console is a no-op; `debug.mjs` returns the
|
|
67
|
+
information to the caller instead, and only when both a binding and a header
|
|
68
|
+
ask for it. Remove that route before publishing a project.
|
|
69
|
+
- **Runtime headers win.** The runtime stamps `x-request-id` on every response,
|
|
70
|
+
which is why the tracing example uses `x-correlation-id`. YAML
|
|
71
|
+
`response.headers` also override middleware headers.
|
|
72
|
+
|
|
73
|
+
Each cookbook route has fixtures for its success path, its early responses, its
|
|
74
|
+
validation failures and every declared method, which is what `urlcode audit`
|
|
75
|
+
expects before it reports a middleware-wrapped route as covered.
|
package/docs/MIDDLEWARE.md
CHANGED
|
@@ -28,6 +28,8 @@ Paths resolve from the project root. Up to 16 entries are allowed; each accepts
|
|
|
28
28
|
`source` and optional `export` (default `default`). Reuse a module across routes.
|
|
29
29
|
There are no global middleware settings or middleware-specific argument fields.
|
|
30
30
|
This is URLCode's small portable API, not Express/Node middleware compatibility.
|
|
31
|
+
Fourteen ready-to-copy patterns (auth, CORS, tracing, error boundary, ETag and
|
|
32
|
+
more) are in [middleware examples](MIDDLEWARE-EXAMPLES.md).
|
|
31
33
|
|
|
32
34
|
## Order and responses
|
|
33
35
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Next-phase implementation status
|
|
2
|
+
|
|
3
|
+
Repository review and source implementation, 2026-09-17. Phase A established
|
|
4
|
+
the capability catalog; the subsequent source work implements the bounded
|
|
5
|
+
Phase B–D features below. This describes the source additions after `0.3.0` that ship in
|
|
6
|
+
`0.4.0-alpha.1`, not a claim that provider deployments have been verified.
|
|
7
|
+
|
|
8
|
+
## Implementation and evidence
|
|
9
|
+
|
|
10
|
+
| Phase | Implemented source | Evidence and remaining limits |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| A: capabilities and normalized representation | Shared catalog, route/project analysis, CLI/SDK and adapter preflight reuse the existing compiled IR | Capability tests and target refusals; compiled routes with secrets/closures are never a portable public artifact |
|
|
13
|
+
| B1: provider conformance | Synthetic 12-case common-subset fixture; local self-hosted/AWS/Vercel/Cloudflare replay; bounded HTTPS deployment runner and versioned reports | Local adapter evidence exists; actual AWS/Vercel/Cloudflare deployments and provider-specific transport/policy guarantees remain unverified |
|
|
14
|
+
| B2: Netlify/Cloudflare conversion | Strict literal redirect import/export, source diagnostics, dry-run, no-clobber output and explicit provider-difference acknowledgment | Provider normalization, query forwarding, method coverage and asset precedence differ; acknowledged migrations are explicitly non-lossless |
|
|
15
|
+
| B3: Vercel/TOML conversion | Conservative Vercel redirect subset and redirects-only Netlify TOML grammar | Unsupported fields, patterns, conditions, forced rules and general TOML syntax are rejected rather than discarded |
|
|
16
|
+
| C1: bounded proxy | Self-hosted native proxy and external revision-pinned HTTPS-origin grants, connection-pinned public DNS, body/time/concurrency limits and header filtering | No guest fetch, host execution fallback, automatic redirects or retries; providers refuse proxy; independent security review remains open |
|
|
17
|
+
| C2: conditions | Exact bounded query/header/cookie/host/method predicates; explicit disjoint redirect/respond cases and fallback; no-store | Duplicate YAML keys remain errors; ambiguous cases fail; self-hosted/AWS/Vercel share logic, Cloudflare refuses pending artifact support |
|
|
18
|
+
| C3: best-effort signals | Self-hosted bounded webhook broker, external pinned grants, fixed redacted event shape, accepted/delivered/failed/dropped counters and shutdown handling | No queue, retry, ordering or durability guarantee; saturation drops; providers refuse signals |
|
|
19
|
+
| D: recipes | Three ordinary local Git-owned recipe projects with list/show/add and new-directory dry-run publication | Runtime integration tests exercise redirect, JSON API and built TypeScript recipe; no remote registry or implicit project merge |
|
|
20
|
+
| D: bulk | Strict CSV/JSON/YAML conversion, input fingerprint/source provenance, sorted 1,000-route include shards | 1k/10k/100k local measurements pass without relaxing loader limits; no implicit merge or arbitrary bulk mutation |
|
|
21
|
+
| D: TypeScript guests | Fixed trusted build-time compiler, bounded relative graph, rewritten JavaScript imports, referenced-asset snapshot and safe new output | Transpilation is not type checking; no tsconfig/plugins/package execution or dotenv copying; runtime remains QuickJS JavaScript only |
|
|
22
|
+
| D: consolidated SDK/MCP | Inspection, semantic validation, path explanation, compatibility, conversion previews and recipe discovery; operator-rooted stdio MCP | Read-only tooling; no arbitrary path, credential, guest execution or write authority; not a remote authenticated service |
|
|
23
|
+
|
|
24
|
+
See [interchange](INTERCHANGE.md), [provider evidence](PROVIDER-VERIFICATION.md),
|
|
25
|
+
[egress](EGRESS.md), [conditions](CONDITIONS.md), [recipes](RECIPES.md),
|
|
26
|
+
[bulk measurements](BULK.md), [TypeScript authoring](TYPESCRIPT-AUTHORING.md), and
|
|
27
|
+
[tooling/MCP](TOOLING.md) for the executable interfaces and exact restrictions.
|
|
28
|
+
|
|
29
|
+
## Preserved architecture
|
|
30
|
+
|
|
31
|
+
The schema and semantic compiler remain the behavior contract. `CompiledRoute`
|
|
32
|
+
and `CompiledRouteTable` remain the runtime IR; `MatchableRoute` remains the
|
|
33
|
+
shared matching representation. New handlers and conditions extend those paths
|
|
34
|
+
rather than introducing a second route compiler. Capability analysis precedes
|
|
35
|
+
binding resolution and activation and distinguishes implementation support from
|
|
36
|
+
actual deployment evidence. Unknown or unsupported targets fail closed.
|
|
37
|
+
|
|
38
|
+
Project YAML describes route behavior. Provider infrastructure and outbound
|
|
39
|
+
origin grants stay in operator configuration. Functions remain untrusted
|
|
40
|
+
QuickJS/WASM guests with no host-code fallback. Existing explicit external
|
|
41
|
+
bindings and new egress grants remain pinned to the exact configuration/source
|
|
42
|
+
revision. Compilation and conversion do not resolve credentials or manufacture
|
|
43
|
+
grants. File authors publish new projects without overwriting unrelated work.
|
|
44
|
+
|
|
45
|
+
The five policy modules remain the authority for their target-sensitive
|
|
46
|
+
compatibility. Delegated compression does not imply identical edge behavior;
|
|
47
|
+
coalesced headers and normalized URLs remain transport limitations. Conditional
|
|
48
|
+
routes are no-store to prevent cross-branch shared-cache leakage. Conditions are
|
|
49
|
+
selection rules, not authentication or authority.
|
|
50
|
+
|
|
51
|
+
## Validation and outstanding external work
|
|
52
|
+
|
|
53
|
+
New regression suites cover conservative conversion refusals, source diagnostics,
|
|
54
|
+
local provider replay, conditions/ambiguity, proxy and signal security boundaries,
|
|
55
|
+
recipe execution, TypeScript graph limits, bulk sharding and MCP authority limits.
|
|
56
|
+
Package smoke exercises actual archive installation with production dependencies,
|
|
57
|
+
CLI authoring/conversion/MCP and the public declaration surface. Schema changes
|
|
58
|
+
require regenerated reference documentation and executable examples. Local integration passed `npm run verify` (384 tests: 383 passed, one existing
|
|
59
|
+
TLS-fixture skip) and `npm run test:package`, including a production-only install
|
|
60
|
+
with TypeScript 6.0.3. Required CI checks and normal pull-request review still
|
|
61
|
+
apply to each exact proposed revision.
|
|
62
|
+
|
|
63
|
+
The bulk benchmark uses fresh sequential processes and records conversion,
|
|
64
|
+
normal runtime activation, memory samples and checked runtime lookups for
|
|
65
|
+
1,000, 10,000 and 100,000 synthetic redirects. Splitting the last dataset into
|
|
66
|
+
100 includes avoids the earlier single-document worker memory failure without
|
|
67
|
+
increasing the 256 MiB worker heap or ten-second loader deadline. This is local
|
|
68
|
+
capacity evidence, not a cross-platform SLO or peak-memory bound.
|
|
69
|
+
|
|
70
|
+
### Deferred follow-up: live provider testing (non-blocking)
|
|
71
|
+
|
|
72
|
+
Decision, 2026-09-17: defer live Cloudflare, AWS and Vercel testing and return to
|
|
73
|
+
it later. This does not block the current implementation work or pull-request
|
|
74
|
+
review and merge, subject to the normal required checks and authorization.
|
|
75
|
+
Provider deployment status remains **unverified** until real tests are recorded;
|
|
76
|
+
deferral does not change capability claims or remove other release/security gates.
|
|
77
|
+
|
|
78
|
+
When resumed, choose a provider and supply an operator-owned test account/project
|
|
79
|
+
with normal local login access and authorization for a temporary deployment
|
|
80
|
+
(including any hosting charges), or supply an already deployed conformance
|
|
81
|
+
fixture URL. Run the existing provider verification tool and record the results.
|
|
82
|
+
No credentials or provider setup are needed from the user for the current work.
|
|
83
|
+
|
|
84
|
+
Actual provider provisioning/deployment observations require operator-owned
|
|
85
|
+
accounts and explicit fixture URLs. Real ingress normalization, repeated header
|
|
86
|
+
and cookie behavior, distributed policy guarantees, soak/recovery tests and
|
|
87
|
+
independent assessment of the new network bridge remain separate release and
|
|
88
|
+
operational gates. No implementation test, capability report, benchmark, or CI
|
|
89
|
+
pass substitutes for that evidence. The self-hosted release remains useful and
|
|
90
|
+
portable without requiring provider accounts or a paid control plane.
|