@jimhoyd/urlcode 0.3.0 → 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 +122 -0
- package/.claude/skills/urlcode-operations/SKILL.md +108 -0
- package/.claude-plugin/marketplace.json +18 -0
- package/CONTRIBUTING.md +30 -2
- package/README.md +195 -255
- package/ROADMAP.md +143 -15
- package/SECURITY.md +31 -9
- package/dist/BUILD-MANIFEST.json +72 -47
- 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/build-static.js +134 -0
- package/dist/bulk.js +37 -0
- package/dist/capabilities.js +262 -0
- package/dist/capability-query.js +71 -0
- package/dist/catalog.js +105 -0
- package/dist/cli.js +165 -34
- package/dist/client-address.js +1 -1
- package/dist/compliance-rules/baseline.js +9 -17
- package/dist/compliance-rules/privacy.js +7 -18
- package/dist/compliance-rules/shared.js +0 -2
- package/dist/compliance-rules/strict.js +5 -5
- package/dist/compliance.js +6 -8
- package/dist/conditions.js +88 -0
- package/dist/config.js +69 -6
- package/dist/context.js +155 -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 +131 -0
- package/dist/extensions.js +231 -0
- package/dist/function-sources.js +49 -5
- package/dist/function-worker.js +3 -1
- package/dist/functions.js +84 -13
- package/dist/guest-api.js +29 -3
- package/dist/index.js +40 -6
- package/dist/init-with.js +165 -0
- package/dist/interchange-cli.js +42 -0
- package/dist/interchange.js +189 -0
- package/dist/manifest.js +109 -0
- package/dist/match.js +2 -2
- package/dist/mcp-authoring.js +147 -0
- package/dist/mcp.js +97 -0
- package/dist/observability.js +7 -21
- package/dist/operator-host.js +29 -0
- package/dist/plugins.js +12 -0
- package/dist/policies/agents.js +2 -2
- package/dist/policies/cache.js +8 -3
- package/dist/policies/compression.js +2 -1
- package/dist/policies/security.js +0 -0
- package/dist/policies.js +1 -1
- package/dist/policy.js +56 -15
- 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 +34 -11
- package/dist/recipes.js +41 -0
- package/dist/route-diff.js +106 -0
- package/dist/router.js +45 -7
- package/dist/runtime.js +164 -64
- package/dist/sandbox.js +48 -0
- package/dist/scaffold.js +0 -0
- package/dist/schema-query.js +62 -0
- package/dist/scripts/operational-drills.js +12 -54
- package/dist/server.js +3 -29
- package/dist/signals.js +24 -0
- package/dist/site.js +0 -0
- package/dist/tooling.js +96 -0
- package/dist/trusted-functions.js +210 -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/build-static.d.ts +43 -0
- package/dist/types/bulk.d.ts +27 -0
- package/dist/types/capabilities.d.ts +64 -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/shared.d.ts +0 -2
- package/dist/types/compliance-rules/strict.d.ts +0 -5
- package/dist/types/compliance.d.ts +0 -3
- package/dist/types/conditions.d.ts +19 -0
- package/dist/types/config.d.ts +21 -2
- package/dist/types/context.d.ts +66 -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 +177 -0
- package/dist/types/function-sources.d.ts +9 -0
- package/dist/types/functions.d.ts +48 -5
- package/dist/types/guest-api.d.ts +1 -0
- package/dist/types/index.d.ts +36 -6
- 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/manifest.d.ts +79 -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 +3 -14
- 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 -2
- package/dist/types/provider-verification.d.ts +53 -0
- package/dist/types/proxy.d.ts +21 -0
- package/dist/types/readiness.d.ts +10 -3
- package/dist/types/recipes.d.ts +30 -0
- package/dist/types/route-diff.d.ts +27 -0
- package/dist/types/router.d.ts +2 -1
- package/dist/types/runtime.d.ts +11 -27
- package/dist/types/sandbox.d.ts +12 -0
- package/dist/types/scaffold.d.ts +0 -2
- package/dist/types/schema-query.d.ts +12 -0
- package/dist/types/server.d.ts +1 -4
- 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/trusted-functions.d.ts +29 -0
- package/dist/types/types.d.ts +71 -7
- 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 +37 -5
- package/dist/typescript-authoring.js +142 -0
- package/dist/vercel.js +4 -3
- package/dist/verify-deployment.js +270 -0
- package/docs/AI-AUTHORING.md +232 -15
- package/docs/AWS.md +4 -4
- package/docs/BEST-PRACTICES.md +3 -2
- package/docs/BULK.md +79 -0
- package/docs/CAPABILITIES.md +192 -0
- package/docs/CAPACITY.md +129 -32
- package/docs/CI.md +142 -0
- package/docs/CLOUDFLARE.md +1 -2
- package/docs/COMPLIANCE.md +6 -9
- package/docs/CONDITIONS.md +74 -0
- package/docs/DEPLOYMENT-CHECKS.md +108 -0
- package/docs/EGRESS.md +125 -0
- package/docs/EXTENSIONS.md +398 -0
- package/docs/FRAMEWORK.md +198 -0
- package/docs/FUNCTION-SECURITY.md +129 -32
- package/docs/INSTALL.md +45 -12
- package/docs/INTERCHANGE.md +134 -0
- package/docs/LOAD-TESTING.md +4 -4
- package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
- package/docs/MIDDLEWARE.md +29 -16
- package/docs/MONITORING.md +2 -19
- package/docs/NEXT-PHASE-PLAN.md +98 -0
- package/docs/NEXT-STEPS.md +634 -0
- package/docs/OBSERVABILITY.md +11 -18
- package/docs/OPEN-DECISIONS.md +212 -0
- package/docs/OPERATIONAL-PROOF.md +30 -31
- package/docs/OPERATIONS.md +29 -35
- package/docs/PLUGINS.md +37 -0
- package/docs/POLICIES.md +23 -309
- package/docs/PRERENDER.md +41 -1
- package/docs/PROJECT-DIRECTION.md +75 -8
- package/docs/PROVIDER-VERIFICATION.md +84 -0
- package/docs/READINESS.md +21 -1
- package/docs/README.md +87 -34
- package/docs/RECIPES.md +99 -0
- package/docs/RELEASE-READINESS.md +57 -35
- package/docs/RELEASE-SECURITY.md +116 -7
- 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 +41 -1
- package/docs/SPECIFICATION.md +150 -29
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +287 -0
- package/docs/SPIKE-BUSINESS-SUITE.md +1021 -0
- package/docs/SPIKE-CORE-LAYERING.md +337 -0
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +209 -0
- package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
- package/docs/SPIKE-EXTENSIONS.md +6 -0
- package/docs/SPIKE-LAMBDA-COMPILE.md +201 -0
- package/docs/SPIKE-MONOREPO.md +322 -0
- package/docs/STANDARDS.md +150 -142
- package/docs/STARTERS.md +21 -1
- package/docs/STATIC.md +94 -0
- package/docs/TOOLING.md +295 -0
- package/docs/TUNNELS.md +0 -3
- package/docs/TYPESCRIPT-AUTHORING.md +82 -0
- package/docs/TYPESCRIPT.md +25 -4
- package/docs/USABILITY-REVIEW.md +129 -0
- package/docs/VERCEL.md +4 -5
- package/docs/VERSION-ALIGNMENT.md +205 -0
- package/docs/YAML-GUIDE.md +15 -479
- package/docs/YAML-REFERENCE.md +143 -22
- package/docs/policies/agents.md +1 -1
- package/docs/policies/cache.md +13 -0
- 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/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 +29 -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 +20 -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/monitoring/example.yaml +8 -0
- package/examples/prerender/README.md +2 -2
- 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 +3084 -0
- package/llms.txt +61 -21
- package/package.json +36 -7
- package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +122 -0
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +108 -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 +29 -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 +20 -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 +8 -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 +20 -0
- package/recipes/webhook-receiver/functions/receive.mjs +16 -0
- package/recipes/webhook-receiver/recipe.yaml +27 -0
- package/recipes/webhook-receiver/tests/requests.json +59 -0
- package/recipes/webhook-receiver/urlcode.yaml +23 -0
- package/schemas/recipe.schema.json +139 -0
- package/schemas/urlcode.schema.json +659 -110
- package/skills/urlcode/SKILL.md +119 -0
- package/starters/default/.github/workflows/urlcode.yml +23 -0
- package/starters/default/.mcp.json +12 -0
- package/starters/default/AGENTS.md +79 -0
- 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 -41
- package/dist/sqlite-version.js +0 -6
- package/dist/types/link-api.d.ts +0 -30
- package/dist/types/link-cli.d.ts +0 -36
- 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 -9
- package/dist/types/sqlite-version.d.ts +0 -1
- package/docs/DYNAMIC-LINKS.md +0 -561
- package/docs/MANAGEMENT-SECURITY.md +0 -82
- package/examples/live-links/README.md +0 -11
- package/examples/live-links/tests/requests.json +0 -6
- package/examples/live-links/urlcode.yaml +0 -16
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {lstat,realpath,open,mkdir,rm,rename} from 'node:fs/promises';
|
|
2
|
+
import {resolve,join,dirname,basename,parse} from 'node:path';
|
|
3
|
+
import {assert} from './errors.js';
|
|
4
|
+
|
|
5
|
+
/** Authoring output never copies hidden state, package hooks, or credential files. */
|
|
6
|
+
export function authoringPath(path ) {
|
|
7
|
+
const parts=path.split('/');
|
|
8
|
+
assert(path.length>0 && path.length<=1024 && parts.length<=32 && parts.every(part=>
|
|
9
|
+
part && !part.startsWith('.') && !/[. ]$/.test(part) && !/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(part) && !/[\\:*?"<>|\u0000-\u001f\u007f]/u.test(part) &&
|
|
10
|
+
!/^(?:node_modules|package(?:-lock)?\.json|.*\.(?:pem|key|p12|pfx|env))$/i.test(part)), 'Authoring paths must be safe non-sensitive project-relative paths');
|
|
11
|
+
return path;
|
|
12
|
+
}
|
|
13
|
+
export async function authoringFile(root ,path ,directory=false) {
|
|
14
|
+
authoringPath(path);let file=root;
|
|
15
|
+
for(const [index,part] of path.split('/').entries()){
|
|
16
|
+
file=join(file,part);const info=await lstat(file);
|
|
17
|
+
assert(!info.isSymbolicLink(),'Authoring through symlinks is forbidden');
|
|
18
|
+
assert(index<path.split('/').length-1 || directory ? info.isDirectory() : info.isFile() && info.nlink===1,'Authoring requires ordinary files and directories');
|
|
19
|
+
}
|
|
20
|
+
return file;
|
|
21
|
+
}
|
|
22
|
+
export async function readAuthoringFile(root ,path ,limit ) {
|
|
23
|
+
const file=await authoringFile(root,path),handle=await open(file,'r');
|
|
24
|
+
try {
|
|
25
|
+
const info=await handle.stat();assert(info.isFile() && info.nlink===1 && info.size<=limit,'Authoring source size limit exceeded');
|
|
26
|
+
const bytes=Buffer.alloc(info.size+1);let offset=0;
|
|
27
|
+
while(offset<bytes.length){const read=await handle.read(bytes,offset,bytes.length-offset,null);if(!read.bytesRead)break;offset+=read.bytesRead;}
|
|
28
|
+
assert(offset<=info.size,'Authoring source changed during read');return bytes.subarray(0,offset);
|
|
29
|
+
}finally{await handle.close();}
|
|
30
|
+
}
|
|
31
|
+
/** Reserve a fresh directory exclusively, then publish the entry config last.
|
|
32
|
+
* Failed writes remove only the directory created by this invocation. No existing
|
|
33
|
+
* directory (even an empty one) is accepted; consumers see no activatable project
|
|
34
|
+
* until every dependency has been written. Callers must own the output parent.
|
|
35
|
+
*/
|
|
36
|
+
export async function publishAuthoringProject(output ,files ,dryRun=false) {
|
|
37
|
+
assert(files.has('urlcode.yaml'),'Authoring output requires urlcode.yaml');
|
|
38
|
+
assert(files.size<=10000,'Authoring file limit exceeded');
|
|
39
|
+
const paths=new Set ();
|
|
40
|
+
for(const path of files.keys()){
|
|
41
|
+
authoringPath(path);const key=path.toLowerCase();assert(!paths.has(key),'Conflicting authoring output paths');paths.add(key);
|
|
42
|
+
}
|
|
43
|
+
for(const path of paths){let parent=dirname(path);while(parent!=='.'){assert(!paths.has(parent),'Authoring file/directory conflict');parent=dirname(parent);}}
|
|
44
|
+
const target=resolve(output),parent=dirname(target);assert(target!==parse(target).root,'Invalid authoring output');
|
|
45
|
+
assert(!(await lstat(parent)).isSymbolicLink(),'Authoring output parent must not be a symlink');
|
|
46
|
+
const destination=join(await realpath(parent),basename(target));
|
|
47
|
+
let exists=false;try{await lstat(destination);exists=true;}catch(error){if(!(error instanceof Error && 'code' in error && error.code==='ENOENT'))throw error;}
|
|
48
|
+
assert(!exists,'Authoring output already exists');
|
|
49
|
+
if(dryRun)return destination;
|
|
50
|
+
await mkdir(destination,{mode:0o700});
|
|
51
|
+
try {
|
|
52
|
+
const entries=[...files].sort(([a],[b])=>a==='urlcode.yaml'?1:b==='urlcode.yaml'?-1:a.localeCompare(b));
|
|
53
|
+
for(const [path,content] of entries){
|
|
54
|
+
const file=join(destination,path==='urlcode.yaml'?'.urlcode.yaml.pending':path);await mkdir(dirname(file),{recursive:true,mode:0o700});
|
|
55
|
+
const handle=await open(file,'wx',0o600);try{await handle.writeFile(content);await handle.sync();}finally{await handle.close();}
|
|
56
|
+
if(path==='urlcode.yaml')await rename(file,join(destination,'urlcode.yaml'));
|
|
57
|
+
}
|
|
58
|
+
return destination;
|
|
59
|
+
}catch(error){await rm(destination,{recursive:true,force:true});throw error;}
|
|
60
|
+
}
|
package/dist/authoring.js
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { randomBytes } from 'node:crypto';
|
|
5
5
|
import { parseDocument } from 'yaml';
|
|
6
6
|
import { loadDocument, validateDocument } from './config.js';
|
|
7
|
+
import { renderAgentsGuide, renderMcpConfig, mcpConfigFile } from './agents-guide.js';
|
|
7
8
|
import { compileRoutes } from './router.js';
|
|
8
9
|
import { prepareFunctionSnapshot, requestedPermissions } from './policy.js';
|
|
9
10
|
import { assert } from './errors.js';
|
|
@@ -17,9 +18,18 @@ export async function initProject(destination ) {
|
|
|
17
18
|
try {
|
|
18
19
|
const source = fileURLToPath(new URL('../starters/default/', import.meta.url));
|
|
19
20
|
for (const file of await readdir(source)) {
|
|
20
|
-
if (file === '.gitignore') continue;
|
|
21
|
+
if (file === '.gitignore' || file === 'AGENTS.md' || file === mcpConfigFile) continue;
|
|
21
22
|
await cp(join(source,file), join(target,file === 'gitignore.template' ? '.gitignore' : file), { recursive: true, force: false, errorOnExist: true });
|
|
22
23
|
}
|
|
24
|
+
// AGENTS.md is generated from the installed runtime's capability catalog so
|
|
25
|
+
// it names only what this version implements; the starter carries a
|
|
26
|
+
// committed copy for clones, kept identical by test.
|
|
27
|
+
const routes = Object.keys((await loadDocument(target)).routes).length;
|
|
28
|
+
const guide = await open(join(target,'AGENTS.md'), 'wx', 0o644);
|
|
29
|
+
try { await guide.writeFile(renderAgentsGuide({ routes })); } finally { await guide.close(); }
|
|
30
|
+
// .mcp.json registers the read-only server for repository-aware agents; the starter carries the same bytes.
|
|
31
|
+
const mcp = await open(join(target,mcpConfigFile), 'wx', 0o644);
|
|
32
|
+
try { await mcp.writeFile(renderMcpConfig('.')); } finally { await mcp.close(); }
|
|
23
33
|
} catch (error) { await rm(target, { recursive: true, force: true }); throw error; }
|
|
24
34
|
return target;
|
|
25
35
|
}
|
package/dist/aws.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { activateNativeOnly, lazyRuntime, resolveOrigin } from './adapters.js';
|
|
3
4
|
|
|
@@ -6,7 +7,7 @@ import { prepareResponse, errorResponse } from './http-response.js';
|
|
|
6
7
|
|
|
7
8
|
import { assert, ConfigError, HttpError } from './errors.js';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
|
|
10
11
|
/** A Lambda payload format 2.0 event, as far as this adapter reads it. */
|
|
11
12
|
|
|
12
13
|
|
|
@@ -64,9 +65,9 @@ function requestBody(event , limit ) {
|
|
|
64
65
|
// Builds a Lambda handler for payload format 2.0. The runtime is created once
|
|
65
66
|
// per execution environment and reused across warm invocations.
|
|
66
67
|
export function createLambdaHandler({ project = process.cwd(), origin, environment = process.env,
|
|
67
|
-
maxBodyBytes = 1048576, plugins } = {}) {
|
|
68
|
+
maxBodyBytes = 1048576, plugins, extensions } = {}) {
|
|
68
69
|
assert(Number.isInteger(maxBodyBytes) && maxBodyBytes >= 1 && maxBodyBytes <= 16777216, 'Request limit must be 1–16777216 bytes');
|
|
69
|
-
const ready = lazyRuntime(() => activateNativeOnly(project, environment, { target: 'aws', plugins }));
|
|
70
|
+
const ready = lazyRuntime(() => activateNativeOnly(project, environment, { target: 'aws', plugins, extensions, origin:resolveOrigin(origin,environment,platformOrigins) }));
|
|
70
71
|
|
|
71
72
|
return async function handler(raw) {
|
|
72
73
|
const requestId = randomUUID();
|
package/dist/build-cloudflare.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { analyzeProjectCapabilities, analyzeCompiledCapabilities, assertTargetCompatibility } from './capabilities.js';
|
|
1
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import Ajv from 'ajv/dist/2020.js';
|
|
@@ -8,23 +9,12 @@ import { assert } from './errors.js';
|
|
|
8
9
|
import { effectivePolicies, registry } from './policies.js';
|
|
9
10
|
import { resolveLists } from './agent-lists.js';
|
|
10
11
|
import { applySite } from './site.js';
|
|
12
|
+
import { buildManifest, renderManifest, manifestPath } from './manifest.js';
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// Handlers this target cannot serve, and why. Declarative routes only in this
|
|
18
|
-
// slice: assets need a platform binding rather than an inline copy, and the
|
|
19
|
-
// sandbox needs facilities the platform does not provide.
|
|
20
|
-
const unsupported = {
|
|
21
|
-
function:'isolated functions need worker threads and the WASM engine',
|
|
22
|
-
link:'stored live links need a durable writable store',
|
|
23
|
-
page:'pages need a static-asset binding',
|
|
24
|
-
static:'static directories need a static-asset binding',
|
|
25
|
-
download:'downloads need a static-asset binding',
|
|
26
|
-
} ;
|
|
27
|
-
const unsupportedHandlers = ['function','link','page','static','download'] ;
|
|
17
|
+
|
|
28
18
|
|
|
29
19
|
// The artifact is this runtime's build output, not a published contract: the
|
|
30
20
|
// format may change with any release, and the runtime refuses a version it does
|
|
@@ -80,24 +70,21 @@ export async function buildCloudflare(project , { out = 'dist/cloudflare'
|
|
|
80
70
|
// Generated site routes are built like declared ones; the ones that need
|
|
81
71
|
// an origin get it from --origin, exactly as the server does.
|
|
82
72
|
await applySite(loaded, { origin, log });
|
|
73
|
+
assertTargetCompatibility(analyzeProjectCapabilities(loaded, 'cloudflare'));
|
|
83
74
|
// No bindings are resolved: a build artifact must never carry a secret, and
|
|
84
75
|
// this target has no per-request operator policy to pin one to.
|
|
85
76
|
const compiled = await compileRoutes(loaded, {}, {}, undefined);
|
|
86
77
|
const routes = [...compiled.exact.values(), ...[...compiled.byLength.values()].flat(), ...compiled.mounts];
|
|
87
78
|
|
|
88
|
-
|
|
79
|
+
assertTargetCompatibility(analyzeCompiledCapabilities(loaded.document, compiled, 'cloudflare'));
|
|
89
80
|
for (const route of routes) {
|
|
90
|
-
for (const handler of unsupportedHandlers) {
|
|
91
|
-
if (route[handler]) refused.push(`${route.pattern}: ${unsupported[handler]}`);
|
|
92
|
-
}
|
|
93
|
-
if (route.middleware?.length) refused.push(`${route.pattern}: middleware needs the sandbox`);
|
|
94
81
|
const policies = effectivePolicies(loaded.document, route);
|
|
95
82
|
for (const name of Object.keys(policies)) {
|
|
96
83
|
assert(isPolicyName(name), `Unknown policy "${name}"`);
|
|
97
84
|
const support = policyModule(name).targets(policies[name]).cloudflare;
|
|
98
85
|
// The platform provides it: accepted and dropped, never carried.
|
|
99
86
|
if (support === 'delegated') { delete policies[name]; continue; }
|
|
100
|
-
|
|
87
|
+
assert(compilablePolicies.has(name) && support === 'compiled', `${route.pattern}: policies.${name} cannot be compiled for this target`);
|
|
101
88
|
}
|
|
102
89
|
// Project-relative agent lists are read here, once, so the artifact
|
|
103
90
|
// carries the patterns and the Worker never needs a filesystem.
|
|
@@ -109,11 +96,7 @@ export async function buildCloudflare(project , { out = 'dist/cloudflare'
|
|
|
109
96
|
// evaluates a configuration the runtime would have rejected.
|
|
110
97
|
for (const name of Object.keys(policies)) if (isPolicyName(name) && compilablePolicies.has(name)) await policyModule(name).compile(policies[name], { route, shared: {}, target: 'cloudflare', document: loaded.document, root: loaded.root });
|
|
111
98
|
if (Object.keys(policies).length) route.compiledPolicies = policies; else delete route.compiledPolicies;
|
|
112
|
-
if (Object.keys(route.env).length || Object.keys(route.secrets).length) {
|
|
113
|
-
refused.push(`${route.pattern}: env and secret bindings would have to be baked into the artifact`);
|
|
114
|
-
}
|
|
115
99
|
}
|
|
116
|
-
assert(!refused.length, `This target serves declarative routes only:\n ${refused.join('\n ')}`);
|
|
117
100
|
assert(routes.length, 'No routes to build');
|
|
118
101
|
|
|
119
102
|
const ajv = new Ajv.default({ code:{ source:true, esm:true }, strict:false, allErrors:false });
|
|
@@ -159,6 +142,10 @@ import * as validators from './validators.js';
|
|
|
159
142
|
|
|
160
143
|
export default { fetch: createFetchHandler(artifact, validators) };
|
|
161
144
|
`);
|
|
145
|
+
// The semantic manifest travels with the artifact so a reviewer can read
|
|
146
|
+
// what was built without the project checkout (docs/TOOLING.md).
|
|
147
|
+
const manifest = manifestPath(out);
|
|
148
|
+
await writeFile(manifest, renderManifest(await buildManifest(project, origin === undefined ? {} : { origin })));
|
|
162
149
|
return { out, format:FORMAT, version:loaded.version, routes:serialised.length,
|
|
163
|
-
validators:Object.keys(validators).length };
|
|
150
|
+
validators:Object.keys(validators).length, manifest };
|
|
164
151
|
}
|
|
@@ -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/bulk.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {stringify} from 'yaml';
|
|
3
|
+
import {importRoutes} from './interchange.js';
|
|
4
|
+
|
|
5
|
+
import {publishAuthoringProject} from './authoring-files.js';
|
|
6
|
+
import {assert} from './errors.js';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/** Import ordinary redirect rows into small, portable include files. There is no
|
|
15
|
+
* merge mode: duplicate rules and existing output directories are refused. */
|
|
16
|
+
export async function importBulkProject(text ,format ,output ,{dryRun=false,source='<input>'} ={}) {
|
|
17
|
+
assert(['csv','json','yaml'].includes(format),'Bulk input must be csv, json or yaml');
|
|
18
|
+
assert(source.length<=1024 && !/[\u0000-\u001f\u007f]/u.test(source),'Invalid bulk provenance source name');
|
|
19
|
+
const provenance={name:source,format,sha256:createHash('sha256').update(text).digest('hex')};
|
|
20
|
+
const converted=await importRoutes({text,format,source});
|
|
21
|
+
const report ={ok:converted.ok,dryRun,routeCount:converted.routeCount,diagnostics:converted.diagnostics,files:[],source:provenance};
|
|
22
|
+
if(!converted.ok || !converted.document)return report;
|
|
23
|
+
const files=new Map (),entries=Object.entries(converted.document.routes),includes =[];
|
|
24
|
+
// 100 includes at the 100,000-route cap; each parser AST stays small without
|
|
25
|
+
// changing the configuration worker heap, wall deadline or include limits.
|
|
26
|
+
for(let offset=0;offset<entries.length;offset+=1000){
|
|
27
|
+
const group=entries.slice(offset,offset+1000),path=`routes/redirects-${String(includes.length+1).padStart(3,'0')}.yaml`;
|
|
28
|
+
includes.push(path);files.set(path,stringify({version:'1',routes:Object.fromEntries(group)}));
|
|
29
|
+
report.files.push({path,routeCount:group.length,firstPath:group[0] [0],lastPath:group.at(-1) [0]});
|
|
30
|
+
}
|
|
31
|
+
files.set('urlcode.yaml',stringify({version:'1',...(includes.length?{includes}:{}),routes:{}}));
|
|
32
|
+
report.files.unshift({path:'urlcode.yaml',routeCount:0});
|
|
33
|
+
const metadata={version:1,source:provenance,routeCount:report.routeCount,files:report.files};
|
|
34
|
+
files.set('provenance.json',JSON.stringify(metadata,null,2)+'\n');report.files.push({path:'provenance.json',routeCount:0});
|
|
35
|
+
report.output=await publishAuthoringProject(output,files,dryRun);
|
|
36
|
+
return report;
|
|
37
|
+
}
|