@jimhoyd/urlcode 0.3.0

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 (254) hide show
  1. package/CONTRIBUTING.md +80 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +20 -0
  4. package/README.md +342 -0
  5. package/ROADMAP.md +248 -0
  6. package/SECURITY.md +40 -0
  7. package/data/agents/LICENSES/ai-robots-txt.txt +24 -0
  8. package/data/agents/LICENSES/crawler-user-agents.txt +23 -0
  9. package/data/agents/ai-crawlers.json +1240 -0
  10. package/data/agents/crawlers.json +10515 -0
  11. package/data/agents/index.d.ts +3 -0
  12. package/data/agents/index.js +2453 -0
  13. package/data/agents/monitoring.json +1520 -0
  14. package/data/agents/seo.json +3865 -0
  15. package/dist/BUILD-MANIFEST.json +60 -0
  16. package/dist/adapters.js +59 -0
  17. package/dist/agent-lists.js +59 -0
  18. package/dist/assets.js +129 -0
  19. package/dist/authoring.js +68 -0
  20. package/dist/aws.js +110 -0
  21. package/dist/build-cloudflare.js +164 -0
  22. package/dist/cli.js +185 -0
  23. package/dist/client-address.js +85 -0
  24. package/dist/cloudflare.js +165 -0
  25. package/dist/compliance-rules/baseline.js +98 -0
  26. package/dist/compliance-rules/privacy.js +38 -0
  27. package/dist/compliance-rules/shared.js +62 -0
  28. package/dist/compliance-rules/strict.js +61 -0
  29. package/dist/compliance.js +219 -0
  30. package/dist/config-worker.js +9 -0
  31. package/dist/config.js +172 -0
  32. package/dist/errors.js +8 -0
  33. package/dist/function-sources.js +54 -0
  34. package/dist/function-worker.js +105 -0
  35. package/dist/functions.js +174 -0
  36. package/dist/guest-api.js +114 -0
  37. package/dist/header-validation.js +18 -0
  38. package/dist/http-policy.js +74 -0
  39. package/dist/http-response.js +84 -0
  40. package/dist/index.js +11 -0
  41. package/dist/link-api.js +136 -0
  42. package/dist/link-cli.js +141 -0
  43. package/dist/link-events.js +76 -0
  44. package/dist/link-records.js +31 -0
  45. package/dist/link-store-worker.js +150 -0
  46. package/dist/link-store.js +250 -0
  47. package/dist/logging.js +22 -0
  48. package/dist/management-policy.js +41 -0
  49. package/dist/match.js +124 -0
  50. package/dist/observability.js +242 -0
  51. package/dist/plugins.js +74 -0
  52. package/dist/policies/agents.js +248 -0
  53. package/dist/policies/cache.js +297 -0
  54. package/dist/policies/compression.js +187 -0
  55. package/dist/policies/security.js +0 -0
  56. package/dist/policies/throttle.js +131 -0
  57. package/dist/policies.js +142 -0
  58. package/dist/policy.js +55 -0
  59. package/dist/prerender.js +165 -0
  60. package/dist/project-tests.js +23 -0
  61. package/dist/readiness.js +216 -0
  62. package/dist/router.js +166 -0
  63. package/dist/runtime.js +244 -0
  64. package/dist/scaffold.js +0 -0
  65. package/dist/scripts/operational-drills.js +75 -0
  66. package/dist/server.js +270 -0
  67. package/dist/site.js +0 -0
  68. package/dist/sqlite-version.js +6 -0
  69. package/dist/types/adapters.d.ts +12 -0
  70. package/dist/types/agent-lists.d.ts +5 -0
  71. package/dist/types/assets.d.ts +13 -0
  72. package/dist/types/authoring.d.ts +2 -0
  73. package/dist/types/aws.d.ts +35 -0
  74. package/dist/types/build-cloudflare.d.ts +14 -0
  75. package/dist/types/cli.d.ts +2 -0
  76. package/dist/types/client-address.d.ts +9 -0
  77. package/dist/types/cloudflare.d.ts +75 -0
  78. package/dist/types/compliance-rules/baseline.d.ts +12 -0
  79. package/dist/types/compliance-rules/privacy.d.ts +6 -0
  80. package/dist/types/compliance-rules/shared.d.ts +24 -0
  81. package/dist/types/compliance-rules/strict.d.ts +9 -0
  82. package/dist/types/compliance.d.ts +119 -0
  83. package/dist/types/config-worker.d.ts +1 -0
  84. package/dist/types/config.d.ts +21 -0
  85. package/dist/types/errors.d.ts +7 -0
  86. package/dist/types/function-sources.d.ts +21 -0
  87. package/dist/types/function-worker.d.ts +1 -0
  88. package/dist/types/functions.d.ts +92 -0
  89. package/dist/types/guest-api.d.ts +16 -0
  90. package/dist/types/header-validation.d.ts +2 -0
  91. package/dist/types/http-policy.d.ts +39 -0
  92. package/dist/types/http-response.d.ts +43 -0
  93. package/dist/types/index.d.ts +11 -0
  94. package/dist/types/link-api.d.ts +30 -0
  95. package/dist/types/link-cli.d.ts +36 -0
  96. package/dist/types/link-events.d.ts +27 -0
  97. package/dist/types/link-records.d.ts +11 -0
  98. package/dist/types/link-store-worker.d.ts +1 -0
  99. package/dist/types/link-store.d.ts +130 -0
  100. package/dist/types/logging.d.ts +9 -0
  101. package/dist/types/management-policy.d.ts +9 -0
  102. package/dist/types/match.d.ts +81 -0
  103. package/dist/types/observability.d.ts +104 -0
  104. package/dist/types/plugins.d.ts +27 -0
  105. package/dist/types/policies/agents.d.ts +86 -0
  106. package/dist/types/policies/cache.d.ts +82 -0
  107. package/dist/types/policies/compression.d.ts +41 -0
  108. package/dist/types/policies/security.d.ts +37 -0
  109. package/dist/types/policies/throttle.d.ts +55 -0
  110. package/dist/types/policies.d.ts +36 -0
  111. package/dist/types/policy.d.ts +21 -0
  112. package/dist/types/prerender.d.ts +31 -0
  113. package/dist/types/project-tests.d.ts +13 -0
  114. package/dist/types/readiness.d.ts +121 -0
  115. package/dist/types/router.d.ts +11 -0
  116. package/dist/types/runtime.d.ts +91 -0
  117. package/dist/types/scaffold.d.ts +17 -0
  118. package/dist/types/server.d.ts +37 -0
  119. package/dist/types/site.d.ts +12 -0
  120. package/dist/types/sqlite-version.d.ts +1 -0
  121. package/dist/types/types.d.ts +332 -0
  122. package/dist/types/vercel.d.ts +12 -0
  123. package/dist/types.js +177 -0
  124. package/dist/vercel.js +65 -0
  125. package/docs/AI-AUTHORING.md +113 -0
  126. package/docs/ASSETS.md +106 -0
  127. package/docs/AWS.md +77 -0
  128. package/docs/BEST-PRACTICES.md +267 -0
  129. package/docs/CAPACITY.md +208 -0
  130. package/docs/CLOUDFLARE.md +110 -0
  131. package/docs/COMPLIANCE.md +242 -0
  132. package/docs/DYNAMIC-LINKS.md +561 -0
  133. package/docs/FUNCTION-SECURITY.md +113 -0
  134. package/docs/HTTP.md +129 -0
  135. package/docs/INSTALL.md +90 -0
  136. package/docs/LOAD-TESTING.md +91 -0
  137. package/docs/LOCAL-DEVELOPMENT.md +99 -0
  138. package/docs/MANAGEMENT-SECURITY.md +82 -0
  139. package/docs/MIDDLEWARE.md +83 -0
  140. package/docs/MONITORING.md +132 -0
  141. package/docs/OBSERVABILITY.md +229 -0
  142. package/docs/OPERATIONAL-PROOF.md +42 -0
  143. package/docs/OPERATIONS.md +207 -0
  144. package/docs/ORGANIZATION.md +135 -0
  145. package/docs/PERFORMANCE.md +72 -0
  146. package/docs/PLUGINS.md +234 -0
  147. package/docs/POLICIES.md +489 -0
  148. package/docs/PRERENDER.md +193 -0
  149. package/docs/PROJECT-DIRECTION.md +51 -0
  150. package/docs/READINESS.md +125 -0
  151. package/docs/README.md +58 -0
  152. package/docs/RELEASE-READINESS.md +95 -0
  153. package/docs/RELEASE-SECURITY.md +72 -0
  154. package/docs/RESILIENCE.md +160 -0
  155. package/docs/ROUTING.md +95 -0
  156. package/docs/SANDBOX-REVIEW.md +59 -0
  157. package/docs/SCAFFOLDING.md +72 -0
  158. package/docs/SECURITY-AUDIT.md +124 -0
  159. package/docs/SITE.md +150 -0
  160. package/docs/SPECIFICATION.md +232 -0
  161. package/docs/SPIKE-EXTENSIONS.md +475 -0
  162. package/docs/STANDARDS.md +303 -0
  163. package/docs/STARTERS.md +51 -0
  164. package/docs/TUNNELS.md +75 -0
  165. package/docs/TYPESCRIPT.md +102 -0
  166. package/docs/VERCEL.md +107 -0
  167. package/docs/YAML-GUIDE.md +521 -0
  168. package/docs/YAML-REFERENCE.md +328 -0
  169. package/docs/policies/agents.md +182 -0
  170. package/docs/policies/cache.md +139 -0
  171. package/docs/policies/compression.md +168 -0
  172. package/docs/policies/security.md +160 -0
  173. package/docs/policies/throttle.md +103 -0
  174. package/examples/assets/.env.example +1 -0
  175. package/examples/assets/.gitattributes +2 -0
  176. package/examples/assets/Makefile +30 -0
  177. package/examples/assets/README.md +18 -0
  178. package/examples/assets/functions/hello.mjs +3 -0
  179. package/examples/assets/gitignore.template +7 -0
  180. package/examples/assets/public/about.html +2 -0
  181. package/examples/assets/public/assets/example.txt +1 -0
  182. package/examples/assets/public/guide.txt +1 -0
  183. package/examples/assets/tests/requests.json +44 -0
  184. package/examples/assets/urlcode.yaml +27 -0
  185. package/examples/aws/README.md +13 -0
  186. package/examples/aws/handler.mjs +5 -0
  187. package/examples/aws/public/index.html +3 -0
  188. package/examples/aws/public/notes.txt +1 -0
  189. package/examples/aws/template.yaml +33 -0
  190. package/examples/aws/tests/requests.json +9 -0
  191. package/examples/aws/urlcode.yaml +19 -0
  192. package/examples/cloudflare/README.md +18 -0
  193. package/examples/cloudflare/package.json +14 -0
  194. package/examples/cloudflare/tests/requests.json +9 -0
  195. package/examples/cloudflare/urlcode.yaml +24 -0
  196. package/examples/cloudflare/wrangler.toml +5 -0
  197. package/examples/compliance/README.md +20 -0
  198. package/examples/compliance/rules.mjs +65 -0
  199. package/examples/cookbook/README.md +21 -0
  200. package/examples/cookbook/functions/choice.mjs +4 -0
  201. package/examples/cookbook/functions/echo.mjs +3 -0
  202. package/examples/cookbook/functions/hello.mjs +3 -0
  203. package/examples/cookbook/functions/text.mjs +3 -0
  204. package/examples/cookbook/middleware/headers.mjs +6 -0
  205. package/examples/cookbook/public/about.html +1 -0
  206. package/examples/cookbook/public/assets/index.html +1 -0
  207. package/examples/cookbook/public/assets/site.css +1 -0
  208. package/examples/cookbook/public/favicon.svg +1 -0
  209. package/examples/cookbook/public/guide.txt +1 -0
  210. package/examples/cookbook/public/llms.txt +6 -0
  211. package/examples/cookbook/routes/code.yaml +55 -0
  212. package/examples/cookbook/routes/files.yaml +17 -0
  213. package/examples/cookbook/routes/policies.yaml +28 -0
  214. package/examples/cookbook/routes/redirects.yaml +38 -0
  215. package/examples/cookbook/routes/responses.yaml +26 -0
  216. package/examples/cookbook/tests/requests.json +254 -0
  217. package/examples/cookbook/urlcode.yaml +22 -0
  218. package/examples/live-links/README.md +11 -0
  219. package/examples/live-links/tests/requests.json +6 -0
  220. package/examples/live-links/urlcode.yaml +16 -0
  221. package/examples/monitoring/blackbox-jobs.yaml +37 -0
  222. package/examples/monitoring/prometheus-rules.yaml +88 -0
  223. package/examples/monitoring/prometheus-scrape.yaml +20 -0
  224. package/examples/monitoring/vector.toml +65 -0
  225. package/examples/prerender/README.md +48 -0
  226. package/examples/prerender/functions/page.mjs +6 -0
  227. package/examples/prerender/middleware/template.mjs +29 -0
  228. package/examples/prerender/prerender.d.mts +4 -0
  229. package/examples/prerender/prerender.mjs +63 -0
  230. package/examples/prerender/tests/requests.json +39 -0
  231. package/examples/prerender/urlcode.yaml +50 -0
  232. package/examples/tunnel/dev-with-ngrok.sh +61 -0
  233. package/examples/vercel/README.md +13 -0
  234. package/examples/vercel/api/index.js +5 -0
  235. package/examples/vercel/package.json +8 -0
  236. package/examples/vercel/public/index.html +3 -0
  237. package/examples/vercel/public/notes.txt +1 -0
  238. package/examples/vercel/tests/requests.json +48 -0
  239. package/examples/vercel/urlcode.yaml +19 -0
  240. package/examples/vercel/vercel.json +10 -0
  241. package/llms.txt +46 -0
  242. package/package.json +123 -0
  243. package/schemas/urlcode.schema.json +1230 -0
  244. package/starters/default/.gitattributes +1 -0
  245. package/starters/default/Makefile +30 -0
  246. package/starters/default/README.md +34 -0
  247. package/starters/default/functions/hello.mjs +3 -0
  248. package/starters/default/gitignore.template +8 -0
  249. package/starters/default/middleware/headers.mjs +6 -0
  250. package/starters/default/routes/functions.yaml +20 -0
  251. package/starters/default/routes/marketing/links.yaml +7 -0
  252. package/starters/default/starter.json +5 -0
  253. package/starters/default/tests/requests.json +56 -0
  254. package/starters/default/urlcode.yaml +8 -0
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ // Prerender recipe: render this project's function/middleware routes once, at
3
+ // build time, into a native page project that serves the same bytes with no
4
+ // guest execution on the request path.
5
+ //
6
+ // The orchestration that is easy to get wrong — runtime lifecycle, response
7
+ // validation, budgets, byte fidelity and output-path safety — lives in the
8
+ // runtime's build helper. What is left here is the part every site does
9
+ // differently: assembling a project from the rendered pages. In your own
10
+ // project, import the helper from the package:
11
+ //
12
+ // import {prerenderPages, assertNativeProject} from '@jimhoyd/urlcode/prerender';
13
+ //
14
+ import {mkdir, writeFile, rm} from 'node:fs/promises';
15
+ import {realpathSync} from 'node:fs';
16
+ import {join, resolve} from 'node:path';
17
+ import {fileURLToPath, pathToFileURL} from 'node:url';
18
+ import {stringify} from 'yaml';
19
+ import {prerenderPages, assertNativeProject} from '@jimhoyd/urlcode/prerender';
20
+
21
+ export async function prerender(project, output, {log = () => {}} = {}) {
22
+ const out = resolve(output);
23
+ // Pages are rendered and written first; nothing below runs unless every one
24
+ // of them produced a complete HTML response.
25
+ const rendered = await prerenderPages(project, join(out, 'public'), {log});
26
+
27
+ // Assemble the serving project. A larger site does this differently: it adds
28
+ // static and download routes, response security headers and its own entry
29
+ // point, and may write a generated include instead of a whole project.
30
+ await writeFile(join(out, 'urlcode.yaml'), stringify({
31
+ version: '1',
32
+ routes: Object.fromEntries(rendered.pages.map(page =>
33
+ [page.path, {page: {file: `public/${page.file}`, cacheControl: 'no-cache'}}])),
34
+ }));
35
+ await mkdir(join(out, 'tests'), {recursive: true});
36
+ await writeFile(join(out, 'tests/requests.json'), JSON.stringify(rendered.fixtures, null, 2) + '\n');
37
+
38
+ // Prove the artifact is inert before anyone deploys it.
39
+ await assertNativeProject(out, {allow: ['page'], log});
40
+ return {pages: rendered.count, bytes: rendered.bytes, output: out};
41
+ }
42
+
43
+ // Node resolves a module's own URL through symlinks, so comparing it to a raw
44
+ // argv[1] misses when this file is reached through one — as it is under macOS's
45
+ // /var -> /private/var temporary directories, where the script would otherwise
46
+ // exit 0 having silently done nothing.
47
+ const invokedDirectly = () => {
48
+ try { return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href; }
49
+ catch { return false; }
50
+ };
51
+ if (process.argv[1] && invokedDirectly()) {
52
+ const [project = fileURLToPath(new URL('.', import.meta.url)), output = 'dist'] = process.argv.slice(2);
53
+ const print = value => process.stdout.write(JSON.stringify(value) + '\n');
54
+ try {
55
+ // The helper refuses to write into an existing directory, so a repeated
56
+ // build clears its own output rather than merging into a stale one.
57
+ await rm(resolve(output), {recursive: true, force: true});
58
+ print({event: 'prerendered-project', ...await prerender(project, output, {log: print})});
59
+ } catch (error) {
60
+ process.stderr.write(JSON.stringify({event: 'error', message: error.message}) + '\n');
61
+ process.exitCode = 1;
62
+ }
63
+ }
@@ -0,0 +1,39 @@
1
+ [
2
+ {
3
+ "path": "/",
4
+ "status": 200,
5
+ "expectHeaders": {"content-type": "text/html; charset=utf-8"}
6
+ },
7
+ {
8
+ "path": "/",
9
+ "method": "HEAD",
10
+ "status": 200,
11
+ "expectBody": ""
12
+ },
13
+ {
14
+ "path": "/guide",
15
+ "status": 200,
16
+ "expectHeaders": {"content-type": "text/html; charset=utf-8"}
17
+ },
18
+ {
19
+ "path": "/guide",
20
+ "method": "HEAD",
21
+ "status": 200,
22
+ "expectBody": ""
23
+ },
24
+ {
25
+ "path": "/about",
26
+ "status": 200,
27
+ "expectHeaders": {"content-type": "text/html; charset=utf-8"}
28
+ },
29
+ {
30
+ "path": "/about",
31
+ "method": "HEAD",
32
+ "status": 200,
33
+ "expectBody": ""
34
+ },
35
+ {
36
+ "path": "/missing",
37
+ "status": 404
38
+ }
39
+ ]
@@ -0,0 +1,50 @@
1
+ version: "1"
2
+ # The dynamic source of the site. Every page is a function response wrapped by
3
+ # one shared template middleware, so `dev` serves it live while you edit. The
4
+ # prerender recipe renders these routes once and emits a native page project.
5
+ routes:
6
+ /:
7
+ description: Home page rendered by a function and the shared template
8
+ middleware:
9
+ - source: middleware/template.mjs
10
+ export: template
11
+ function:
12
+ source: functions/page.mjs
13
+ export: page
14
+ args:
15
+ title: URLCode prerender
16
+ body: |
17
+ <p>This page is generated by a function and wrapped by shared template
18
+ middleware. Running the prerender recipe turns it into a native page
19
+ route that serves the same bytes with no sandbox on the request path.</p>
20
+ <ul>
21
+ <li><a href="/guide">Guide</a></li>
22
+ <li><a href="/about">About</a></li>
23
+ </ul>
24
+ /guide:
25
+ description: A second page reusing the same function and template
26
+ middleware:
27
+ - source: middleware/template.mjs
28
+ export: template
29
+ function:
30
+ source: functions/page.mjs
31
+ export: page
32
+ args:
33
+ title: Guide
34
+ body: |
35
+ <p>Content is reviewed literal input in YAML. Guest code never reads the
36
+ filesystem, so a larger site generates this project from host-prepared
37
+ content before rendering.</p>
38
+ /about:
39
+ description: A third page proving the template is shared, not copied
40
+ middleware:
41
+ - source: middleware/template.mjs
42
+ export: template
43
+ function:
44
+ source: functions/page.mjs
45
+ export: page
46
+ args:
47
+ title: About & "quoting"
48
+ body: |
49
+ <p>The title above is escaped by the template, so page data cannot inject
50
+ markup into the surrounding document.</p>
@@ -0,0 +1,61 @@
1
+ #!/bin/sh
2
+ # Start `urlcode dev` with the public origin of an ngrok tunnel that is already
3
+ # running, so functions, redirects and absolute URLs see the address a visitor
4
+ # actually used rather than 127.0.0.1.
5
+ #
6
+ # Terminal 1: ngrok http 3000
7
+ # Terminal 2: PROJECT=. ./dev-with-ngrok.sh
8
+ #
9
+ # This script discovers a tunnel; it does not start, stop or configure ngrok.
10
+ # That keeps it honest about what it controls and leaves the tunnel's own
11
+ # authentication and access policy where it belongs: in ngrok.
12
+ set -eu
13
+
14
+ PROJECT=${PROJECT:-.}
15
+ PORT=${PORT:-3000}
16
+ URLCODE=${URLCODE:-urlcode}
17
+ # The ngrok agent's local inspection API. Overridable for a different agent port
18
+ # or for this repository's test, which serves a recorded response.
19
+ NGROK_API=${URLCODE_NGROK_API:-http://127.0.0.1:4040/api/tunnels}
20
+ PRINT_ONLY=0
21
+
22
+ while [ $# -gt 0 ]; do
23
+ case "$1" in
24
+ --print) PRINT_ONLY=1; shift ;;
25
+ --help|-h) sed -n '2,11p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
26
+ *) echo "dev-with-ngrok: unknown option $1" >&2; exit 2 ;;
27
+ esac
28
+ done
29
+
30
+ command -v node >/dev/null 2>&1 || { echo "dev-with-ngrok: node is required" >&2; exit 1; }
31
+
32
+ # Select the HTTPS tunnel that forwards to this port. Anything else is a
33
+ # different tunnel on the same agent and must not be guessed at.
34
+ ORIGIN=$(NGROK_API="$NGROK_API" PORT="$PORT" node -e '
35
+ const api = process.env.NGROK_API, port = process.env.PORT;
36
+ fetch(api, {headers:{accept:"application/json"}})
37
+ .then(r => r.ok ? r.json() : Promise.reject(new Error("HTTP " + r.status)))
38
+ .then(body => {
39
+ const tunnels = Array.isArray(body?.tunnels) ? body.tunnels : [];
40
+ const match = tunnels.find(t =>
41
+ typeof t?.public_url === "string" && t.public_url.startsWith("https://") &&
42
+ typeof t?.config?.addr === "string" && new RegExp(`(^|[^0-9])${port}$`).test(t.config.addr));
43
+ if (!match) throw new Error(`no https tunnel forwarding to port ${port}`);
44
+ const url = new URL(match.public_url);
45
+ if (url.origin !== match.public_url.replace(/\/$/,"")) throw new Error("tunnel URL is not a bare origin");
46
+ process.stdout.write(url.origin);
47
+ })
48
+ .catch(e => { console.error("dev-with-ngrok: " + e.message); process.exit(1); });
49
+ ') || {
50
+ echo "dev-with-ngrok: could not read a tunnel from $NGROK_API" >&2
51
+ echo " start one first, e.g. ngrok http $PORT" >&2
52
+ exit 1
53
+ }
54
+
55
+ echo "dev-with-ngrok: public origin $ORIGIN"
56
+ if [ "$PRINT_ONLY" -eq 1 ]; then exit 0; fi
57
+
58
+ # dev loads .env.local and watches files. A tunnel makes that reachable from the
59
+ # internet: expose a development project only deliberately, and never tunnel the
60
+ # separate link-management API.
61
+ exec "$URLCODE" dev --project "$PROJECT" --port "$PORT" --origin "$ORIGIN"
@@ -0,0 +1,13 @@
1
+ # URLCode on Vercel
2
+
3
+ A native-handler project deployed as a Vercel Node function. See
4
+ [the adapter guide](../../docs/VERCEL.md) for what is and is not supported.
5
+
6
+ ```sh
7
+ urlcode test --project . # the same assertions run locally
8
+ vercel deploy
9
+ ```
10
+
11
+ `vercel.json` rewrites every path to `api/index.js`, which serves the project
12
+ with `createVercelHandler`. `includeFiles` must list every file the project
13
+ reads; add to it when you add a page, download or static directory.
@@ -0,0 +1,5 @@
1
+ // Every request is rewritten here by vercel.json. The handler is created once
2
+ // per instance and reused across warm invocations.
3
+ import { createVercelHandler } from '@jimhoyd/urlcode/vercel';
4
+
5
+ export default createVercelHandler({ project: process.cwd() });
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "urlcode-vercel-example",
3
+ "private": true,
4
+ "type": "module",
5
+ "dependencies": {
6
+ "@jimhoyd/urlcode": "^0.3.0"
7
+ }
8
+ }
@@ -0,0 +1,3 @@
1
+ <!doctype html>
2
+ <html lang="en"><meta charset="utf-8"><title>URLCode on Vercel</title>
3
+ <body><h1>URLCode on Vercel</h1><p>Native handlers served by the runtime adapter.</p></body>
@@ -0,0 +1 @@
1
+ Served by the static handler through the Vercel adapter.
@@ -0,0 +1,48 @@
1
+ [
2
+ {
3
+ "path": "/go",
4
+ "status": 302,
5
+ "expectHeaders": {
6
+ "location": "https://example.com/target"
7
+ }
8
+ },
9
+ {
10
+ "path": "/u/42",
11
+ "status": 302,
12
+ "expectHeaders": {
13
+ "location": "https://example.com/users/42"
14
+ }
15
+ },
16
+ {
17
+ "path": "/health",
18
+ "status": 200,
19
+ "expectBody": "{\"ok\":true}"
20
+ },
21
+ {
22
+ "path": "/",
23
+ "status": 200,
24
+ "expectHeaders": {
25
+ "content-type": "text/html; charset=utf-8"
26
+ }
27
+ },
28
+ {
29
+ "path": "/files/notes.txt",
30
+ "status": 200,
31
+ "expectHeaders": {
32
+ "content-type": "text/plain; charset=utf-8"
33
+ }
34
+ },
35
+ {
36
+ "path": "/nothing-here",
37
+ "status": 404
38
+ },
39
+ {
40
+ "path": "/u/42",
41
+ "method": "HEAD",
42
+ "status": 302,
43
+ "expectHeaders": {
44
+ "location": "https://example.com/users/42"
45
+ },
46
+ "expectBody": ""
47
+ }
48
+ ]
@@ -0,0 +1,19 @@
1
+ version: "1"
2
+ routes:
3
+ /go:
4
+ redirect:
5
+ url: https://example.com/target
6
+ /u/{id}:
7
+ parameters:
8
+ - {name: id, in: path, required: true, schema: {type: string}}
9
+ redirect:
10
+ url: https://example.com/users/{id}
11
+ /health:
12
+ respond:
13
+ json: {ok: true}
14
+ /:
15
+ page:
16
+ file: public/index.html
17
+ /files/*:
18
+ static:
19
+ directory: public
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://openapi.vercel.sh/vercel.json",
3
+ "functions": {
4
+ "api/index.js": {
5
+ "runtime": "nodejs22.x",
6
+ "includeFiles": "{urlcode.yaml,routes/**,public/**}"
7
+ }
8
+ },
9
+ "rewrites": [{ "source": "/(.*)", "destination": "/api" }]
10
+ }
package/llms.txt ADDED
@@ -0,0 +1,46 @@
1
+ # URLCode
2
+
3
+ > Portable YAML-defined URLs that run code. Current contract: 0.1.0,
4
+ > stable project format version "1". Local/self-hosted first; provider adapters are future.
5
+
6
+ Use the schema and docs from the same runtime revision. Do not assume Node/fetch,
7
+ regex routes, database access, global middleware or arbitrary YAML interpolation.
8
+ The runtime is licensed under Apache-2.0. Secrets require external revision-pinned grants.
9
+
10
+ ## Authoring
11
+ - [AI authoring contract](docs/AI-AUTHORING.md): workflow, capability matrix, checks.
12
+ - [YAML guide](docs/YAML-GUIDE.md): recipes for all handlers and common options.
13
+ - [JSON Schema](schemas/urlcode.schema.json): accepted fields and types.
14
+ - [Field reference](docs/YAML-REFERENCE.md): generated exhaustive field inventory.
15
+ - [Semantics](docs/SPECIFICATION.md): validation, defaults and sandbox API.
16
+ - [Runnable cookbook](examples/cookbook/README.md): 25 routes with HTTP fixtures.
17
+ - [Dynamic links](docs/DYNAMIC-LINKS.md): optional SQLite, live mutations, management API and limits.
18
+ - [Route matching](docs/ROUTING.md): precedence, non-greedy parameters, updates.
19
+ - [Middleware](docs/MIDDLEWARE.md): next(), state, ordering and native body limits.
20
+ - [Policies](docs/POLICIES.md): optional host-enforced `policies`/`profiles` keys, all off by default: `throttle`, `agents`, `security`, `compression`, `cache`; merge rules and per-target support.
21
+ - [Plugins](docs/PLUGINS.md): host hook API operators pass in code; never named in YAML.
22
+ - [TypeScript](docs/TYPESCRIPT.md): the package ships declarations for every export (`urlcode`, `@jimhoyd/urlcode/plugins`, `@jimhoyd/urlcode/policies`, `@jimhoyd/urlcode/observability`, `@jimhoyd/urlcode/compliance`, `@jimhoyd/urlcode/prerender`, `@jimhoyd/urlcode/aws`, `@jimhoyd/urlcode/vercel`, `@jimhoyd/urlcode/cloudflare`); the runtime source is TypeScript, `dist/` is its stripped JavaScript.
23
+ - [HTTP](docs/HTTP.md): methods, request bodies and response headers.
24
+ - [Assets](docs/ASSETS.md): pages, MIME, downloads, cache and ranges.
25
+ - [Site conventions](docs/SITE.md): optional top-level `site` key, all off by default: `robots`, `sitemap`, `favicon`, `securityTxt`, `llms` generate native routes; declared routes win; absolute URLs need `--origin`.
26
+ - [Prerendering](docs/PRERENDER.md): `@jimhoyd/urlcode/prerender` build helper and recipe; render function/middleware routes once into native page routes, no request-time guest code.
27
+ - [Organization](docs/ORGANIZATION.md): entry point and included files.
28
+
29
+ - [Best practices](docs/BEST-PRACTICES.md): layouts, readable YAML/code, testing and refactoring.
30
+
31
+ ## Operations
32
+ - [Security](docs/FUNCTION-SECURITY.md): untrusted code and external binding policy.
33
+ - [Readiness](docs/READINESS.md): fixture coverage and count checks.
34
+ - [Capacity](docs/CAPACITY.md): concurrency, limits and theoretical sizing.
35
+ - [Resilience](docs/RESILIENCE.md): DDoS, overload, incident and recovery plans.
36
+ - [Operations](docs/OPERATIONS.md): deploy, observe, rotate and roll back.
37
+ - [Performance](docs/PERFORMANCE.md): measured results and their limitations.
38
+ - [Roadmap](ROADMAP.md): clearly separates implemented and planned features.
39
+
40
+ - [Release readiness](docs/RELEASE-READINESS.md): verified safeguards, open gates and supported deployment scope.
41
+
42
+ - [Scaffolding](docs/SCAFFOLDING.md): generate missing placeholders from YAML, preserve existing files, fail-closed code stubs.
43
+
44
+ Security hardening: docs/MANAGEMENT-SECURITY.md, docs/SANDBOX-REVIEW.md,
45
+ docs/OPERATIONAL-PROOF.md and docs/RELEASE-SECURITY.md describe private operator
46
+ management, resource limits, acceptance evidence and signed alpha candidates.
package/package.json ADDED
@@ -0,0 +1,123 @@
1
+ {
2
+ "name": "@jimhoyd/urlcode",
3
+ "version": "0.3.0",
4
+ "description": "Portable runtime for programmable URL behavior",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "engines": {
8
+ "node": ">=22.13.0"
9
+ },
10
+ "devEngines": {
11
+ "runtime": {
12
+ "name": "node",
13
+ "version": ">=22.18.0",
14
+ "onFail": "error"
15
+ }
16
+ },
17
+ "bin": {
18
+ "urlcode": "./dist/cli.js"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/types/index.d.ts",
23
+ "development": "./src/index.ts",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./aws": {
27
+ "types": "./dist/types/aws.d.ts",
28
+ "development": "./src/aws.ts",
29
+ "default": "./dist/aws.js"
30
+ },
31
+ "./cloudflare": {
32
+ "types": "./dist/types/cloudflare.d.ts",
33
+ "development": "./src/cloudflare.ts",
34
+ "default": "./dist/cloudflare.js"
35
+ },
36
+ "./prerender": {
37
+ "types": "./dist/types/prerender.d.ts",
38
+ "development": "./src/prerender.ts",
39
+ "default": "./dist/prerender.js"
40
+ },
41
+ "./schema": "./schemas/urlcode.schema.json",
42
+ "./vercel": {
43
+ "types": "./dist/types/vercel.d.ts",
44
+ "development": "./src/vercel.ts",
45
+ "default": "./dist/vercel.js"
46
+ },
47
+ "./plugins": {
48
+ "types": "./dist/types/plugins.d.ts",
49
+ "development": "./src/plugins.ts",
50
+ "default": "./dist/plugins.js"
51
+ },
52
+ "./policies": {
53
+ "types": "./dist/types/policies.d.ts",
54
+ "development": "./src/policies.ts",
55
+ "default": "./dist/policies.js"
56
+ },
57
+ "./compliance": {
58
+ "types": "./dist/types/compliance.d.ts",
59
+ "development": "./src/compliance.ts",
60
+ "default": "./dist/compliance.js"
61
+ },
62
+ "./observability": {
63
+ "types": "./dist/types/observability.d.ts",
64
+ "development": "./src/observability.ts",
65
+ "default": "./dist/observability.js"
66
+ }
67
+ },
68
+ "files": [
69
+ "dist",
70
+ "schemas",
71
+ "data",
72
+ "starters",
73
+ "README.md",
74
+ "docs",
75
+ "SECURITY.md",
76
+ "ROADMAP.md",
77
+ "CONTRIBUTING.md",
78
+ "examples",
79
+ "llms.txt",
80
+ "NOTICE"
81
+ ],
82
+ "scripts": {
83
+ "build": "node --disable-warning=ExperimentalWarning scripts/build.ts",
84
+ "typecheck": "tsc -p tsconfig.json",
85
+ "test": "node --conditions=development --test test/*.test.ts",
86
+ "lint": "eslint .",
87
+ "check": "node scripts/check.ts && node scripts/generate-yaml-reference.ts --check",
88
+ "verify": "npm run lint && npm run typecheck && npm run check && npm run build && npm test",
89
+ "benchmark": "node benchmarks/routing.ts",
90
+ "test:package": "npm run build && node scripts/package-smoke.ts",
91
+ "dev": "node src/cli.ts dev --project starters/default",
92
+ "serve": "node src/cli.ts serve --project starters/default",
93
+ "validate": "node src/cli.ts validate --local --project starters/default",
94
+ "test:project": "node src/cli.ts test --project starters/default",
95
+ "init": "node src/cli.ts init",
96
+ "doctor": "node src/cli.ts doctor",
97
+ "routes": "node src/cli.ts routes --project starters/default",
98
+ "audit:routes": "node src/cli.ts audit --project starters/default",
99
+ "benchmark:project": "node src/cli.ts benchmark --project starters/default",
100
+ "docs:reference": "node scripts/generate-yaml-reference.ts"
101
+ },
102
+ "repository": {
103
+ "type": "git",
104
+ "url": "https://github.com/jimhoyd-com/urlcode.git"
105
+ },
106
+ "dependencies": {
107
+ "ajv": "8.20.0",
108
+ "content-disposition": "3.0.0",
109
+ "es-module-lexer": "3.0.2",
110
+ "mime-types": "3.0.2",
111
+ "quickjs-emscripten": "0.32.0",
112
+ "yaml": "2.9.1"
113
+ },
114
+ "devDependencies": {
115
+ "@eslint/js": "10.0.1",
116
+ "@types/mime-types": "3.0.1",
117
+ "@types/node": "22.20.3",
118
+ "eslint": "10.10.0",
119
+ "globals": "17.12.0",
120
+ "typescript": "5.9.3",
121
+ "typescript-eslint": "8.70.0"
122
+ }
123
+ }