From d2c8bdefb080c576632436f663ba1e0f5e8463d4 Mon Sep 17 00:00:00 2001 From: SatoshiQaziMuhammed Date: Thu, 17 Sep 2026 17:16:47 +0400 Subject: [PATCH] Let the Deno build reach its dependencies, and let its check fail (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The build rewrites every @pezkuwi import into a deno.land URL, so @pezkuwi/util becomes https://deno.land/x/pezkuwi/util/mod.ts. Upstream can do that because deno.land/x/polkadot is published. Our fork renamed the prefix to x/pezkuwi and never published there: deno.land/x/pezkuwi answers 404, and `deno check` has failed on three modules ever since. Reproduced locally with the same Deno the workflow installs, 2.9.7: TS2307 Cannot find module 'https://deno.land/x/pezkuwi/util/mod.ts' TS2307 Cannot find module 'https://deno.land/x/pezkuwi/util-crypto/mod.ts' TS2307 Cannot find module 'https://deno.land/x/pezkuwi/x-fetch/mod.ts' import_map.in.json is the tooling's own hook for exactly this — pezkuwi-dev-deno-map merges its entries into the generated import_map.json. All three are mapped to npm: specifiers, which Deno resolves natively, at the same version ranges the node build uses. The three packages are already on npm, so nothing new is published and the Deno build now checks against the very code we ship. `yarn deno` exits 0. This deliberately does not decide whether we ever claim deno.land/x/pezkuwi. If we do, the mappings come out again; until then the build works with what we have. The reason none of this was noticed is the second half of the change. The PR job carried continue-on-error from upstream, so its four steps could never turn a pull request red: build had been failing since 2026-09-13 and deno for far longer, and the job reported success throughout. All four pass now — verified locally, lint included, on a clean tree — so the flag is gone and they are gates again. deno.lock joins mod.ts and import_map.json in .gitignore: same check, same kind of generated artifact. package.json carries the key order its own build:before step produces. Gate: scripts/checkDenoMap.mjs, in the Lists workflow so it runs even when the toolchain cannot be installed. Nails: removing the map, bumping a dependency without the map, adding a @pezkuwi dependency with no mapping, and leaving a mapping behind after its dependency goes — each turns it red. Co-authored-by: Claude Opus 5 (1M context) --- .github/workflows/lists.yml | 5 +++ .github/workflows/pr-any.yml | 5 ++- .gitignore | 1 + import_map.in.json | 7 ++++ package.json | 4 +- scripts/checkDenoMap.mjs | 74 ++++++++++++++++++++++++++++++++++++ 6 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 import_map.in.json create mode 100644 scripts/checkDenoMap.mjs diff --git a/.github/workflows/lists.yml b/.github/workflows/lists.yml index 892007044..705f57b4e 100644 --- a/.github/workflows/lists.yml +++ b/.github/workflows/lists.yml @@ -34,6 +34,11 @@ jobs: # in the build job because it must run even when the build cannot. - name: The repository's own metadata run: node scripts/checkRepoMeta.mjs + # Whether the Deno build can still reach its dependencies. Same reason as + # above: it must run even when the toolchain cannot be installed, and it + # is the check that would have caught the broken map years earlier. + - name: The Deno import map + run: node scripts/checkDenoMap.mjs # What phishing.pezkuwichain.io runs before it serves a new master: against a scratch # repository and web root, with these lists. - name: The host's publish checks diff --git a/.github/workflows/pr-any.yml b/.github/workflows/pr-any.yml index f59f09f3c..807336066 100644 --- a/.github/workflows/pr-any.yml +++ b/.github/workflows/pr-any.yml @@ -5,7 +5,10 @@ permissions: {} jobs: pr: - continue-on-error: true + # continue-on-error was inherited from upstream and meant these four steps + # could never turn a pull request red. Two of them were failing — build since + # 2026-09-13 and deno for far longer — and nobody saw it, because the job + # reported success either way. A check that cannot fail is not a check. strategy: matrix: step: ['lint', 'test', 'build', 'deno'] diff --git a/.gitignore b/.gitignore index e996b5602..c5122996d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ target/ tmp/ /mod.ts /import_map.json +/deno.lock .DS_Store .env.local .env.development.local diff --git a/import_map.in.json b/import_map.in.json new file mode 100644 index 000000000..5ac05cf0c --- /dev/null +++ b/import_map.in.json @@ -0,0 +1,7 @@ +{ + "imports": { + "https://deno.land/x/pezkuwi/util/mod.ts": "npm:@pezkuwi/util@^14.0.5", + "https://deno.land/x/pezkuwi/util-crypto/mod.ts": "npm:@pezkuwi/util-crypto@^14.0.5", + "https://deno.land/x/pezkuwi/x-fetch/mod.ts": "npm:@pezkuwi/x-fetch@^14.0.5" + } +} diff --git a/package.json b/package.json index daeb871ab..0256c7324 100644 --- a/package.json +++ b/package.json @@ -45,8 +45,8 @@ "@types/node": "^22.10.5" }, "resolutions": { - "typescript": "^5.5.4", "axios": "^1.13.5", - "semver": "^7.5.2" + "semver": "^7.5.2", + "typescript": "^5.5.4" } } diff --git a/scripts/checkDenoMap.mjs b/scripts/checkDenoMap.mjs new file mode 100644 index 000000000..f2aff94d6 --- /dev/null +++ b/scripts/checkDenoMap.mjs @@ -0,0 +1,74 @@ +// Copyright 2020-2026 @pezkuwi/phishing authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +// Whether the Deno build can still reach its dependencies. +// +// The build rewrites every @pezkuwi/* import into a deno.land URL: +// @pezkuwi/util becomes https://deno.land/x/pezkuwi/util/mod.ts. Upstream can do +// that because deno.land/x/polkadot is published; our fork renamed the prefix to +// x/pezkuwi and never published there, so deno.land/x/pezkuwi answers 404 and +// `deno check` failed on three modules — for months, unnoticed, because the PR +// job carries continue-on-error and can never turn a pull request red. +// +// import_map.in.json is the tooling's own hook for this: pezkuwi-dev-deno-map +// merges its entries into the generated import_map.json. The three externals are +// mapped to npm: specifiers, which Deno resolves natively, so the Deno build uses +// the very packages we already publish. Nothing new has to be published, and +// claiming the deno.land/x/pezkuwi namespace stays a decision we can take later +// rather than one this build forces. +// +// What can rot: a dependency is bumped in packages/phishing/package.json and the +// map keeps pointing at the old range, so `deno check` type-checks a different +// version of @pezkuwi/util than the node build compiles against. That divergence +// would not fail anything — it would just quietly check the wrong code. +// +// node scripts/checkDenoMap.mjs + +import { existsSync, readFileSync } from 'node:fs'; + +const errors = []; + +function check (name, ok, detail) { + console.log((ok ? 'ok ' : 'FAIL ') + name + (ok ? '' : ` — got: ${JSON.stringify(detail)}`)); + + if (!ok) { + errors.push(name); + } +} + +check('the deno import map exists', existsSync('import_map.in.json')); + +if (errors.length) { + console.log(`FAILED: ${errors.length}`); + process.exit(1); +} + +const map = JSON.parse(readFileSync('import_map.in.json', 'utf8')).imports || {}; +const pkg = JSON.parse(readFileSync('packages/phishing/package.json', 'utf8')); +const deps = pkg.dependencies || {}; + +// Every @pezkuwi dependency the package pulls in has to be reachable from Deno, +// because the build turns all of them into deno.land URLs. +const external = Object.keys(deps).filter((d) => d.startsWith('@pezkuwi/')); + +check('the package has @pezkuwi dependencies to map', external.length > 0, external); + +for (const dep of external) { + const short = dep.replace('@pezkuwi/', ''); + const key = `https://deno.land/x/pezkuwi/${short}/mod.ts`; + const want = `npm:${dep}@${deps[dep]}`; + + check(`${dep}: the deno build has somewhere to resolve it`, !!map[key], { have: Object.keys(map), key }); + check(`${dep}: the deno build and the node build agree on the version`, map[key] === want, { map: map[key], want }); +} + +// And nothing in the map points at a package we no longer depend on, which would +// hide a removed dependency behind a mapping that still resolves. +for (const [key, value] of Object.entries(map)) { + const named = String(value).replace(/^npm:/, '').replace(/@[^@/]*$/, ''); + + check(`${key}: maps to a dependency we still declare`, external.includes(named), { named, value }); +} + +console.log(errors.length ? `FAILED: ${errors.length}` : 'all deno map checks passed'); +process.exit(errors.length ? 1 : 0);