@jimhoyd/urlcode 0.5.5 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/urlcode-authoring/SKILL.md +16 -1
- package/.claude/skills/urlcode-operations/SKILL.md +5 -1
- package/README.md +24 -14
- package/dist/BUILD-MANIFEST.json +55 -54
- package/dist/agents-guide.js +5 -2
- package/dist/assets.js +2 -2
- package/dist/authoring.js +3 -58
- package/dist/aws.js +21 -4
- package/dist/body-schema.js +2 -3
- package/dist/capabilities.js +1 -1
- package/dist/capability-query.js +3 -3
- package/dist/catalog.js +1 -1
- package/dist/cli.js +42 -15
- package/dist/cloudflare.js +26 -1
- package/dist/config.js +1 -1
- package/dist/context.js +6 -6
- package/dist/egress.js +1 -1
- package/dist/examples.js +2 -2
- package/dist/explain.js +4 -6
- package/dist/extension-artifacts.js +8 -8
- package/dist/extension-transport.js +6 -4
- package/dist/function-worker.js +5 -2
- package/dist/functions.js +1 -1
- package/dist/guest-api.js +22 -15
- package/dist/http-policy.js +1 -3
- package/dist/http-response.js +61 -10
- package/dist/init-with.js +8 -8
- package/dist/interchange.js +3 -2
- package/dist/logging.js +4 -4
- package/dist/match.js +1 -1
- package/dist/mcp-authoring.js +1 -2
- package/dist/mcp.js +2 -2
- package/dist/object-guards.js +17 -0
- package/dist/pattern-guard.js +33 -3
- package/dist/policies/agents.js +1 -1
- package/dist/policies/cache.js +16 -4
- package/dist/policies/compression.js +1 -1
- package/dist/policies/security.js +1 -1
- package/dist/policies/throttle.js +1 -1
- package/dist/policies.js +1 -1
- package/dist/policy.js +3 -3
- package/dist/prerender.js +1 -2
- package/dist/project-dependencies.js +8 -8
- package/dist/proxy.js +1 -1
- package/dist/readiness.js +16 -17
- package/dist/review.js +2 -2
- package/dist/route-diff.js +4 -4
- package/dist/router.js +1 -1
- package/dist/runtime.js +4 -1
- package/dist/scaffold.js +1 -1
- package/dist/schema-query.js +1 -1
- package/dist/server.js +49 -7
- package/dist/signals.js +2 -2
- package/dist/site.js +2 -2
- package/dist/trusted-functions.js +1 -1
- package/dist/types/assets.d.ts +3 -2
- package/dist/types/authoring.d.ts +3 -6
- package/dist/types/body-schema.d.ts +2 -1
- package/dist/types/catalog.d.ts +2 -1
- package/dist/types/config.d.ts +0 -8
- package/dist/types/context.d.ts +3 -1
- package/dist/types/egress.d.ts +1 -1
- package/dist/types/explain.d.ts +4 -4
- package/dist/types/extension-artifacts.d.ts +4 -13
- package/dist/types/extension-transport.d.ts +6 -4
- package/dist/types/functions.d.ts +1 -1
- package/dist/types/http-response.d.ts +7 -3
- package/dist/types/init-with.d.ts +3 -9
- package/dist/types/logging.d.ts +4 -3
- package/dist/types/match.d.ts +2 -1
- package/dist/types/object-guards.d.ts +7 -0
- package/dist/types/pattern-guard.d.ts +0 -1
- package/dist/types/policies/cache.d.ts +4 -3
- package/dist/types/policies/compression.d.ts +1 -1
- package/dist/types/policies/throttle.d.ts +1 -1
- package/dist/types/policy.d.ts +4 -3
- package/dist/types/project-dependencies.d.ts +1 -40
- package/dist/types/proxy.d.ts +2 -1
- package/dist/types/readiness.d.ts +12 -16
- package/dist/types/review.d.ts +0 -2
- package/dist/types/route-diff.d.ts +4 -3
- package/dist/types/router.d.ts +1 -1
- package/dist/types/server.d.ts +22 -0
- package/dist/types/signals.d.ts +3 -2
- package/dist/types/site.d.ts +1 -2
- package/dist/types/trusted-functions.d.ts +1 -1
- package/dist/types/types.d.ts +17 -11
- package/dist/types/vercel.d.ts +1 -0
- package/dist/types/verify-deployment.d.ts +1 -1
- package/dist/types.js +19 -12
- package/dist/vercel.js +12 -3
- package/dist/verify-deployment.js +29 -11
- package/docs/AI-AUTHORING.md +10 -1
- package/docs/FUNCTION-SECURITY.md +4 -0
- package/docs/README.md +2 -2
- package/docs/TOOLING.md +27 -1
- package/examples/assets/Makefile +1 -1
- package/examples/assets/example.yaml +3 -3
- package/examples/aws/example.yaml +3 -3
- package/examples/cloudflare/example.yaml +3 -3
- package/examples/compliance/README.md +1 -1
- package/examples/compliance/example.yaml +1 -1
- package/examples/compliance/rules.mjs +2 -2
- package/examples/conditions/example.yaml +3 -3
- package/examples/cookbook/README.md +4 -4
- package/examples/cookbook/example.yaml +3 -3
- package/examples/coverage-waiver/example.yaml +3 -3
- package/examples/egress/example.yaml +2 -2
- package/examples/extensions/example.yaml +1 -1
- package/examples/lifecycle/example.yaml +2 -2
- package/examples/not-found/README.md +2 -2
- package/examples/not-found/example.yaml +3 -3
- package/examples/prerender/README.md +4 -4
- package/examples/prerender/example.yaml +2 -2
- package/examples/provider-conformance/example.yaml +2 -2
- package/examples/shared-blocks/example.yaml +3 -3
- package/examples/vercel/example.yaml +3 -3
- package/llms-full.txt +49 -18
- package/llms.txt +9 -2
- package/package.json +14 -13
- package/skills/urlcode/SKILL.md +7 -7
- package/starters/default/.github/workflows/urlcode.yml +2 -1
- package/starters/default/AGENTS.md +3 -3
- package/starters/default/Makefile +1 -1
- package/starters/default/README.md +15 -13
- package/starters/default/tests/requests.json +0 -50
- package/starters/default/urlcode.yaml +1 -4
- package/dist/scripts/operational-drills.js +0 -33
- package/starters/default/functions/hello.mjs +0 -3
- package/starters/default/middleware/headers.mjs +0 -6
- package/starters/default/routes/functions.yaml +0 -20
- package/starters/default/routes/marketing/links.yaml +0 -7
- package/starters/page/README.md +0 -14
- package/starters/page/public/index.html +0 -12
- package/starters/page/tests/requests.json +0 -17
- package/starters/page/urlcode.yaml +0 -6
package/llms-full.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- Generated by scripts/build-llms-full.ts (npm run docs:llms). Do not edit; edit the source documents. -->
|
|
2
|
-
<!-- Consolidated URLCode authoring reference: 16 documents, about 61,
|
|
2
|
+
<!-- Consolidated URLCode authoring reference: 16 documents, about 61,970 tokens (estimate: characters / 4). -->
|
|
3
3
|
|
|
4
4
|
<!-- urlcode-current-version:start -->
|
|
5
5
|
# URLCode
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
> A portable runtime for programmable URL behavior, and the framework that grows
|
|
8
8
|
> from it: routes in YAML, functions and middleware, then accounts, administration and
|
|
9
9
|
> stored links as operator-installed extensions. Stable project format
|
|
10
|
-
> `version: "1"`. Core is Apache-2.0; this revision is `0.5.
|
|
10
|
+
> `version: "1"`. Core is Apache-2.0; this revision is `0.5.8`. `function`/`middleware`
|
|
11
11
|
> routes are trusted by default with `sandbox: true` as the per-route opt-in. The
|
|
12
12
|
> auth, admin, ui and store workspace packages are bundle sources. New sites install
|
|
13
13
|
> core from npm and verified executable extensions from an immutable GitHub Release;
|
|
@@ -114,8 +114,10 @@ Each rung's YAML is valid on every rung above it.
|
|
|
114
114
|
|
|
115
115
|
Stored short links previously sat here as a native `link` route; that handler
|
|
116
116
|
was removed from core. A `urlcode-dynamic-link` package owned them the same way
|
|
117
|
-
`auth`/`admin` own their mounts, but it has been retired and unpublished
|
|
118
|
-
|
|
117
|
+
`auth`/`admin` own their mounts, but it has been retired and unpublished. It has
|
|
118
|
+
no direct successor; a project that wants stored short links declares a
|
|
119
|
+
collection through the `store` extension above (see [docs/STORE.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/STORE.md))
|
|
120
|
+
rather than a native `link` route.
|
|
119
121
|
|
|
120
122
|
Rungs 1 to 3 need only the core package. Rungs 4 to 7 need a verified extension
|
|
121
123
|
bundle installed into an explicit operator host, once its source package appears
|
|
@@ -364,6 +366,11 @@ The root [llms.txt](https://github.com/jimhoyd-com/urlcode/blob/main/llms.txt) i
|
|
|
364
366
|
reading order for agents that want complete context in one fetch. It is a convenience,
|
|
365
367
|
not a runtime protocol or a guarantee that AI clients automatically consume it.
|
|
366
368
|
The generated reference is checked against the schema in `npm run verify`.
|
|
369
|
+
[URLCode AI](https://urlcode.ai/) is the optional hosted companion for shared
|
|
370
|
+
skills and LLM tooling. It complements the project-local MCP server; see
|
|
371
|
+
[hosted AI MCP setup](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md#optional-hosted-ai-mcp) for its authenticated
|
|
372
|
+
remote connection details. Its machine-readable entry point is
|
|
373
|
+
[`https://urlcode.ai/llms.txt`](https://urlcode.ai/llms.txt).
|
|
367
374
|
|
|
368
375
|
Follow [organization and readability practices](https://github.com/jimhoyd-com/urlcode/blob/main/docs/BEST-PRACTICES.md): preserve local
|
|
369
376
|
conventions, use clear names, keep middleware focused and avoid needless layers.
|
|
@@ -439,7 +446,7 @@ urlcode benchmark --project ./my-links --requests 100 --concurrency 2
|
|
|
439
446
|
```
|
|
440
447
|
|
|
441
448
|
Use the intentional actual count, not always 2. Runtime checkout users can replace
|
|
442
|
-
`urlcode` with `node src/cli.ts`. Template users can use the equivalent npm scripts.
|
|
449
|
+
`urlcode` with `node packages/core/src/cli.ts`. Template users can use the equivalent npm scripts.
|
|
443
450
|
External bindings require an already reviewed policy; add `--policy` where needed.
|
|
444
451
|
The benchmark operates locally; it is not a load test of an external deployment.
|
|
445
452
|
|
|
@@ -733,6 +740,10 @@ the operator, never by tool arguments; `--allow-authoring` on the operator's
|
|
|
733
740
|
command line adds project-confined route, recipe, scaffold and runner tools.
|
|
734
741
|
`urlcode init` writes `.mcp.json` so Claude Code and Codex register the read-only
|
|
735
742
|
server for the project ([registering the server](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md#registering-the-server)).
|
|
743
|
+
The optional hosted URLCode AI MCP is a separate authenticated connection for
|
|
744
|
+
shared skills and LLM tools; it does not replace the local project server. Its
|
|
745
|
+
endpoint and credential-handling requirements are in
|
|
746
|
+
[hosted AI MCP setup](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md#optional-hosted-ai-mcp).
|
|
736
747
|
Inspection is not activation/deployment readiness: real grants, asset snapshots
|
|
737
748
|
and service availability still need normal runtime checks. Provider conformance replay is local evidence; only
|
|
738
749
|
explicit live [deployment observations](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PROVIDER-VERIFICATION.md) test ingress.
|
|
@@ -773,7 +784,7 @@ programmatic compatibility analysis and provider verification limits.
|
|
|
773
784
|
Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/YAML-GUIDE.md
|
|
774
785
|
|
|
775
786
|
<!-- urlcode-current-version:start -->
|
|
776
|
-
This guide targets URLCode 0.5.
|
|
787
|
+
This guide targets URLCode 0.5.8. Start with the function example below,
|
|
777
788
|
then add only the fields your route needs. The authoritative machine-readable
|
|
778
789
|
shape is [JSON Schema](https://github.com/jimhoyd-com/urlcode/blob/main/schemas/urlcode.schema.json); semantic rules are in the
|
|
779
790
|
[specification](https://github.com/jimhoyd-com/urlcode/blob/main/docs/SPECIFICATION.md). Unsupported fields fail validation.
|
|
@@ -788,15 +799,16 @@ From the runtime checkout:
|
|
|
788
799
|
|
|
789
800
|
```sh
|
|
790
801
|
npm ci
|
|
791
|
-
node src/cli.ts validate --project examples/cookbook
|
|
792
|
-
node src/cli.ts test --project examples/cookbook
|
|
793
|
-
node src/cli.ts audit --project examples/cookbook --expect-routes 40
|
|
794
|
-
node src/cli.ts dev --project examples/cookbook
|
|
802
|
+
node packages/core/src/cli.ts validate --project examples/cookbook
|
|
803
|
+
node packages/core/src/cli.ts test --project examples/cookbook
|
|
804
|
+
node packages/core/src/cli.ts audit --project examples/cookbook --expect-routes 40
|
|
805
|
+
node packages/core/src/cli.ts dev --project examples/cookbook
|
|
795
806
|
```
|
|
796
807
|
|
|
797
808
|
The cookbook is a larger learning project. The normal `urlcode init ../my-links`
|
|
798
|
-
|
|
799
|
-
runtime dependency, clone
|
|
809
|
+
creates a bare zero-route scaffold; add only the routes the application needs.
|
|
810
|
+
For an independent application with a pinned runtime dependency, clone
|
|
811
|
+
[urlcode-template](https://github.com/jimhoyd-com/urlcode-template).
|
|
800
812
|
|
|
801
813
|
### Pages
|
|
802
814
|
|
|
@@ -1920,10 +1932,18 @@ values into header literals. Header configuration applies to handler responses,
|
|
|
1920
1932
|
including declared error statuses, but not runtime validation/errors (400, 404,
|
|
1921
1933
|
405, 413, 415, 500, etc.). Defaults remain `no-store`, `nosniff` and a request ID.
|
|
1922
1934
|
Header policy is bounded to 64 keys/16 KiB; merged function headers remain bounded.
|
|
1935
|
+
A header repeated in the result the runtime writes (from a policy, or from a
|
|
1936
|
+
handler result that carries more than one pair for the same name) is sent as
|
|
1937
|
+
separate wire lines, the same as a declared `Set-Cookie` list; it is never
|
|
1938
|
+
collapsed to only its last value.
|
|
1923
1939
|
|
|
1924
1940
|
Framing, hop-by-hop headers, Location, Allow, range/cache validators,
|
|
1925
1941
|
Content-Encoding, X-Request-ID and X-Content-Type-Options are reserved to the
|
|
1926
|
-
runtime/handler.
|
|
1942
|
+
runtime/handler. The runtime frames every response the same way on every host:
|
|
1943
|
+
Content-Length is the UTF-8 byte length of the body it sends, whatever length a
|
|
1944
|
+
handler states. Only a HEAD answer carries a stated length, the one GET would
|
|
1945
|
+
send, and no body. The self-hosted and Vercel writers also make Node refuse a
|
|
1946
|
+
body that differs from the stated length. Configure redirect URLs/status on `redirect`; asset content type,
|
|
1927
1947
|
cache and disposition on its own handler. Asset metadata cannot be overridden by
|
|
1928
1948
|
`response.headers`. On functions/declared responses, Content-Type may be configured;
|
|
1929
1949
|
JSON declarations require a JSON type. No response header secret interpolation.
|
|
@@ -2393,9 +2413,11 @@ cannot enforce`; the Cloudflare build reports
|
|
|
2393
2413
|
build time and carried in the Worker artifact; the Worker has no filesystem and
|
|
2394
2414
|
no Node imports, so only modules free of both qualify. The Worker's client
|
|
2395
2415
|
identity is the platform's `cf-connecting-ip`; the serverless adapters use the
|
|
2396
|
-
platform-set source address (`sourceIp` on Lambda,
|
|
2397
|
-
`X-Forwarded-For`
|
|
2398
|
-
|
|
2416
|
+
platform-set source address (`sourceIp` on Lambda, Vercel's own
|
|
2417
|
+
`X-Vercel-Forwarded-For` — Vercel's documented copy of the client IP that
|
|
2418
|
+
survives even when a project puts another proxy in front of Vercel, unlike
|
|
2419
|
+
plain `X-Forwarded-For`, which that outer proxy can overwrite before Vercel
|
|
2420
|
+
ever sees it). None of these read a forwarded header a client could have set.
|
|
2399
2421
|
|
|
2400
2422
|
The cross-request state a policy keeps (throttle counters, the origin cache) is
|
|
2401
2423
|
per runtime instance on every target, never shared between replicas or
|
|
@@ -2760,6 +2782,11 @@ portable scalar header contract. Upstream redirects are returned as responses;
|
|
|
2760
2782
|
Location is forwarded only if explicitly selected and is never fetched. Headers
|
|
2761
2783
|
nominated by an incoming or upstream Connection field are removed even if selected.
|
|
2762
2784
|
Header names are normalized to lowercase; array-valued upstream fields are omitted.
|
|
2785
|
+
A proxy route reads incoming headers from the same credential-free projection a
|
|
2786
|
+
trusted function receives: a header an installed plugin or extension declares
|
|
2787
|
+
as a credential is withheld before `requestHeaders` selection runs, even when a
|
|
2788
|
+
project's own `requestHeaders` names it explicitly, so a route cannot forward a
|
|
2789
|
+
credential header upstream by declaring it.
|
|
2763
2790
|
An encoded request body requires explicitly selecting its Content-Encoding
|
|
2764
2791
|
header; literal header injection cannot change or replace that coding. Bodies
|
|
2765
2792
|
remain raw bytes, including content encoding; select Content-Encoding when
|
|
@@ -3673,9 +3700,9 @@ navigation or labels to an auth/admin view without editing either package.
|
|
|
3673
3700
|
|
|
3674
3701
|
| Hook | Input | Returns | Called |
|
|
3675
3702
|
|---|---|---|---|
|
|
3676
|
-
| `beforeRoleChange` | `{accountId, currentRoles, requestedRoles, actorId, reason}` | `{allow: boolean, reason?}` | Before roles are applied, after the administrator's permission check. A veto means the auth service is never asked. |
|
|
3703
|
+
| `beforeRoleChange` | `{accountId, currentRoles, requestedRoles, actorId, reason}` | `{allow: boolean, reason?}` | Before roles are applied, after the administrator's permission check, on every path that assigns roles: `/users/roles`, bulk role assignment through `/account-operations`, and a role-change support case's creation and approval. A veto means the auth service is never asked; for a bulk assignment it runs once per affected account before anything is staged. |
|
|
3677
3704
|
| `onRegistrationApproved` | `{requestId, accountId, email, actorId, reason}` | ignored | After a registration request is approved. |
|
|
3678
|
-
| `onAccountStatusChanged` | `{accountId, status, actorId, reason}` | ignored | After an account is locked or unlocked. |
|
|
3705
|
+
| `onAccountStatusChanged` | `{accountId, status, actorId, reason}` | ignored | After an account is locked or unlocked, on every path: `/users/status`, `/users/bulk` (once per affected account), and an approved lock/unlock support case. |
|
|
3679
3706
|
|
|
3680
3707
|
#### Verdicts and failure
|
|
3681
3708
|
|
|
@@ -3816,6 +3843,10 @@ into it; only the default for routes that declare neither option has changed.
|
|
|
3816
3843
|
- A fresh guest heap/module state per invocation prevents state crossing requests.
|
|
3817
3844
|
- 32 MiB guest heap, 512 KiB stack, source/input/output/header limits, bounded
|
|
3818
3845
|
concurrency, guest interruption and an independent worker termination deadline.
|
|
3846
|
+
- The guest's result is recorded where guest code cannot rewrite it, and the
|
|
3847
|
+
host checks its shape before trusting it. A result that states a body length
|
|
3848
|
+
for any method other than HEAD is invalid and answers 502; the runtime frames
|
|
3849
|
+
the response by the bytes it sends ([responses](https://github.com/jimhoyd-com/urlcode/blob/main/docs/HTTP.md#responses)).
|
|
3819
3850
|
- External bindings are denied by default. Project YAML cannot self-authorize.
|
|
3820
3851
|
Operator grants are exact-name, route-scoped and pinned to configuration/source.
|
|
3821
3852
|
|
package/llms.txt
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
> A portable runtime for programmable URL behavior, and the framework that grows
|
|
5
5
|
> from it: routes in YAML, functions and middleware, then accounts, administration and
|
|
6
6
|
> stored links as operator-installed extensions. Stable project format
|
|
7
|
-
> `version: "1"`. Core is Apache-2.0; this revision is `0.5.
|
|
7
|
+
> `version: "1"`. Core is Apache-2.0; this revision is `0.5.8`. `function`/`middleware`
|
|
8
8
|
> routes are trusted by default with `sandbox: true` as the per-route opt-in. The
|
|
9
9
|
> auth, admin, ui and store workspace packages are bundle sources. New sites install
|
|
10
10
|
> core from npm and verified executable extensions from an immutable GitHub Release;
|
|
@@ -21,9 +21,16 @@ grants. Unsupported features fail with the route named; nothing degrades silentl
|
|
|
21
21
|
1. Run `npx --no --package @jimhoyd/urlcode urlcode context --project DIR` (MCP `get_context`). For redirect-only work run
|
|
22
22
|
`npx --no --package @jimhoyd/urlcode urlcode context --project DIR --task redirects`: supported shapes with exact YAML, gaps
|
|
23
23
|
with their exact validation error, a paste-ready `starter`, and this project's own redirects.
|
|
24
|
-
2. After installing `@jimhoyd/urlcode`, run `npx --no --package @jimhoyd/urlcode urlcode init
|
|
24
|
+
2. After installing `@jimhoyd/urlcode`, run `npx --no --package @jimhoyd/urlcode urlcode init .` (it works in a directory holding only `package.json`, `package-lock.json`, `node_modules` or `.git` and preserves existing package metadata). It writes a bare agent-ready scaffold: `urlcode.yaml`, a minimal 404 fixture, `AGENTS.md`, local `.mcp.json`, and project CI. Then run `npx --no --package @jimhoyd/urlcode urlcode validate --local --project DIR` and `npx --no --package @jimhoyd/urlcode urlcode test`. `--no` refuses a registry fetch, so it always uses the installed scoped runtime.
|
|
25
25
|
3. Do not read or grep [llms-full.txt](llms-full.txt) (about 60k tokens) for a routine task; it is the
|
|
26
26
|
deliberate fallback when the steps above do not answer the question.
|
|
27
|
+
4. Hosted skills and LLM assistance are optional: connect a compatible HTTP MCP
|
|
28
|
+
client to `https://mcp.urlcode.ai/mcp` with
|
|
29
|
+
`Authorization: Bearer <URLCODE_AI_TOKEN>`. Keep `urlcode mcp` registered
|
|
30
|
+
for project-aware inspection and validation, and store the hosted token in
|
|
31
|
+
the client's secret facility rather than in project files. Start with the
|
|
32
|
+
hosted [agent guide](https://urlcode.ai/llms.txt), then see
|
|
33
|
+
[hosted AI MCP setup](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md#optional-hosted-ai-mcp).
|
|
27
34
|
|
|
28
35
|
| Shape | Supported? | Use |
|
|
29
36
|
|---|---|---|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jimhoyd/urlcode",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.8",
|
|
4
4
|
"description": "Portable runtime for programmable URL behavior",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"files": [
|
|
76
76
|
".claude/skills",
|
|
77
77
|
"dist",
|
|
78
|
+
"!dist/scripts",
|
|
78
79
|
"schemas",
|
|
79
80
|
"data",
|
|
80
81
|
"starters",
|
|
@@ -99,22 +100,22 @@
|
|
|
99
100
|
"test:shard": "node --conditions=development --test",
|
|
100
101
|
"lint": "eslint .",
|
|
101
102
|
"check": "npm run check:code && npm run check:docs",
|
|
102
|
-
"check:code": "node scripts/check.ts && node scripts/check-release-tags.ts && node scripts/check-workspace-links.ts && node scripts/check-issue-labels.ts && npm run release:check",
|
|
103
|
+
"check:code": "node scripts/check.ts && node scripts/check-core-boundaries.ts && node scripts/check-release-tags.ts && node scripts/check-workspace-links.ts && node scripts/check-issue-labels.ts && npm run release:check",
|
|
103
104
|
"test:workerd": "node scripts/workerd-parity.ts",
|
|
104
105
|
"verify": "npm run lint && npm run typecheck && npm run check && npm run build && npm test && npm run verify:workspaces",
|
|
105
106
|
"workspace:styles": "npm run styles --workspace @jimhoyd/urlcode-ui",
|
|
106
107
|
"verify:workspaces": "npm run verify --workspace @jimhoyd/urlcode-ui && npm run verify --workspace @jimhoyd/urlcode-auth && npm run verify --workspace @jimhoyd/urlcode-admin && npm run verify --workspace @jimhoyd/urlcode-store && npm run verify --workspace @jimhoyd/urlcode-forms && npm run audit:packages && npm run test:workspace-integration",
|
|
107
108
|
"audit:packages": "node scripts/package-audit.ts --all",
|
|
108
109
|
"test:package": "npm run build && npm run test:package:built",
|
|
109
|
-
"dev": "node src/cli.ts dev --project starters/default",
|
|
110
|
-
"serve": "node src/cli.ts serve --project starters/default",
|
|
111
|
-
"validate": "node src/cli.ts validate --local --project starters/default",
|
|
112
|
-
"test:project": "node src/cli.ts test --project starters/default",
|
|
113
|
-
"init": "node src/cli.ts init",
|
|
114
|
-
"doctor": "node src/cli.ts doctor",
|
|
115
|
-
"routes": "node src/cli.ts routes --project starters/default",
|
|
116
|
-
"audit:routes": "node src/cli.ts audit --project starters/default",
|
|
117
|
-
"benchmark:project": "node src/cli.ts benchmark --project starters/default",
|
|
110
|
+
"dev": "node packages/core/src/cli.ts dev --project starters/default",
|
|
111
|
+
"serve": "node packages/core/src/cli.ts serve --project starters/default",
|
|
112
|
+
"validate": "node packages/core/src/cli.ts validate --local --project starters/default",
|
|
113
|
+
"test:project": "node packages/core/src/cli.ts test --project starters/default",
|
|
114
|
+
"init": "node packages/core/src/cli.ts init",
|
|
115
|
+
"doctor": "node packages/core/src/cli.ts doctor",
|
|
116
|
+
"routes": "node packages/core/src/cli.ts routes --project starters/default",
|
|
117
|
+
"audit:routes": "node packages/core/src/cli.ts audit --project starters/default",
|
|
118
|
+
"benchmark:project": "node packages/core/src/cli.ts benchmark --project starters/default",
|
|
118
119
|
"docs:reference": "node scripts/generate-yaml-reference.ts",
|
|
119
120
|
"docs:plugin": "node scripts/generate-claude-plugin.ts",
|
|
120
121
|
"docs:llms": "node scripts/build-llms-full.ts",
|
|
@@ -155,9 +156,9 @@
|
|
|
155
156
|
"@changesets/cli": "3.0.3",
|
|
156
157
|
"@eslint/js": "10.0.1",
|
|
157
158
|
"@types/mime-types": "3.0.1",
|
|
158
|
-
"@types/node": "26.
|
|
159
|
+
"@types/node": "26.6.2",
|
|
159
160
|
"@types/semver": "7.8.0",
|
|
160
|
-
"eslint": "10.
|
|
161
|
+
"eslint": "10.11.0",
|
|
161
162
|
"globals": "17.12.0",
|
|
162
163
|
"semver": "7.8.5",
|
|
163
164
|
"typescript": "6.0.3",
|
package/skills/urlcode/SKILL.md
CHANGED
|
@@ -12,24 +12,23 @@ A URLCode project declares URL behavior in YAML; the installed runtime serves it
|
|
|
12
12
|
> Use URLCode's highest-level declarative features whenever possible. Generate custom code only when the framework cannot express the requirement.
|
|
13
13
|
|
|
14
14
|
Check installed primitives, YAML configuration, policies, extensions and recipes/templates before writing custom code. Keep it focused and report the gap; never invent fields or bypass target limits or operator grants. Source checkouts have `docs/PROJECT-DIRECTION.md`; npm installations have it in `llms-full.txt`.
|
|
15
|
-
|
|
16
15
|
## 1. Recognize the project
|
|
17
16
|
|
|
17
|
+
- No `urlcode.yaml` yet? `npm install @jimhoyd/urlcode` (scoped; no unscoped `urlcode` package exists), then `npx urlcode init .`.
|
|
18
18
|
- The root has `urlcode.yaml` with `version: "1"`. Included route files are
|
|
19
|
-
listed under `includes`; functions, middleware and assets are
|
|
20
|
-
the project root.
|
|
19
|
+
listed under `includes`; functions, middleware and assets are project-relative.
|
|
21
20
|
- Read the project's `AGENTS.md` first if present; it lists the handlers,
|
|
22
21
|
policies and commands this runtime version supports.
|
|
23
22
|
- Find the runtime: `urlcode` on the PATH, or
|
|
24
23
|
`node node_modules/@jimhoyd/urlcode/dist/cli.js`, or
|
|
25
|
-
`node /path/to/urlcode/src/cli.ts` for a source checkout. Use one form for
|
|
24
|
+
`node /path/to/urlcode/packages/core/src/cli.ts` for a source checkout. Use one form for
|
|
26
25
|
every command below.
|
|
27
|
-
|
|
28
26
|
## 2. Retrieve the minimum, do not read everything
|
|
29
27
|
|
|
30
28
|
**First step: one bounded query.** MCP `get_context` when the `urlcode` server is registered, otherwise `urlcode context --project DIR` (add `--budget N` to cap it). It returns a compact summary, constraints and exact commands, never a schema dump. Then retrieve only what the task needs: `capabilities NAME`/`get_capability` (its limits), `get_schema`, `recipes search`/`search_recipes`, `explain`, and `get_extensions` when an operator host file exists. The bare `urlcode capabilities` and `recipes list` catalogs, `llms.txt` and `llms-full.txt` are deliberate fallback/reference, not the opening move.
|
|
31
29
|
|
|
32
30
|
If the project carries `.mcp.json` (written by `urlcode init`) and your client has the `urlcode` server, prefer its tools: `get_context` (project summary, constraints, exact commands), `get_capability` and `get_schema` (one capability or YAML fragment), `search_recipes`, `search_examples`, `explain` (a route's effective behavior) and `get_manifest`. For framework discovery, use `list_skills` before `get_skill`, `search_docs` for a short package-owned excerpt, and `get_example` for one runnable example. Use `validate_yaml` for pasted YAML syntax/schema feedback only; use `validate` for the actual project. The server is read-only; `--allow-authoring` is an operator opt-in you never add yourself.
|
|
31
|
+
[URLCode AI](https://urlcode.ai/) is an optional, separate hosted service for shared skills and LLM tooling. Its remote MCP supplements this local project server; never replace `.mcp.json` or put its bearer token in project files. Its machine-readable entry point is `https://urlcode.ai/llms.txt`; connection details belong to the MCP client's secret facility and are documented in the URLCode tooling guide.
|
|
33
32
|
When the MCP server was started with an operator host file, `get_extensions`
|
|
34
33
|
returns installed extension configuration/policy schemas, declared project
|
|
35
34
|
hook contracts, supported authoring surfaces and fast checks. Otherwise use `urlcode extensions --project DIR --host-file
|
|
@@ -118,8 +117,9 @@ urlcode audit --project DIR --expect-routes N
|
|
|
118
117
|
```
|
|
119
118
|
|
|
120
119
|
Add fixtures to `tests/requests.json` for each new route: positive and negative
|
|
121
|
-
cases, every active method, `HEAD`.
|
|
122
|
-
|
|
120
|
+
cases, every active method, `HEAD`. `N` is declared routes plus one per active
|
|
121
|
+
`site.*` convention; an audit mismatch reports the declared/generated split. Update it
|
|
122
|
+
deliberately when routes are added or removed, and update any `--expect-routes` in the project's README,
|
|
123
123
|
Makefile or CI workflow to match. A failing validation names the route; fix
|
|
124
124
|
the declaration rather than working around it.
|
|
125
125
|
|
|
@@ -19,5 +19,6 @@ jobs:
|
|
|
19
19
|
# Pin to a release tag or commit of the runtime you depend on.
|
|
20
20
|
- uses: jimhoyd-com/urlcode/action@main
|
|
21
21
|
with:
|
|
22
|
-
expect-routes:
|
|
22
|
+
expect-routes: 0 # update when adding or removing routes
|
|
23
|
+
allow-empty-project: true # remove after adding the first active route
|
|
23
24
|
compliance: baseline
|
|
@@ -22,7 +22,7 @@ static serving and authentication. Read this file before changing anything.
|
|
|
22
22
|
|
|
23
23
|
When present, `.mcp.json` registers the read-only `urlcode mcp` server; prefer its
|
|
24
24
|
tools (also `get_manifest`) to reading documents. Inspect `get_extensions` before
|
|
25
|
-
replacing extension behavior. `--allow-authoring` is an operator opt-in; never add it. For a committed artifact lock, use `get_extension_artifacts`/`get_extension_artifact`; they expose verified inert data and never activate an extension.
|
|
25
|
+
replacing extension behavior. `--allow-authoring` is an operator opt-in; never add it. For a committed artifact lock, use `get_extension_artifacts`/`get_extension_artifact`; they expose verified inert data and never activate an extension. [URLCode AI](https://urlcode.ai/) is a separate optional hosted service for shared skills and LLM tooling; its remote MCP never replaces this local project server, and its credential belongs only in a client secret facility, never project files. Its machine-readable entry point is `https://urlcode.ai/llms.txt`.
|
|
26
26
|
|
|
27
27
|
## What the runtime provides (this version)
|
|
28
28
|
|
|
@@ -56,10 +56,10 @@ record the reason in `sandboxReason`. Try `redirect` (relative or `/**`) or `res
|
|
|
56
56
|
```sh
|
|
57
57
|
urlcode validate --local
|
|
58
58
|
urlcode test
|
|
59
|
-
urlcode audit --expect-routes
|
|
59
|
+
urlcode audit --expect-routes 0
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
With no active routes, this initial audit intentionally exits nonzero with `no-active-routes`. Add the first route and its fixture, then make the audit pass; remove `allow-empty-project: true` from the generated GitHub workflow at that point. `N` counts declared routes plus one route for each active `site.*` convention; an audit mismatch reports the declared/generated split. Update it deliberately and add `tests/requests.json` fixtures for every new route (positive/negative, every active method, HEAD). No global install: use `node /path/to/urlcode/packages/core/src/cli.ts`.
|
|
63
63
|
|
|
64
64
|
## Feedback
|
|
65
65
|
|
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
# Your URLCode project
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
This is a bare, agent-ready URLCode scaffold. It starts with no routes so your
|
|
4
|
+
application's YAML and tests describe only the behavior you intend to ship. Its
|
|
5
|
+
single fixture proves the empty project returns 404.
|
|
5
6
|
|
|
6
7
|
```sh
|
|
7
8
|
urlcode dev
|
|
8
9
|
# In another terminal:
|
|
9
10
|
urlcode test
|
|
10
|
-
urlcode audit --expect-routes 2
|
|
11
|
-
urlcode benchmark --requests 1000 --concurrency 2
|
|
12
11
|
```
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
`urlcode audit --expect-routes 0` intentionally reports `no-active-routes` until
|
|
14
|
+
you add the first route. The included GitHub workflow permits only that initial
|
|
15
|
+
audit result; remove `allow-empty-project: true` after adding a route.
|
|
16
|
+
|
|
17
|
+
Start with the local MCP `get_context` tool (or `urlcode context --project .`),
|
|
18
|
+
then add the smallest declarative route or custom code the task requires. Keep
|
|
19
|
+
`tests/requests.json` aligned with every route you add and update the audit
|
|
20
|
+
count deliberately. `AGENTS.md` explains the workflow and points to the
|
|
21
|
+
optional hosted shared tooling at https://urlcode.ai/llms.txt.
|
|
20
22
|
|
|
21
23
|
This app uses the runtime you installed separately (compatible with 0.1.0).
|
|
22
|
-
Without a global install, invoke `node /path/to/urlcode/src/cli.ts` instead of
|
|
23
|
-
`urlcode`. Optional Make shortcuts accept `URLCODE='node /path/to/urlcode/src/cli.ts'`.
|
|
24
|
+
Without a global install, invoke `node /path/to/urlcode/packages/core/src/cli.ts` instead of
|
|
25
|
+
`urlcode`. Optional Make shortcuts accept `URLCODE='node /path/to/urlcode/packages/core/src/cli.ts'`.
|
|
24
26
|
For a cloneable project with a pinned npm runtime dependency, use
|
|
25
27
|
[urlcode-template](https://github.com/jimhoyd-com/urlcode-template). Both start
|
|
26
|
-
with the same
|
|
28
|
+
with the same bare scaffold. No runtime fork or provider account is needed.
|
|
27
29
|
|
|
28
30
|
Keep this app in your own Git repository. Secrets stay in ignored `.env.local`
|
|
29
31
|
or provider environment values, with external operator policy for function grants.
|
|
@@ -1,56 +1,6 @@
|
|
|
1
1
|
[
|
|
2
|
-
{
|
|
3
|
-
"path": "/hello/Ada",
|
|
4
|
-
"status": 200,
|
|
5
|
-
"expectBody": "{\"message\":\"Hello, Ada!\"}",
|
|
6
|
-
"expectHeaders": {
|
|
7
|
-
"x-powered-by": "URLCode",
|
|
8
|
-
"cache-control": "no-store",
|
|
9
|
-
"x-example-middleware": "active"
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"path": "/hello/Ada",
|
|
14
|
-
"method": "HEAD",
|
|
15
|
-
"status": 200,
|
|
16
|
-
"expectBody": "",
|
|
17
|
-
"expectHeaders": {
|
|
18
|
-
"x-example-middleware": "active"
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"path": "/go",
|
|
23
|
-
"status": 302,
|
|
24
|
-
"expectHeaders": {
|
|
25
|
-
"location": "https://example.com/"
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"path": "/go",
|
|
30
|
-
"method": "POST",
|
|
31
|
-
"status": 405,
|
|
32
|
-
"expectHeaders": {
|
|
33
|
-
"allow": "GET, HEAD"
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
2
|
{
|
|
37
3
|
"path": "/missing",
|
|
38
4
|
"status": 404
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"path": "/hello/Ada",
|
|
42
|
-
"method": "POST",
|
|
43
|
-
"status": 405,
|
|
44
|
-
"expectHeaders": {
|
|
45
|
-
"allow": "GET, HEAD"
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"path": "/hello/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
50
|
-
"status": 400
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"path": "/hello/%ZZ",
|
|
54
|
-
"status": 400
|
|
55
5
|
}
|
|
56
6
|
]
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
# yaml-language-server: $schema=https://raw.githubusercontent.com/jimhoyd-com/urlcode/669632fb4f188f293a0f622847f87ee019c0627f/schemas/urlcode.schema.json
|
|
2
|
-
#
|
|
2
|
+
# Start with no routes. Add only the files and routes your application needs.
|
|
3
3
|
version: "1"
|
|
4
|
-
includes:
|
|
5
|
-
- routes/functions.yaml
|
|
6
|
-
- routes/marketing/links.yaml
|
|
7
4
|
routes: {}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// Disposable local proof, never a claim about the production deployment.
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import {mkdtemp,mkdir,writeFile,rm} from 'node:fs/promises';
|
|
4
|
-
import {tmpdir} from 'node:os';
|
|
5
|
-
import {join} from 'node:path';
|
|
6
|
-
import {startServer} from '../server.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const seconds=Number(process.env.URLCODE_SOAK_SECONDS||5);
|
|
10
|
-
assert(Number.isInteger(seconds)&&seconds>=1&&seconds<=3600,'Soak must be 1–3600 seconds');
|
|
11
|
-
const directory=await mkdtemp(join(tmpdir(),'urlcode-drills-')),project=join(directory,'app');
|
|
12
|
-
let app ;
|
|
13
|
-
try {
|
|
14
|
-
await mkdir(project);
|
|
15
|
-
const config='version: "1"\nroutes:\n /go:\n redirect: {url: "https://example.com/v1"}\n /function:\n function: {source: f.mjs}\n';
|
|
16
|
-
await writeFile(join(project,'urlcode.yaml'),config);
|
|
17
|
-
await writeFile(join(project,'f.mjs'),'export default () => new Response("isolated");');
|
|
18
|
-
app=await startServer({project,port:0,log:()=>{}});
|
|
19
|
-
const server=app;
|
|
20
|
-
const get=async(path )=>{const r=await fetch(`http://127.0.0.1:${server.address.port}${path}`,{redirect:'manual'});const body=await r.text();return {status:r.status,location:r.headers.get('location'),body};};
|
|
21
|
-
const start=performance.now(),samples =[];let requests=0;
|
|
22
|
-
while(performance.now()-start<seconds*1000){
|
|
23
|
-
const before=performance.now();const responses=await Promise.all(['/go','/function'].map(get));
|
|
24
|
-
assert.deepEqual(responses.map(r=>r.status),[302,200]);assert.equal(responses[1]?.body,'isolated');
|
|
25
|
-
samples.push(performance.now()-before);requests+=2;
|
|
26
|
-
}
|
|
27
|
-
await writeFile(join(project,'urlcode.yaml'),'invalid: configuration');assert.equal(await app.reload(),false);assert.equal((await get('/go')).location,'https://example.com/v1');
|
|
28
|
-
await writeFile(join(project,'urlcode.yaml'),config.replace('/v1','/v2'));assert.equal(await app.reload(),true);assert.equal((await get('/go')).location,'https://example.com/v2');
|
|
29
|
-
await writeFile(join(project,'urlcode.yaml'),config);assert.equal(await app.reload(),true);assert.equal((await get('/go')).location,'https://example.com/v1');
|
|
30
|
-
await app.close();app=undefined;
|
|
31
|
-
samples.sort((a,b)=>a-b);
|
|
32
|
-
console.log(JSON.stringify({event:'local-operational-proof',seconds,requests,batchP99Ms:samples[Math.floor(samples.length*.99)],rssBytes:process.memoryUsage().rss,checks:['mixed HTTP load','invalid reload preserves last-good','configuration rollback'],deploymentProof:false}));
|
|
33
|
-
} finally {await app?.close();await rm(directory,{recursive:true,force:true});}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# yaml-language-server: $schema=https://raw.githubusercontent.com/jimhoyd-com/urlcode/669632fb4f188f293a0f622847f87ee019c0627f/schemas/urlcode.schema.json
|
|
2
|
-
# File locations do not change route paths. GET/HEAD are the default methods.
|
|
3
|
-
version: "1"
|
|
4
|
-
routes:
|
|
5
|
-
# Your code runs when this URL is requested.
|
|
6
|
-
/hello/{name}:
|
|
7
|
-
parameters:
|
|
8
|
-
- name: name
|
|
9
|
-
in: path
|
|
10
|
-
required: true
|
|
11
|
-
schema: {type: string, minLength: 1, maxLength: 80}
|
|
12
|
-
middleware:
|
|
13
|
-
- source: middleware/headers.mjs
|
|
14
|
-
function:
|
|
15
|
-
source: functions/hello.mjs
|
|
16
|
-
args:
|
|
17
|
-
name: {from: path, name: name}
|
|
18
|
-
response:
|
|
19
|
-
headers:
|
|
20
|
-
X-Powered-By: URLCode
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# yaml-language-server: $schema=https://raw.githubusercontent.com/jimhoyd-com/urlcode/669632fb4f188f293a0f622847f87ee019c0627f/schemas/urlcode.schema.json
|
|
2
|
-
version: "1"
|
|
3
|
-
routes:
|
|
4
|
-
# A regular redirect needs no custom code.
|
|
5
|
-
/go:
|
|
6
|
-
redirect:
|
|
7
|
-
url: https://example.com
|
package/starters/page/README.md
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Your URLCode page
|
|
2
|
-
|
|
3
|
-
The smallest project: one route that serves `public/index.html`.
|
|
4
|
-
Created with `urlcode init <dir> --template page`.
|
|
5
|
-
|
|
6
|
-
```sh
|
|
7
|
-
urlcode validate --local
|
|
8
|
-
urlcode test
|
|
9
|
-
urlcode dev # http://127.0.0.1:3000/
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
Edit `public/index.html`. Add more pages, a `static` directory, downloads,
|
|
13
|
-
redirects or functions as routes in `urlcode.yaml` (see docs/ASSETS.md in the
|
|
14
|
-
URLCode repository). Keep `tests/requests.json` in step with the routes.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>Hello, URLCode</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<h1>Hello, URLCode</h1>
|
|
10
|
-
<p>This page is served by a page route in urlcode.yaml.</p>
|
|
11
|
-
</body>
|
|
12
|
-
</html>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"path": "/",
|
|
4
|
-
"status": 200,
|
|
5
|
-
"expectBody": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Hello, URLCode</title>\n</head>\n<body>\n<h1>Hello, URLCode</h1>\n<p>This page is served by a page route in urlcode.yaml.</p>\n</body>\n</html>\n"
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
"path": "/",
|
|
9
|
-
"method": "HEAD",
|
|
10
|
-
"status": 200,
|
|
11
|
-
"expectBody": ""
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"path": "/missing",
|
|
15
|
-
"status": 404
|
|
16
|
-
}
|
|
17
|
-
]
|