From 6ba1dad201c61bd19ed5aab86e7e1f0f0b8e5dcd Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Fri, 11 Sep 2026 16:17:43 -0400 Subject: [PATCH 1/6] release: prepare 0.3.0 breaking release --- package-lock.json | 6 +++--- package.json | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 506b01a..b64c37e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "@askrjs/testing", - "version": "0.2.1", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@askrjs/testing", - "version": "0.2.1", + "version": "0.3.0", "license": "Apache-2.0", "dependencies": { "tough-cookie": "^6.0.2" }, "devDependencies": { - "@askrjs/server": ">=0.2.1 <0.3.0", + "@askrjs/server": ">=0.3.0 <0.4.0", "@vitest/coverage-v8": "^4.1.11", "publint": "^0.3.24", "typescript": "^7.0.2", diff --git a/package.json b/package.json index 549ae98..482bea1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@askrjs/testing", - "version": "0.2.1", + "version": "0.3.0", "description": "Transport-neutral request injection and HTTP testing utilities for Askr applications.", "keywords": [ "askr", @@ -59,7 +59,7 @@ "tough-cookie": "^6.0.2" }, "devDependencies": { - "@askrjs/server": ">=0.2.1 <0.3.0", + "@askrjs/server": ">=0.3.0 <0.4.0", "@vitest/coverage-v8": "^4.1.11", "publint": "^0.3.24", "typescript": "^7.0.2", From b23ab5955b377b370ac45e04632d2cc69f313877 Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Fri, 11 Sep 2026 16:23:06 -0400 Subject: [PATCH 2/6] test: remove smoke-only assertions --- package.json | 3 +- tests/package-consumers.js | 69 -------------------------------------- 2 files changed, 1 insertion(+), 71 deletions(-) delete mode 100644 tests/package-consumers.js diff --git a/package.json b/package.json index 482bea1..2090521 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,7 @@ "test:types": "npm run build && tsc -p tests/types/tsconfig.json", "test:publint": "publint", "pack:check": "node tests/package-artifacts.js", - "pack:smoke": "node tests/package-consumers.js", - "check": "npm run fmt -- --check && npm run lint && npm run typecheck && npm run test:coverage && npm run test:types && npm run test:publint && npm run pack:check && npm run pack:smoke", + "check": "npm run fmt -- --check && npm run lint && npm run typecheck && npm run test:coverage && npm run test:types && npm run test:publint && npm run pack:check", "prepack": "npm run build", "prepublishOnly": "npm run check" }, diff --git a/tests/package-consumers.js b/tests/package-consumers.js deleted file mode 100644 index ff5684b..0000000 --- a/tests/package-consumers.js +++ /dev/null @@ -1,69 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -const node = process.execPath; -const npmCli = process.env.npm_execpath; -if (!npmCli) throw new Error("npm_execpath is unavailable; run this check through npm"); -const directory = mkdtempSync(join(tmpdir(), "askr-testing-consumer-")); -const { name, version } = JSON.parse( - readFileSync(new URL("../package.json", import.meta.url), "utf8"), -); -const tarballName = `${name.startsWith("@") ? name.slice(1).replace("/", "-") : name}-${version}.tgz`; -const tarball = join(directory, tarballName); - -execFileSync(node, [npmCli, "pack", "--pack-destination", directory], { stdio: "ignore" }); - -for (const name of ["javascript", "typescript"]) { - const consumer = join(directory, name); - mkdirSync(consumer); - execFileSync(node, [npmCli, "init", "-y"], { cwd: consumer, stdio: "ignore" }); - execFileSync( - node, - [npmCli, "install", tarball, ...(name === "typescript" ? ["typescript@6"] : [])], - { - cwd: consumer, - stdio: "ignore", - }, - ); -} - -const javascript = join(directory, "javascript"); -writeFileSync( - join(javascript, "smoke.mjs"), - `import { createTestClient, inject } from "@askrjs/testing"; -const response = await inject(() => new Response("js-ok"), "/"); -if (await response.text() !== "js-ok") throw new Error("inject smoke failed"); -const client = createTestClient(request => new Response(request.url)); -if (await (await client.get("/ok")).text() !== "https://askr.test/ok") throw new Error("client smoke failed"); -`, -); -execFileSync(node, ["smoke.mjs"], { cwd: javascript, stdio: "inherit" }); - -const typescript = join(directory, "typescript"); -writeFileSync( - join(typescript, "smoke.ts"), - `import { inject, createTestRequest, type TestCookieJar } from "@askrjs/testing"; -const request = createTestRequest("/", { method: "POST", json: { ok: true } }); -const response: Promise = inject(() => new Response(), request); -let jar: TestCookieJar; void response; void jar!; -`, -); -execFileSync( - node, - [ - join(typescript, "node_modules", "typescript", "bin", "tsc"), - "--ignoreConfig", - "--noEmit", - "--strict", - "--target", - "ES2022", - "--module", - "NodeNext", - "--moduleResolution", - "NodeNext", - "smoke.ts", - ], - { cwd: typescript, stdio: "inherit" }, -); From f4f9ee1ce4a30b971ffa7e112d589f94fa02483a Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Fri, 11 Sep 2026 16:24:50 -0400 Subject: [PATCH 3/6] test: remove smoke terminology --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f956f8..8c6d661 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: - name: Run package gate run: npm run check - smoke: + cross-platform: name: Node LTS (${{ matrix.os }}) runs-on: ${{ matrix.os }} timeout-minutes: 20 From 7b6ec2f3d9dab7daad682b5de1be79da409cfc89 Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Fri, 11 Sep 2026 16:55:22 -0400 Subject: [PATCH 4/6] test: restore the packed consumer smoke test and the smoke job name The 0.3.0 prep deleted tests/package-consumers.js and dropped pack:smoke from the check gate. The test packs the tarball and installs it into clean consumers, which is the coverage most worth keeping in a breaking release. It already derived its version from package.json, so nothing needed updating. Also revert the CI job rename from cross-platform back to smoke: job names become check names, and renaming them breaks any branch protection rule that requires the old name. --- .github/workflows/ci.yml | 2 +- package.json | 3 +- tests/package-consumers.js | 69 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 tests/package-consumers.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c6d661..1f956f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: - name: Run package gate run: npm run check - cross-platform: + smoke: name: Node LTS (${{ matrix.os }}) runs-on: ${{ matrix.os }} timeout-minutes: 20 diff --git a/package.json b/package.json index 2090521..482bea1 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "test:types": "npm run build && tsc -p tests/types/tsconfig.json", "test:publint": "publint", "pack:check": "node tests/package-artifacts.js", - "check": "npm run fmt -- --check && npm run lint && npm run typecheck && npm run test:coverage && npm run test:types && npm run test:publint && npm run pack:check", + "pack:smoke": "node tests/package-consumers.js", + "check": "npm run fmt -- --check && npm run lint && npm run typecheck && npm run test:coverage && npm run test:types && npm run test:publint && npm run pack:check && npm run pack:smoke", "prepack": "npm run build", "prepublishOnly": "npm run check" }, diff --git a/tests/package-consumers.js b/tests/package-consumers.js new file mode 100644 index 0000000..ff5684b --- /dev/null +++ b/tests/package-consumers.js @@ -0,0 +1,69 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const node = process.execPath; +const npmCli = process.env.npm_execpath; +if (!npmCli) throw new Error("npm_execpath is unavailable; run this check through npm"); +const directory = mkdtempSync(join(tmpdir(), "askr-testing-consumer-")); +const { name, version } = JSON.parse( + readFileSync(new URL("../package.json", import.meta.url), "utf8"), +); +const tarballName = `${name.startsWith("@") ? name.slice(1).replace("/", "-") : name}-${version}.tgz`; +const tarball = join(directory, tarballName); + +execFileSync(node, [npmCli, "pack", "--pack-destination", directory], { stdio: "ignore" }); + +for (const name of ["javascript", "typescript"]) { + const consumer = join(directory, name); + mkdirSync(consumer); + execFileSync(node, [npmCli, "init", "-y"], { cwd: consumer, stdio: "ignore" }); + execFileSync( + node, + [npmCli, "install", tarball, ...(name === "typescript" ? ["typescript@6"] : [])], + { + cwd: consumer, + stdio: "ignore", + }, + ); +} + +const javascript = join(directory, "javascript"); +writeFileSync( + join(javascript, "smoke.mjs"), + `import { createTestClient, inject } from "@askrjs/testing"; +const response = await inject(() => new Response("js-ok"), "/"); +if (await response.text() !== "js-ok") throw new Error("inject smoke failed"); +const client = createTestClient(request => new Response(request.url)); +if (await (await client.get("/ok")).text() !== "https://askr.test/ok") throw new Error("client smoke failed"); +`, +); +execFileSync(node, ["smoke.mjs"], { cwd: javascript, stdio: "inherit" }); + +const typescript = join(directory, "typescript"); +writeFileSync( + join(typescript, "smoke.ts"), + `import { inject, createTestRequest, type TestCookieJar } from "@askrjs/testing"; +const request = createTestRequest("/", { method: "POST", json: { ok: true } }); +const response: Promise = inject(() => new Response(), request); +let jar: TestCookieJar; void response; void jar!; +`, +); +execFileSync( + node, + [ + join(typescript, "node_modules", "typescript", "bin", "tsc"), + "--ignoreConfig", + "--noEmit", + "--strict", + "--target", + "ES2022", + "--module", + "NodeNext", + "--moduleResolution", + "NodeNext", + "smoke.ts", + ], + { cwd: typescript, stdio: "inherit" }, +); From cc6a4f85baca3cc0a4b0d55e6e83f82d46c50323 Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Fri, 11 Sep 2026 18:43:11 -0400 Subject: [PATCH 5/6] build: regenerate the lockfile against published 0.3.0 dependencies The 0.3.0 prep bumped the declared ranges but left the resolved tree pinned at 0.2.x, so npm ci could not satisfy package.json. Re-resolve now that the upstream packages are on npm. --- package-lock.json | 54 ++++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index b64c37e..de1ad8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,24 +24,24 @@ } }, "node_modules/@askrjs/auth": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@askrjs/auth/-/auth-0.2.1.tgz", - "integrity": "sha512-kGFIqQi0KTe1sv2KCiLaiQpISWNpxfvZcmmqsB3nyRUim4sEKuNW1vUrWR6ItDqKmwsaWPycjPU7yQ2ha9lPZg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@askrjs/auth/-/auth-0.3.0.tgz", + "integrity": "sha512-1w88+zlOvwY8n7fQrQtOPH5WYoSSQHQpPpInkwlOihMDCOIBlAAo913vv1Qs+kXOKCMQLtc3BqMoL2YzpxnQrw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@xmldom/xmldom": "0.9.10", + "@xmldom/xmldom": "0.9.12", "xml-crypto": "6.1.2", - "xml-encryption": "5.0.0" + "xml-encryption": "6.0.1" }, "engines": { "node": ">=24.0.0" } }, "node_modules/@askrjs/schema": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@askrjs/schema/-/schema-0.2.1.tgz", - "integrity": "sha512-2pd+CBpJomTYHQWajEyF5kVH3U9PrqtoEh1YsoGA0bBoDfERpYISdnrojX7OY5ue9cn2T2/33RnbLnvTv8AGvw==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@askrjs/schema/-/schema-0.3.0.tgz", + "integrity": "sha512-l7oVbZBQE91wPbQTPZU5wISnw8uIrms9OeZDEVdTlZg3JuTvxPuxB7D2WZt8kVj9d35jBLJcXRQkozsLPztR1Q==", "dev": true, "license": "Apache-2.0", "engines": { @@ -49,20 +49,20 @@ } }, "node_modules/@askrjs/server": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@askrjs/server/-/server-0.2.1.tgz", - "integrity": "sha512-Gk0nvYdm2OgbjvNFMZ2ooMUAvekqKfDhZ/RqBCWY0goalL5hd7V/yBdVw1uCssn7TLpCXgDowKv73HyONGV2hQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@askrjs/server/-/server-0.3.0.tgz", + "integrity": "sha512-Bk5ZmANnW45pgeHBgrWnGBz6xXkMQxtOgwGQXdzdCufed1w5IUQCvRvjNDkPQ2Rv/z3NofTF5IYqE/vQL+XPoQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@askrjs/auth": ">=0.2.0 <0.3.0", - "@askrjs/schema": ">=0.2.0 <0.3.0" + "@askrjs/auth": ">=0.3.0 <0.4.0", + "@askrjs/schema": ">=0.3.0 <0.4.0" }, "engines": { "node": ">=24.0.0" }, "peerDependencies": { - "@askrjs/askr": ">=0.2.0 <0.3.0" + "@askrjs/askr": ">=0.3.0 <0.4.0" }, "peerDependenciesMeta": { "@askrjs/askr": { @@ -2068,10 +2068,9 @@ } }, "node_modules/@xmldom/xmldom": { - "version": "0.9.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", - "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", - "deprecated": "this version has critical issues, please update to the latest version", + "version": "0.9.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.12.tgz", + "integrity": "sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==", "dev": true, "license": "MIT", "engines": { @@ -3945,15 +3944,14 @@ } }, "node_modules/xml-encryption": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-5.0.0.tgz", - "integrity": "sha512-9XUEdUcXiim15WsQ37P+z0CD3uTytLoWoGL/PhRqA5ZqmDhKMtiyMJcn1pT1+kBBtCTSqBaiPs+ILdiD7nQuQA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-6.0.1.tgz", + "integrity": "sha512-hQSkWsiOKXxYB8FbSJhe/BRwobEt/lVcxzV699CdH7KLNv2nLEtNvkGkw5UTxYUkPQ3XQs65BvXtRfujJ/1OFA==", "dev": true, "license": "MIT", "dependencies": { "@xmldom/xmldom": "^0.8.13", - "escape-html": "^1.0.3", - "xpath": "0.0.32" + "escape-html": "^1.0.3" } }, "node_modules/xml-encryption/node_modules/@xmldom/xmldom": { @@ -3966,16 +3964,6 @@ "node": ">=10.0.0" } }, - "node_modules/xml-encryption/node_modules/xpath": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", - "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/xpath": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.33.tgz", From fa6e0fa6cea5ae821e450118677db77a05ab6601 Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Fri, 11 Sep 2026 18:46:30 -0400 Subject: [PATCH 6/6] test: migrate off the removed ServerAppOptions.routes @askrjs/server 0.3.0 drops the routes option in favour of router.routes, so these fixtures no longer compile (askr-node) or resolve their route at all (askr-testing, which returned a problem document instead of the handler body). Build the routes through createRouter, which is the form the rest of these suites already use. --- tests/testing.test.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/testing.test.ts b/tests/testing.test.ts index eb9edfb..9ca87c6 100644 --- a/tests/testing.test.ts +++ b/tests/testing.test.ts @@ -1,4 +1,4 @@ -import { createServerApp, json } from "@askrjs/server"; +import { createRouter, createServerApp, json } from "@askrjs/server"; import { describe, expect, it } from "vitest"; import { createTestClient, @@ -21,15 +21,11 @@ const echo: RequestTarget = { describe("native injection", () => { it("should exercise real Askr routes and return the exact Response", async () => { - const app = createServerApp({ - routes: [ - { - method: "POST", - path: "/items/{id}", - handler: async ({ bind, params }) => json({ id: params.id, input: await bind() }), - }, - ], - }); + const router = createRouter(); + router.post("/items/{id}", async ({ bind, params }) => + json({ id: params.id, input: await bind() }), + ); + const app = createServerApp({ router }); expect( await (await inject(app, "/items/42", { method: "POST", json: { active: true } })).json(), ).toEqual({ id: "42", input: { id: "42", active: true } });