@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.
Files changed (380) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +122 -0
  2. package/.claude/skills/urlcode-operations/SKILL.md +108 -0
  3. package/.claude-plugin/marketplace.json +18 -0
  4. package/CONTRIBUTING.md +30 -2
  5. package/README.md +195 -255
  6. package/ROADMAP.md +143 -15
  7. package/SECURITY.md +31 -9
  8. package/dist/BUILD-MANIFEST.json +72 -47
  9. package/dist/adapters.js +4 -23
  10. package/dist/agent-lists.js +1 -1
  11. package/dist/agents-guide.js +113 -0
  12. package/dist/authoring-files.js +60 -0
  13. package/dist/authoring.js +11 -1
  14. package/dist/aws.js +4 -3
  15. package/dist/build-cloudflare.js +11 -24
  16. package/dist/build-static.js +134 -0
  17. package/dist/bulk.js +37 -0
  18. package/dist/capabilities.js +262 -0
  19. package/dist/capability-query.js +71 -0
  20. package/dist/catalog.js +105 -0
  21. package/dist/cli.js +165 -34
  22. package/dist/client-address.js +1 -1
  23. package/dist/compliance-rules/baseline.js +9 -17
  24. package/dist/compliance-rules/privacy.js +7 -18
  25. package/dist/compliance-rules/shared.js +0 -2
  26. package/dist/compliance-rules/strict.js +5 -5
  27. package/dist/compliance.js +6 -8
  28. package/dist/conditions.js +88 -0
  29. package/dist/config.js +69 -6
  30. package/dist/context.js +155 -0
  31. package/dist/ecosystem-cli.js +88 -0
  32. package/dist/egress.js +98 -0
  33. package/dist/examples.js +92 -0
  34. package/dist/explain-cli.js +64 -0
  35. package/dist/explain.js +131 -0
  36. package/dist/extensions.js +231 -0
  37. package/dist/function-sources.js +49 -5
  38. package/dist/function-worker.js +3 -1
  39. package/dist/functions.js +84 -13
  40. package/dist/guest-api.js +29 -3
  41. package/dist/index.js +40 -6
  42. package/dist/init-with.js +165 -0
  43. package/dist/interchange-cli.js +42 -0
  44. package/dist/interchange.js +189 -0
  45. package/dist/manifest.js +109 -0
  46. package/dist/match.js +2 -2
  47. package/dist/mcp-authoring.js +147 -0
  48. package/dist/mcp.js +97 -0
  49. package/dist/observability.js +7 -21
  50. package/dist/operator-host.js +29 -0
  51. package/dist/plugins.js +12 -0
  52. package/dist/policies/agents.js +2 -2
  53. package/dist/policies/cache.js +8 -3
  54. package/dist/policies/compression.js +2 -1
  55. package/dist/policies/security.js +0 -0
  56. package/dist/policies.js +1 -1
  57. package/dist/policy.js +56 -15
  58. package/dist/prerender.js +100 -41
  59. package/dist/project-tests.js +3 -3
  60. package/dist/provider-verification.js +92 -0
  61. package/dist/proxy.js +44 -0
  62. package/dist/readiness.js +34 -11
  63. package/dist/recipes.js +41 -0
  64. package/dist/route-diff.js +106 -0
  65. package/dist/router.js +45 -7
  66. package/dist/runtime.js +164 -64
  67. package/dist/sandbox.js +48 -0
  68. package/dist/scaffold.js +0 -0
  69. package/dist/schema-query.js +62 -0
  70. package/dist/scripts/operational-drills.js +12 -54
  71. package/dist/server.js +3 -29
  72. package/dist/signals.js +24 -0
  73. package/dist/site.js +0 -0
  74. package/dist/tooling.js +96 -0
  75. package/dist/trusted-functions.js +210 -0
  76. package/dist/types/adapters.d.ts +7 -4
  77. package/dist/types/agent-lists.d.ts +0 -1
  78. package/dist/types/agents-guide.d.ts +17 -0
  79. package/dist/types/authoring-files.d.ts +10 -0
  80. package/dist/types/aws.d.ts +3 -1
  81. package/dist/types/build-cloudflare.d.ts +1 -0
  82. package/dist/types/build-static.d.ts +43 -0
  83. package/dist/types/bulk.d.ts +27 -0
  84. package/dist/types/capabilities.d.ts +64 -0
  85. package/dist/types/capability-query.d.ts +24 -0
  86. package/dist/types/catalog.d.ts +65 -0
  87. package/dist/types/client-address.d.ts +0 -1
  88. package/dist/types/compliance-rules/baseline.d.ts +1 -9
  89. package/dist/types/compliance-rules/privacy.d.ts +1 -4
  90. package/dist/types/compliance-rules/shared.d.ts +0 -2
  91. package/dist/types/compliance-rules/strict.d.ts +0 -5
  92. package/dist/types/compliance.d.ts +0 -3
  93. package/dist/types/conditions.d.ts +19 -0
  94. package/dist/types/config.d.ts +21 -2
  95. package/dist/types/context.d.ts +66 -0
  96. package/dist/types/ecosystem-cli.d.ts +17 -0
  97. package/dist/types/egress.d.ts +46 -0
  98. package/dist/types/examples.d.ts +50 -0
  99. package/dist/types/explain-cli.d.ts +11 -0
  100. package/dist/types/explain.d.ts +95 -0
  101. package/dist/types/extensions.d.ts +177 -0
  102. package/dist/types/function-sources.d.ts +9 -0
  103. package/dist/types/functions.d.ts +48 -5
  104. package/dist/types/guest-api.d.ts +1 -0
  105. package/dist/types/index.d.ts +36 -6
  106. package/dist/types/init-with.d.ts +30 -0
  107. package/dist/types/interchange-cli.d.ts +16 -0
  108. package/dist/types/interchange.d.ts +42 -0
  109. package/dist/types/manifest.d.ts +79 -0
  110. package/dist/types/match.d.ts +1 -0
  111. package/dist/types/mcp-authoring.d.ts +92 -0
  112. package/dist/types/mcp.d.ts +12 -0
  113. package/dist/types/observability.d.ts +3 -14
  114. package/dist/types/operator-host.d.ts +8 -0
  115. package/dist/types/plugins.d.ts +2 -0
  116. package/dist/types/policies/agents.d.ts +0 -2
  117. package/dist/types/policies/compression.d.ts +2 -0
  118. package/dist/types/policies/security.d.ts +0 -1
  119. package/dist/types/policy.d.ts +15 -4
  120. package/dist/types/project-tests.d.ts +3 -2
  121. package/dist/types/provider-verification.d.ts +53 -0
  122. package/dist/types/proxy.d.ts +21 -0
  123. package/dist/types/readiness.d.ts +10 -3
  124. package/dist/types/recipes.d.ts +30 -0
  125. package/dist/types/route-diff.d.ts +27 -0
  126. package/dist/types/router.d.ts +2 -1
  127. package/dist/types/runtime.d.ts +11 -27
  128. package/dist/types/sandbox.d.ts +12 -0
  129. package/dist/types/scaffold.d.ts +0 -2
  130. package/dist/types/schema-query.d.ts +12 -0
  131. package/dist/types/server.d.ts +1 -4
  132. package/dist/types/signals.d.ts +25 -0
  133. package/dist/types/site.d.ts +0 -1
  134. package/dist/types/tooling.d.ts +115 -0
  135. package/dist/types/trusted-functions.d.ts +29 -0
  136. package/dist/types/types.d.ts +71 -7
  137. package/dist/types/typescript-authoring.d.ts +12 -0
  138. package/dist/types/vercel.d.ts +3 -1
  139. package/dist/types/verify-deployment.d.ts +47 -0
  140. package/dist/types.js +37 -5
  141. package/dist/typescript-authoring.js +142 -0
  142. package/dist/vercel.js +4 -3
  143. package/dist/verify-deployment.js +270 -0
  144. package/docs/AI-AUTHORING.md +232 -15
  145. package/docs/AWS.md +4 -4
  146. package/docs/BEST-PRACTICES.md +3 -2
  147. package/docs/BULK.md +79 -0
  148. package/docs/CAPABILITIES.md +192 -0
  149. package/docs/CAPACITY.md +129 -32
  150. package/docs/CI.md +142 -0
  151. package/docs/CLOUDFLARE.md +1 -2
  152. package/docs/COMPLIANCE.md +6 -9
  153. package/docs/CONDITIONS.md +74 -0
  154. package/docs/DEPLOYMENT-CHECKS.md +108 -0
  155. package/docs/EGRESS.md +125 -0
  156. package/docs/EXTENSIONS.md +398 -0
  157. package/docs/FRAMEWORK.md +198 -0
  158. package/docs/FUNCTION-SECURITY.md +129 -32
  159. package/docs/INSTALL.md +45 -12
  160. package/docs/INTERCHANGE.md +134 -0
  161. package/docs/LOAD-TESTING.md +4 -4
  162. package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
  163. package/docs/MIDDLEWARE.md +29 -16
  164. package/docs/MONITORING.md +2 -19
  165. package/docs/NEXT-PHASE-PLAN.md +98 -0
  166. package/docs/NEXT-STEPS.md +634 -0
  167. package/docs/OBSERVABILITY.md +11 -18
  168. package/docs/OPEN-DECISIONS.md +212 -0
  169. package/docs/OPERATIONAL-PROOF.md +30 -31
  170. package/docs/OPERATIONS.md +29 -35
  171. package/docs/PLUGINS.md +37 -0
  172. package/docs/POLICIES.md +23 -309
  173. package/docs/PRERENDER.md +41 -1
  174. package/docs/PROJECT-DIRECTION.md +75 -8
  175. package/docs/PROVIDER-VERIFICATION.md +84 -0
  176. package/docs/READINESS.md +21 -1
  177. package/docs/README.md +87 -34
  178. package/docs/RECIPES.md +99 -0
  179. package/docs/RELEASE-READINESS.md +57 -35
  180. package/docs/RELEASE-SECURITY.md +116 -7
  181. package/docs/RESILIENCE.md +16 -15
  182. package/docs/ROUTING.md +8 -10
  183. package/docs/SANDBOX-REVIEW.md +19 -6
  184. package/docs/SCAFFOLDING.md +0 -2
  185. package/docs/SECURITY-AUDIT.md +41 -1
  186. package/docs/SPECIFICATION.md +150 -29
  187. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +287 -0
  188. package/docs/SPIKE-BUSINESS-SUITE.md +1021 -0
  189. package/docs/SPIKE-CORE-LAYERING.md +337 -0
  190. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +209 -0
  191. package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
  192. package/docs/SPIKE-EXTENSIONS.md +6 -0
  193. package/docs/SPIKE-LAMBDA-COMPILE.md +201 -0
  194. package/docs/SPIKE-MONOREPO.md +322 -0
  195. package/docs/STANDARDS.md +150 -142
  196. package/docs/STARTERS.md +21 -1
  197. package/docs/STATIC.md +94 -0
  198. package/docs/TOOLING.md +295 -0
  199. package/docs/TUNNELS.md +0 -3
  200. package/docs/TYPESCRIPT-AUTHORING.md +82 -0
  201. package/docs/TYPESCRIPT.md +25 -4
  202. package/docs/USABILITY-REVIEW.md +129 -0
  203. package/docs/VERCEL.md +4 -5
  204. package/docs/VERSION-ALIGNMENT.md +205 -0
  205. package/docs/YAML-GUIDE.md +15 -479
  206. package/docs/YAML-REFERENCE.md +143 -22
  207. package/docs/policies/agents.md +1 -1
  208. package/docs/policies/cache.md +13 -0
  209. package/docs/policies/contract.md +52 -0
  210. package/docs/policies/hardened.md +56 -0
  211. package/docs/policies/interoperability.md +169 -0
  212. package/docs/policies/operations.md +45 -0
  213. package/docs/yaml/assets.md +36 -0
  214. package/docs/yaml/conditions.md +20 -0
  215. package/docs/yaml/functions.md +160 -0
  216. package/docs/yaml/middleware.md +29 -0
  217. package/docs/yaml/organization.md +74 -0
  218. package/docs/yaml/policies.md +37 -0
  219. package/docs/yaml/redirects.md +64 -0
  220. package/docs/yaml/responses.md +57 -0
  221. package/docs/yaml/site.md +24 -0
  222. package/examples/assets/example.yaml +17 -0
  223. package/examples/aws/example.yaml +20 -0
  224. package/examples/cloudflare/example.yaml +19 -0
  225. package/examples/compliance/example.yaml +11 -0
  226. package/examples/conditions/README.md +12 -0
  227. package/examples/conditions/example.yaml +19 -0
  228. package/examples/conditions/tests/requests.json +13 -0
  229. package/examples/conditions/urlcode.yaml +24 -0
  230. package/examples/cookbook/README.md +8 -4
  231. package/examples/cookbook/example.yaml +17 -0
  232. package/examples/cookbook/functions/catalog.mjs +3 -0
  233. package/examples/cookbook/functions/fail.mjs +4 -0
  234. package/examples/cookbook/functions/items.mjs +3 -0
  235. package/examples/cookbook/functions/profile.mjs +3 -0
  236. package/examples/cookbook/functions/resource.mjs +3 -0
  237. package/examples/cookbook/functions/status.mjs +3 -0
  238. package/examples/cookbook/middleware/auth.mjs +48 -0
  239. package/examples/cookbook/middleware/body.mjs +15 -0
  240. package/examples/cookbook/middleware/bucket.mjs +29 -0
  241. package/examples/cookbook/middleware/cors.mjs +21 -0
  242. package/examples/cookbook/middleware/debug.mjs +13 -0
  243. package/examples/cookbook/middleware/envelope.mjs +11 -0
  244. package/examples/cookbook/middleware/errors.mjs +11 -0
  245. package/examples/cookbook/middleware/etag.mjs +18 -0
  246. package/examples/cookbook/middleware/locale.mjs +20 -0
  247. package/examples/cookbook/middleware/maintenance.mjs +10 -0
  248. package/examples/cookbook/middleware/methods.mjs +15 -0
  249. package/examples/cookbook/middleware/negotiate.mjs +20 -0
  250. package/examples/cookbook/middleware/referer.mjs +12 -0
  251. package/examples/cookbook/middleware/request-id.mjs +16 -0
  252. package/examples/cookbook/route-index.json +676 -0
  253. package/examples/cookbook/routes/middleware.yaml +126 -0
  254. package/examples/cookbook/tests/requests.json +526 -0
  255. package/examples/cookbook/urlcode.yaml +1 -0
  256. package/examples/egress/README.md +22 -0
  257. package/examples/egress/example.yaml +19 -0
  258. package/examples/egress/urlcode.yaml +19 -0
  259. package/examples/extensions/README.md +7 -0
  260. package/examples/extensions/example.yaml +21 -0
  261. package/examples/extensions/urlcode.yaml +25 -0
  262. package/examples/monitoring/example.yaml +8 -0
  263. package/examples/prerender/README.md +2 -2
  264. package/examples/prerender/example.yaml +16 -0
  265. package/examples/provider-conformance/README.md +12 -0
  266. package/examples/provider-conformance/example.yaml +14 -0
  267. package/examples/provider-conformance/urlcode.yaml +34 -0
  268. package/examples/tunnel/example.yaml +8 -0
  269. package/examples/vercel/example.yaml +19 -0
  270. package/llms-full.txt +3084 -0
  271. package/llms.txt +61 -21
  272. package/package.json +36 -7
  273. package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
  274. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +122 -0
  275. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +108 -0
  276. package/recipes/authenticated-json-api/README.md +51 -0
  277. package/recipes/authenticated-json-api/functions/profile.mjs +5 -0
  278. package/recipes/authenticated-json-api/recipe.yaml +34 -0
  279. package/recipes/authenticated-json-api/tests/requests.json +39 -0
  280. package/recipes/authenticated-json-api/urlcode.yaml +12 -0
  281. package/recipes/contact-form/README.md +25 -0
  282. package/recipes/contact-form/functions/contact.mjs +17 -0
  283. package/recipes/contact-form/recipe.yaml +33 -0
  284. package/recipes/contact-form/tests/requests.json +47 -0
  285. package/recipes/contact-form/urlcode.yaml +18 -0
  286. package/recipes/cors-api/README.md +16 -0
  287. package/recipes/cors-api/functions/items.mjs +3 -0
  288. package/recipes/cors-api/middleware/cors.mjs +21 -0
  289. package/recipes/cors-api/recipe.yaml +26 -0
  290. package/recipes/cors-api/tests/requests.json +65 -0
  291. package/recipes/cors-api/urlcode.yaml +12 -0
  292. package/recipes/health-page/README.md +13 -0
  293. package/recipes/health-page/recipe.yaml +23 -0
  294. package/recipes/health-page/tests/requests.json +36 -0
  295. package/recipes/health-page/urlcode.yaml +19 -0
  296. package/recipes/json-api/README.md +6 -0
  297. package/recipes/json-api/functions/echo.mjs +3 -0
  298. package/recipes/json-api/recipe.yaml +25 -0
  299. package/recipes/json-api/tests/requests.json +34 -0
  300. package/recipes/json-api/urlcode.yaml +12 -0
  301. package/recipes/middleware/README.md +34 -0
  302. package/recipes/middleware/functions/catalog.mjs +3 -0
  303. package/recipes/middleware/functions/fail.mjs +4 -0
  304. package/recipes/middleware/functions/items.mjs +3 -0
  305. package/recipes/middleware/functions/profile.mjs +3 -0
  306. package/recipes/middleware/functions/resource.mjs +3 -0
  307. package/recipes/middleware/functions/status.mjs +3 -0
  308. package/recipes/middleware/middleware/auth.mjs +48 -0
  309. package/recipes/middleware/middleware/body.mjs +15 -0
  310. package/recipes/middleware/middleware/bucket.mjs +29 -0
  311. package/recipes/middleware/middleware/cors.mjs +21 -0
  312. package/recipes/middleware/middleware/debug.mjs +13 -0
  313. package/recipes/middleware/middleware/envelope.mjs +11 -0
  314. package/recipes/middleware/middleware/errors.mjs +11 -0
  315. package/recipes/middleware/middleware/etag.mjs +18 -0
  316. package/recipes/middleware/middleware/locale.mjs +20 -0
  317. package/recipes/middleware/middleware/maintenance.mjs +10 -0
  318. package/recipes/middleware/middleware/methods.mjs +15 -0
  319. package/recipes/middleware/middleware/negotiate.mjs +20 -0
  320. package/recipes/middleware/middleware/referer.mjs +12 -0
  321. package/recipes/middleware/middleware/request-id.mjs +16 -0
  322. package/recipes/middleware/public/guide.txt +1 -0
  323. package/recipes/middleware/recipe.yaml +50 -0
  324. package/recipes/middleware/tests/requests.json +528 -0
  325. package/recipes/middleware/urlcode.yaml +127 -0
  326. package/recipes/protected-download/README.md +22 -0
  327. package/recipes/protected-download/files/report.txt +1 -0
  328. package/recipes/protected-download/recipe.yaml +31 -0
  329. package/recipes/protected-download/tests/requests.json +32 -0
  330. package/recipes/protected-download/urlcode.yaml +15 -0
  331. package/recipes/redirect/README.md +7 -0
  332. package/recipes/redirect/recipe.yaml +25 -0
  333. package/recipes/redirect/tests/requests.json +19 -0
  334. package/recipes/redirect/urlcode.yaml +9 -0
  335. package/recipes/static-plus-api/README.md +15 -0
  336. package/recipes/static-plus-api/functions/info.mjs +3 -0
  337. package/recipes/static-plus-api/public/assets/index.html +3 -0
  338. package/recipes/static-plus-api/public/assets/site.css +1 -0
  339. package/recipes/static-plus-api/public/index.html +8 -0
  340. package/recipes/static-plus-api/recipe.yaml +29 -0
  341. package/recipes/static-plus-api/tests/requests.json +56 -0
  342. package/recipes/static-plus-api/urlcode.yaml +23 -0
  343. package/recipes/typescript/README.md +8 -0
  344. package/recipes/typescript/functions/hello.ts +5 -0
  345. package/recipes/typescript/recipe.yaml +23 -0
  346. package/recipes/typescript/tests/requests.json +18 -0
  347. package/recipes/typescript/urlcode.yaml +5 -0
  348. package/recipes/webhook-receiver/README.md +20 -0
  349. package/recipes/webhook-receiver/functions/receive.mjs +16 -0
  350. package/recipes/webhook-receiver/recipe.yaml +27 -0
  351. package/recipes/webhook-receiver/tests/requests.json +59 -0
  352. package/recipes/webhook-receiver/urlcode.yaml +23 -0
  353. package/schemas/recipe.schema.json +139 -0
  354. package/schemas/urlcode.schema.json +659 -110
  355. package/skills/urlcode/SKILL.md +119 -0
  356. package/starters/default/.github/workflows/urlcode.yml +23 -0
  357. package/starters/default/.mcp.json +12 -0
  358. package/starters/default/AGENTS.md +79 -0
  359. package/starters/default/urlcode.yaml +0 -1
  360. package/dist/link-api.js +0 -136
  361. package/dist/link-cli.js +0 -141
  362. package/dist/link-events.js +0 -76
  363. package/dist/link-records.js +0 -31
  364. package/dist/link-store-worker.js +0 -150
  365. package/dist/link-store.js +0 -250
  366. package/dist/management-policy.js +0 -41
  367. package/dist/sqlite-version.js +0 -6
  368. package/dist/types/link-api.d.ts +0 -30
  369. package/dist/types/link-cli.d.ts +0 -36
  370. package/dist/types/link-events.d.ts +0 -27
  371. package/dist/types/link-records.d.ts +0 -11
  372. package/dist/types/link-store-worker.d.ts +0 -1
  373. package/dist/types/link-store.d.ts +0 -130
  374. package/dist/types/management-policy.d.ts +0 -9
  375. package/dist/types/sqlite-version.d.ts +0 -1
  376. package/docs/DYNAMIC-LINKS.md +0 -561
  377. package/docs/MANAGEMENT-SECURITY.md +0 -82
  378. package/examples/live-links/README.md +0 -11
  379. package/examples/live-links/tests/requests.json +0 -6
  380. 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();
@@ -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
- const refused = [];
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
- if (!compilablePolicies.has(name) || support !== 'compiled') refused.push(`${route.pattern}: policies.${name} cannot be compiled for this target`);
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
+ }