@jimhoyd/urlcode 0.4.0-alpha.1 → 0.4.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/urlcode-authoring/SKILL.md +24 -8
- package/.claude/skills/urlcode-operations/SKILL.md +10 -16
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +75 -62
- package/ROADMAP.md +61 -16
- package/SECURITY.md +23 -8
- package/dist/BUILD-MANIFEST.json +38 -43
- package/dist/agents-guide.js +18 -18
- package/dist/build-static.js +134 -0
- package/dist/capabilities.js +85 -31
- package/dist/capability-query.js +0 -1
- package/dist/cli.js +23 -31
- package/dist/compliance-rules/baseline.js +2 -10
- package/dist/compliance-rules/privacy.js +5 -16
- package/dist/compliance-rules/shared.js +0 -2
- package/dist/compliance.js +6 -8
- package/dist/config.js +15 -8
- package/dist/context.js +9 -10
- package/dist/examples.js +2 -2
- package/dist/explain-cli.js +3 -3
- package/dist/explain.js +5 -6
- package/dist/extensions.js +61 -2
- package/dist/function-sources.js +34 -2
- package/dist/function-worker.js +3 -1
- package/dist/functions.js +84 -13
- package/dist/guest-api.js +29 -3
- package/dist/index.js +3 -6
- package/dist/manifest.js +5 -7
- package/dist/mcp.js +1 -1
- package/dist/observability.js +1 -21
- package/dist/policies/cache.js +8 -3
- package/dist/policy.js +28 -9
- package/dist/project-tests.js +3 -3
- package/dist/readiness.js +32 -9
- package/dist/router.js +5 -7
- package/dist/runtime.js +77 -58
- package/dist/sandbox.js +48 -0
- package/dist/scaffold.js +0 -0
- package/dist/scripts/operational-drills.js +12 -54
- package/dist/server.js +3 -29
- package/dist/tooling.js +1 -1
- package/dist/trusted-functions.js +210 -0
- package/dist/types/build-static.d.ts +43 -0
- package/dist/types/capabilities.d.ts +14 -5
- package/dist/types/compliance-rules/shared.d.ts +0 -2
- package/dist/types/compliance.d.ts +0 -3
- package/dist/types/config.d.ts +2 -1
- package/dist/types/context.d.ts +2 -1
- package/dist/types/examples.d.ts +1 -1
- package/dist/types/extensions.d.ts +55 -0
- package/dist/types/function-sources.d.ts +4 -0
- package/dist/types/functions.d.ts +48 -5
- package/dist/types/guest-api.d.ts +1 -0
- package/dist/types/index.d.ts +3 -6
- package/dist/types/manifest.d.ts +1 -3
- package/dist/types/observability.d.ts +1 -14
- package/dist/types/project-tests.d.ts +1 -2
- package/dist/types/readiness.d.ts +10 -3
- package/dist/types/router.d.ts +2 -1
- package/dist/types/runtime.d.ts +0 -27
- package/dist/types/sandbox.d.ts +12 -0
- package/dist/types/scaffold.d.ts +0 -2
- package/dist/types/server.d.ts +1 -4
- package/dist/types/tooling.d.ts +3 -3
- package/dist/types/trusted-functions.d.ts +29 -0
- package/dist/types/types.d.ts +14 -7
- package/dist/types/verify-deployment.d.ts +2 -2
- package/dist/types.js +17 -4
- package/dist/typescript-authoring.js +55 -17
- package/dist/verify-deployment.js +3 -3
- package/docs/AI-AUTHORING.md +104 -9
- package/docs/AWS.md +4 -4
- package/docs/BEST-PRACTICES.md +3 -2
- package/docs/CAPABILITIES.md +29 -16
- package/docs/CAPACITY.md +128 -31
- package/docs/CLOUDFLARE.md +1 -2
- package/docs/COMPLIANCE.md +6 -9
- package/docs/DEPLOYMENT-CHECKS.md +1 -1
- package/docs/EXTENSIONS.md +183 -11
- package/docs/FRAMEWORK.md +35 -19
- package/docs/FUNCTION-SECURITY.md +129 -32
- package/docs/INSTALL.md +0 -5
- package/docs/LOAD-TESTING.md +4 -4
- package/docs/MIDDLEWARE.md +27 -16
- package/docs/MONITORING.md +2 -19
- package/docs/NEXT-PHASE-PLAN.md +12 -4
- package/docs/NEXT-STEPS.md +259 -40
- package/docs/OBSERVABILITY.md +7 -16
- package/docs/OPEN-DECISIONS.md +212 -0
- package/docs/OPERATIONAL-PROOF.md +26 -30
- package/docs/OPERATIONS.md +23 -32
- package/docs/POLICIES.md +15 -4
- package/docs/PRERENDER.md +2 -2
- package/docs/PROJECT-DIRECTION.md +34 -9
- package/docs/README.md +13 -11
- package/docs/RELEASE-READINESS.md +49 -29
- package/docs/RELEASE-SECURITY.md +89 -3
- package/docs/RESILIENCE.md +16 -15
- package/docs/ROUTING.md +8 -10
- package/docs/SANDBOX-REVIEW.md +19 -6
- package/docs/SCAFFOLDING.md +0 -2
- package/docs/SECURITY-AUDIT.md +40 -0
- package/docs/SPECIFICATION.md +60 -26
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +287 -0
- package/docs/SPIKE-BUSINESS-SUITE.md +8 -0
- package/docs/SPIKE-CORE-LAYERING.md +337 -0
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +209 -0
- package/docs/SPIKE-EXTENSION-MODEL.md +2 -2
- package/docs/SPIKE-EXTENSIONS.md +5 -0
- package/docs/SPIKE-LAMBDA-COMPILE.md +4 -2
- package/docs/SPIKE-MONOREPO.md +322 -0
- package/docs/STARTERS.md +1 -1
- package/docs/STATIC.md +94 -0
- package/docs/TOOLING.md +13 -9
- package/docs/TUNNELS.md +0 -3
- package/docs/TYPESCRIPT-AUTHORING.md +27 -12
- package/docs/TYPESCRIPT.md +25 -4
- package/docs/USABILITY-REVIEW.md +7 -1
- package/docs/VERCEL.md +4 -5
- package/docs/VERSION-ALIGNMENT.md +205 -0
- package/docs/YAML-GUIDE.md +0 -3
- package/docs/YAML-REFERENCE.md +16 -6
- package/docs/policies/cache.md +13 -0
- package/examples/assets/example.yaml +1 -1
- package/examples/aws/example.yaml +1 -1
- package/examples/cloudflare/example.yaml +1 -1
- package/examples/conditions/example.yaml +1 -1
- package/examples/cookbook/example.yaml +1 -1
- package/examples/cookbook/middleware/bucket.mjs +12 -2
- package/examples/cookbook/middleware/locale.mjs +7 -3
- package/examples/egress/example.yaml +1 -1
- package/examples/extensions/example.yaml +1 -1
- package/examples/prerender/README.md +2 -2
- package/examples/prerender/example.yaml +1 -1
- package/examples/provider-conformance/example.yaml +1 -1
- package/examples/vercel/example.yaml +1 -1
- package/llms-full.txt +594 -219
- package/llms.txt +23 -12
- package/package.json +10 -3
- package/packaging/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +24 -8
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +10 -16
- package/recipes/authenticated-json-api/recipe.yaml +1 -1
- package/recipes/contact-form/recipe.yaml +1 -1
- package/recipes/cors-api/recipe.yaml +1 -1
- package/recipes/health-page/recipe.yaml +1 -1
- package/recipes/json-api/recipe.yaml +1 -1
- package/recipes/middleware/middleware/bucket.mjs +12 -2
- package/recipes/middleware/middleware/locale.mjs +7 -3
- package/recipes/middleware/recipe.yaml +1 -1
- package/recipes/protected-download/recipe.yaml +1 -1
- package/recipes/redirect/recipe.yaml +1 -1
- package/recipes/static-plus-api/recipe.yaml +1 -1
- package/recipes/typescript/README.md +2 -1
- package/recipes/typescript/recipe.yaml +1 -1
- package/recipes/webhook-receiver/README.md +5 -1
- package/recipes/webhook-receiver/recipe.yaml +2 -1
- package/recipes/webhook-receiver/urlcode.yaml +7 -0
- package/schemas/recipe.schema.json +4 -3
- package/schemas/urlcode.schema.json +14 -41
- package/skills/urlcode/SKILL.md +29 -8
- package/starters/default/AGENTS.md +18 -18
- package/starters/default/urlcode.yaml +0 -1
- package/dist/link-api.js +0 -136
- package/dist/link-cli.js +0 -141
- package/dist/link-events.js +0 -76
- package/dist/link-records.js +0 -31
- package/dist/link-store-worker.js +0 -150
- package/dist/link-store.js +0 -250
- package/dist/management-policy.js +0 -40
- package/dist/sqlite-version.js +0 -6
- package/dist/types/link-api.d.ts +0 -30
- package/dist/types/link-cli.d.ts +0 -37
- package/dist/types/link-events.d.ts +0 -27
- package/dist/types/link-records.d.ts +0 -11
- package/dist/types/link-store-worker.d.ts +0 -1
- package/dist/types/link-store.d.ts +0 -130
- package/dist/types/management-policy.d.ts +0 -8
- package/dist/types/sqlite-version.d.ts +0 -1
- package/docs/DYNAMIC-LINKS.md +0 -61
- package/docs/MANAGEMENT-SECURITY.md +0 -82
- package/docs/links/cli.md +0 -110
- package/docs/links/limits.md +0 -175
- package/docs/links/management-api.md +0 -80
- package/docs/links/pools.md +0 -75
- package/docs/links/setup.md +0 -135
- package/docs/yaml/links.md +0 -30
- package/examples/live-links/README.md +0 -11
- package/examples/live-links/example.yaml +0 -21
- package/examples/live-links/tests/requests.json +0 -6
- package/examples/live-links/urlcode.yaml +0 -16
package/dist/BUILD-MANIFEST.json
CHANGED
|
@@ -3,88 +3,83 @@
|
|
|
3
3
|
"files": {
|
|
4
4
|
"dist/adapters.js": "a7153ec52f2ed7e0cdd8ed7d433504815f402ec53b2b1c3d38d0cf0e2fcf6509",
|
|
5
5
|
"dist/agent-lists.js": "35ab484198897501d011cb6cc00b5a6787192e19df29c97646e8c118fcc19eef",
|
|
6
|
-
"dist/agents-guide.js": "
|
|
6
|
+
"dist/agents-guide.js": "a76e9818ec995a349085a53e8badd3ef49734275557b50bc7dcbe995ce3d0e9f",
|
|
7
7
|
"dist/assets.js": "0833b093d2fe457ce68281110dc07d47914992753607741c13b339d92c76cf5c",
|
|
8
8
|
"dist/authoring-files.js": "ad32814f9ae9c549c6247396f982aa700d13a293cc0a589609eccc68f8965550",
|
|
9
9
|
"dist/authoring.js": "ec5240e8686c124f4601229b45f89ce112d9eeb9d35e3059fe55e93056c45209",
|
|
10
10
|
"dist/aws.js": "7d8e3a97b4f6dbefbd68da058f05192469d1efcaed5458b284863ebe462e060e",
|
|
11
11
|
"dist/build-cloudflare.js": "af4f49ad45287d7c4105bb84b1592b5b8e76c930f7159505338298f7c5fd5651",
|
|
12
|
+
"dist/build-static.js": "a2d2fcef3099805392da9140096d9843226bfb582dc046acf9a77f37b8a0d528",
|
|
12
13
|
"dist/bulk.js": "aac88d422bd9a7a341a421fb4e29ff5580b28d39250b7f1f5315ba20ae92da8a",
|
|
13
|
-
"dist/capabilities.js": "
|
|
14
|
-
"dist/capability-query.js": "
|
|
14
|
+
"dist/capabilities.js": "28dba05b3851b8b12b3c1b809af87b732f84d601c737afe9225abe429886e691",
|
|
15
|
+
"dist/capability-query.js": "ba148b27c6a60f8c895f13f26468b16c18ae0a5cddea26b602965c8efbba90ae",
|
|
15
16
|
"dist/catalog.js": "ab1c01c9296cc277d2bd4fd682aeb053656a7d9f91c61974f4b78feea0211de8",
|
|
16
|
-
"dist/cli.js": "
|
|
17
|
+
"dist/cli.js": "f6097c246c6b9006df58e3463648013ec8837ab3296a8c294809313503be167a",
|
|
17
18
|
"dist/client-address.js": "9d0d01466aab23124330605c5a0e0f981f87c897858acfad909f546a63df165b",
|
|
18
19
|
"dist/cloudflare.js": "17b43b0a6b6a0ef8a893415c989e4ae3976d97cc966adbf897b785f9adc472e3",
|
|
19
|
-
"dist/compliance-rules/baseline.js": "
|
|
20
|
-
"dist/compliance-rules/privacy.js": "
|
|
21
|
-
"dist/compliance-rules/shared.js": "
|
|
20
|
+
"dist/compliance-rules/baseline.js": "6296dea6bcb9f9bb80ed6ed5ab2f456e7f74e6de2be5d2d98ca8c983fb4df3ab",
|
|
21
|
+
"dist/compliance-rules/privacy.js": "4cfb1c07d7df698c362dfc8761cd3bba893769e92d7fb0a3901e1c2d25e801f1",
|
|
22
|
+
"dist/compliance-rules/shared.js": "86fd6db6a93375689acc64975cbc394ce9452dea21ae5941e743f99822515995",
|
|
22
23
|
"dist/compliance-rules/strict.js": "73ee13c2f893acf20cc3d44bf054e58c1474dec52eafe0cf2ce7f4eb96a3bc53",
|
|
23
|
-
"dist/compliance.js": "
|
|
24
|
+
"dist/compliance.js": "29a18da3cefea94802ac7d817d1ab4e3674abd5c3ac4ef893cb8b01c6a23fb61",
|
|
24
25
|
"dist/conditions.js": "ff25e97da550a3bdd80af55522045669438aa31a77d2bc83ad602bcefdee0475",
|
|
25
26
|
"dist/config-worker.js": "bc2170d60c35f8d5de227cbfc4c5067dc61d7cbc5b98aed88e68a11797072da5",
|
|
26
|
-
"dist/config.js": "
|
|
27
|
-
"dist/context.js": "
|
|
27
|
+
"dist/config.js": "0cd82b4c407848f9c66e804a6dd4bf9a8a8503264e51584456578073e0f01921",
|
|
28
|
+
"dist/context.js": "549a53e4738b9c20a1ec55614027f758026fb5860bf4ac962ce22d7029e48df0",
|
|
28
29
|
"dist/ecosystem-cli.js": "1e71bacd53d6fa8c7857cfcce3c3012ce054771a434b78d1f39e892e32642edd",
|
|
29
30
|
"dist/egress.js": "2ae29fe2cb4590fd2f715abe6817db47fed2f50dd31f37946ecf42eafbb0dd9e",
|
|
30
31
|
"dist/errors.js": "a908e66496afebbdd632a8c159a6e14d23f8f38798a0b4342ca122f2511c6cde",
|
|
31
|
-
"dist/examples.js": "
|
|
32
|
-
"dist/explain-cli.js": "
|
|
33
|
-
"dist/explain.js": "
|
|
34
|
-
"dist/extensions.js": "
|
|
35
|
-
"dist/function-sources.js": "
|
|
36
|
-
"dist/function-worker.js": "
|
|
37
|
-
"dist/functions.js": "
|
|
38
|
-
"dist/guest-api.js": "
|
|
32
|
+
"dist/examples.js": "89ae2b764a688fe52acfb9b94d3d262d7742401ebd0ef10c9ebfc76edfa5631c",
|
|
33
|
+
"dist/explain-cli.js": "dd342b21a96fc97a11488ac3528793ba0f5d4744b74a1d99085e142c4737b69a",
|
|
34
|
+
"dist/explain.js": "9450d2699cbe65e5c078c443ba7a6cba95c748e75d455e05264083b3c93c92a2",
|
|
35
|
+
"dist/extensions.js": "fed2b394466a7d406f9edeaedb71d9da81e50e3150eb7d01f2e7659104afeac7",
|
|
36
|
+
"dist/function-sources.js": "16fad4abc81c7ee07b6cbcef2d23a9fc50e97a17cb4dde1db53469bbb555c96f",
|
|
37
|
+
"dist/function-worker.js": "35771790ada4e1b36d447d4967a5e6cf0b543f32944e4c904f5fbf8998e824a5",
|
|
38
|
+
"dist/functions.js": "0f09d1bd27eb655ceb5900a4a5e2b760d2028b02cb637a6a89a4eb5940828008",
|
|
39
|
+
"dist/guest-api.js": "49aec9ed72733ad119e63bcfcee18158dcc8ceb951e1823b8947f6407ff24afc",
|
|
39
40
|
"dist/header-validation.js": "465181dbb08ff05f52defdd29fda025c0c64589bf319fa87fa6d3ab4b68216d5",
|
|
40
41
|
"dist/http-policy.js": "4cb187642269078ddf3460a3f1813dc57878a616a6f8132ef8f8d3b4b91ebb56",
|
|
41
42
|
"dist/http-response.js": "5fc3bb9eb932a3fd2fbc72c361416a5f1439b8bf45b2be37baca1c6446abf40c",
|
|
42
|
-
"dist/index.js": "
|
|
43
|
+
"dist/index.js": "7e9f6ec8a125e2dfd4230b2ecd17ac513c0153d778a57bbcee7a1a9cd9681f81",
|
|
43
44
|
"dist/init-with.js": "8371f568867ebdc6ab8e9aa762234ae2aa015865f46aa901254528e20d69b791",
|
|
44
45
|
"dist/interchange-cli.js": "35bd70ba8077af5c3e39404ff0e5d28707632090a59a90645141252449fd3f04",
|
|
45
46
|
"dist/interchange.js": "26789420af33344d9a08c00fe6b2708aa71994224df527b3075295be2151611e",
|
|
46
|
-
"dist/link-api.js": "0d1f2fb5ef8cd6fda7b071d3e08c53eb4b1c01d4003167af2f400949b70dea4f",
|
|
47
|
-
"dist/link-cli.js": "c4e2121a3e66f33c640832a7cb362a6a1db8b6d91d55a4239ce38ba0ced67b22",
|
|
48
|
-
"dist/link-events.js": "502a6038a538fa43094a9ba9c7546f504b4a1832a1377f26ca8cb69c2326670e",
|
|
49
|
-
"dist/link-records.js": "ef67a58cd107db0c3ac1c0ffd7b32817553606cf69bb3002da4ca0138c272f69",
|
|
50
|
-
"dist/link-store-worker.js": "0080452bbada2154b923449cd14596579bde94ad5f8dab94ecc84aa0d59c089b",
|
|
51
|
-
"dist/link-store.js": "526cdbe1b917cc7a74fb6edbbf13717d823ace5b5d6a5f89bc91438056a10b37",
|
|
52
47
|
"dist/logging.js": "8ec57fea26983aa071b05fe15d680d6550b03488101163ed9666da674118cf31",
|
|
53
|
-
"dist/
|
|
54
|
-
"dist/manifest.js": "d4f58f936febbabf09ec1706baae7c1cb186dcd8a3d29b5725dad5b6eaf43a3f",
|
|
48
|
+
"dist/manifest.js": "977f0f1cbe4466f7fbd9f2a0745e016cb9ebae640bc264fba04b4d02f4dbaca9",
|
|
55
49
|
"dist/match.js": "53ebcc2cda529a8d07fb83f8cb69a1446036ffc1641af3c4bd45a55851bded28",
|
|
56
50
|
"dist/mcp-authoring.js": "5030f52ef9c7ebc7b97305fcce687eb0a0d6164e34fda87f8278135a1e6f3d65",
|
|
57
|
-
"dist/mcp.js": "
|
|
58
|
-
"dist/observability.js": "
|
|
51
|
+
"dist/mcp.js": "e2d70f63481dc40aae60f7c27128e6a09ec370b69fd68aee8890bcc990f4f304",
|
|
52
|
+
"dist/observability.js": "f4b1ab496f051fe3ef2ed36b2b2e469c63a8940700e2df852e6ff2d9cccdd5ae",
|
|
59
53
|
"dist/operator-host.js": "e3dac9d43a83beb775202b4ab9eeeba7ac63cebfc5dcc669be38d825be587bd0",
|
|
60
54
|
"dist/plugins.js": "a61aa933cd9e1b41020b86fae78aa3a7f45f5313124aa11b50cb57898722385e",
|
|
61
55
|
"dist/policies/agents.js": "c1dbc1e90339f472eeeb2372f7f4637f59a5d210797ea680ac8120a02aa20c10",
|
|
62
|
-
"dist/policies/cache.js": "
|
|
56
|
+
"dist/policies/cache.js": "03f79b91694bb78a98de4473384e5110e1ae1c3c83d46f929495d03af04d9da1",
|
|
63
57
|
"dist/policies/compression.js": "800f3ae3b1a4c1fe58c3d9908364ea9d78f655ff50c7f7589f584378e7f194b0",
|
|
64
58
|
"dist/policies/security.js": "2b3bfdf82140156fea4b162adc28ee43ccd85c402fb200680dbb8d8706417420",
|
|
65
59
|
"dist/policies/throttle.js": "bbb0cb1c24cf70d0a1ac32fc0618efb6c44e5b5d42564842f9c585ff7e501603",
|
|
66
60
|
"dist/policies.js": "23fa019b7bf1cdedcc5f7772d2f157e4b381a554bce9e84594c2be4a0f8f76b8",
|
|
67
|
-
"dist/policy.js": "
|
|
61
|
+
"dist/policy.js": "33c527008fafe4af18243ba668d069517c20659150f9f1d1a5a1d076e8724f89",
|
|
68
62
|
"dist/prerender.js": "0386ea2411e00b9563b5349133f1d723947b01f837fa92d4c61a699c9cfb5cdb",
|
|
69
|
-
"dist/project-tests.js": "
|
|
63
|
+
"dist/project-tests.js": "445eaa139d2f1a2534369cfdeef081ef518de03218a467e7c5c2059caf840270",
|
|
70
64
|
"dist/provider-verification.js": "ffcf9f05e8cb5f71ea46a97e279e8a613fdfdf9435ab02bd57af11e2680a9107",
|
|
71
65
|
"dist/proxy.js": "34107e59ac0dbbea090efa3cf5badf3226c5302ba25e8f8ff9b2b5596ed80e50",
|
|
72
|
-
"dist/readiness.js": "
|
|
66
|
+
"dist/readiness.js": "6ce69a005d7c45cf0ef97fa5eaba34279d43ebf28124eca18359d9db4f599808",
|
|
73
67
|
"dist/recipes.js": "11853f23e63c3c65c5bed03a278cde0349d28c9fa23266c9907a2df318bb5fc3",
|
|
74
68
|
"dist/route-diff.js": "eebac076a886ddfb2bf5b97ea09d81f888e7bb0063d482eddb8e5fef9acea107",
|
|
75
|
-
"dist/router.js": "
|
|
76
|
-
"dist/runtime.js": "
|
|
77
|
-
"dist/
|
|
69
|
+
"dist/router.js": "cf831025d5ed6778d1d6a0e541177799794e76766c82084ded81d3e87317ca1e",
|
|
70
|
+
"dist/runtime.js": "0470dcab0774d2da3336e9ce76a4853757bf96081e52bd741f6b44c1bdd4161f",
|
|
71
|
+
"dist/sandbox.js": "c88c5aa5dd95b4f55f1ba62da7969ad66dbd97c30f72e5b5f2817a2098ac1c65",
|
|
72
|
+
"dist/scaffold.js": "3ef243aceca7ddf2780aa2c8c04ae17edd07682d3a418fd5d65ba00f5c762f14",
|
|
78
73
|
"dist/schema-query.js": "bfd1844acd8d54ac361115191fcfbaf828a67e223b9ec6e9c22f158761af149b",
|
|
79
|
-
"dist/server.js": "
|
|
74
|
+
"dist/server.js": "d878bc8f0d6a05f8fd25997cace3150e2ff4d44ee9c8f4b5530e29601e164be1",
|
|
80
75
|
"dist/signals.js": "b55e54efc8fb6703e08f2a1e808ec8e011bda67738db8ceadf1b248f695795be",
|
|
81
76
|
"dist/site.js": "c40a06a6f9e2d6d3bea94727783c115fed4309bb0190787f58434eed64cbde84",
|
|
82
|
-
"dist/
|
|
83
|
-
"dist/
|
|
84
|
-
"dist/types.js": "
|
|
85
|
-
"dist/typescript-authoring.js": "
|
|
77
|
+
"dist/tooling.js": "7409db98422325cd4181d073d645ea57debca0e54cb151123474b85f4d4ed7dc",
|
|
78
|
+
"dist/trusted-functions.js": "befd256848be786f97e372c6af0e05ac08a0c93baed38bf2470ea49bc00dd3e4",
|
|
79
|
+
"dist/types.js": "a1213d033b6a347c63e94685ab9056d8cd7a301d9c83150142afe0f3ab2c6536",
|
|
80
|
+
"dist/typescript-authoring.js": "27829098603029ab565174e540fb1d5ef68c1a923272b7186f64a362f5244872",
|
|
86
81
|
"dist/vercel.js": "a0da20c095d0c7fa8f579ed4dd5951e7a34191f8a72a57c5e782c9eab39f6f08",
|
|
87
|
-
"dist/verify-deployment.js": "
|
|
88
|
-
"dist/scripts/operational-drills.js": "
|
|
82
|
+
"dist/verify-deployment.js": "0c2e68f3cf258a364728c801411df5284343bc01b75a64fe275ff84e397e998b",
|
|
83
|
+
"dist/scripts/operational-drills.js": "c26c406d28013cc554b25a5b6bf8baf4e71d5e67e50d866fae546f538a192317"
|
|
89
84
|
}
|
|
90
85
|
}
|
package/dist/agents-guide.js
CHANGED
|
@@ -16,7 +16,7 @@ export function renderMcpConfig(project = '.') {
|
|
|
16
16
|
assert(/^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/.test(project) && !project.split('/').includes('..'), 'MCP project path must be a relative path without ..');
|
|
17
17
|
return JSON.stringify({ mcpServers: { urlcode: { command: 'urlcode', args: ['mcp', '--project', project] } } }, null, 2) + '\n';
|
|
18
18
|
}
|
|
19
|
-
const handlerNames = ['redirect','respond','page','static','download','function','
|
|
19
|
+
const handlerNames = ['redirect','respond','page','static','download','function','proxy','conditional','extension'];
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* The application-level AGENTS.md written by `urlcode init`. Built from the
|
|
@@ -48,11 +48,11 @@ authentication; the runtime provides them. Read this file before changing anythi
|
|
|
48
48
|
3. Run \`urlcode recipes list\` and \`urlcode recipes show NAME\` before writing a
|
|
49
49
|
route from scratch. If a recipe covers the need, add it with
|
|
50
50
|
\`urlcode recipes add NAME --out DIR\` and adapt the copy.
|
|
51
|
-
4.
|
|
51
|
+
4. Use URLCode's highest-level declarative features whenever possible. Generate custom code only when the framework cannot express the requirement. Check supported extensions and recipes first; explain any capability gap.
|
|
52
52
|
|
|
53
53
|
## Ask the runtime through MCP first
|
|
54
54
|
|
|
55
|
-
\`${mcpConfigFile}\` registers the read-only \`urlcode mcp\` server. When it is
|
|
55
|
+
When present, \`${mcpConfigFile}\` registers the read-only \`urlcode mcp\` server. When it is
|
|
56
56
|
available, prefer its tools over reading documents: \`get_context\`,
|
|
57
57
|
\`get_capability\`, \`get_schema\`, \`search_recipes\`, \`explain\`, \`get_manifest\`.
|
|
58
58
|
The CLI equivalents are the fallback: \`urlcode context\`, \`urlcode capabilities NAME\`,
|
|
@@ -62,23 +62,24 @@ The CLI equivalents are the fallback: \`urlcode context\`, \`urlcode capabilitie
|
|
|
62
62
|
## What the runtime provides (this version)
|
|
63
63
|
|
|
64
64
|
- Handlers, exactly one per route: ${handlers.map(name => `\`${name}\``).join(', ')}.
|
|
65
|
-
- Ordered \`middleware\` around any handler, declared in YAML,
|
|
65
|
+
- Ordered \`middleware\` around any handler, declared in YAML, trusted by default.
|
|
66
66
|
- Validated inputs: \`parameters\`, \`request.body\` and \`methods\` on the route;
|
|
67
67
|
functions receive validated \`args\`, never raw user input.
|
|
68
68
|
- Policies, host-enforced and off by default: ${policies.map(name => `\`${name}\``).join(', ')}.
|
|
69
69
|
- Site conventions under \`site\`, each generating one native route: ${site.join(', ')}.
|
|
70
70
|
- Bindings: named \`env\` and \`secrets\` references resolved by the operator, never values in YAML.
|
|
71
71
|
|
|
72
|
-
Never recreate any of these in a function
|
|
73
|
-
|
|
72
|
+
Never recreate any of these in a function; a missing one is a report, not an
|
|
73
|
+
invitation to reimplement it.
|
|
74
74
|
|
|
75
|
-
## Functions and middleware are
|
|
75
|
+
## Functions and middleware are trusted by default; sandbox is opt-in
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
\`env\`.
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
A route's \`function\`/\`middleware\` code runs trusted, in-process, with full
|
|
78
|
+
Node/filesystem/\`fetch\` access, receiving only the declared/granted \`args\`
|
|
79
|
+
and \`env\`/\`secrets\`. Add \`sandbox: true\` when code warrants isolation
|
|
80
|
+
(untrusted input, an unreviewed contribution, an especially sensitive
|
|
81
|
+
secret): that route then gets a text/JSON subset only, no Node/filesystem/
|
|
82
|
+
outside imports — use \`proxy\`/a binding instead, and say why in \`description\`.
|
|
82
83
|
|
|
83
84
|
## Checks that count as evidence
|
|
84
85
|
|
|
@@ -88,10 +89,9 @@ urlcode test
|
|
|
88
89
|
urlcode audit --expect-routes ${routes}
|
|
89
90
|
\`\`\`
|
|
90
91
|
|
|
91
|
-
Run all three after every change
|
|
92
|
-
|
|
93
|
-
every
|
|
94
|
-
Without a global install, invoke \`node /path/to/urlcode/src/cli.ts\` instead of \`urlcode\`.
|
|
92
|
+
Run all three after every change, updating the route count deliberately and
|
|
93
|
+
adding \`tests/requests.json\` fixtures for every new route (positive/negative,
|
|
94
|
+
every active method, HEAD). No global install: use \`node /path/to/urlcode/src/cli.ts\`.
|
|
95
95
|
|
|
96
96
|
## Rules
|
|
97
97
|
|
|
@@ -101,8 +101,8 @@ Without a global install, invoke \`node /path/to/urlcode/src/cli.ts\` instead of
|
|
|
101
101
|
stop; the operator grants it outside this project, pinned to the revision.
|
|
102
102
|
- Secrets stay out of the project: no keys, tokens or credentials in YAML,
|
|
103
103
|
functions, fixtures, \`.env\` files that are not ignored, or commit messages.
|
|
104
|
-
-
|
|
105
|
-
|
|
104
|
+
- Protect a route with \`auth: true\`/\`auth: { role: admin }\` where an \`auth\`
|
|
105
|
+
extension is declared; \`cache\` likewise expands to \`policies.cache\`.
|
|
106
106
|
- Validation, tests and the audit are the evidence. Local checks are not a
|
|
107
107
|
deployment, a soak test or a security review; do not claim otherwise.
|
|
108
108
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { analyzeProjectCapabilities, analyzeCompiledCapabilities, assertTargetCompatibility } from './capabilities.js';
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
import { loadDocument } from './config.js';
|
|
5
|
+
import { compileRoutes } from './router.js';
|
|
6
|
+
import { compileAssets } from './assets.js';
|
|
7
|
+
import { assert, ConfigError } from './errors.js';
|
|
8
|
+
import { applySite } from './site.js';
|
|
9
|
+
import { buildManifest, renderManifest, manifestPath } from './manifest.js';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
// The manifests below are this target's own build output, not a published
|
|
16
|
+
// contract: their shape may change with any release.
|
|
17
|
+
const FORMAT = 1;
|
|
18
|
+
|
|
19
|
+
/** One S3 per-object website redirect: a zero-byte object at `key` carrying
|
|
20
|
+
* `x-amz-website-redirect-location: location`, which S3 always answers with a
|
|
21
|
+
* 301 regardless of the object's own metadata. */
|
|
22
|
+
|
|
23
|
+
/** One served file: the bytes are written under `out/objects/<key>`; the
|
|
24
|
+
* metadata here is what a deploy step sets as the S3 object's own headers. */
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
// S3 static-website object keys never start with a slash; the bucket's own
|
|
30
|
+
// "Index document" suffix (conventionally index.html) is what answers a
|
|
31
|
+
// request for a folder path or the site root, exactly the same convention
|
|
32
|
+
// used here for pattern '/'.
|
|
33
|
+
function objectKey(pattern ) {
|
|
34
|
+
const trimmed = pattern.slice(1);
|
|
35
|
+
return trimmed === '' ? 'index.html' : trimmed;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function buildStatic(project , { out = 'dist/static', origin, log = () => {} } = {}) {
|
|
39
|
+
const loaded = await loadDocument(project);
|
|
40
|
+
// Generated site routes are built like declared ones; the ones that need
|
|
41
|
+
// an origin get it from --origin, exactly as the server does.
|
|
42
|
+
await applySite(loaded, { origin, log });
|
|
43
|
+
assertTargetCompatibility(analyzeProjectCapabilities(loaded, 'static'));
|
|
44
|
+
const compiled = await compileRoutes(loaded, {}, {}, undefined);
|
|
45
|
+
const routes = [...compiled.exact.values(), ...[...compiled.byLength.values()].flat(), ...compiled.mounts];
|
|
46
|
+
|
|
47
|
+
assertTargetCompatibility(analyzeCompiledCapabilities(loaded.document, compiled, 'static'));
|
|
48
|
+
assert(routes.length, 'No routes to build');
|
|
49
|
+
|
|
50
|
+
// Capability analysis already refused function/middleware/extension,
|
|
51
|
+
// parameters, request.body, response.headers, bindings, proxy/signals,
|
|
52
|
+
// conditional/conditions and every policy for this target, so every
|
|
53
|
+
// surviving route is a plain redirect/respond/page/static/download with no
|
|
54
|
+
// path placeholders. What is left below are shapes capability analysis
|
|
55
|
+
// cannot see because they are configuration, not capability, distinctions:
|
|
56
|
+
// a redirect that needs request-time logic (query passthrough/mapping, a
|
|
57
|
+
// non-301 status), and a route that needs request-time lifecycle checks
|
|
58
|
+
// (`enabled: false`, `expires`) or a method other than GET/HEAD.
|
|
59
|
+
const defaultMethods = new Set(['GET', 'HEAD']);
|
|
60
|
+
for (const route of routes) {
|
|
61
|
+
assert(route.enabled !== false, `${route.pattern}: static hosting has no server to answer a disabled route with 404; remove the route instead of disabling it`);
|
|
62
|
+
assert(route.expiresAt === undefined, `${route.pattern}: static hosting has no server to answer an expired route with 410; remove the route when it expires`);
|
|
63
|
+
assert(route.methods.every(method => defaultMethods.has(method)), `${route.pattern}: static hosting only ever answers GET/HEAD; this target refuses declared methods ${route.methods.join(', ')}`);
|
|
64
|
+
if (route.redirect) {
|
|
65
|
+
assert(!route.names.length, `${route.pattern}: static hosting cannot redirect a path pattern; only an exact literal path can carry an S3 per-object redirect`);
|
|
66
|
+
const query = route.redirect.query;
|
|
67
|
+
assert(!query?.pass?.length && !(query?.map && Object.keys(query.map).length), `${route.pattern}: static hosting cannot pass or map query parameters into a redirect target; this needs request-time logic S3 does not have`);
|
|
68
|
+
assert(!route.redirect.status || route.redirect.status === 301, `${route.pattern}: S3's per-object website redirect always answers 301; this route declares ${route.redirect.status}, which would silently change on this target`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await compileAssets(loaded.root, routes);
|
|
73
|
+
|
|
74
|
+
const objectsDir = join(out, 'objects');
|
|
75
|
+
await mkdir(objectsDir, { recursive: true });
|
|
76
|
+
const redirects = [];
|
|
77
|
+
const objects = [];
|
|
78
|
+
const writeObject = async (key , body ) => {
|
|
79
|
+
const target = join(objectsDir, ...key.split('/'));
|
|
80
|
+
await mkdir(dirname(target), { recursive: true });
|
|
81
|
+
await writeFile(target, body);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
for (const route of routes) {
|
|
85
|
+
if (route.redirect) {
|
|
86
|
+
const key = objectKey(route.pattern);
|
|
87
|
+
redirects.push({ key, location: route.redirect.url, status: 301 });
|
|
88
|
+
await writeObject(key, new Uint8Array());
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (route.static) {
|
|
92
|
+
const asset = route.asset;
|
|
93
|
+
assert(asset instanceof Map, `${route.pattern}: static mount has no compiled asset snapshot`);
|
|
94
|
+
const prefix = route.prefix .slice(1);
|
|
95
|
+
for (const [name, file] of asset) {
|
|
96
|
+
const key = prefix + name;
|
|
97
|
+
objects.push({ key, contentType: file.type, cacheControl: file.cache });
|
|
98
|
+
await writeObject(key, file.body);
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (route.page || route.download) {
|
|
103
|
+
const asset = route.asset;
|
|
104
|
+
assert(asset && !(asset instanceof Map), `${route.pattern}: route has no compiled asset snapshot`);
|
|
105
|
+
const key = objectKey(route.pattern);
|
|
106
|
+
objects.push({ key, contentType: asset.type, cacheControl: asset.cache, ...(asset.attachment ? { contentDisposition: asset.attachment } : {}) });
|
|
107
|
+
await writeObject(key, asset.body);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (route.reply) {
|
|
111
|
+
const key = objectKey(route.pattern);
|
|
112
|
+
const contentType = route.reply.headers.find(([name]) => name === 'content-type')?.[1] ?? 'application/octet-stream';
|
|
113
|
+
objects.push({ key, contentType });
|
|
114
|
+
await writeObject(key, route.reply.body);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// Unreachable: capability analysis already refuses every other handler for this target.
|
|
118
|
+
throw new ConfigError(`${route.pattern}: static target has no compiler for this route`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const redirectManifest = { format: FORMAT, redirects };
|
|
122
|
+
const objectManifest = { format: FORMAT, objects };
|
|
123
|
+
await writeFile(join(out, 'redirects.json'), JSON.stringify(redirectManifest, null, 2) + '\n');
|
|
124
|
+
await writeFile(join(out, 'objects.json'), JSON.stringify(objectManifest, null, 2) + '\n');
|
|
125
|
+
// The semantic manifest travels with the artifact so a reviewer can read
|
|
126
|
+
// what was built without the project checkout (docs/TOOLING.md).
|
|
127
|
+
const manifest = manifestPath(out);
|
|
128
|
+
await writeFile(manifest, renderManifest(await buildManifest(project, origin === undefined ? {} : { origin })));
|
|
129
|
+
return { out, format: FORMAT, version: loaded.version, routes: routes.length, redirects: redirects.length, files: objects.length, manifest };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Exported for tests that need the exact key a route compiles to without
|
|
133
|
+
// duplicating the mapping rule.
|
|
134
|
+
export const staticObjectKey = objectKey;
|
package/dist/capabilities.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import { hasExtensionPolicy } from './extensions.js';
|
|
1
|
+
import { hasExtensionPolicy, effectiveExtensionPolicies } from './extensions.js';
|
|
2
|
+
|
|
2
3
|
import { ConfigError } from './errors.js';
|
|
3
4
|
import { effectivePolicies, registry } from './policies.js';
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
export const capabilityTargets = ['self-hosted', 'cloudflare', 'aws', 'vercel'] ;
|
|
7
|
+
export const capabilityTargets = ['self-hosted', 'cloudflare', 'aws', 'vercel', 'static'] ;
|
|
7
8
|
|
|
9
|
+
/** `static` never reaches the policy-module handshake (src/types.ts `TargetName`): it refuses every
|
|
10
|
+
* runtime policy outright, so it needs no entry in that exhaustive per-target record. */
|
|
11
|
+
|
|
8
12
|
|
|
9
|
-
export const capabilityNames = ['extension','policies.extensions','proxy', 'signals', 'conditional', 'conditions', 'redirect', 'respond', 'page', 'static', 'download', 'function', 'middleware', '
|
|
13
|
+
export const capabilityNames = ['extension','policies.extensions','proxy', 'signals', 'conditional', 'conditions', 'redirect', 'respond', 'page', 'static', 'download', 'function', 'middleware', 'parameters', 'methods', 'enabled', 'expires', 'request.body', 'response.headers', 'bindings', 'policies.agents', 'policies.security', 'policies.cache', 'policies.compression', 'policies.throttle'] ;
|
|
10
14
|
|
|
15
|
+
/** The resolved operator registration set, when known (loaded via --host-file, same as `inspectExtensions`). Keyed by extension name. */
|
|
16
|
+
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
|
|
@@ -23,17 +29,55 @@ export const capabilityNames = ['extension','policies.extensions','proxy', 'sign
|
|
|
23
29
|
export function normalizeCapabilityTarget(target ) {
|
|
24
30
|
if (target === 'node') return 'self-hosted';
|
|
25
31
|
if ((capabilityTargets ).includes(target)) return target ;
|
|
26
|
-
throw new ConfigError('Unknown capability target; use self-hosted, cloudflare, aws or
|
|
32
|
+
throw new ConfigError('Unknown capability target; use self-hosted, cloudflare, aws, vercel or static');
|
|
27
33
|
}
|
|
28
|
-
const internalTarget = (target
|
|
34
|
+
const internalTarget = (target ) => target === 'self-hosted' ? 'node' : target;
|
|
29
35
|
const deployment = (target ) => target === 'self-hosted' ? 'local-runtime' : 'unverified';
|
|
30
36
|
const policyNames = Object.keys(registry) ;
|
|
31
37
|
|
|
38
|
+
// static hosting (S3 + CloudFront) has no server at all, so no capability
|
|
39
|
+
// needing request-time evaluation can be represented; only capabilities a
|
|
40
|
+
// build can lower ahead of time (redirect/respond/page/static/download,
|
|
41
|
+
// methods/enabled/expires) survive.
|
|
42
|
+
const staticRefusals = {
|
|
43
|
+
extension: 'no server, so no operator extension registry to delegate to',
|
|
44
|
+
'policies.extensions': 'no server, so no operator extension registry to delegate to',
|
|
45
|
+
proxy: 'no server, so no bounded egress at request time',
|
|
46
|
+
signals: 'no server, so no fire-and-forget egress after a reply',
|
|
47
|
+
conditional: 'no server, so request-time condition matching is not possible',
|
|
48
|
+
conditions: 'no server, so request-time condition matching is not possible',
|
|
49
|
+
function: 'no server, so no dynamic execution',
|
|
50
|
+
middleware: 'no server, so no middleware execution',
|
|
51
|
+
parameters: 'no server, so no request-time parameter validation',
|
|
52
|
+
'request.body': 'no server, so there is no request body to read or validate',
|
|
53
|
+
'response.headers': 'no server, so response headers cannot be added per request; set them via S3 object metadata or a CloudFront response headers policy instead',
|
|
54
|
+
bindings: 'no server, so env/secret bindings cannot be resolved per request',
|
|
55
|
+
};
|
|
56
|
+
|
|
32
57
|
// Policy modules remain the authority for configuration-dependent support.
|
|
33
|
-
|
|
34
|
-
|
|
58
|
+
// `extension`/`policies.extensions` cannot get a target-independent answer:
|
|
59
|
+
// each registered extension declares its own `targets` (src/extensions.ts),
|
|
60
|
+
// so refusal on aws/vercel/self-hosted depends on that extension, not on the
|
|
61
|
+
// capability name. `extensionNames` names the specific extension(s) this
|
|
62
|
+
// requirement involves; `extensions` is the resolved registration set, when
|
|
63
|
+
// the caller has one (loaded via --host-file, same as `inspectExtensions`).
|
|
64
|
+
function decision(capability , target , policies , extensionNames , extensions ) {
|
|
65
|
+
if(capability==='extension'||capability==='policies.extensions'){
|
|
66
|
+
if(target==='cloudflare')return {support:'refused',reason:'Operator extensions have no Worker artifact lowering'};
|
|
67
|
+
if(target==='static')return {support:'refused',reason:staticRefusals[capability] };
|
|
68
|
+
if(!extensions)return {support:'conditional',reason:'Depends on the specific registered extension\'s declared targets; resolve with --host-file (CLI) or the runtime\'s extensions option'};
|
|
69
|
+
const names=extensionNames?.length?extensionNames:[...extensions.keys()];
|
|
70
|
+
if(!names.length)return {support:'unknown',reason:'No extension registration to check'};
|
|
71
|
+
const checked=names.map(name=>{const registration=extensions.get(name);return {name,support:(registration?(registration.targets.includes(internalTarget(target))?'native':'refused'):'unknown') };});
|
|
72
|
+
const unknown=checked.filter(item=>item.support==='unknown').map(item=>item.name);
|
|
73
|
+
if(unknown.length)return {support:'unknown',reason:`Not registered by the host file: ${unknown.join(', ')}`};
|
|
74
|
+
const refused=checked.filter(item=>item.support==='refused').map(item=>item.name);
|
|
75
|
+
if(refused.length)return {support:'refused',reason:`Refused by the extension's own declared targets: ${refused.join(', ')}`};
|
|
76
|
+
return {support:'native',reason:'Registered extension declares support for this target'};
|
|
77
|
+
}
|
|
35
78
|
const policy = policyNames.find(name => capability === `policies.${name}`);
|
|
36
79
|
if (policy) {
|
|
80
|
+
if (target === 'static') return { support: 'refused', reason: 'no server, so runtime policies are not enforced for static hosting' };
|
|
37
81
|
if (policy === 'throttle' && !policies && (target === 'aws' || target === 'vercel')) {
|
|
38
82
|
return { support: 'conditional', reason: 'Only partition: route is implemented; counters are per instance' };
|
|
39
83
|
}
|
|
@@ -44,19 +88,24 @@ function decision(capability , target , policies
|
|
|
44
88
|
: support === 'unknown' ? 'No implementation evidence for this target'
|
|
45
89
|
: 'Implemented by the existing policy module; configuration validation still applies' };
|
|
46
90
|
}
|
|
47
|
-
if (target
|
|
91
|
+
if (target === 'static') {
|
|
92
|
+
const reason = staticRefusals[capability];
|
|
93
|
+
if (reason) return { support: 'refused', reason };
|
|
94
|
+
// redirect/respond/page/static/download/methods/enabled/expires fall through to the target's own success reason below.
|
|
95
|
+
} else if (target !== 'self-hosted') {
|
|
48
96
|
const reason = ['proxy','signals'].includes(capability) ? 'bounded egress currently requires the self-hosted Node lifecycle'
|
|
49
97
|
: target === 'cloudflare' && ['conditional','conditions'].includes(capability) ? 'conditional routing has no Worker artifact lowering yet'
|
|
50
|
-
: capability === 'function' ? '
|
|
51
|
-
: capability === 'middleware' ? '
|
|
52
|
-
: capability === 'link' || capability === 'dynamicLinks' ? 'stored live links need a durable writable store'
|
|
98
|
+
: capability === 'function' ? 'functions need the self-hosted Node lifecycle, whether trusted (in-process) or sandboxed (worker threads and the WASM engine)'
|
|
99
|
+
: capability === 'middleware' ? 'middleware needs the self-hosted Node lifecycle, whether trusted (in-process) or sandboxed (worker threads and the WASM engine)'
|
|
53
100
|
: target === 'cloudflare' && ['page', 'static', 'download'].includes(capability) ? 'assets need a static-asset binding'
|
|
54
101
|
: target === 'cloudflare' && capability === 'bindings' ? 'env and secret bindings would have to be baked into the artifact'
|
|
55
102
|
: undefined;
|
|
56
103
|
if (reason) return { support: 'refused', reason };
|
|
57
104
|
}
|
|
58
|
-
return { support: target === 'cloudflare' ? 'compiled' : 'native', reason: target === 'self-hosted'
|
|
105
|
+
return { support: target === 'cloudflare' || target === 'static' ? 'compiled' : 'native', reason: target === 'self-hosted'
|
|
59
106
|
? 'Implemented and tested in the local runtime'
|
|
107
|
+
: target === 'static'
|
|
108
|
+
? 'Compiled ahead of time into S3/CloudFront redirect metadata or static objects; provider deployment unverified'
|
|
60
109
|
: 'Implemented with local adapter tests; provider deployment unverified; transport normalization limits apply' };
|
|
61
110
|
}
|
|
62
111
|
|
|
@@ -83,7 +132,7 @@ export function routeCapabilities(route , document
|
|
|
83
132
|
if (branch.respond && !result.includes('respond')) result.push('respond');
|
|
84
133
|
}
|
|
85
134
|
}
|
|
86
|
-
for (const name of ['redirect', 'respond', 'page', 'static', 'download', 'function'
|
|
135
|
+
for (const name of ['redirect', 'respond', 'page', 'static', 'download', 'function'] ) if (route[name]) result.push(name);
|
|
87
136
|
if (route.middleware?.length) result.push('middleware');
|
|
88
137
|
if (route.parameters?.length) result.push('parameters');
|
|
89
138
|
// Defaults are still semantics required by every route.
|
|
@@ -97,32 +146,42 @@ export function routeCapabilities(route , document
|
|
|
97
146
|
return result;
|
|
98
147
|
}
|
|
99
148
|
|
|
100
|
-
function analyze(document , routes , requestedTarget ) {
|
|
149
|
+
function analyze(document , routes , requestedTarget , registrations ) {
|
|
101
150
|
const target = normalizeCapabilityTarget(requestedTarget);
|
|
151
|
+
const extensions = registrations ? new Map(registrations.map(registration => [registration.name, registration])) : undefined;
|
|
102
152
|
const requirements = [];
|
|
103
|
-
if (Object.keys(document.extensions??{}).length) requirements.push({path:'(project)',capability:'extension',...decision('extension',target)});
|
|
104
|
-
if (document.dynamicLinks) requirements.push({ path: '(project)', capability: 'dynamicLinks', ...decision('dynamicLinks', target) });
|
|
153
|
+
if (Object.keys(document.extensions??{}).length) requirements.push({path:'(project)',capability:'extension',...decision('extension',target,undefined,Object.keys(document.extensions??{}),extensions)});
|
|
105
154
|
for (const [path, route] of routes) {
|
|
106
155
|
const policies = effectivePolicies(document, route);
|
|
107
|
-
for (const capability of routeCapabilities(route, document))
|
|
156
|
+
for (const capability of routeCapabilities(route, document)) {
|
|
157
|
+
const extensionNames = capability==='extension' ? (route.extension?[route.extension]:[])
|
|
158
|
+
: capability==='policies.extensions' ? Object.keys(effectiveExtensionPolicies(document,route)) : undefined;
|
|
159
|
+
requirements.push({ path, capability, ...decision(capability, target, policies, extensionNames, extensions) });
|
|
160
|
+
}
|
|
108
161
|
}
|
|
109
162
|
const issues = requirements.filter(item => item.support === 'refused' || item.support === 'unknown' || item.support === 'conditional');
|
|
110
163
|
return { target, deployment: deployment(target), compatible: issues.length === 0, requirements, issues };
|
|
111
164
|
}
|
|
112
165
|
|
|
113
|
-
/**
|
|
114
|
-
|
|
115
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Preflight only: call after schema validation/site expansion, before resolving bindings.
|
|
168
|
+
* `registrations` is the resolved operator extension set, when known (the same
|
|
169
|
+
* shape --host-file loads for `inspectExtensions`); without it, `extension`/
|
|
170
|
+
* `policies.extensions` requirements report `conditional`/`unknown` rather
|
|
171
|
+
* than a blanket `native` that ignores the specific extension's own targets.
|
|
172
|
+
*/
|
|
173
|
+
export function analyzeProjectCapabilities(loaded , target , registrations ) {
|
|
174
|
+
return analyze(loaded.document, Object.entries(loaded.routes), target, registrations);
|
|
116
175
|
}
|
|
117
176
|
|
|
118
177
|
/** Analyze normalized route semantics without exporting validators, resources or resolved secrets. */
|
|
119
|
-
export function analyzeCompiledCapabilities(document , compiled , target ) {
|
|
178
|
+
export function analyzeCompiledCapabilities(document , compiled , target , registrations ) {
|
|
120
179
|
function* entries() {
|
|
121
180
|
for (const route of compiled.exact.values()) yield [route.pattern, route];
|
|
122
181
|
for (const bucket of compiled.byLength.values()) for (const route of bucket) yield [route.pattern, route];
|
|
123
182
|
for (const route of compiled.mounts) yield [route.pattern, route];
|
|
124
183
|
}
|
|
125
|
-
return analyze(document, entries(), target);
|
|
184
|
+
return analyze(document, entries(), target, registrations);
|
|
126
185
|
}
|
|
127
186
|
|
|
128
187
|
export function assertTargetCompatibility(report ) {
|
|
@@ -178,15 +237,10 @@ export const capabilityDetails = {
|
|
|
178
237
|
constraints: ['`directory` required, project-relative, 1 to 1024 characters; `index` must be a .html name', 'Refused on Cloudflare: assets need a static-asset binding'], grants: [] },
|
|
179
238
|
download: { kind: 'handler', summary: 'Serve a project file as an attachment.', schema: ['download'],
|
|
180
239
|
constraints: ['`file` required, project-relative, 1 to 1024 characters; `filename` at most 255 characters', 'Refused on Cloudflare: assets need a static-asset binding'], grants: [] },
|
|
181
|
-
function: { kind: 'handler', summary: '
|
|
182
|
-
constraints: ['`source` at most 1024 characters, project-relative; `export` defaults to the default export', '`args` are literals, `{from: path|query|header}` inputs or `{env}` references', 'Self-hosted only:
|
|
183
|
-
middleware: { kind: 'middleware', summary: '
|
|
184
|
-
constraints: ['At most 16 entries, each with a project-relative `source` and optional `export`', 'Self-hosted only:
|
|
185
|
-
link: { kind: 'handler', summary: 'Live stored short link resolved from an operator collection.', schema: ['link', 'dynamicLinks'],
|
|
186
|
-
constraints: ['`collection` matches ^[A-Za-z][A-Za-z0-9_-]{0,63}$; `code` comes from a path parameter', 'Entry urlcode.yaml must set `dynamicLinks: true`', 'Self-hosted only: needs a durable writable store'],
|
|
187
|
-
grants: ['--link-store collection=/absolute/file binding owned by the operator'] },
|
|
188
|
-
dynamicLinks: { kind: 'project', summary: 'Entry-point opt-in for live stored-link routes.', schema: ['dynamicLinks'],
|
|
189
|
-
constraints: ['Entry urlcode.yaml only; ignored defaults to false', 'Does not enable guest storage or management access'], grants: ['--link-store binding when any link route is declared'] },
|
|
240
|
+
function: { kind: 'handler', summary: 'Project function producing the reply; trusted and unsandboxed by default, sandboxed opt-in.', schema: ['function'],
|
|
241
|
+
constraints: ['`source` at most 1024 characters, project-relative; `export` defaults to the default export', '`args` are literals, `{from: path|query|header}` inputs or `{env}` references', 'Self-hosted only', 'Trusted by default (`sandbox` false or absent): runs in-process with full Node network/filesystem access, like any other project code', 'Route-level `sandbox: true` runs the whole `function`/`middleware` chain isolated instead: worker threads, the WASM engine, no network or filesystem in the guest (docs/FUNCTION-SECURITY.md)'], grants: [] },
|
|
242
|
+
middleware: { kind: 'middleware', summary: 'Modules run before the handler; trusted and unsandboxed by default, sandboxed opt-in.', schema: ['middleware'],
|
|
243
|
+
constraints: ['At most 16 entries, each with a project-relative `source` and optional `export`', 'Self-hosted only', 'Same trusted-by-default / `sandbox: true` opt-in as `function`, applied uniformly to the whole route'], grants: [] },
|
|
190
244
|
parameters: { kind: 'request', summary: 'Validated path, query and header inputs.', schema: ['parameters'],
|
|
191
245
|
constraints: ['Names match ^[A-Za-z_][A-Za-z0-9_-]*$ and `in` is path, query or header', 'Schema types: string, integer, number, boolean, array; length bounds up to 8192'], grants: [] },
|
|
192
246
|
methods: { kind: 'routing', summary: 'Allowed HTTP methods; defaults to GET and HEAD.', schema: ['methods'],
|
package/dist/capability-query.js
CHANGED
|
@@ -35,7 +35,6 @@ function usage(base ,files ,name )
|
|
|
35
35
|
let document ;try{document=parse(readFileSync(file,'utf8'));}catch{continue;}
|
|
36
36
|
if(!object(document))continue;
|
|
37
37
|
const routes =[];
|
|
38
|
-
if(name==='dynamicLinks'&&document.dynamicLinks===true)routes.push('(project)');
|
|
39
38
|
if(name==='extension'&&object(document.extensions)&&Object.keys(document.extensions).length)routes.push('(project)');
|
|
40
39
|
for(const [path,route] of Object.entries(object(document.routes)?document.routes:{})) {
|
|
41
40
|
if(!object(route))continue;
|