@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.
- package/CONTRIBUTING.md +80 -0
- package/LICENSE +202 -0
- package/NOTICE +20 -0
- package/README.md +342 -0
- package/ROADMAP.md +248 -0
- package/SECURITY.md +40 -0
- package/data/agents/LICENSES/ai-robots-txt.txt +24 -0
- package/data/agents/LICENSES/crawler-user-agents.txt +23 -0
- package/data/agents/ai-crawlers.json +1240 -0
- package/data/agents/crawlers.json +10515 -0
- package/data/agents/index.d.ts +3 -0
- package/data/agents/index.js +2453 -0
- package/data/agents/monitoring.json +1520 -0
- package/data/agents/seo.json +3865 -0
- package/dist/BUILD-MANIFEST.json +60 -0
- package/dist/adapters.js +59 -0
- package/dist/agent-lists.js +59 -0
- package/dist/assets.js +129 -0
- package/dist/authoring.js +68 -0
- package/dist/aws.js +110 -0
- package/dist/build-cloudflare.js +164 -0
- package/dist/cli.js +185 -0
- package/dist/client-address.js +85 -0
- package/dist/cloudflare.js +165 -0
- package/dist/compliance-rules/baseline.js +98 -0
- package/dist/compliance-rules/privacy.js +38 -0
- package/dist/compliance-rules/shared.js +62 -0
- package/dist/compliance-rules/strict.js +61 -0
- package/dist/compliance.js +219 -0
- package/dist/config-worker.js +9 -0
- package/dist/config.js +172 -0
- package/dist/errors.js +8 -0
- package/dist/function-sources.js +54 -0
- package/dist/function-worker.js +105 -0
- package/dist/functions.js +174 -0
- package/dist/guest-api.js +114 -0
- package/dist/header-validation.js +18 -0
- package/dist/http-policy.js +74 -0
- package/dist/http-response.js +84 -0
- package/dist/index.js +11 -0
- package/dist/link-api.js +136 -0
- package/dist/link-cli.js +141 -0
- package/dist/link-events.js +76 -0
- package/dist/link-records.js +31 -0
- package/dist/link-store-worker.js +150 -0
- package/dist/link-store.js +250 -0
- package/dist/logging.js +22 -0
- package/dist/management-policy.js +41 -0
- package/dist/match.js +124 -0
- package/dist/observability.js +242 -0
- package/dist/plugins.js +74 -0
- package/dist/policies/agents.js +248 -0
- package/dist/policies/cache.js +297 -0
- package/dist/policies/compression.js +187 -0
- package/dist/policies/security.js +0 -0
- package/dist/policies/throttle.js +131 -0
- package/dist/policies.js +142 -0
- package/dist/policy.js +55 -0
- package/dist/prerender.js +165 -0
- package/dist/project-tests.js +23 -0
- package/dist/readiness.js +216 -0
- package/dist/router.js +166 -0
- package/dist/runtime.js +244 -0
- package/dist/scaffold.js +0 -0
- package/dist/scripts/operational-drills.js +75 -0
- package/dist/server.js +270 -0
- package/dist/site.js +0 -0
- package/dist/sqlite-version.js +6 -0
- package/dist/types/adapters.d.ts +12 -0
- package/dist/types/agent-lists.d.ts +5 -0
- package/dist/types/assets.d.ts +13 -0
- package/dist/types/authoring.d.ts +2 -0
- package/dist/types/aws.d.ts +35 -0
- package/dist/types/build-cloudflare.d.ts +14 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/client-address.d.ts +9 -0
- package/dist/types/cloudflare.d.ts +75 -0
- package/dist/types/compliance-rules/baseline.d.ts +12 -0
- package/dist/types/compliance-rules/privacy.d.ts +6 -0
- package/dist/types/compliance-rules/shared.d.ts +24 -0
- package/dist/types/compliance-rules/strict.d.ts +9 -0
- package/dist/types/compliance.d.ts +119 -0
- package/dist/types/config-worker.d.ts +1 -0
- package/dist/types/config.d.ts +21 -0
- package/dist/types/errors.d.ts +7 -0
- package/dist/types/function-sources.d.ts +21 -0
- package/dist/types/function-worker.d.ts +1 -0
- package/dist/types/functions.d.ts +92 -0
- package/dist/types/guest-api.d.ts +16 -0
- package/dist/types/header-validation.d.ts +2 -0
- package/dist/types/http-policy.d.ts +39 -0
- package/dist/types/http-response.d.ts +43 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/link-api.d.ts +30 -0
- package/dist/types/link-cli.d.ts +36 -0
- package/dist/types/link-events.d.ts +27 -0
- package/dist/types/link-records.d.ts +11 -0
- package/dist/types/link-store-worker.d.ts +1 -0
- package/dist/types/link-store.d.ts +130 -0
- package/dist/types/logging.d.ts +9 -0
- package/dist/types/management-policy.d.ts +9 -0
- package/dist/types/match.d.ts +81 -0
- package/dist/types/observability.d.ts +104 -0
- package/dist/types/plugins.d.ts +27 -0
- package/dist/types/policies/agents.d.ts +86 -0
- package/dist/types/policies/cache.d.ts +82 -0
- package/dist/types/policies/compression.d.ts +41 -0
- package/dist/types/policies/security.d.ts +37 -0
- package/dist/types/policies/throttle.d.ts +55 -0
- package/dist/types/policies.d.ts +36 -0
- package/dist/types/policy.d.ts +21 -0
- package/dist/types/prerender.d.ts +31 -0
- package/dist/types/project-tests.d.ts +13 -0
- package/dist/types/readiness.d.ts +121 -0
- package/dist/types/router.d.ts +11 -0
- package/dist/types/runtime.d.ts +91 -0
- package/dist/types/scaffold.d.ts +17 -0
- package/dist/types/server.d.ts +37 -0
- package/dist/types/site.d.ts +12 -0
- package/dist/types/sqlite-version.d.ts +1 -0
- package/dist/types/types.d.ts +332 -0
- package/dist/types/vercel.d.ts +12 -0
- package/dist/types.js +177 -0
- package/dist/vercel.js +65 -0
- package/docs/AI-AUTHORING.md +113 -0
- package/docs/ASSETS.md +106 -0
- package/docs/AWS.md +77 -0
- package/docs/BEST-PRACTICES.md +267 -0
- package/docs/CAPACITY.md +208 -0
- package/docs/CLOUDFLARE.md +110 -0
- package/docs/COMPLIANCE.md +242 -0
- package/docs/DYNAMIC-LINKS.md +561 -0
- package/docs/FUNCTION-SECURITY.md +113 -0
- package/docs/HTTP.md +129 -0
- package/docs/INSTALL.md +90 -0
- package/docs/LOAD-TESTING.md +91 -0
- package/docs/LOCAL-DEVELOPMENT.md +99 -0
- package/docs/MANAGEMENT-SECURITY.md +82 -0
- package/docs/MIDDLEWARE.md +83 -0
- package/docs/MONITORING.md +132 -0
- package/docs/OBSERVABILITY.md +229 -0
- package/docs/OPERATIONAL-PROOF.md +42 -0
- package/docs/OPERATIONS.md +207 -0
- package/docs/ORGANIZATION.md +135 -0
- package/docs/PERFORMANCE.md +72 -0
- package/docs/PLUGINS.md +234 -0
- package/docs/POLICIES.md +489 -0
- package/docs/PRERENDER.md +193 -0
- package/docs/PROJECT-DIRECTION.md +51 -0
- package/docs/READINESS.md +125 -0
- package/docs/README.md +58 -0
- package/docs/RELEASE-READINESS.md +95 -0
- package/docs/RELEASE-SECURITY.md +72 -0
- package/docs/RESILIENCE.md +160 -0
- package/docs/ROUTING.md +95 -0
- package/docs/SANDBOX-REVIEW.md +59 -0
- package/docs/SCAFFOLDING.md +72 -0
- package/docs/SECURITY-AUDIT.md +124 -0
- package/docs/SITE.md +150 -0
- package/docs/SPECIFICATION.md +232 -0
- package/docs/SPIKE-EXTENSIONS.md +475 -0
- package/docs/STANDARDS.md +303 -0
- package/docs/STARTERS.md +51 -0
- package/docs/TUNNELS.md +75 -0
- package/docs/TYPESCRIPT.md +102 -0
- package/docs/VERCEL.md +107 -0
- package/docs/YAML-GUIDE.md +521 -0
- package/docs/YAML-REFERENCE.md +328 -0
- package/docs/policies/agents.md +182 -0
- package/docs/policies/cache.md +139 -0
- package/docs/policies/compression.md +168 -0
- package/docs/policies/security.md +160 -0
- package/docs/policies/throttle.md +103 -0
- package/examples/assets/.env.example +1 -0
- package/examples/assets/.gitattributes +2 -0
- package/examples/assets/Makefile +30 -0
- package/examples/assets/README.md +18 -0
- package/examples/assets/functions/hello.mjs +3 -0
- package/examples/assets/gitignore.template +7 -0
- package/examples/assets/public/about.html +2 -0
- package/examples/assets/public/assets/example.txt +1 -0
- package/examples/assets/public/guide.txt +1 -0
- package/examples/assets/tests/requests.json +44 -0
- package/examples/assets/urlcode.yaml +27 -0
- package/examples/aws/README.md +13 -0
- package/examples/aws/handler.mjs +5 -0
- package/examples/aws/public/index.html +3 -0
- package/examples/aws/public/notes.txt +1 -0
- package/examples/aws/template.yaml +33 -0
- package/examples/aws/tests/requests.json +9 -0
- package/examples/aws/urlcode.yaml +19 -0
- package/examples/cloudflare/README.md +18 -0
- package/examples/cloudflare/package.json +14 -0
- package/examples/cloudflare/tests/requests.json +9 -0
- package/examples/cloudflare/urlcode.yaml +24 -0
- package/examples/cloudflare/wrangler.toml +5 -0
- package/examples/compliance/README.md +20 -0
- package/examples/compliance/rules.mjs +65 -0
- package/examples/cookbook/README.md +21 -0
- package/examples/cookbook/functions/choice.mjs +4 -0
- package/examples/cookbook/functions/echo.mjs +3 -0
- package/examples/cookbook/functions/hello.mjs +3 -0
- package/examples/cookbook/functions/text.mjs +3 -0
- package/examples/cookbook/middleware/headers.mjs +6 -0
- package/examples/cookbook/public/about.html +1 -0
- package/examples/cookbook/public/assets/index.html +1 -0
- package/examples/cookbook/public/assets/site.css +1 -0
- package/examples/cookbook/public/favicon.svg +1 -0
- package/examples/cookbook/public/guide.txt +1 -0
- package/examples/cookbook/public/llms.txt +6 -0
- package/examples/cookbook/routes/code.yaml +55 -0
- package/examples/cookbook/routes/files.yaml +17 -0
- package/examples/cookbook/routes/policies.yaml +28 -0
- package/examples/cookbook/routes/redirects.yaml +38 -0
- package/examples/cookbook/routes/responses.yaml +26 -0
- package/examples/cookbook/tests/requests.json +254 -0
- package/examples/cookbook/urlcode.yaml +22 -0
- package/examples/live-links/README.md +11 -0
- package/examples/live-links/tests/requests.json +6 -0
- package/examples/live-links/urlcode.yaml +16 -0
- package/examples/monitoring/blackbox-jobs.yaml +37 -0
- package/examples/monitoring/prometheus-rules.yaml +88 -0
- package/examples/monitoring/prometheus-scrape.yaml +20 -0
- package/examples/monitoring/vector.toml +65 -0
- package/examples/prerender/README.md +48 -0
- package/examples/prerender/functions/page.mjs +6 -0
- package/examples/prerender/middleware/template.mjs +29 -0
- package/examples/prerender/prerender.d.mts +4 -0
- package/examples/prerender/prerender.mjs +63 -0
- package/examples/prerender/tests/requests.json +39 -0
- package/examples/prerender/urlcode.yaml +50 -0
- package/examples/tunnel/dev-with-ngrok.sh +61 -0
- package/examples/vercel/README.md +13 -0
- package/examples/vercel/api/index.js +5 -0
- package/examples/vercel/package.json +8 -0
- package/examples/vercel/public/index.html +3 -0
- package/examples/vercel/public/notes.txt +1 -0
- package/examples/vercel/tests/requests.json +48 -0
- package/examples/vercel/urlcode.yaml +19 -0
- package/examples/vercel/vercel.json +10 -0
- package/llms.txt +46 -0
- package/package.json +123 -0
- package/schemas/urlcode.schema.json +1230 -0
- package/starters/default/.gitattributes +1 -0
- package/starters/default/Makefile +30 -0
- package/starters/default/README.md +34 -0
- package/starters/default/functions/hello.mjs +3 -0
- package/starters/default/gitignore.template +8 -0
- package/starters/default/middleware/headers.mjs +6 -0
- package/starters/default/routes/functions.yaml +20 -0
- package/starters/default/routes/marketing/links.yaml +7 -0
- package/starters/default/starter.json +5 -0
- package/starters/default/tests/requests.json +56 -0
- package/starters/default/urlcode.yaml +8 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"path": "/hello/Ada",
|
|
4
|
+
"status": 200,
|
|
5
|
+
"expectBody": "{\"message\":\"Hello, Ada.\"}",
|
|
6
|
+
"expectHeaders": {
|
|
7
|
+
"x-middleware": "cookbook",
|
|
8
|
+
"x-app": "cookbook"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"path": "/hello/Ada?excited=true",
|
|
13
|
+
"status": 200,
|
|
14
|
+
"expectBody": "{\"message\":\"Hello, Ada!\"}"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"path": "/hello/Ada",
|
|
18
|
+
"status": 200,
|
|
19
|
+
"method": "HEAD",
|
|
20
|
+
"expectBody": "",
|
|
21
|
+
"expectHeaders": {
|
|
22
|
+
"x-middleware": "cookbook"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"path": "/hello/Ada?excited=maybe",
|
|
27
|
+
"status": 400
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"path": "/echo",
|
|
31
|
+
"method": "POST",
|
|
32
|
+
"headers": {
|
|
33
|
+
"content-type": "application/json"
|
|
34
|
+
},
|
|
35
|
+
"body": "{\"ok\":true}",
|
|
36
|
+
"status": 200,
|
|
37
|
+
"expectBody": "{\"ok\":true}"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"path": "/echo",
|
|
41
|
+
"method": "POST",
|
|
42
|
+
"headers": {
|
|
43
|
+
"content-type": "application/json"
|
|
44
|
+
},
|
|
45
|
+
"body": "{",
|
|
46
|
+
"status": 400
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"path": "/echo",
|
|
50
|
+
"method": "POST",
|
|
51
|
+
"headers": {
|
|
52
|
+
"content-type": "text/plain"
|
|
53
|
+
},
|
|
54
|
+
"body": "hello",
|
|
55
|
+
"status": 415
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"path": "/text",
|
|
59
|
+
"method": "POST",
|
|
60
|
+
"headers": {
|
|
61
|
+
"content-type": "text/plain"
|
|
62
|
+
},
|
|
63
|
+
"body": "hello",
|
|
64
|
+
"status": 200,
|
|
65
|
+
"expectBody": "hello"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"path": "/choice",
|
|
69
|
+
"status": 302,
|
|
70
|
+
"method": "GET",
|
|
71
|
+
"expectHeaders": {
|
|
72
|
+
"location": "https://example.com/docs"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"path": "/product/abc?page=2&tag=red&tag=blue&utm_source=news&ignored=no",
|
|
77
|
+
"status": 302,
|
|
78
|
+
"method": "GET",
|
|
79
|
+
"expectHeaders": {
|
|
80
|
+
"location": "https://example.com/products/abc?page=2&label=red&label=blue&channel=web&utm_source=news"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"path": "/choice",
|
|
85
|
+
"status": 302,
|
|
86
|
+
"method": "HEAD",
|
|
87
|
+
"expectHeaders": {
|
|
88
|
+
"location": "https://example.com/docs"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"path": "/product/abc?page=2&tag=red&tag=blue&utm_source=news&ignored=no",
|
|
93
|
+
"status": 302,
|
|
94
|
+
"method": "HEAD",
|
|
95
|
+
"expectHeaders": {
|
|
96
|
+
"location": "https://example.com/products/abc?page=2&label=red&label=blue&channel=web&utm_source=news"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"path": "/product/abc?page=0",
|
|
101
|
+
"status": 400
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"path": "/preflight",
|
|
105
|
+
"status": 204,
|
|
106
|
+
"method": "OPTIONS",
|
|
107
|
+
"expectBody": "",
|
|
108
|
+
"expectHeaders": {
|
|
109
|
+
"access-control-allow-origin": "https://app.example.com"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"path": "/download",
|
|
114
|
+
"status": 200,
|
|
115
|
+
"expectBody": "URLCode guide\n",
|
|
116
|
+
"expectHeaders": {
|
|
117
|
+
"content-disposition": "attachment; filename=urlcode-guide.txt"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"path": "/assets/site.css",
|
|
122
|
+
"status": 206,
|
|
123
|
+
"expectBody": "body",
|
|
124
|
+
"expectHeaders": {
|
|
125
|
+
"content-range": "bytes 0-3/22"
|
|
126
|
+
},
|
|
127
|
+
"headers": {
|
|
128
|
+
"range": "bytes=0-3"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"path": "/paused",
|
|
133
|
+
"status": 404
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"path": "/expired",
|
|
137
|
+
"status": 410
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"path": "/missing",
|
|
141
|
+
"status": 404
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"path": "/secure",
|
|
145
|
+
"status": 200,
|
|
146
|
+
"expectBody": "Hardened response",
|
|
147
|
+
"expectHeaders": {
|
|
148
|
+
"referrer-policy": "no-referrer",
|
|
149
|
+
"x-frame-options": "deny",
|
|
150
|
+
"cross-origin-opener-policy": "same-origin"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"path": "/secure",
|
|
155
|
+
"method": "HEAD",
|
|
156
|
+
"status": 200,
|
|
157
|
+
"expectBody": "",
|
|
158
|
+
"expectHeaders": {
|
|
159
|
+
"x-frame-options": "deny"
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"path": "/no-bots",
|
|
164
|
+
"status": 302,
|
|
165
|
+
"expectHeaders": {
|
|
166
|
+
"location": "https://example.com/article"
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"path": "/no-bots",
|
|
171
|
+
"method": "HEAD",
|
|
172
|
+
"status": 302
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"path": "/no-bots",
|
|
176
|
+
"headers": {
|
|
177
|
+
"user-agent": "Mozilla/5.0 (compatible; GPTBot/1.0; +https://openai.com/gptbot)"
|
|
178
|
+
},
|
|
179
|
+
"status": 403
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"path": "/no-bots",
|
|
183
|
+
"headers": {
|
|
184
|
+
"user-agent": "curl/8.0"
|
|
185
|
+
},
|
|
186
|
+
"status": 403
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"path": "/no-bots",
|
|
190
|
+
"headers": {
|
|
191
|
+
"user-agent": "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
|
192
|
+
},
|
|
193
|
+
"status": 302
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"path": "/cached",
|
|
197
|
+
"status": 200,
|
|
198
|
+
"expectBody": "{\"cached\":true}",
|
|
199
|
+
"expectHeaders": {
|
|
200
|
+
"cache-control": "public, max-age=60"
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"path": "/cached",
|
|
205
|
+
"method": "HEAD",
|
|
206
|
+
"status": 200,
|
|
207
|
+
"expectBody": ""
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"path": "/budget",
|
|
211
|
+
"status": 200,
|
|
212
|
+
"expectBody": "Budgeted",
|
|
213
|
+
"expectHeaders": {
|
|
214
|
+
"ratelimit-policy": "\"default\";q=100;w=60"
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"path": "/budget",
|
|
219
|
+
"method": "HEAD",
|
|
220
|
+
"status": 200,
|
|
221
|
+
"expectBody": ""
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"path": "/robots.txt",
|
|
225
|
+
"status": 200,
|
|
226
|
+
"expectHeaders": {
|
|
227
|
+
"content-type": "text/plain; charset=utf-8"
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"path": "/.well-known/security.txt",
|
|
232
|
+
"status": 200,
|
|
233
|
+
"expectBody": "Contact: mailto:security@example.com\nExpires: 2099-01-01T00:00:00Z\nPolicy: https://example.com/security-policy\nPreferred-Languages: en\n"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"path": "/favicon.ico",
|
|
237
|
+
"status": 200,
|
|
238
|
+
"expectHeaders": {
|
|
239
|
+
"content-type": "image/svg+xml",
|
|
240
|
+
"cache-control": "public, max-age=3600"
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"path": "/llms.txt",
|
|
245
|
+
"status": 200,
|
|
246
|
+
"expectHeaders": {
|
|
247
|
+
"content-type": "text/plain; charset=utf-8"
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"path": "/sitemap.xml",
|
|
252
|
+
"status": 404
|
|
253
|
+
}
|
|
254
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://raw.githubusercontent.com/jimhoyd-com/urlcode/669632fb4f188f293a0f622847f87ee019c0627f/schemas/urlcode.schema.json
|
|
2
|
+
version: "1"
|
|
3
|
+
includes:
|
|
4
|
+
- routes/code.yaml
|
|
5
|
+
- routes/redirects.yaml
|
|
6
|
+
- routes/responses.yaml
|
|
7
|
+
- routes/files.yaml
|
|
8
|
+
- routes/policies.yaml
|
|
9
|
+
routes: {}
|
|
10
|
+
# Site conventions: each key generates one native route (see docs/SITE.md).
|
|
11
|
+
# sitemap is left out here because the cookbook audit runs without --origin.
|
|
12
|
+
site:
|
|
13
|
+
robots:
|
|
14
|
+
disallow: [ai-crawlers, /admin]
|
|
15
|
+
sitemap: true
|
|
16
|
+
favicon: public/favicon.svg
|
|
17
|
+
securityTxt:
|
|
18
|
+
contact: [mailto:security@example.com]
|
|
19
|
+
expires: "2099-01-01T00:00:00Z"
|
|
20
|
+
policy: [https://example.com/security-policy]
|
|
21
|
+
preferredLanguages: [en]
|
|
22
|
+
llms: public/llms.txt
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Live links without route reloads
|
|
2
|
+
|
|
3
|
+
Follow [the dynamic-link guide](../../docs/DYNAMIC-LINKS.md) to create an external
|
|
4
|
+
SQLite store and seed `demo -> https://example.com/demo`. Then start this project
|
|
5
|
+
with `--link-store links=/absolute/path/links.sqlite`. Its fixtures expect that
|
|
6
|
+
seed and an unused `not-created` code; use a disposable test store, not production.
|
|
7
|
+
|
|
8
|
+
Create/update/delete records through the CLI or separate authenticated API while
|
|
9
|
+
the public server keeps serving. YAML, code, route counts and health version do
|
|
10
|
+
not change per link. This example deliberately requires explicit operator storage
|
|
11
|
+
binding; the default starter still runs without a database.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
[
|
|
2
|
+
{"path":"/r/demo","status":302,"expectHeaders":{"location":"https://example.com/demo","cache-control":"no-store"}},
|
|
3
|
+
{"path":"/r/demo","method":"HEAD","status":302,"expectBody":"","expectHeaders":{"location":"https://example.com/demo"}},
|
|
4
|
+
{"path":"/r/not-created","status":404},
|
|
5
|
+
{"path":"/r/demo","method":"POST","status":405,"expectHeaders":{"allow":"GET, HEAD"}}
|
|
6
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://raw.githubusercontent.com/jimhoyd-com/urlcode/669632fb4f188f293a0f622847f87ee019c0627f/schemas/urlcode.schema.json
|
|
2
|
+
version: "1"
|
|
3
|
+
dynamicLinks: true
|
|
4
|
+
routes:
|
|
5
|
+
/r/{code}:
|
|
6
|
+
parameters:
|
|
7
|
+
- name: code
|
|
8
|
+
in: path
|
|
9
|
+
required: true
|
|
10
|
+
schema: {type: string, minLength: 1, maxLength: 128}
|
|
11
|
+
link:
|
|
12
|
+
collection: links
|
|
13
|
+
code: {from: path, name: code}
|
|
14
|
+
/about:
|
|
15
|
+
respond:
|
|
16
|
+
text: Links are stored separately from this YAML.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Prometheus scrape jobs for URLCode's two probes, via blackbox_exporter.
|
|
2
|
+
#
|
|
3
|
+
# Keep both: /_urlcode/health answers whenever the process is alive, while
|
|
4
|
+
# /_urlcode/ready reports 503 whenever a function worker or link-store reader is
|
|
5
|
+
# unavailable. Alerting only on health hides a process that is up and serving
|
|
6
|
+
# nothing; alerting only on ready pages for a brief, self-healing replacement.
|
|
7
|
+
#
|
|
8
|
+
# Both probes are unauthenticated and report the configuration digest and route
|
|
9
|
+
# count. Keep them on an internal interface or restrict them at the ingress.
|
|
10
|
+
scrape_configs:
|
|
11
|
+
- job_name: urlcode-ready
|
|
12
|
+
metrics_path: /probe
|
|
13
|
+
params:
|
|
14
|
+
module: [http_2xx]
|
|
15
|
+
static_configs:
|
|
16
|
+
- targets: ['http://urlcode.internal:3000/_urlcode/ready']
|
|
17
|
+
relabel_configs:
|
|
18
|
+
- source_labels: [__address__]
|
|
19
|
+
target_label: __param_target
|
|
20
|
+
- source_labels: [__param_target]
|
|
21
|
+
target_label: instance
|
|
22
|
+
- target_label: __address__
|
|
23
|
+
replacement: blackbox:9115
|
|
24
|
+
|
|
25
|
+
- job_name: urlcode-health
|
|
26
|
+
metrics_path: /probe
|
|
27
|
+
params:
|
|
28
|
+
module: [http_2xx]
|
|
29
|
+
static_configs:
|
|
30
|
+
- targets: ['http://urlcode.internal:3000/_urlcode/health']
|
|
31
|
+
relabel_configs:
|
|
32
|
+
- source_labels: [__address__]
|
|
33
|
+
target_label: __param_target
|
|
34
|
+
- source_labels: [__param_target]
|
|
35
|
+
target_label: instance
|
|
36
|
+
- target_label: __address__
|
|
37
|
+
replacement: blackbox:9115
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Alert rules for a URLCode deployment.
|
|
2
|
+
#
|
|
3
|
+
# These rules read blackbox probes of the two health endpoints and counters
|
|
4
|
+
# derived from the JSON request log by a log pipeline (see vector.toml in this
|
|
5
|
+
# directory). Where the built-in endpoint (prometheus-scrape.yaml, enabled with
|
|
6
|
+
# startServer({ metrics: true })) offers the same signal, a comment gives the
|
|
7
|
+
# equivalent expression; it labels by status_class rather than exact status.
|
|
8
|
+
# Rule names are explained in docs/MONITORING.md.
|
|
9
|
+
groups:
|
|
10
|
+
- name: urlcode-availability
|
|
11
|
+
interval: 30s
|
|
12
|
+
rules:
|
|
13
|
+
- alert: UrlcodeNotReady
|
|
14
|
+
# /_urlcode/ready is 503 while a function worker or link-store reader is
|
|
15
|
+
# being replaced. Brief flaps are normal during replacement; sustained
|
|
16
|
+
# means replacement is not converging.
|
|
17
|
+
expr: probe_success{job="urlcode-ready"} == 0
|
|
18
|
+
for: 5m
|
|
19
|
+
labels:
|
|
20
|
+
severity: page
|
|
21
|
+
annotations:
|
|
22
|
+
summary: "URLCode has not been ready for 5 minutes"
|
|
23
|
+
runbook: "Check function_worker and link_store_worker records for repeated restarts."
|
|
24
|
+
|
|
25
|
+
- alert: UrlcodeDown
|
|
26
|
+
# /_urlcode/health answers whenever the process is alive and not shutting
|
|
27
|
+
# down. Failing it is a process-level problem, not a capacity one.
|
|
28
|
+
expr: probe_success{job="urlcode-health"} == 0
|
|
29
|
+
for: 2m
|
|
30
|
+
labels:
|
|
31
|
+
severity: page
|
|
32
|
+
annotations:
|
|
33
|
+
summary: "URLCode is not answering liveness probes"
|
|
34
|
+
runbook: "The process is gone, wedged or out of file descriptors. Check the supervisor."
|
|
35
|
+
|
|
36
|
+
- name: urlcode-serving
|
|
37
|
+
interval: 30s
|
|
38
|
+
rules:
|
|
39
|
+
- alert: UrlcodeServerErrors
|
|
40
|
+
# 5xx as a share of served requests, by matched route pattern.
|
|
41
|
+
# Built-in endpoint (no per-route status split):
|
|
42
|
+
# sum(rate(urlcode_requests_total{status_class="5xx"}[5m]))
|
|
43
|
+
# / clamp_min(sum(rate(urlcode_requests_total[5m])), 0.001) > 0.05
|
|
44
|
+
expr: |
|
|
45
|
+
sum by (route) (rate(urlcode_requests_total{status=~"5.."}[5m]))
|
|
46
|
+
/ clamp_min(sum by (route) (rate(urlcode_requests_total[5m])), 0.001) > 0.05
|
|
47
|
+
for: 10m
|
|
48
|
+
labels:
|
|
49
|
+
severity: page
|
|
50
|
+
annotations:
|
|
51
|
+
summary: "Over 5% of requests to {{ $labels.route }} are failing"
|
|
52
|
+
runbook: "502 is a function error, 503 is capacity or a dead pool, 504 is a deadline."
|
|
53
|
+
|
|
54
|
+
- alert: UrlcodeCapacityShedding
|
|
55
|
+
# 503 specifically means admission or the function pool refused work.
|
|
56
|
+
# Built-in endpoint: sum(rate(urlcode_shed_total[5m])) > 1
|
|
57
|
+
expr: sum(rate(urlcode_requests_total{status="503"}[5m])) > 1
|
|
58
|
+
for: 10m
|
|
59
|
+
labels:
|
|
60
|
+
severity: ticket
|
|
61
|
+
annotations:
|
|
62
|
+
summary: "URLCode is shedding requests"
|
|
63
|
+
runbook: "Raise --max-in-flight or --workers, or find what is occupying the pool."
|
|
64
|
+
|
|
65
|
+
- alert: UrlcodeWorkerRestartLoop
|
|
66
|
+
# Replacement backs off but never stops, so a persistent cause shows up
|
|
67
|
+
# as a steady restart rate rather than a stopped pool.
|
|
68
|
+
# Built-in endpoint: urlcode_function_worker_restarts_total and
|
|
69
|
+
# urlcode_link_store_worker_restarts_total, summed.
|
|
70
|
+
expr: sum(rate(urlcode_worker_restarts_total[15m])) > 0
|
|
71
|
+
for: 15m
|
|
72
|
+
labels:
|
|
73
|
+
severity: ticket
|
|
74
|
+
annotations:
|
|
75
|
+
summary: "URLCode workers have been restarting for 15 minutes"
|
|
76
|
+
runbook: "A function is exceeding its deadline or exhausting its heap on real traffic."
|
|
77
|
+
|
|
78
|
+
- alert: UrlcodeLogsDropped
|
|
79
|
+
# The logger drops records when the collector cannot keep up, so the
|
|
80
|
+
# rules above quietly lose their input. The built-in endpoint exports
|
|
81
|
+
# the same name, so this rule works unchanged with either source.
|
|
82
|
+
expr: sum(rate(urlcode_logs_dropped_total[15m])) > 0
|
|
83
|
+
for: 15m
|
|
84
|
+
labels:
|
|
85
|
+
severity: ticket
|
|
86
|
+
annotations:
|
|
87
|
+
summary: "URLCode is dropping log records"
|
|
88
|
+
runbook: "The collector is behind or its sink failed; these alerts are blind until it recovers."
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Prometheus scrape job for URLCode's built-in metrics endpoint.
|
|
2
|
+
#
|
|
3
|
+
# The endpoint exists only when the operator starts the server with
|
|
4
|
+
# startServer({ metrics: true }); otherwise /_urlcode/metrics answers 404. It
|
|
5
|
+
# shares the probes' admission budget (--max-in-flight-health) and bind host,
|
|
6
|
+
# is unauthenticated, and discloses route patterns and traffic shape: keep it
|
|
7
|
+
# on an internal interface or restrict it at the ingress. Never expose it
|
|
8
|
+
# publicly.
|
|
9
|
+
#
|
|
10
|
+
# Metric names and labels are fixed in docs/OBSERVABILITY.md. Requests are
|
|
11
|
+
# labelled by status_class (2xx..5xx), not exact status, so the example rules
|
|
12
|
+
# in prometheus-rules.yaml carry a status_class form of each expression next
|
|
13
|
+
# to the log-derived one from vector.toml.
|
|
14
|
+
scrape_configs:
|
|
15
|
+
- job_name: urlcode
|
|
16
|
+
metrics_path: /_urlcode/metrics
|
|
17
|
+
scrape_interval: 15s
|
|
18
|
+
scrape_timeout: 5s
|
|
19
|
+
static_configs:
|
|
20
|
+
- targets: ['urlcode.internal:3000']
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Turn URLCode's JSON request log into the counters the example alert rules use.
|
|
2
|
+
#
|
|
3
|
+
# URLCode writes one JSON object per line to stdout. Its built-in endpoint
|
|
4
|
+
# (prometheus-scrape.yaml) is opt-in and labels by status class only; this
|
|
5
|
+
# pipeline keeps the exact status, at the cost of running a collector. Point your
|
|
6
|
+
# supervisor's stdout at Vector (or adapt this to Fluent Bit, Promtail or
|
|
7
|
+
# Alloy — the field names are what matter).
|
|
8
|
+
|
|
9
|
+
[sources.urlcode]
|
|
10
|
+
type = "file"
|
|
11
|
+
include = ["/var/log/urlcode/*.log"]
|
|
12
|
+
|
|
13
|
+
[transforms.parsed]
|
|
14
|
+
type = "remap"
|
|
15
|
+
inputs = ["urlcode"]
|
|
16
|
+
source = '''
|
|
17
|
+
. = parse_json!(string!(.message))
|
|
18
|
+
# Records without an event field are not ours; drop rather than mislabel them.
|
|
19
|
+
if !exists(.event) { abort }
|
|
20
|
+
'''
|
|
21
|
+
|
|
22
|
+
[transforms.requests]
|
|
23
|
+
type = "filter"
|
|
24
|
+
inputs = ["parsed"]
|
|
25
|
+
condition = '.event == "request"'
|
|
26
|
+
|
|
27
|
+
[transforms.workers]
|
|
28
|
+
type = "filter"
|
|
29
|
+
inputs = ["parsed"]
|
|
30
|
+
# Both pools report replacement the same way, so one counter covers them.
|
|
31
|
+
condition = '(.event == "function_worker" || .event == "link_store_worker") && .status == "restarting"'
|
|
32
|
+
|
|
33
|
+
[transforms.dropped]
|
|
34
|
+
type = "filter"
|
|
35
|
+
inputs = ["parsed"]
|
|
36
|
+
condition = '.event == "logs_dropped"'
|
|
37
|
+
|
|
38
|
+
[transforms.metrics]
|
|
39
|
+
type = "log_to_metric"
|
|
40
|
+
inputs = ["requests", "workers", "dropped"]
|
|
41
|
+
|
|
42
|
+
# route is present only with --request-log detailed; it is the configured route
|
|
43
|
+
# pattern, never the requested path, so it is safe as a metric label.
|
|
44
|
+
[[transforms.metrics.metrics]]
|
|
45
|
+
type = "counter"
|
|
46
|
+
field = "status"
|
|
47
|
+
name = "urlcode_requests_total"
|
|
48
|
+
namespace = ""
|
|
49
|
+
tags.status = "{{status}}"
|
|
50
|
+
tags.route = "{{route}}"
|
|
51
|
+
|
|
52
|
+
[[transforms.metrics.metrics]]
|
|
53
|
+
type = "counter"
|
|
54
|
+
field = "status"
|
|
55
|
+
name = "urlcode_worker_restarts_total"
|
|
56
|
+
|
|
57
|
+
[[transforms.metrics.metrics]]
|
|
58
|
+
type = "counter"
|
|
59
|
+
field = "count"
|
|
60
|
+
name = "urlcode_logs_dropped_total"
|
|
61
|
+
|
|
62
|
+
[sinks.prometheus]
|
|
63
|
+
type = "prometheus_exporter"
|
|
64
|
+
inputs = ["metrics"]
|
|
65
|
+
address = "127.0.0.1:9598"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Prerender a dynamic project into a native static one
|
|
2
|
+
|
|
3
|
+
Three pages rendered by a function and one shared template middleware, then
|
|
4
|
+
rendered once at build time into a project that serves the same bytes with
|
|
5
|
+
native `page` routes and no sandbox on the request path.
|
|
6
|
+
|
|
7
|
+
From the runtime checkout:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
node src/cli.ts dev --project examples/prerender # the dynamic source, live
|
|
11
|
+
node src/cli.ts test --project examples/prerender # 7 fixtures
|
|
12
|
+
node examples/prerender/prerender.mjs examples/prerender /absolute/out
|
|
13
|
+
node src/cli.ts test --project /absolute/out # fixtures the build wrote
|
|
14
|
+
node src/cli.ts audit --project /absolute/out --expect-routes 3
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The same three URLs answer identically before and after. The difference is what
|
|
18
|
+
runs to serve them: the source project executes a QuickJS/WASM guest per request,
|
|
19
|
+
the generated project reads a prevalidated byte buffer.
|
|
20
|
+
|
|
21
|
+
| | Source project | Generated project |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Handlers | 3 × `function` | 3 × `page` |
|
|
24
|
+
| Middleware | shared template | none |
|
|
25
|
+
| Guest execution per request | yes | none |
|
|
26
|
+
| Content | reviewed literals in YAML | rendered HTML files |
|
|
27
|
+
|
|
28
|
+
`prerender.mjs` is operator build tooling that runs in Node, not guest code. The
|
|
29
|
+
orchestration lives in the runtime's build helper:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import {prerenderPages, assertNativeProject} from '@jimhoyd/urlcode/prerender';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`prerenderPages` activates the source project, renders each page through its
|
|
36
|
+
middleware, validates every response, enforces the budgets, derives safe
|
|
37
|
+
filenames, writes the files and closes the runtime. It fails the build rather
|
|
38
|
+
than publishing a bad page. What is left in this recipe is the part every site
|
|
39
|
+
does differently: assembling a project from the returned `pages` and `fixtures`,
|
|
40
|
+
then calling `assertNativeProject` to prove the artifact is inert. A larger site
|
|
41
|
+
assembles differently — its own static and download routes, response security
|
|
42
|
+
headers and a generated include — using the same helper.
|
|
43
|
+
|
|
44
|
+
The recipe renders content that is already prepared. It is not a Markdown
|
|
45
|
+
compiler, an HTML sanitizer, an asset pipeline or an incremental build, and it
|
|
46
|
+
copies no static tree. [Prerendering](../../docs/PRERENDER.md) documents the
|
|
47
|
+
helper, its options and guarantees, the limits and how a larger site generates
|
|
48
|
+
its source project.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// One page's content, supplied as reviewed literal arguments in urlcode.yaml.
|
|
2
|
+
// This runs in the QuickJS/WASM sandbox with no filesystem, network or host
|
|
3
|
+
// code, at build time exactly as it would at request time.
|
|
4
|
+
export function page(request, {args}) {
|
|
5
|
+
return new Response(args.body, {headers: {'content-type': 'text/html; charset=utf-8'}});
|
|
6
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The shared site template. It wraps a function response, whose body is
|
|
2
|
+
// readable through text(); a native page/static/download body is deliberately
|
|
3
|
+
// opaque, which is why the template is applied here, before prerendering, and
|
|
4
|
+
// never to the generated files.
|
|
5
|
+
const escape = value => String(value).replace(/[&<>"]/g, character => ({'&':'&','<':'<','>':'>','"':'"'}[character]));
|
|
6
|
+
export async function template(request, context, next) {
|
|
7
|
+
const response = await next();
|
|
8
|
+
// Never wrap a failed render: the build must see the original status and stop.
|
|
9
|
+
if (!response.ok) return response;
|
|
10
|
+
const content = await response.text();
|
|
11
|
+
const title = escape(context.args.title);
|
|
12
|
+
return new Response(`<!doctype html>
|
|
13
|
+
<html lang="en">
|
|
14
|
+
<head>
|
|
15
|
+
<meta charset="utf-8">
|
|
16
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
17
|
+
<title>${title}</title>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<header><a href="/">URLCode prerender example</a></header>
|
|
21
|
+
<main>
|
|
22
|
+
<h1>${title}</h1>
|
|
23
|
+
${content}
|
|
24
|
+
</main>
|
|
25
|
+
<footer>Prerendered at build time. No guest code runs to serve this page.</footer>
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
28
|
+
`, {status: response.status, headers: {'content-type': 'text/html; charset=utf-8'}});
|
|
29
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Declarations for the recipe, so a TypeScript build script (or the test
|
|
2
|
+
// suite) can import it without a cast. The runtime is prerender.mjs.
|
|
3
|
+
export interface PrerenderReport { pages: number; bytes: number; output: string }
|
|
4
|
+
export function prerender(project: string, output: string, options?: { log?: (event: object) => void }): Promise<PrerenderReport>;
|