From 28174aca5ec983dcc2de26e8608a90eae616e7a8 Mon Sep 17 00:00:00 2001 From: Naor Peled Date: Sun, 30 Aug 2026 21:40:10 +0300 Subject: [PATCH] fix: keep every published file reachable, and test all four build artifacts (#631) --- package.json | 4 +++ test/acceptance/test-list-of-ua.js | 45 ++++++++++++++++++++++++++---- test/package/assertions.cjs | 13 +++++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 151c5ec..2ef035d 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,10 @@ "./src/parser-platforms": "./src/parser-platforms.js", "./src/utils.js": "./src/utils.js", "./src/utils": "./src/utils.js", + "./LICENSE": "./LICENSE", + "./README.md": "./README.md", + "./index.d.ts": "./index.d.ts", + "./index.d.mts": "./index.d.mts", "./package.json": "./package.json" }, "repository": { diff --git a/test/acceptance/test-list-of-ua.js b/test/acceptance/test-list-of-ua.js index 2f1b13f..ad20716 100644 --- a/test/acceptance/test-list-of-ua.js +++ b/test/acceptance/test-list-of-ua.js @@ -2,7 +2,41 @@ import test from 'ava'; import yaml from 'yamljs'; import path from 'path'; import Bowser from '../../src/bowser'; -import BowserBuild from '../../es5'; +import BowserEs5 from '../../es5'; +import BowserBundled from '../../bundled'; +import BowserMjs from '../../bowser.mjs'; + +/** + * Every published JS artifact is built from `src/` through a different + * pipeline — raw ES modules, babel + terser UMD, UMD with core-js baked in, + * and the rolldown ESM build. A regression in any one of them ships silently + * unless each is asserted against the spec independently. + * + * `bowser.mjs` matters most: it is what the `import` condition of the exports + * map resolves to, so it is the file every modern ESM and bundler consumer + * actually runs. Testing only `src/` and `es5.js` leaves it uncovered. + * + * These are build outputs, so `pnpm build` has to run before `pnpm test`. + * Importing `bowser.mjs` from this CommonJS test file relies on Node's + * require(esm) support (Node >= 22.12) — the build toolchain already requires + * a newer Node than that, so anything able to produce these files can load them. + */ +const artifacts = [ + ['src/bowser.js', Bowser], + ['es5.js', BowserEs5], + ['bundled.js', BowserBundled], + ['bowser.mjs', BowserMjs], +]; + +// A missing build output fails at import with a plain "Cannot find module". +// This catches the quieter failure: an artifact that loads but is not the +// Bowser class — a broken UMD wrapper or a lost CJS interop unwrap would +// otherwise make every assertion below vacuous instead of failing. +artifacts.forEach(([name, artifact]) => { + if (typeof artifact !== 'function' || typeof artifact.parse !== 'function') { + throw new Error(`${name} did not load as the Bowser class — run \`pnpm build\` first`); + } +}); const listOfUA = yaml.load(path.join(__dirname, 'useragentstrings.yml')); @@ -11,11 +45,10 @@ const browserNames = Object.keys(listOfUA); browserNames.forEach((browserName) => { listOfUA[browserName].forEach((browser, index) => { test(`Test ${browserName} ${index}`, (t) => { - const parsed = Bowser.parse(browser.ua); - const parsedBuild = BowserBuild.parse(browser.ua); - t.deepEqual(parsed, browser.spec, `${browser.ua}`); - t.deepEqual(parsedBuild, browser.spec, `${browser.ua}`); - t.is(parsed.browser.name, browserName, `${browser.ua}`); + artifacts.forEach(([name, artifact]) => { + t.deepEqual(artifact.parse(browser.ua), browser.spec, `${name}: ${browser.ua}`); + }); + t.is(Bowser.parse(browser.ua).browser.name, browserName, `${browser.ua}`); }); }); }); diff --git a/test/package/assertions.cjs b/test/package/assertions.cjs index 1c6feb8..083ff36 100644 --- a/test/package/assertions.cjs +++ b/test/package/assertions.cjs @@ -93,6 +93,19 @@ check('require.resolve("bowser/package.json") works', function () { assert.ok(fs.existsSync(require.resolve('bowser/package.json'))); }); +// Before the exports map existed, *every* published file was reachable by +// subpath. An exports map is a closed list, so anything published but not +// enumerated silently becomes ERR_PACKAGE_PATH_NOT_EXPORTED. These four are +// not code, but they did resolve on every previous version — license and +// attribution tooling and `/// ` +// consumers can all depend on them. +['bowser/LICENSE', 'bowser/README.md', 'bowser/index.d.ts', 'bowser/index.d.mts'] + .forEach(function (id) { + check('resolves "' + id + '"', function () { + assert.ok(fs.existsSync(require.resolve(id))); + }); + }); + // --- The published manifest ------------------------------------------------ check('main/browser/module/types fields are unchanged', function () {