docs: specs for apis blocks, migration, and editor tooling
Three specs completing the set, each depending on the one before it:
- apis {}: one container, mode-less declarations, api.<name>() callable
anywhere with build-time dispatch, AsyncLocalStorage for server context,
usage-driven emission, three render-binding forms. Replaces the ssr {} /
client {} data blocks and the untypeable with($data) legacy body.
- update: migrations to the new syntax. The legacy bare-body rewrite is
deliberately manual -- which free identifiers are payload fields is not
knowable from the source, so an automatic guess would compile and be wrong.
- editor tooling: grammar, completions for api. and the api= attribute,
diagnostics for removed constructs, and resolving by observation whether
generated type errors surface inline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
# The `apis { }` block and location-transparent dispatch — Design
|
||||
|
||||
**Date:** 2026-08-19
|
||||
**Status:** Approved for implementation
|
||||
**Scope:** One container block for API declarations, callable from anywhere as `api.<name>(input)`,
|
||||
dispatched in-process on the server and over `fetch` in the browser.
|
||||
|
||||
**Depends on:** `2026-08-19-legacy-and-config-cleanup-design.md`. That spec removes the
|
||||
compatibility surface and the `"legacy"` function runtime; this one removes the legacy `api` forms
|
||||
by replacing them.
|
||||
|
||||
## Goal
|
||||
|
||||
Today a `.wrn` page has several unrelated ways to reach data: an `api` block inside `ssr {}`, an
|
||||
`api` block inside `client {}`, a `server function` over RPC, a `load server` block, and hand-written
|
||||
`fetch`. Each has different placement, different capabilities, and a different call shape. The
|
||||
result is that "how do I fetch this?" has no single answer, and the answer that is right depends on
|
||||
where the code happens to sit.
|
||||
|
||||
This collapses the API half of that into one declaration and one call, and draws a line a developer
|
||||
can hold in their head:
|
||||
|
||||
- **`api.<name>()`** — call an API route that exists as a real HTTP endpoint.
|
||||
- **`server.<name>()`** — call server-side logic that has no public surface.
|
||||
|
||||
The question becomes "is there a route?", not "where am I running?". `server.<name>()` is unchanged
|
||||
by this spec.
|
||||
|
||||
### Non-goals
|
||||
|
||||
- Changing `server function`, actions, or `load` blocks. They keep working exactly as they do.
|
||||
- External or third-party API targets. Still this app's `/api/*` routes only, preserving
|
||||
`isSafeApiPath`.
|
||||
- Any new configuration key. This spec adds none.
|
||||
- Author-settable request headers, still excluded.
|
||||
|
||||
## Decisions
|
||||
|
||||
| Question | Decision |
|
||||
| ---------------------- | ---------------------------------------------------------------------- |
|
||||
| Container | Page-level `apis { }`, matching `functions { }` |
|
||||
| Declaration | Mode-less — no `ssr` / `client` prefix |
|
||||
| Call | `api.<name>(input)` from any context |
|
||||
| Dispatch | Chosen at build time: in-process on the server, `fetch` in the browser |
|
||||
| Server request context | `AsyncLocalStorage` |
|
||||
| Browser emission | Only blocks the client actually calls |
|
||||
| Render binding | `api="name"`, `api="name()"`, `api="name({ … })"` |
|
||||
| Old forms | Removed and replaced |
|
||||
|
||||
## Syntax
|
||||
|
||||
```wrn
|
||||
apis {
|
||||
searchUsers POST /api/users {
|
||||
request {
|
||||
body {
|
||||
name?: string
|
||||
age?: number
|
||||
}
|
||||
}
|
||||
|
||||
response { return data.data.users }
|
||||
error { return [] }
|
||||
}
|
||||
|
||||
listTeams GET /api/teams {
|
||||
response { return data.data.teams }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`GET` and `HEAD` declare `parameters`, which become a query string; other methods declare `body`,
|
||||
sent as JSON. The path stays a plain literal so `isSafeApiPath` is satisfied without relaxing it.
|
||||
|
||||
Names are unique per page — `codegen.ts` already rejects duplicates, and that stays.
|
||||
|
||||
### Why the container
|
||||
|
||||
`functions { }` puts the modifier first inside a container named for the concept:
|
||||
`functions { client function x() }`. Today's api form inverts that — `client { api x … }` — and is
|
||||
the only construct in the language shaped that way, which is why APIs are hard to find in a page.
|
||||
`apis` (plural) is the container; `api` remains the call namespace and the binding attribute.
|
||||
|
||||
## Calling
|
||||
|
||||
```wrn
|
||||
functions {
|
||||
client async function search(): Promise<void> {
|
||||
users = await api.searchUsers({ name: nameFilter })
|
||||
}
|
||||
}
|
||||
|
||||
load server directory {
|
||||
return await api.searchUsers({ name: ctx.url.searchParams.get("name") ?? "" })
|
||||
}
|
||||
```
|
||||
|
||||
The same call works in client functions, `load` blocks, actions, and server functions. Nothing at
|
||||
the call site says where it runs.
|
||||
|
||||
### Dispatch
|
||||
|
||||
Dispatch is decided at build time, not by a runtime check. The compiler emits an `api` object into
|
||||
each execution context, with the same member names and different transports behind them:
|
||||
|
||||
- **Browser** — the existing `callApi` transport: query string or JSON body, `credentials:
|
||||
"same-origin"`, `x-csrf-token` on non-GET, the established failure contract.
|
||||
- **Server** — `callApiFromContext`, which dispatches to the route in-process. No network hop, no
|
||||
serialisation round trip, and the caller's cookies, session, and locals are already forwarded.
|
||||
|
||||
Two objects that never meet, so nothing ships to the browser that only the server uses, and the
|
||||
runtime never branches on `typeof window` for something known at compile time.
|
||||
|
||||
### `callApiFromContext` must learn to carry input
|
||||
|
||||
It currently builds `new Request(apiUrl, { method, headers })` — no body, no query string. It has to
|
||||
assemble the request the same way the browser transport does, from the same rules, or the two sides
|
||||
will disagree about what an identical call sends. **The assembly rules must be shared, not
|
||||
reimplemented**: a second copy will drift, and the drift will be silent because each side is tested
|
||||
separately.
|
||||
|
||||
### The request context
|
||||
|
||||
A server-side call needs `ctx` to resolve the URL and forward cookies and session. `ctx` is not
|
||||
uniformly available: `load` blocks have it, schema actions have it as a second parameter, plain
|
||||
actions and server functions have neither.
|
||||
|
||||
The server stores the request context in an `AsyncLocalStorage` at request entry, and the server
|
||||
`api` object reads it. This is new machinery — the framework uses none today — and it must be
|
||||
established in both the dev server and the production server, or a call that works in development
|
||||
fails in production.
|
||||
|
||||
When no context is present, the call throws with a message naming the block and explaining that an
|
||||
API call needs a request context — never a silent `undefined`.
|
||||
|
||||
## Emission
|
||||
|
||||
A block's `response` and `error` bodies are page code. They ship to the browser **only when a
|
||||
client-side call to that block exists**. The compiler already knows which `api.<name>()` calls
|
||||
appear in client functions.
|
||||
|
||||
This keeps server-only transforms off the wire and the client bundle proportional to what it uses.
|
||||
The consequence to know: adding the first client call to a block starts shipping that block's
|
||||
bodies. Anything secret belongs in the endpoint, not in a `response` body.
|
||||
|
||||
## Render binding
|
||||
|
||||
A block can be bound into markup, which calls it during render and substitutes the result:
|
||||
|
||||
```wrn
|
||||
<p api="listTeams">loading…</p>
|
||||
<ul api="searchUsers({ name: nameFilter })">
|
||||
{#each searchUsers as person}<li>{person.name}</li>{/each}
|
||||
</ul>
|
||||
```
|
||||
|
||||
Three accepted forms: `api="name"`, `api="name()"` — equivalent — and `api="name({ … })"`, which
|
||||
passes arguments. The argument expression is evaluated in the same scope as other view expressions
|
||||
at render time. This mirrors `@click="search()"`, so it introduces no new escaping or naming rules.
|
||||
|
||||
### The edge this creates, stated plainly
|
||||
|
||||
A block that is both render-bound and called from code **runs twice** — once for the binding, once
|
||||
for the call. They are two different lifecycles wearing one name, and no deduplication is attempted:
|
||||
a render-time fetch and a user-triggered fetch are usually meant to be different requests, and
|
||||
silently collapsing them would be worse than the duplication. Authors binding a block _and_ calling
|
||||
it should expect two requests.
|
||||
|
||||
## Replacing the old forms
|
||||
|
||||
`ssr { … }` and `client { … }` data blocks are removed. Each could contain only two things, and both
|
||||
have a home:
|
||||
|
||||
| Old | New |
|
||||
| ------------------------------------------------ | ------------------------------------------------------ |
|
||||
| `ssr { api x … }` / `client { api x … }` | `apis { x … }` |
|
||||
| `ssr { functions { function helper() } }` | `functions { shared function helper() }` |
|
||||
| legacy bare-body `api x GET /p { return users }` | `apis { x GET /p { response { return data.users } } }` |
|
||||
|
||||
The legacy bare body injected the payload with `with ($data ?? {})`, which is **untypeable** —
|
||||
TypeScript cannot see through `with`, and that is the entire reason sectioned blocks bind a named
|
||||
`data`. Removing the legacy form removes that fork: one payload binding, typed.
|
||||
|
||||
`client state { }` is a different construct that shares the keyword and is **not** affected.
|
||||
|
||||
`examples/basic-app/app/pages/hello.wrn` uses both old forms and is the migration's worked example.
|
||||
|
||||
## Type safety
|
||||
|
||||
Assertions are generated into `app/types/wrnexus.generated.api-checks.ts` and checked by the
|
||||
project's own `tsc`, as established. Two changes follow from mode-less declarations:
|
||||
|
||||
- The current generator skips blocks whose `mode !== "client"`. That skip exists because an `ssr`
|
||||
block could never declare a `request`. Mode-less blocks invalidate the reasoning, so **every block
|
||||
with declared fields gets an assertion**.
|
||||
- The zero-field skip stays: a block with no declared fields has nothing to check, and asserting
|
||||
`Record<string, never>` against a contract fails spuriously.
|
||||
|
||||
## Known edges
|
||||
|
||||
- **`state api` stops working.** The name is currently excluded from destructuring only when a page
|
||||
has client api blocks, so pages without them keep using it. Once `api` is universal that
|
||||
protection goes, and a page with `state api` breaks. It is a build-time failure, not silent.
|
||||
- **Every block is browser-reachable in principle.** The routes were already publicly reachable by
|
||||
`fetch`, so this exposes no new surface — but a block is no longer implicitly server-only by
|
||||
virtue of its placement.
|
||||
|
||||
## Testing
|
||||
|
||||
**Parser**
|
||||
|
||||
- `apis { }` parses multiple mode-less entries; duplicate names are rejected.
|
||||
- `ssr { api … }` and `client { api … }` are rejected with a message naming the replacement.
|
||||
- All three binding forms parse, including an argument expression containing a nested object.
|
||||
|
||||
**Dispatch**
|
||||
|
||||
- A client-side call issues one `fetch` with the expected URL, method, body, and CSRF header.
|
||||
- A server-side call dispatches in-process and issues **no** network request — asserted by observing
|
||||
that no fetch occurs, not merely that the result is right.
|
||||
- A server-side call with no request context throws a message naming the block.
|
||||
- The same declared input produces the same request on both sides — the shared-assembly guard.
|
||||
|
||||
**Emission**
|
||||
|
||||
- A block called only from server code does not appear in the browser module.
|
||||
- A block called from a client function does.
|
||||
|
||||
**Render binding**
|
||||
|
||||
- Each of the three forms renders the resolved value.
|
||||
- A bound block that is also called issues two requests, pinning the documented edge.
|
||||
|
||||
**Type safety**
|
||||
|
||||
- A block declaring a field its endpoint rejects fails `bun run typecheck`, asserted by running
|
||||
`tsc` and reading its diagnostics — not by matching generated text.
|
||||
|
||||
**End to end**
|
||||
|
||||
- `examples/basic-app` migrated to `apis { }`, driven in a browser: a client call updates state, a
|
||||
render-bound block appears in the served HTML, and a deliberately failing call takes the `error`
|
||||
path.
|
||||
|
||||
## Deferred
|
||||
|
||||
- External API targets, and the allowlist and credential handling they need.
|
||||
- Author-settable headers.
|
||||
- Deduplicating a render-bound block against a code call.
|
||||
- Response caching and request de-duplication.
|
||||
@@ -0,0 +1,131 @@
|
||||
# Editor support for the new syntax — Design
|
||||
|
||||
**Date:** 2026-08-19
|
||||
**Status:** Approved for implementation
|
||||
**Scope:** Language server and VS Code extension updated for `apis { }`, the `api.<name>()` call, and
|
||||
the removal of the mode data blocks.
|
||||
|
||||
**Depends on:** `2026-08-19-apis-block-design.md`. The syntax must exist before the editor can
|
||||
describe it.
|
||||
|
||||
## Goal
|
||||
|
||||
A syntax change that the editor does not know about is worse than no change: valid code is
|
||||
red-underlined, removed constructs still autocomplete, and the new block gets no highlighting. This
|
||||
spec keeps the tooling level with the language.
|
||||
|
||||
It also settles a question left open by earlier work: whether type errors from the generated
|
||||
assertions actually appear inside the `.wrn` file, or only in the generated file. That was never
|
||||
verified — it was inferred from reading source — and inference is not good enough for the thing
|
||||
developers rely on to tell them their code is wrong.
|
||||
|
||||
### Non-goals
|
||||
|
||||
- New editor features unrelated to this syntax change.
|
||||
- Editors other than VS Code beyond what standard LSP provides.
|
||||
- HTML formatting — `formatWrn` still owns markup.
|
||||
|
||||
## What exists
|
||||
|
||||
- **Grammar:** `editors/vscode/syntaxes/wrn.tmLanguage.json` names block keywords directly —
|
||||
`api` appears 4 times, `client` 6, `server` 5, `ssr` once.
|
||||
- **Keyword list:** `WRN_KEYWORDS` in `packages/language-server/src/index.ts` drives completion and
|
||||
includes `api` but not `apis`.
|
||||
- **Extension completions:** `editors/vscode/src/completion.js` carries block snippets.
|
||||
- **Server features:** completion, hover, folding, linked editing, tag completion, and TypeScript
|
||||
diagnostics over a virtual document.
|
||||
|
||||
## Changes by surface
|
||||
|
||||
### Grammar
|
||||
|
||||
Add `apis` as a block keyword. Remove the `ssr` / `client` **data block** patterns, keeping `client`
|
||||
and `server` where they mean other things — `client state { }`, the `runtime` values, and the
|
||||
function modifiers in `functions { }`. This is the change most likely to over-reach: `client` is one
|
||||
word with several jobs in this language, and blanket removal would un-highlight constructs that
|
||||
still exist.
|
||||
|
||||
Highlight an `apis` entry's shape — name, method, path — so a declaration reads as a declaration
|
||||
rather than as loose identifiers.
|
||||
|
||||
### Keyword and block completion
|
||||
|
||||
- `apis` joins `WRN_KEYWORDS`.
|
||||
- A snippet for the container and a snippet for an entry, including the `request` / `response` /
|
||||
`error` sections, so the shape is discoverable without the docs.
|
||||
- `ssr` and `client` data-block snippets are removed from `completion.js`. Offering a construct the
|
||||
compiler rejects is worse than offering nothing.
|
||||
|
||||
### Call completion — the feature worth building
|
||||
|
||||
Inside a function body, `api.` should complete to the names declared in that page's `apis { }`
|
||||
block, with the method and path as detail. The server already indexes the document to build
|
||||
completions, and the block names are in the AST.
|
||||
|
||||
This is where the syntax pays off in the editor: the set of legal calls is knowable, so the editor
|
||||
should know it. Without it, `api.` is an empty namespace and every call is typed from memory.
|
||||
|
||||
Hovering a name inside `api.<name>()` shows its method, path, and declared request fields.
|
||||
|
||||
### The `api=` attribute in markup
|
||||
|
||||
`api="searchUsers({ name: nameFilter })"` is an attribute whose value is a call expression. The HTML
|
||||
service must not flag it as an unknown attribute, and the expression must not be treated as plain
|
||||
text. Completion inside the quotes offers the page's block names, matching the `api.` behaviour.
|
||||
|
||||
### Diagnostics for removed constructs
|
||||
|
||||
An `ssr { api … }` or `client { api … }` block gets a diagnostic naming `apis { }` as the
|
||||
replacement, positioned on the block keyword. The compiler already rejects these; the editor should
|
||||
say so while typing rather than at build time, and it should say what to do instead.
|
||||
|
||||
## Inline type errors — verifying, not assuming
|
||||
|
||||
The `apis` spec generates assertions into `app/types/wrnexus.generated.api-checks.ts`, and `tsc`
|
||||
fails when a block declares a field its endpoint rejects. Whether that failure surfaces **inside the
|
||||
`.wrn` file** has never been confirmed.
|
||||
|
||||
This spec resolves it in two steps, in order:
|
||||
|
||||
1. **Observe the current behaviour.** With a deliberately wrong field in place, open the page in VS
|
||||
Code and record where the error appears: on the block, only in the generated file, or nowhere.
|
||||
2. **Act on what is observed.** If the error already surfaces usefully, document it and stop. If it
|
||||
appears only in the generated file, map the diagnostic back to the block that produced it — the
|
||||
generator knows which page and block each assertion came from, so the mapping is available if it
|
||||
is recorded rather than discarded.
|
||||
|
||||
If step 2 proves larger than this spec can hold, it becomes its own work, and the spec says so
|
||||
plainly rather than leaving an unfinished feature implied. **Nothing here should claim inline
|
||||
diagnostics work until someone has seen them work.**
|
||||
|
||||
## Testing
|
||||
|
||||
**Grammar** — a fixture page using `apis { }`, `client state { }`, `functions { shared function }`,
|
||||
and `runtime = "client"` tokenizes correctly; `client` keeps its highlighting everywhere it is still
|
||||
valid. This is the guard against over-reaching removal.
|
||||
|
||||
**Completion**
|
||||
|
||||
- `apis` is offered at page level; `ssr` / `client` data blocks are not.
|
||||
- `api.` inside a function body offers the page's declared names with method and path.
|
||||
- Inside `api="…"` in markup, the same names are offered.
|
||||
- Outside those contexts, completion is unchanged — the guard that non-API editing is undisturbed.
|
||||
|
||||
**Hover** — a name inside `api.<name>()` reports its method, path, and request fields.
|
||||
|
||||
**Diagnostics** — an `ssr { api … }` block produces a diagnostic naming `apis { }`, positioned on
|
||||
the block keyword.
|
||||
|
||||
**Bundles** — `check:editor-compiler`, `check:editor-language-server`, and
|
||||
`check:editor-extension` pass. These embed the compiler and language server, so they must be rebuilt
|
||||
after the syntax change; a stale bundle fails the gate.
|
||||
|
||||
**Manual, and recorded in the implementation notes** — open the migrated `examples/basic-app` in VS
|
||||
Code: `apis { }` highlights, `api.` completes, a removed construct is flagged, and the inline
|
||||
type-error question above is answered by observation.
|
||||
|
||||
## Deferred
|
||||
|
||||
- Mapping generated assertion diagnostics back into `.wrn`, if step 2 above proves too large.
|
||||
- Moving the remaining component intelligence out of `completion.js` and into the server.
|
||||
- Editors other than VS Code.
|
||||
@@ -0,0 +1,140 @@
|
||||
# Carrying projects to the new syntax with `wrnexus update` — Design
|
||||
|
||||
**Date:** 2026-08-19
|
||||
**Status:** Approved for implementation
|
||||
**Scope:** Migrations that take an existing project from today's syntax to the syntax left by the
|
||||
cleanup and `apis { }` specs.
|
||||
|
||||
**Depends on:** `2026-08-19-legacy-and-config-cleanup-design.md` and
|
||||
`2026-08-19-apis-block-design.md`. Both define the target this migrates to, so both must land first.
|
||||
|
||||
## Goal
|
||||
|
||||
After the two preceding specs, every existing project is written in a syntax the framework no longer
|
||||
accepts. One `wrnexus update` should carry a project across — config keys removed, `api` blocks
|
||||
moved into `apis { }`, mode-scoped helpers relocated — and, where it cannot do that safely, say so
|
||||
precisely instead of guessing.
|
||||
|
||||
## What already exists
|
||||
|
||||
This is an extension of working machinery, not a new subsystem:
|
||||
|
||||
- `Migration { version, id, description, apply(ctx) }`, run when `from < version <= to`.
|
||||
- `MigrationCtx` carries `appRoot`, `from`, `to`, **`dryRun`**, a report, and a logger.
|
||||
- `MigrationReport` already separates `changedAutomatically`, `needsReview`, `parseFailures`,
|
||||
`ambiguousFunctions`, and `unresolvedImports`.
|
||||
- `update.ts` already imports `parse` and `formatWrn` from `@wrnexus/syntax`, so parsing a `.wrn`
|
||||
file, transforming it, and re-emitting formatted source is an established pattern here.
|
||||
|
||||
The report's shape matters: it was built around the idea that some changes are safe to make and
|
||||
others must be handed back to a human. That distinction is the backbone of this spec.
|
||||
|
||||
### Non-goals
|
||||
|
||||
- Migrating projects below `0.8.0`. The cleanup spec removes those migrations; no such project
|
||||
exists.
|
||||
- Rewriting application logic. Only the constructs these specs changed.
|
||||
|
||||
## The safety contract
|
||||
|
||||
**A file is transformed correctly, or it is left untouched and reported.** There is no third
|
||||
outcome. Concretely:
|
||||
|
||||
1. Parse the file. A parse failure records the path in `parseFailures` and moves on — the file is
|
||||
never partially rewritten.
|
||||
2. Transform, then re-emit through `formatWrn`.
|
||||
3. If any part of a file's transform cannot be completed, **the whole file is skipped** and recorded
|
||||
in `needsReview` with the reason and the construct involved.
|
||||
|
||||
Running the migration twice must be a no-op: every transform detects already-migrated input and
|
||||
does nothing. Dry-run must report exactly what a real run would change.
|
||||
|
||||
## The migrations
|
||||
|
||||
### 1. Remove the dead config keys
|
||||
|
||||
Delete `compatibility`, `functions`, `compatibilityDate`, and `frameworkBehaviour` from
|
||||
`wrnexus.config.ts`. Mechanical, fully automatic, and safe because none of them was ever read.
|
||||
|
||||
### 2. `ssr { api … }` / `client { api … }` → `apis { … }`
|
||||
|
||||
Move each `api` entry into a page-level `apis { }` block, dropping the mode. Sectioned bodies —
|
||||
those already using `request` / `response` / `error` — carry across unchanged, because the payload
|
||||
is already bound to `data`.
|
||||
|
||||
Fully automatic. If a page has entries in both an `ssr` and a `client` block sharing a name, that is
|
||||
a duplicate under the new rules and the **file is skipped and reported**, since choosing which one
|
||||
survives is a decision about intent.
|
||||
|
||||
### 3. `ssr { functions { … } }` → `functions { shared function … }`
|
||||
|
||||
Relocate mode-scoped helpers to the page-level `functions { }` block with the `shared` modifier.
|
||||
Automatic. If the page already has a function of the same name, the file is skipped and reported.
|
||||
|
||||
### 4. Legacy bare-body `api` blocks — **needs review, not automatic**
|
||||
|
||||
This is the one transform that cannot be done safely, and the spec is explicit about it rather than
|
||||
attempting a best effort.
|
||||
|
||||
A legacy bare body is evaluated inside `with ($data ?? {})`, so it references payload fields as bare
|
||||
identifiers:
|
||||
|
||||
```wrn
|
||||
api ssrUsers GET /api/users/ssr {
|
||||
return userNames(users)
|
||||
}
|
||||
```
|
||||
|
||||
The sectioned form binds the payload to `data`, so this must become `data.users`. But **which free
|
||||
identifiers are payload fields is not knowable from the source.** In the example, `users` comes from
|
||||
the response and `userNames` is a page helper — and nothing in the file distinguishes them. The
|
||||
response shape belongs to the route, and the route may not even be typed.
|
||||
|
||||
A migration that guessed would produce code that compiles and is wrong: `data.userNames(...)` or an
|
||||
untouched `users` that silently resolves to `undefined`. That is precisely the silent-wrong-answer
|
||||
failure this project keeps paying for.
|
||||
|
||||
So: legacy bare-body blocks are **detected, reported in `needsReview` with the file, the block name,
|
||||
and the free identifiers found**, and left untouched. The report tells the author exactly what to
|
||||
decide. `wrnexus update` prints a short explanation of why this one is manual.
|
||||
|
||||
### 5. Deprecated `@wrnexus/auth` options
|
||||
|
||||
Only if the cleanup spec's optional auth section is included. Rename call sites of the superseded
|
||||
options. Automatic where the rename is unambiguous; reported otherwise.
|
||||
|
||||
## Version
|
||||
|
||||
All of these attach to the release that ships the breaking change. After the cleanup spec the
|
||||
migration floor is `0.8.0`, so the list is short and every entry is reachable.
|
||||
|
||||
## Output
|
||||
|
||||
At the end of a run the command prints, in this order: what it changed, what needs review and why,
|
||||
and what failed to parse. A run with anything in `needsReview` or `parseFailures` exits non-zero, so
|
||||
a scripted upgrade cannot appear to succeed while leaving a project half-migrated.
|
||||
|
||||
## Testing
|
||||
|
||||
Each migration gets a fixture project and three assertions: the transform produces the expected
|
||||
source, running it a second time changes nothing, and a dry run reports the same set without writing.
|
||||
|
||||
- **Config removal** — keys gone, rest of the config untouched.
|
||||
- **`api` relocation** — a page with both `ssr` and `client` api blocks lands in one `apis { }`;
|
||||
entries keep their names, methods, paths, and sections.
|
||||
- **Name collision across modes** — the file is skipped and reported, not silently merged.
|
||||
- **Mode functions** — relocated with the `shared` modifier; a name collision skips and reports.
|
||||
- **Legacy bare body** — reported in `needsReview` with the block name and free identifiers, and the
|
||||
file is byte-identical afterwards. This is the most important test in the spec: it pins that the
|
||||
migration does _not_ attempt the rewrite.
|
||||
- **Parse failure** — a malformed `.wrn` is recorded in `parseFailures` and left untouched.
|
||||
- **Exit code** — non-zero when anything needs review.
|
||||
- **End to end** — `examples/basic-app` migrated by the command alone, then built and tested. If the
|
||||
framework's own example cannot be migrated by the tool, the tool is not finished.
|
||||
|
||||
## What this does not promise
|
||||
|
||||
Automated source rewriting cannot be promised as "perfect". What is promised is bounded: every file
|
||||
is either correctly transformed or untouched and named in the report, with the reason. Nothing is
|
||||
half-rewritten, and nothing is guessed. The legacy bare-body case is deliberately manual because a
|
||||
correct automatic answer does not exist.
|
||||
Reference in New Issue
Block a user