Refresh Project#527
Open
kadamwhite wants to merge 55 commits into
Open
Conversation
Companion to modernization-plan.md (the vision): a concrete, sequenced execution plan broken into independently-mergeable phases, each with its own regression net. Records the decisions made during planning. Locked decisions: - Bundler: tsdown (Rolldown), replacing webpack + Grunt + Babel - Native fetch as the default transport; Node 18+; drop IE11 - Remove superagent entirely (no legacy adapter) - Batteries-included main export (restores v1.x ergonomics) - Build-time pre-parsed route tree to cut startup parsing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The integration suite asserts against the classic "Theme Unit Test" dataset, previously supplied by a separate Chassis VM (kadamwhite/wpapi.local). Replace that with a reproducible wp-env seed: - Commit the WXR as a fixture (tests/fixtures/theme-unit-test-data.xml). - Add `npm run env:seed`: empties the site (drops the default "Hello world!" post and "Sample Page" for determinism), then imports the WXR. - Install the WP-API/Basic-Auth plugin via .wp-env.json so authenticated tests work -- modern WP core rejects plain username/password over REST. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two coupled changes across the integration suite: - Endpoint is no longer hardcoded to http://wpapi.local/wp-json. It comes from tests/helpers/constants.js, which reads WPAPI_HOST (default the wp-env address http://localhost:2747) and derives the /wp-json endpoint. Pagination and upload-URL assertions build off the same constants. - Expected data values updated to match modern WordPress. The WXR "Scheduled" post was future-dated when these tests were written; today it is published, which grows the post set 38 -> 39 and shifts pagination and date-filter results. Modern WP also exposes more settings keys and defaults to an empty tagline. Only expected values changed; every assertion still verifies the same behavior (pagination, filtering, ordering, CRUD round-trips). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WPAPI.discover() read the API root's _links.self as a bare string, but current
WordPress returns an array of link objects ([ { href } ]). Accept either shape.
discover() still cannot bootstrap fully against modern WP: it builds a route
tree from the live route list, which now includes routes whose named groups
contain nested patterns (e.g. wp/v2/templates/(?P<id>...)), and the route-tree
regex parser throws "Unterminated group". That parser work is scoped to phase 5,
so the discover suite is skipped with a TODO. Its beforeAll .catch()es the
rejection so the skipped-but-still-executed hook can't leak into other suites.
Default-mode instances are unaffected -- they use the bundled default-routes.json.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- handoff.md: living status doc; records the Phase 0 baseline, the deferred discover/route-parser gap, and the dev workflow. - modernization-plan.md: backlog note to add a secondary block/FSE-oriented seed in phase 5 (the classic WXR predates the block editor), kept separate from the classic dataset that guards the wp/v2 regression net. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Establishes a reproducible local integration environment (wp-env + seeded Theme Unit Test data + Basic-Auth) and gets the full suite green on modern WordPress: 805 passed, 16 skipped (discover, deferred to phase 5), 0 failed. No request/library logic changed except an isolated discover() self-link compatibility fix.
Call out moving all automation to GitHub Actions as a first-class concern: ci.yml (phase 1), docs.yml deploy (phase 6), and release.yml publish. Replaces Travis and the hand-run docs/release scripts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Swaps the browser/Node build entirely for tsdown (Rolldown-based): dual
ESM+CJS for the `index`/`fetch`/`superagent` entries plus a browser UMD
bundle per transport, with generated .d.ts and package.json `exports` map.
Each Node entry gets its own independent tsdown config rather than one
shared multi-entry config, because `fetch`/`superagent` both `require`
`wpapi.js`: sharing a chunk across entries breaks rolldown's CJS
`module.exports` codegen (it emits multiple sequential `module.exports =`
assignments into one file, so only the last one takes effect). Bundling
each entry's full dependency closure independently avoids that.
The browser UMD builds alias `node-fetch`/`form-data`/`fs` to thin stubs
in build/browser-shims/ that resolve to the native browser globals,
mirroring the old webpack `externals`/`node.fs` config; tsdown's default
auto-externalization of package.json dependencies is overridden for the
browser build so those aliases (and plain bundling of qs/li/superagent)
actually take effect.
.d.ts generation needs tsconfig.json's `module: "commonjs"` (the source
is plain CJS) and TypeScript's classic `tsc` generator. Two JSDoc
`@returns {WPRequest}` annotations in wpapi.js are widened to `{Object}`:
rolldown-plugin-dts can't bundle the cross-file CJS-shaped declaration
that annotation forces, and richer per-route typing is Phase 3's job
anyway.
Deletes webpack.config*.js, Gruntfile.js, and build/grunt/ (docs-only
Grunt tasks; Phase 6 replaces the whole docs pipeline).
vitest.config.js sets `globals: true` (Jest-compatible describe/it/expect
globals) and a setup file aliasing the `jest` global to `vi`, so the
~30 existing jest.fn()/jest.spyOn() call sites across the suite port
without any changes.
tests/unit/wpapi.js required the package root (`require('../../')`),
which resolves through package.json's `main` field. That field now
points at the tsdown-built dist/index.cjs instead of source wpapi.js,
so the WPRequest class that test imports directly no longer matched
the one used inside the WPAPI instance it was constructing, breaking
an `instanceof` assertion. Fixed by requiring `../../wpapi` directly,
which also decouples this suite from wherever `main` happens to point
(relevant again once Phase 2 rebinds `main` to the fetch-bound export).
eslint.config.js carries forward the exact same custom rules as the old .eslintrc.js (and its per-directory overrides for tests/, build/, and lib/data/), including the project's space-inside-parens/brackets/braces convention. Prettier is added (.prettierrc.json: tabs, single quotes) but deliberately NOT run across the existing codebase or wired into `lint`/CI: Prettier has no option to reproduce that bracket-spacing convention, and running it would fight the ESLint rules that enforce it on every file it touches. It's available via `npm run format`/`format:check` for new code or a future full-codebase decision. Deletes .eslintrc.js, .eslintignore, and the per-directory .eslintrc.js overrides, all superseded by the flat config.
Mechanical comma-dangle fixes across files not previously covered by the old eslint script's target list (it never linted fetch/ or superagent/ directly), applied via `eslint --fix`. ESLint 9's eslint:recommended also added two rules the old ESLint 4 didn't have, surfacing two pre-existing, harmless-but-worth-fixing issues: no-prototype-builtins (two `.hasOwnProperty()` calls, now `Object.prototype.hasOwnProperty.call(...)`) and no-unused-vars (one unused `catch (e)` binding, now an optional catch binding).
package.json: engines.node >=18, drop browserslist/IE11 support, drop the Jest config block (superseded by vitest.config.js). Scripts: jest -> vitest across the board; eslint invocation updated for the flat config; a `typecheck` script is added (`tsc --noEmit`; not yet meaningful since checkJs is off until Phase 3); `docs` no longer depends on the now-deleted Grunt (runs jsdoc directly); the `zip` script (grunt-zip against the old browser/ output) is dropped. Removes the corresponding webpack/Grunt/Babel/ESLint4/Jest devDependencies. package-lock.json is now committed. The old "consuming applications maintain their own lockfile" rationale doesn't hold once this repo has its own CI: actions/setup-node's `cache: npm` and `npm ci` both need one checked in for reproducible builds. Deletes .travis.yml, replaced by GitHub Actions.
ci.yml has three jobs, not a flat 18/20/latest matrix: the toolchain itself doesn't support that range (tsdown/rolldown need Node >=22.18, vitest needs >=20). `test` (Node 22/24/latest) runs lint, typecheck, build, and unit tests, and uploads dist/ as an artifact from the `latest` leg. `dist-smoke` (Node 18/20) downloads that artifact and just requires the pre-built CJS/ESM entries, checking the published `engines.node: >=18` promise against the actual output without needing the build tooling to run on old Node. `integration` (Node 22) boots @wordpress/env and runs test:integration; note `wp-env logs` defaults to `--watch: true` and never exits, so it's invoked directly with `--no-watch` rather than through the `env:logs` dev-convenience script. release.yml is scaffolded per the plan (build, test, `npm publish --provenance` on a GitHub Release) but inert until an NPM_TOKEN secret is added and publishing resumes.
Replaces webpack + Grunt + Babel with tsdown (dual ESM+CJS+UMD+.d.ts, generated exports map), Jest with Vitest, ESLint 4 with ESLint 9 flat config + Prettier scaffold, sets Node 18+ / drops IE11, and stands up GitHub Actions CI (ci.yml) with a release.yml scaffold, replacing .travis.yml. No library behavior changed. Full suite green against wp-env: 805 passed, 16 skipped, 0 failed — identical to the Phase 0 baseline. See handoff.md for the tricky decisions and workarounds this phase required.
Two suites written ahead of the implementation, per break-tests-first: - fetch/tests/unit/fetch-transport.js exercises the fetch transport's internals directly (the existing fetch/tests/unit/fetch.js only spies on the transport boundary, so the actual HTTP code had no unit coverage). It stubs the global fetch and asserts the rewritten transport will use it — with native FormData/Blob/File uploads covering all documented .file() input shapes: Blob, File, Buffer + name, and disk path (with and without a name override). - superagent/tests/unit/superagent.js replaces the old superagent-bound WPAPI suite: wpapi/superagent is being removed, and the subpath must throw a clear migration error pointing at the fetch-based default. All 19 tests fail against the current node-fetch/form-data implementation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The transport now calls the global fetch available in every supported runtime (Node 18+, browsers, workers) instead of requiring node-fetch, and media uploads build a native FormData: Blob/File attachments are appended as-is, Buffers are wrapped in a Blob, and string paths are read from disk into a Blob (with the on-disk basename as the default upload name, matching form-data's old behavior). The node:fs/promises require is lazy, inside the path branch, to keep Node built-ins out of browser bundles. .file() now also validates that a name accompanies any attachment that cannot carry its own (a Buffer, or a Blob that is not a File) — WordPress rejects uploads without an extension, so fail early and clearly instead. This makes the 19 transport-contract tests from the previous commit pass and drops the node-fetch, form-data and fs requires; the dependencies themselves are removed from package.json in a following commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rror The superagent transport directory is deleted, and superagent dropped from optionalDependencies. wpapi/superagent remains an exported subpath, but now throws an error explaining the removal and pointing at the fetch-based default export (and at custom transports for anyone who depended on superagent-specific behavior). Keeping the entry means upgrading consumers get that message rather than a bare ERR_PACKAGE_PATH_NOT_EXPORTED. Integration suites previously ran once per transport via describe.each; the superagent rows are dropped and the fetch rows kept. The wpapi-superagent browser UMD bundle is no longer built. build/scripts/update-default-routes-json.js was the last remaining superagent consumer; it now downloads the endpoint JSON with native fetch (same redirect-following, content-type validation and error-exit behavior). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
require('wpapi') / import WPAPI from 'wpapi' is batteries-included
again: the index entry now builds from fetch/index.js, restoring v1.x
ergonomics by reversing the 2.0.0-alpha's transport-less split.
wpapi/fetch remains as an alias subpath for code written against the
alpha. The transport-less base constructor is still available in-source
(wpapi.js + lib/bind-transport.js) but is no longer shipped as its own
entry.
With node-fetch and form-data gone from the transport, the browser UMD
build no longer needs shim aliasing — build/browser-shims/ is deleted
and the last optionalDependencies are dropped. The dist-smoke CI job now
also asserts the main entry has a bound transport and that the
superagent stub throws its migration error on Node 18/20.
Known tradeoff: dist/index.d.cts now carries the fetch entry's weaker
generated type (a bare callable) instead of wpapi.js's fuller shape.
Real typings are Phase 3's TypeScript migration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The plugin registered its callback on 'wp enqueue scripts' (spaces), a hook that never fires, and read package.json relative to the undefined constant __FILE. Both typos meant the bundled convenience plugin never actually enqueued the script. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
README installation, upgrade, media-upload and custom-transport sections now describe the 2.0.0 behavior: native fetch built in, no superagent, Node 18+ floor, no IE11. The fetch/ subpath README no longer instructs installing isomorphic-unfetch, and the CHANGELOG's v2.0.0 entry reflects the final (non-alpha) breaking-change list. Full docs overhaul remains Phase 6. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…safe globals Three review findings against the rewritten transport: - The unsupported-method guard (checkMethodSupport) lived only in the deleted superagent transport, so e.g. calling .delete() on a GET-only route would now transmit a live DELETE instead of throwing locally as v1 defaults did. All five verbs now check the route's supported methods before dispatch. - _httpHead resolved the response headers without checking response.ok, so .headers() on a missing resource resolved instead of rejecting. HEAD responses carry no body to extract an API error object from, so reject with the response itself. - global.Buffer / global.btoa throw ReferenceError in browsers (no `global` identifier outside Node; the old webpack build shimmed it, rolldown does not). Now globalThis, which is universal. Also collapses the FormData.append name branch: WebIDL treats an explicitly-undefined filename as omitted, so the two calls were equivalent (covered by the File-name-preservation test). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review findings against the new .file() validation: - The File-instanceof exemption used the File global, which only exists from Node 20 — on Node 18 (our documented floor) a named File object was misclassified as a bare Blob and rejected. A Blob attachment is now exempted from the name requirement by its own name property, which any File carries regardless of runtime. - Stream attachments (accepted by the old form-data/superagent stack, impossible with native FormData) previously fell through validation and produced an opaque undici error or a stringified '[object Object]' form field. .file() now rejects anything that is not a path string, Buffer, Blob or File with an explicit streams-are-gone message. - global.Buffer swapped for globalThis.Buffer, which does not throw as an undeclared identifier in browsers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two review findings against the dist output, fixed with a post-build pass (build/scripts/finalize-dist.js, now the last step of npm run build): - tsdown emitted the index and fetch entries as two independent, byte-identical ~95K bundles, so 'wpapi' and 'wpapi/fetch' resolved to two separate module instances (distinct constructors, instanceof failures across the boundary) and the package carried ~190 kB of duplication. fetch.* are now one-line re-exports of index.*. - The generated index.d.* declarations erased the export to an anonymous Function shape with no construct signature — worse than no types, since shipping `types` also disables any @types/wpapi fallback and `new WPAPI()` failed to compile. Typing the source directly hits rolldown-plugin-dts's cross-file CJS declaration crash (the known Phase 1 constraint), so the script writes an honest interim declaration instead: constructable, known statics, permissive instance surface. Verified with a strict-mode nodenext consumer compile. Phase 3's TypeScript migration deletes this workaround. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- node-wpapi.php enqueued browser/wpapi.min.js, a path nothing has ever built; with the hook-name typo fixed that would have become a live 404 on every page load. Point it at the UMD bundle tsdown actually emits (dist/browser/wpapi.umd.js) and drop the SCRIPT_DEBUG branch — there is only one browser artifact now. - update-default-routes-json.js attached its download-failure handler as a trailing .catch, so an exception thrown by the downstream simplify-and-write callback was misreported as "Could not download endpoint JSON". The handler now sits alongside the callback so downstream errors surface with their real stack. - The superagent test directory held a single stub assertion but kept four config references alive (vitest include, eslint files, two npm scripts). Moved the test to tests/unit/superagent-stub.js and dropped the extra config surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Also notes the accepted upload behavior delta in CHANGELOG/README: native Blobs carry no filename-inferred MIME type, so the multipart part is sent as application/octet-stream and WordPress resolves the real type from the file name (a typed Blob/File gives explicit control). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Native fetch/FormData/Blob transport, batteries-included main export, superagent removed (subpath stubs a migration error), node-wpapi.php fixes, and a review pass restoring v1 client-side guards. Suite: 599 unit + 110 integration passed, 8 skipped (discover, deferred to Phase 5). See handoff.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 3 groundwork. tsconfig turns on strict + checkJs and includes .ts sources; every not-yet-converted JS file gets a temporary @ts-nocheck header that is removed as each file converts, so 'tsc --noEmit' stays clean at every commit along the migration. Supporting pieces: - tests/helpers/ts-require-hook.js: Vitest loads CommonJS source through native require, which cannot resolve or execute .ts files. The hook registers a require.extensions['.ts'] transpiler (using the project's own typescript package), which also makes extensionless requires resolve to .ts. This is what lets the suite stay green while the graph is mixed JS/TS. - @types/node (^20 -- vitest 4's floor; the published runtime floor remains Node 18) and @types/qs; ambient declaration for li (no published types). - ESLint style rules now also apply to .ts files; import-assignment form of require is allowed since converted source keeps CommonJS semantics. - Integration tests drop explicit .js extensions when requiring wp-request, so the specifier survives the upcoming rename. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First conversion layer: the 13 pure-leaf utility modules, plus a new type-only lib/types.ts holding minimal structural interfaces (MethodSupportRequestLike, ParamRequestLike) for the helpers that operate on request-like objects. Converted files keep CommonJS semantics via 'export =' so the unconverted JS graph and the tests' require() calls see the exact same shapes. One critical constraint discovered here, applied throughout: a top-level ESM import declaration -- even 'import type' -- makes rolldown classify a file as ESM and silently compile its 'export =' to an empty object in the bundle. Type-only cross-file references therefore use inline 'type X = import( ... ).X' aliases, and value imports use the 'import x = require( ... )' assignment form. One deliberate behavior delta in an unused code path: isEmptyObject( null ) now returns false (was true, since typeof null === 'object' passed the old guard). The helper currently has no production callers -- Phase 2's transport rewrite dropped its last use -- and no test pinned the null case. Also fixed copy-pasted/incorrect JSDoc in argument-is-numeric and a duplicated param name in alphanumeric-sort. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second conversion layer. lib/types.ts gains RouteTreeNode (the recursive node shape route-tree.js builds -- layer 3 converts against this same interface), PathPartRequestLike, and FilterRequestLike; mixin methods and path-part setter closures are 'this'-typed against those structural interfaces, to be reconciled with the real WPRequest class when it converts. Notes: - alphanumeric-sort's params widened to string | number -- its documented contract, and taxonomy terms genuinely pass numbers. - unique()'s always-ignored second argument dropped at its one call site (a lodash.uniq leftover; no-op since the Set-based rewrite). - isNaN( monthDate ) became isNaN( monthDate.getTime() ) -- identical NaN-for-invalid-date semantics without relying on implicit coercion. - new Date( date ) call sites cast to string | number: the pinned TS 5.9 es5 lib omits the Date-instance overload the runtime accepts. - Fixed copy-pasted @method/@param names on .category()/.excludeTags(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Third conversion layer: route-tree, resource-handler-spec, endpoint-request, endpoint-factories. lib/types.ts gains the shared shapes for the dynamic surface: RouteDefinition (default-routes.json values and wp-register-route's synthetic route objects -- methods and endpoints are both optional), RouteTree/RouteTreeLevels, HandlerSpec, and EndpointFactory/EndpointRequestCtor (the factory-function-with-.Ctor pattern, instance type deliberately loose until WPRequest converts). The runtime-generated 'class EndpointRequest extends WPRequest' works against tsc's JSDoc-inferred constructor type for the still-JS WPRequest; its prototype-mutation loops (mixins + path setters) go through a single locally-scoped cast of the prototype to a plain dictionary. Casts in route-tree cover invariants TS cannot correlate (mandatory regex capture groups, reduce accumulator narrowing across closures); runtime code is unchanged throughout. Full suite verified at this milestone: 599 unit + 110 integration passed, 8 skipped (deferred discover suite). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fourth conversion layer, the core request class. The constructor-function + prototype-assignment pattern becomes an ES class -- verified safe first: nothing calls WPRequest() without new, and nothing enumerates the prototype, so new-enforcement and method-enumerability deltas are unobservable. Method bodies are otherwise untouched; the .file() validation and _renderQuery assembly from Phase 2 are byte-identical modulo type annotations. The ten paramSetter()-generated methods (context, page, perPage, offset, order, orderby, search, include, exclude, slug) remain prototype assignments after the class body, with their signatures supplied through declaration merging on an adjacent 'interface WPRequest' -- the merged interface is also where their per-method JSDoc lives now. lib/types.ts gains HTTPTransport and WPRequestOptions, shared with the transport and entry layers next. Instance properties are all declared on the class: ctor-assigned ones concretely, mixin/subclass-added ones optional. .then() mirrors PromiseLike's loose typing so awaiting a request chain keeps a useful resolved type for consumers. Full suite verified: 599 unit + 110 integration passed, 8 skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Final conversion layer: wpapi.ts, lib/bind-transport.ts, lib/wp-register-route.ts, fetch/fetch-transport.ts, fetch/index.ts, and the superagent stub. Every source file is now strict TypeScript; no @ts-nocheck headers remain. One deliberate behavior change, updated break-tests-first: the base WPAPI constructor function became an ES class, dropping its auto-new guard (bare WPAPI() calls now throw instead of self-instantiating). The guard has been unreachable from the published entries since Phase 2 made the main export a bound subclass -- bare calls on the package entry already threw -- so this only affects direct consumers of the unpublished base module. The 'enforces new' unit test now asserts the throw. Typing decisions for the dynamic surface, per the execution plan's pragmatic mandate (richer per-route types hang off Phase 4's precompute): - WPAPI declares its real members and statics, plus an '[ routeHandler: string ]: any' index signature for the dynamically attached route handler factories (same contract as the interim hand-written d.ts). - auth/setHeaders remain runtime aliases of WPRequest.prototype methods, with signatures declared via interface merge; url()/root() regain their true WPRequest return type (undoing a Phase 1 JSDoc widening). - bind-transport uses the TS mixin pattern (generic constructor param), so the fetch entry's export type is a constructable class with statics. - The transport's lazy node:fs/promises require and globalThis guards are untouched; response payloads type as any by design. tsdown entries now point at the .ts sources. Suite: 599 unit + 110 integration passed, 8 skipped; all four dist smokes green (CJS get, static site + headers, superagent throw, ESM get). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…itten typings
With the entries now real TypeScript, tsdown's generated dist/*.d.* are
constructable and carry the full typed surface (WPRequest methods, WPAPI
statics, the pragmatic index signature for route handlers), so the
hand-written interim declaration in finalize-dist.js is deleted. The
script keeps its other job: rewriting the fetch.* entry files (runtime
and declarations) as one-line re-exports of index.*, preserving
single-module-instance semantics for the alias subpath.
Verified with strict-mode scratch consumers in .scratchpad/type-consumer/
across both module paths: consumer.cts (require, incl. the fetch alias)
and consumer.mts (import) typecheck clean, and negative.cts's three
intentional misuses all error -- generated types now reject bad code,
which is exactly what the Phase 2 hand-patch could not do. The two
remaining 'Failed to emit declaration' build warnings belong to the
exportless superagent throw-stub (its declarations emit as 'export {}',
which is correct for that module).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Also replaces endpoint-request's ConstructorParameters-derived options alias with the shared WPRequestOptions type now that WPRequest is real TypeScript, and adds the .scratchpad/type-consumer README. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All source is now strict-mode TypeScript, converted leaf-up in five layers with CommonJS semantics preserved (export =). Generated .d.ts replace Phase 2's hand-written interim typings; verified constructable and misuse-rejecting via strict scratch consumers. Suite unchanged: 599 unit + 110 integration passed, 8 skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the Node 22 CI failure (present on main since the Phase 2 merge -- the Node 22 leg has been red, masked locally by developing on Node 24). FormData coerces an explicitly-passed undefined filename argument to the string 'undefined' on Node 18-22, clobbering a File attachment's own name; Node 24+ treats a trailing undefined as absent. Omit the argument entirely when no name override exists, which is what the adjacent comment always claimed the code did. Verified: transport unit suite 20/20 under Node 22.14 (was 19/20), full 599 unit suite and the media-upload integration suite green on Node 24. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dead weight since December 2018: commit 4bf3a31 switched pagination back to li (parse-link-header's transitive url dependency bloated the browser bundle) but never dropped parse-link-header from package.json. Nothing imports it. Removing it also resolves the repository's one open Dependabot alert (high, uncontrolled resource consumption, runtime scope). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
engines.node >=24, matching what the build tooling already required in practice and retiring the runtime-18/toolchain-22 split. CI simplifies accordingly: the test matrix is 24/latest, integration runs on 24, and dist-smoke keeps a single Node 24 leg -- retained not for old-Node coverage but for its distinct check that the built artifact works under a production-only install. @types/node moves to ^24, so type checking now reflects the real runtime surface. The FormData explicit-omission branch in the transport stays (it also protects browser-bundle consumers on engines we don't control); its comment and the .file() name-check comment are updated where the Node 18 rationale no longer applies. Newly-unlocked modernizations (File instanceof, fs.openAsBlob streaming for path uploads, newer tsconfig target) are recorded in handoff.md as Phase 4+ candidates rather than taken now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Measures dist require time, first default-mode instantiation (the lazy route parse), and cached re-instantiation across fresh node processes, plus an in-process bootstrap micro-benchmark. Baseline on the pre-Phase-4 build: first init median 1.230ms cold / 0.348ms warmed; load 11.8ms. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Route tree nodes previously embedded a validate closure compiled from the
route's capture-group pattern -- the only place that pattern survived,
which made the parsed tree unserializable. Nodes now store the RegExp
source as a validatePattern string ('' = accept anything), and
resource-handler-spec derives the validator function when building the
handler spec's _levels. The tree is now pure JSON-serializable data,
which is what allows Phase 4's build-time precompute to ship a pre-parsed
tree. Validation behavior is unchanged: same pattern, same anchoring,
same case-insensitive flag.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New build/scripts/precompute-default-routes.js parses lib/data/default-routes.json (still the human-editable source of truth) through route-tree.build and commits the result as lib/data/default-route-tree.json, pretty-printed for reviewable diffs when the route data is regenerated. Now that tree nodes are pure data the output is plain JSON. npm run precompute-routes regenerates it, and is chained onto update-default-routes-json so Phase 5's route refresh keeps the two files in sync. Both data files are prettier-ignored so a repo-wide format run cannot invalidate the generated output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Default-mode instances now feed the build-time-parsed default-route-tree.json straight into generateEndpointFactories, removing buildRouteTree from the default startup path. Custom routes (the options.routes branch and registerRoute) still parse at runtime. Verified equivalent to the runtime parse across every namespace and resource: same factories, prototype methods, request URLs, _levels shapes, and validator behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The precompute script now also emits lib/data/default-route-handlers.ts, a generated type-only module declaring one request interface per default resource (path-part setters plus the parameter/filter mixin methods, all chainable) and one factory interface per namespace. Method names are read off the actual generated EndpointRequest prototypes, so the declared surface cannot drift from the runtime one; the generator throws on any prototype method it cannot classify. wpapi.ts merges DefaultRouteHandlers into the WPAPI interface, so wp.posts() and friends are now fully typed (chainable, typo-checked) instead of falling through to the index signature. .namespace() gains typed overloads for the default namespaces. The index signature itself stays: handlers registered at runtime from custom route data still typecheck as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Regenerates the precomputed tree and handler typings in the test job and diffs them against the committed copies, so edits to default-routes.json or the route pipeline cannot land without re-running precompute-routes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rolldown-plugin-dts rewrites `typeof import()` references to an
`export =` class into a synthetic namespace ({ Class as default }), so
the generated interfaces' InstanceType<> base silently became `any` in
dist -- declared methods stayed typed, but unknown methods stopped
erroring. Switch the generated module to the import-assignment pattern
wpapi.ts already uses, which the bundler resolves to the class directly.
Extend the type-consumer checks to cover the generated surface: positive
typed chains and namespace() overloads in consumer.cts, and two negative
lines (unknown method, mistyped mixin argument) that specifically catch
an any-degradation like this one. negative.cts now expects 5 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review-gate fixes: the generated default-route-tree.json ships in the npm tarball, so emit it minified like its sibling default-routes.json (9.6KB vs 15.9KB pretty; bundled dist output is unaffected either way since rolldown re-serializes inlined JSON). Update a route-tree comment that still referenced the removed validate member, and cross-reference the generator's mirrored walk from resource-handler-spec so the two stay in sync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Default-mode init skips route parsing: the route tree is now pure data, precomputed at build time into lib/data/default-route-tree.json, and the same pass generates typed declarations for every default route handler (lib/data/default-route-handlers.ts). First default-mode instantiation drops from 1.23ms to 0.62ms median; CI enforces generated-file freshness. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.