From 0a19a167aa054747ced82236ff8874c98fa12af9 Mon Sep 17 00:00:00 2001 From: botandbang <337765928+botandbang@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:59:45 -0500 Subject: [PATCH 1/4] test(fetchindex): drop the optionsParser tests and the luxon devDependency The two tests passed core's optionsParser output to fetchindex, which repeated the two tests above them with the same options. They required core's optionsParser by relative path, and fetchindex listed luxon only so that file could load. knip.jsonc no longer needs its fetchindex exception. --- fetchindex/package.json | 1 - fetchindex/test/test.js | 18 ------------------ knip.jsonc | 5 ----- package-lock.json | 1 - 4 files changed, 25 deletions(-) diff --git a/fetchindex/package.json b/fetchindex/package.json index 1cbdbba..8801323 100644 --- a/fetchindex/package.json +++ b/fetchindex/package.json @@ -32,7 +32,6 @@ "homepage": "https://github.com/cutenode/nodevu#readme", "devDependencies": { "c8": "^12.0.0", - "luxon": "^3.5.0", "undici": "^8.12.2" }, "types": "types/index.d.ts" diff --git a/fetchindex/test/test.js b/fetchindex/test/test.js index 510dd32..d90189a 100644 --- a/fetchindex/test/test.js +++ b/fetchindex/test/test.js @@ -2,7 +2,6 @@ const { deepStrictEqual, match, rejects } = require('node:assert'); const { describe, it } = require('node:test'); const { fetch: undiciFetch } = require('undici'); const fetchindex = require('../index'); -const optionsParser = require('../../core/util/prod/optionsParser'); // checks that verify the result of data returned function check(data) { @@ -46,23 +45,6 @@ describe('under normal condiditons, versions should work', async () => { }); }); -describe('versions should work with optionsParser', async () => { - it('should work with the default output of optionsParser', async () => { - const parsedOptions = optionsParser({}); - const data = await fetchindex(parsedOptions); - check(data); - }); - - it('should work with a different fetch pased to optionsParser', async () => { - const parsedOptions = optionsParser({ - fetch: undiciFetch, - }); - - const data = await fetchindex(parsedOptions); - check(data); - }); -}); - describe('fetchindex', async () => { it('should reject naming the URL and status when the response is not ok', async () => { // Arrange diff --git a/knip.jsonc b/knip.jsonc index ed90ded..59cfe52 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -21,11 +21,6 @@ // aliases doc-kit's React generator defines while it builds the site "ignoreUnresolved": ["^#theme/"], }, - "fetchindex": { - // the tests require core's optionsParser, which requires luxon, and CI - // installs only this workspace's dependencies - "ignoreDependencies": ["luxon"], - }, "static": { // the build test resolves core's own luxon to stop core's clock "ignoreDependencies": ["luxon"], diff --git a/package-lock.json b/package-lock.json index a59119b..66c8310 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,7 +64,6 @@ "license": "MIT", "devDependencies": { "c8": "^12.0.0", - "luxon": "^3.5.0", "undici": "^8.12.2" } }, From cbb282355bdfbdd973d9660bc379bd06808977a4 Mon Sep 17 00:00:00 2001 From: botandbang <337765928+botandbang@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:59:45 -0500 Subject: [PATCH 2/4] test(static): stop the build test's clock with Date.now instead of core's luxon luxon reads the time through Date.now, so stubbing it in the build's child process stops core's clock without resolving core's own copy of luxon. That drops static's luxon exception from knip.jsonc, and the root's @types/luxon, which knip then reported as unused. core still lists @types/luxon, so npm run types is unchanged. --- knip.jsonc | 4 ---- package-lock.json | 1 - package.json | 1 - static/test/test.build.js | 9 +++------ 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/knip.jsonc b/knip.jsonc index 59cfe52..c18e386 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -21,9 +21,5 @@ // aliases doc-kit's React generator defines while it builds the site "ignoreUnresolved": ["^#theme/"], }, - "static": { - // the build test resolves core's own luxon to stop core's clock - "ignoreDependencies": ["luxon"], - }, }, } diff --git a/package-lock.json b/package-lock.json index 66c8310..ebceb7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,6 @@ "@lavamoat/harden": "0.8.1", "@node-core/ui-components": "1.8.1", "@oxlint/plugins": "1.89.0", - "@types/luxon": "3.7.6", "@types/node": "26.6.5", "@types/semver": "7.8.0", "knip": "6.44.1", diff --git a/package.json b/package.json index c1554b0..958225f 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "@lavamoat/harden": "0.8.1", "@node-core/ui-components": "1.8.1", "@oxlint/plugins": "1.89.0", - "@types/luxon": "3.7.6", "@types/node": "26.6.5", "@types/semver": "7.8.0", "knip": "6.44.1", diff --git a/static/test/test.build.js b/static/test/test.build.js index aad25ab..d5f631e 100644 --- a/static/test/test.build.js +++ b/static/test/test.build.js @@ -2,7 +2,7 @@ const assert = require('node:assert'); const { execFile } = require('node:child_process'); const { mkdir, mkdtemp, readFile } = require('node:fs/promises'); const { tmpdir } = require('node:os'); -const { dirname, join } = require('node:path'); +const { join } = require('node:path'); const { describe, it } = require('node:test'); const { promisify } = require('node:util'); const nodevu = require('@nodevu/core'); @@ -90,12 +90,9 @@ async function runBuild(name) { const cwd = join(await mkdtemp(join(tmpdir(), 'nodevu-')), 'static'); await mkdir(join(cwd, 'data'), { recursive: true }); - const luxon = require.resolve('luxon', { - paths: [dirname(require.resolve('@nodevu/core'))], - }); - const setup = [ - `require(${JSON.stringify(luxon)}).Settings.now = () => ${Date.parse(now)};`, + // core reads the time through luxon, which calls Date.now() + `Date.now = () => ${Date.parse(now)};`, `globalThis.fetch = require(${JSON.stringify(require.resolve('../../core/util/dev/fixtures'))}).at(${JSON.stringify(now)}).fetch;`, ].join(' '); From d458d1564be8eec0f7854711a748862526c096fe Mon Sep 17 00:00:00 2001 From: botandbang <337765928+botandbang@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:59:46 -0500 Subject: [PATCH 3/4] test: share the release snapshot through a private fixtures workspace core's index.json, schedule.json and now.json snapshots, the at() and beforeEachTemplate helpers, and the scripts that refresh the snapshots move into fixtures/, published nowhere (private: true). core, newest, oldest, parsefiles, ranges, static and translate list @nodevu/fixtures as a devDependency and require it by name, so no test reaches into another package's folder. ranges' at() was core's with a default date, so the two are now one at() with that default. --- .github/workflows/ci.yml | 15 +++++----- README.md | 2 +- core/package.json | 4 +-- core/test/combos.js | 4 +-- core/test/dynamic-values.js | 4 +-- core/test/properties.js | 2 +- core/test/static-values.js | 2 +- core/test/unique-source.js | 2 +- .../dev => fixtures}/beforeEachTemplate.js | 6 ++-- .../data/static => fixtures/data}/index.json | 0 .../data/static => fixtures/data}/now.json | 0 .../static => fixtures/data}/schedule.json | 0 .../util/dev/fixtures.js => fixtures/index.js | 13 +++++--- fixtures/package.json | 26 ++++++++++++++++ .../dev-fixtures.js => fixtures/test/at.js | 14 +++++++-- .../test/scripts.js | 19 ++++++------ .../dev-write.js => fixtures/test/write.js | 2 +- .../dev => fixtures/util}/updateStaticData.js | 4 +-- .../dev => fixtures/util}/updateStaticNow.js | 2 +- {core/util/dev => fixtures/util}/write.js | 0 newest/package.json | 1 + newest/test/test.js | 4 +-- oldest/package.json | 1 + oldest/test/test.js | 4 +-- package-lock.json | 28 +++++++++++++++-- package.json | 3 +- parsefiles/package.json | 1 + parsefiles/test/parseFilename.test.js | 2 +- ranges/package.json | 1 + ranges/test/alpha.test.js | 2 +- ranges/test/bounds.test.js | 2 +- ranges/test/emptyFields.test.js | 4 +-- ranges/test/fixtures.js | 30 +------------------ ranges/test/properties.test.js | 4 +-- static/package.json | 1 + static/test/test.build.js | 8 ++--- translate/package.json | 1 + translate/test/examples.test.js | 4 +-- translate/test/ranges.test.js | 4 +-- 39 files changed, 133 insertions(+), 93 deletions(-) rename {core/util/dev => fixtures}/beforeEachTemplate.js (87%) rename {core/test/data/static => fixtures/data}/index.json (100%) rename {core/test/data/static => fixtures/data}/now.json (100%) rename {core/test/data/static => fixtures/data}/schedule.json (100%) rename core/util/dev/fixtures.js => fixtures/index.js (55%) create mode 100644 fixtures/package.json rename core/test/dev-fixtures.js => fixtures/test/at.js (86%) rename core/test/dev-scripts.js => fixtures/test/scripts.js (75%) rename core/test/dev-write.js => fixtures/test/write.js (93%) rename {core/util/dev => fixtures/util}/updateStaticData.js (76%) rename {core/util/dev => fixtures/util}/updateStaticNow.js (75%) rename {core/util/dev => fixtures/util}/write.js (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd6b9c3..ace3fc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,15 +46,16 @@ jobs: - '.npmrc' - 'package-lock.json' - '.github/workflows/ci.yml' - core: [*shared, 'core/**'] + core: [*shared, 'core/**', 'fixtures/**'] fetchindex: [*shared, 'fetchindex/**'] - newest: [*shared, 'newest/**', 'core/**'] - oldest: [*shared, 'oldest/**', 'core/**'] + newest: [*shared, 'newest/**', 'core/**', 'fixtures/**'] + oldest: [*shared, 'oldest/**', 'core/**', 'fixtures/**'] opt: [*shared, 'opt/**'] - parsefiles: [*shared, 'parsefiles/**'] - ranges: [*shared, 'ranges/**', 'core/**', 'aliases/**'] - static: [*shared, 'static/**', 'core/**'] - translate: [*shared, 'translate/**', 'ranges/**', 'core/**', 'aliases/**'] + parsefiles: [*shared, 'parsefiles/**', 'fixtures/**'] + ranges: [*shared, 'ranges/**', 'core/**', 'aliases/**', 'fixtures/**'] + static: [*shared, 'static/**', 'core/**', 'fixtures/**'] + translate: [*shared, 'translate/**', 'ranges/**', 'core/**', 'aliases/**', 'fixtures/**'] + fixtures: [*shared, 'fixtures/**'] # a manual run tests every workspace that has a coverage script - name: List packages to test id: packages diff --git a/README.md b/README.md index f71ee79..131b7da 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ nodevu is composed of a set of modules: Installing needs npm 11.17 or newer, which ships with Node.js 24 and later. `.npmrc` skips packages published in the last 3 days and blocks git and URL dependencies. npm only runs the dependency install scripts that `allowScripts` in `package.json` approves, and `npm approve-scripts ` adds one pinned to its installed version. -- `npm test` and `npm run coverage` run every module's tests. +- `npm test` and `npm run coverage` run every module's tests. The tests share the release data snapshot and its helpers through `fixtures/`, a private workspace that isn't published. `npm run test:update -w fixtures` refreshes the snapshot. - `npm run lint` checks the whole repo with oxlint and oxfmt, and checks with [docov](https://github.com/cutenode/docov) that everything the modules export has JSDoc. `docov.jsonc` sets what it scans. `npm run lint:harden` tests `tools/harden/` and runs [@lavamoat/harden](https://github.com/LavaMoat/LavaMoat/tree/main/packages/harden) to check that `.npmrc` and `package.json` keep every hardening setting, except the ones `tools/harden/decisions.json` turns off. `npm run knip` runs [knip](https://knip.dev) to find unused files, exports and dependencies. `knip.jsonc` lists what it skips and why. `npm run lint` runs both. - `npm run types` type-checks the JSDoc and writes each module's TypeScript declarations to its `types/` folder. Commit them: CI fails when they're out of date. - `npm run docs:dev` builds the docs from `docs/pages/` with [doc-kit](https://doc-kit.nodejs.org/) and serves them locally. `npm run docs` only builds them, into `out/`. diff --git a/core/package.json b/core/package.json index 69beb09..bac6bf2 100644 --- a/core/package.json +++ b/core/package.json @@ -7,9 +7,6 @@ "lint": "oxlint && oxfmt --check", "lint:write": "oxlint --fix && oxfmt", "test": "node --test", - "test:update": "npm run test:update:static && npm run test:update:now", - "test:update:static": "node ./util/dev/updateStaticData.js", - "test:update:now": "node ./util/dev/updateStaticNow.js", "coverage": "c8 node --test", "updates:check": "npx npm-check-updates", "updates:update": "npx npm-check-updates -u" @@ -29,6 +26,7 @@ "semver": "^7.6.3" }, "devDependencies": { + "@nodevu/fixtures": "^0.0.0", "c8": "^12.0.0", "fast-check": "^4.10.2", "undici": "^8.12.2" diff --git a/core/test/combos.js b/core/test/combos.js index d3aa96a..fcd6e94 100644 --- a/core/test/combos.js +++ b/core/test/combos.js @@ -3,7 +3,7 @@ const nodevu = require('../index'); const { describe, it, beforeEach } = require('node:test'); const { fetch: undiciFetch } = require('undici'); -const beforeEachTemplate = require('../util/dev/beforeEachTemplate'); +const beforeEachTemplate = require('@nodevu/fixtures/beforeEachTemplate'); function check(data) { // releases checks @@ -26,7 +26,7 @@ function check(data) { assert.deepStrictEqual(data.v12.security.oldest, '12.8.1'); } -const now = require('./data/static/now.json'); +const now = require('@nodevu/fixtures/data/now.json'); describe('check to make sure that combining options works as expected', async () => { beforeEach(beforeEachTemplate); diff --git a/core/test/dynamic-values.js b/core/test/dynamic-values.js index aba3639..76d8890 100644 --- a/core/test/dynamic-values.js +++ b/core/test/dynamic-values.js @@ -2,9 +2,9 @@ const assert = require('node:assert'); const nodevu = require('../index'); const { describe, it, beforeEach } = require('node:test'); -const beforeEachTemplate = require('../util/dev/beforeEachTemplate'); +const beforeEachTemplate = require('@nodevu/fixtures/beforeEachTemplate'); -const now = require('./data/static/now.json'); +const now = require('@nodevu/fixtures/data/now.json'); // this is a set of tests that only test against static data effectively frozen at a point in time. // these tests should *probably* be updated sometimes. see the npm scripts for update scripts. diff --git a/core/test/properties.js b/core/test/properties.js index 535c622..5fee931 100644 --- a/core/test/properties.js +++ b/core/test/properties.js @@ -3,7 +3,7 @@ const { describe, it } = require('node:test'); const fc = require('fast-check'); const semver = require('semver'); const nodevu = require('../index'); -const { at } = require('../util/dev/fixtures'); +const { at } = require('@nodevu/fixtures'); const phases = ['alpha', 'start', 'lts', 'maintenance', 'end']; diff --git a/core/test/static-values.js b/core/test/static-values.js index 1ebf4e1..5545db6 100644 --- a/core/test/static-values.js +++ b/core/test/static-values.js @@ -2,7 +2,7 @@ const assert = require('node:assert'); const nodevu = require('../index'); const { describe, it, beforeEach } = require('node:test'); -const beforeEachTemplate = require('../util/dev/beforeEachTemplate'); +const beforeEachTemplate = require('@nodevu/fixtures/beforeEachTemplate'); // these are tests that run with static data but do not need to be frozen in time. describe('check that we get the values we expect from values that should not ever change', async () => { diff --git a/core/test/unique-source.js b/core/test/unique-source.js index 90b978b..1eb8219 100644 --- a/core/test/unique-source.js +++ b/core/test/unique-source.js @@ -2,7 +2,7 @@ const { deepStrictEqual } = require('node:assert'); const nodevu = require('../index'); const { describe, it, beforeEach } = require('node:test'); -const beforeEachTemplate = require('../util/dev/beforeEachTemplate'); +const beforeEachTemplate = require('@nodevu/fixtures/beforeEachTemplate'); describe('check to make sure that changing sources works as expected', async () => { beforeEach(beforeEachTemplate); diff --git a/core/util/dev/beforeEachTemplate.js b/fixtures/beforeEachTemplate.js similarity index 87% rename from core/util/dev/beforeEachTemplate.js rename to fixtures/beforeEachTemplate.js index 9ebabf2..0bc959f 100644 --- a/core/util/dev/beforeEachTemplate.js +++ b/fixtures/beforeEachTemplate.js @@ -5,12 +5,12 @@ const { setGlobalDispatcher, } = require('undici'); -const staticIndex = require('../../test/data/static/index.json'); -const staticSchedule = require('../../test/data/static/schedule.json'); +const staticIndex = require('./data/index.json'); +const staticSchedule = require('./data/schedule.json'); /** * Mocks the network for a test: the default and `https://bnb.im` index.json - * and schedule.json URLs serve core's static snapshots, and any other request + * and schedule.json URLs serve the snapshots, and any other request * fails. */ function beforeEachTemplate() { diff --git a/core/test/data/static/index.json b/fixtures/data/index.json similarity index 100% rename from core/test/data/static/index.json rename to fixtures/data/index.json diff --git a/core/test/data/static/now.json b/fixtures/data/now.json similarity index 100% rename from core/test/data/static/now.json rename to fixtures/data/now.json diff --git a/core/test/data/static/schedule.json b/fixtures/data/schedule.json similarity index 100% rename from core/test/data/static/schedule.json rename to fixtures/data/schedule.json diff --git a/core/util/dev/fixtures.js b/fixtures/index.js similarity index 55% rename from core/util/dev/fixtures.js rename to fixtures/index.js index 562bc5b..20716b4 100644 --- a/core/util/dev/fixtures.js +++ b/fixtures/index.js @@ -1,13 +1,18 @@ -const index = require('../../test/data/static/index.json'); -const schedule = require('../../test/data/static/schedule.json'); +const index = require('./data/index.json'); +const schedule = require('./data/schedule.json'); /** * Returns options for @nodevu/core (and the packages built on it) that serve - * `data` (core's index.json and schedule.json snapshots by default) instead of + * `data` (the index.json and schedule.json snapshots by default) instead of * fetching them, only including releases that were out by `now`, like the * real index.json. Any other URL throws, so tests never touch the network. + * + * `now` defaults to 2025-09-01, picked so that current, active LTS, + * maintenance LTS and end of life each have a release line, and each of those + * lines already has a security release, so no range or security field is + * empty. */ -function at(now, data = { index, schedule }) { +function at(now = '2025-09-01', data = { index, schedule }) { return { now, fetch: async (url) => { diff --git a/fixtures/package.json b/fixtures/package.json new file mode 100644 index 0000000..4d5ea7a --- /dev/null +++ b/fixtures/package.json @@ -0,0 +1,26 @@ +{ + "name": "@nodevu/fixtures", + "version": "0.0.0", + "description": "Snapshot data and helpers the nodevu packages' tests share. Not published.", + "main": "index.js", + "scripts": { + "test": "node --test", + "lint": "oxlint && oxfmt --check", + "lint:write": "oxlint --fix && oxfmt", + "coverage": "c8 node --test", + "test:update": "npm run test:update:static && npm run test:update:now", + "test:update:static": "node ./util/updateStaticData.js", + "test:update:now": "node ./util/updateStaticNow.js" + }, + "author": "Tierney Cyren (https://bnb.im/)", + "license": "MIT", + "private": true, + "dependencies": { + "undici": "^8.12.4" + }, + "devDependencies": { + "c8": "^12.0.0", + "fast-check": "^4.10.2", + "luxon": "^3.7.2" + } +} diff --git a/core/test/dev-fixtures.js b/fixtures/test/at.js similarity index 86% rename from core/test/dev-fixtures.js rename to fixtures/test/at.js index ca9020f..8023a05 100644 --- a/core/test/dev-fixtures.js +++ b/fixtures/test/at.js @@ -1,8 +1,8 @@ const assert = require('node:assert'); const { describe, it } = require('node:test'); const fc = require('fast-check'); -const { at } = require('../util/dev/fixtures'); -const schedule = require('./data/static/schedule.json'); +const { at } = require('../index'); +const schedule = require('../data/schedule.json'); describe('at', async () => { it('should pass now through as an option', () => { @@ -16,7 +16,15 @@ describe('at', async () => { assert.strictEqual(options.now, now); }); - it("should serve core's schedule.json snapshot by default", async () => { + it('should default now to 2025-09-01', () => { + // Act + const options = at(); + + // Assert + assert.strictEqual(options.now, '2025-09-01'); + }); + + it('should serve the schedule.json snapshot by default', async () => { // Arrange const { fetch } = at('2025-09-01'); diff --git a/core/test/dev-scripts.js b/fixtures/test/scripts.js similarity index 75% rename from core/test/dev-scripts.js rename to fixtures/test/scripts.js index 5c0147f..ea86e21 100644 --- a/core/test/dev-scripts.js +++ b/fixtures/test/scripts.js @@ -6,8 +6,8 @@ const { join } = require('node:path'); const { describe, it } = require('node:test'); const { promisify } = require('node:util'); const { DateTime } = require('luxon'); -const index = require('./data/static/index.json'); -const schedule = require('./data/static/schedule.json'); +const index = require('../data/index.json'); +const schedule = require('../data/schedule.json'); describe('test:update:static', async () => { it('should save the index.json and schedule.json it downloads', async () => { @@ -17,7 +17,7 @@ describe('test:update:static', async () => { // Act await runScript( 'updateStaticData.js', - `require(${JSON.stringify(require.resolve('../util/dev/beforeEachTemplate'))})();`, + `require(${JSON.stringify(require.resolve('../beforeEachTemplate'))})();`, cwd, ); @@ -53,24 +53,23 @@ describe('test:update:now', async () => { // Helpers -// a directory laid out like core, for the scripts to write their snapshots into +// a directory laid out like this package, for the scripts to write their +// snapshots into async function snapshotDirectory() { const cwd = await mkdtemp(join(tmpdir(), 'nodevu-')); - await mkdir(join(cwd, 'test/data/static'), { recursive: true }); + await mkdir(join(cwd, 'data')); return cwd; } async function readSnapshot(cwd, file) { - return JSON.parse( - await readFile(join(cwd, 'test/data/static', file), 'utf8'), - ); + return JSON.parse(await readFile(join(cwd, 'data', file), 'utf8')); } -// runs a util/dev script the way its npm script does, after `setup` stubs +// runs a util script the way its npm script does, after `setup` stubs // whatever it reaches outside the process for function runScript(script, setup, cwd) { - const path = require.resolve(`../util/dev/${script}`); + const path = require.resolve(`../util/${script}`); return promisify(execFile)( process.execPath, diff --git a/core/test/dev-write.js b/fixtures/test/write.js similarity index 93% rename from core/test/dev-write.js rename to fixtures/test/write.js index 7d39329..fc65c0f 100644 --- a/core/test/dev-write.js +++ b/fixtures/test/write.js @@ -3,7 +3,7 @@ const { mkdtemp, readFile } = require('node:fs/promises'); const { tmpdir } = require('node:os'); const { join } = require('node:path'); const { describe, it } = require('node:test'); -const write = require('../util/dev/write'); +const write = require('../util/write'); describe('write', async () => { it('should save data as tab-indented JSON', async () => { diff --git a/core/util/dev/updateStaticData.js b/fixtures/util/updateStaticData.js similarity index 76% rename from core/util/dev/updateStaticData.js rename to fixtures/util/updateStaticData.js index d6aa131..b6a5408 100644 --- a/core/util/dev/updateStaticData.js +++ b/fixtures/util/updateStaticData.js @@ -11,8 +11,8 @@ async function update() { const index = await rawIndex.json(); const schedule = await rawSchedule.json(); - await write('./test/data/static/index.json', index); - await write('./test/data/static/schedule.json', schedule); + await write('./data/index.json', index); + await write('./data/schedule.json', schedule); } update(); diff --git a/core/util/dev/updateStaticNow.js b/fixtures/util/updateStaticNow.js similarity index 75% rename from core/util/dev/updateStaticNow.js rename to fixtures/util/updateStaticNow.js index c8cb6b9..a1704b3 100644 --- a/core/util/dev/updateStaticNow.js +++ b/fixtures/util/updateStaticNow.js @@ -3,7 +3,7 @@ const write = require('./write'); async function updateStaticDateTime() { // write serializes the DateTime to its ISO string - await write('./test/data/static/now.json', DateTime.now()); + await write('./data/now.json', DateTime.now()); } updateStaticDateTime(); diff --git a/core/util/dev/write.js b/fixtures/util/write.js similarity index 100% rename from core/util/dev/write.js rename to fixtures/util/write.js diff --git a/newest/package.json b/newest/package.json index 07d6b0e..f78f058 100644 --- a/newest/package.json +++ b/newest/package.json @@ -34,6 +34,7 @@ "@nodevu/core": "^1.0.0" }, "devDependencies": { + "@nodevu/fixtures": "^0.0.0", "c8": "^12.0.0" }, "types": "types/index.d.ts" diff --git a/newest/test/test.js b/newest/test/test.js index c5a33e6..0d15bc5 100644 --- a/newest/test/test.js +++ b/newest/test/test.js @@ -1,9 +1,9 @@ const assert = require('node:assert'); const { newest, lts, security } = require('../index'); const { describe, it } = require('node:test'); -const fixtures = require('../../core/util/dev/fixtures'); +const fixtures = require('@nodevu/fixtures'); -// the day core's snapshot was taken (see now.json), so every release in it is +// the day the snapshot was taken (see now.json), so every release in it is // out, and releases a later snapshot refresh adds get filtered out, so the // expected values don't move const at = (now = '2025-10-30') => fixtures.at(now); diff --git a/oldest/package.json b/oldest/package.json index 90eecfa..0a95bc7 100644 --- a/oldest/package.json +++ b/oldest/package.json @@ -34,6 +34,7 @@ "@nodevu/core": "^1.0.0" }, "devDependencies": { + "@nodevu/fixtures": "^0.0.0", "c8": "^12.0.0" }, "types": "types/index.d.ts" diff --git a/oldest/test/test.js b/oldest/test/test.js index 323d8f0..8f032da 100644 --- a/oldest/test/test.js +++ b/oldest/test/test.js @@ -1,9 +1,9 @@ const assert = require('node:assert'); const { oldest, lts, security } = require('../index'); const { describe, it } = require('node:test'); -const fixtures = require('../../core/util/dev/fixtures'); +const fixtures = require('@nodevu/fixtures'); -// the day core's snapshot was taken (see now.json), so every release in it is +// the day the snapshot was taken (see now.json), so every release in it is // out, and releases a later snapshot refresh adds get filtered out, so the // expected values don't move const at = (now = '2025-10-30') => fixtures.at(now); diff --git a/package-lock.json b/package-lock.json index ebceb7a..7b65951 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "aliases", "translate", "fetchindex", - "opt" + "opt", + "fixtures" ], "devDependencies": { "@doc-kit/cli": "1.0.5", @@ -52,6 +53,7 @@ "semver": "^7.6.3" }, "devDependencies": { + "@nodevu/fixtures": "^0.0.0", "c8": "^12.0.0", "fast-check": "^4.10.2", "undici": "^8.12.2" @@ -66,6 +68,19 @@ "undici": "^8.12.2" } }, + "fixtures": { + "name": "@nodevu/fixtures", + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "undici": "^8.12.4" + }, + "devDependencies": { + "c8": "^12.0.0", + "fast-check": "^4.10.2", + "luxon": "^3.7.2" + } + }, "newest": { "name": "@nodevu/newest", "version": "1.1.0", @@ -74,6 +89,7 @@ "@nodevu/core": "^1.0.0" }, "devDependencies": { + "@nodevu/fixtures": "^0.0.0", "c8": "^12.0.0" } }, @@ -1128,6 +1144,10 @@ "resolved": "fetchindex", "link": true }, + "node_modules/@nodevu/fixtures": { + "resolved": "fixtures", + "link": true + }, "node_modules/@nodevu/newest": { "resolved": "newest", "link": true @@ -9465,7 +9485,6 @@ "version": "8.12.4", "resolved": "https://registry.npmjs.org/undici/-/undici-8.12.4.tgz", "integrity": "sha512-8Mx+uNKKER2bA4AdtedkqRbwUCIJFr2nJckXRe5yrDqzePqwLP7VNcctN8/XdmDSbF7mS+jJKIszlL+vbam1VA==", - "dev": true, "license": "MIT", "engines": { "node": ">=22.19.0" @@ -10082,6 +10101,7 @@ "@nodevu/core": "^1.0.0" }, "devDependencies": { + "@nodevu/fixtures": "^0.0.0", "c8": "^12.0.0" } }, @@ -10104,6 +10124,7 @@ "version": "1.1.0", "license": "MIT", "devDependencies": { + "@nodevu/fixtures": "^0.0.0", "c8": "^12.0.0", "fast-check": "^4.10.2" } @@ -10117,6 +10138,7 @@ "@nodevu/core": "^1.0.0" }, "devDependencies": { + "@nodevu/fixtures": "^0.0.0", "c8": "^12.0.0", "fast-check": "^4.10.2" } @@ -10127,6 +10149,7 @@ "license": "MIT", "devDependencies": { "@nodevu/core": "^1.1.0", + "@nodevu/fixtures": "^0.0.0", "c8": "^12.0.0" }, "peerDependencies": { @@ -10144,6 +10167,7 @@ "license": "MIT", "devDependencies": { "@nodevu/aliases": "^1.1.0", + "@nodevu/fixtures": "^0.0.0", "@nodevu/ranges": "^1.0.0", "c8": "^12.0.0", "fast-check": "^4.10.2" diff --git a/package.json b/package.json index 958225f..4835ffa 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "aliases", "translate", "fetchindex", - "opt" + "opt", + "fixtures" ], "devDependencies": { "@doc-kit/cli": "1.0.5", diff --git a/parsefiles/package.json b/parsefiles/package.json index 882c428..ace63f4 100644 --- a/parsefiles/package.json +++ b/parsefiles/package.json @@ -25,6 +25,7 @@ "author": "Tierney Cyren ", "license": "MIT", "devDependencies": { + "@nodevu/fixtures": "^0.0.0", "c8": "^12.0.0", "fast-check": "^4.10.2" }, diff --git a/parsefiles/test/parseFilename.test.js b/parsefiles/test/parseFilename.test.js index cb2838c..fdd2567 100644 --- a/parsefiles/test/parseFilename.test.js +++ b/parsefiles/test/parseFilename.test.js @@ -3,7 +3,7 @@ const { describe, it } = require('node:test'); const fc = require('fast-check'); const parseFilename = require('../index'); -const releases = require('../../core/test/data/static/index.json'); +const releases = require('@nodevu/fixtures/data/index.json'); describe('parseFilename', () => { it('should only list the win-x86 directory for win-x86-exe', () => { diff --git a/ranges/package.json b/ranges/package.json index 7694cbf..1b45d4b 100644 --- a/ranges/package.json +++ b/ranges/package.json @@ -31,6 +31,7 @@ "@nodevu/core": "^1.0.0" }, "devDependencies": { + "@nodevu/fixtures": "^0.0.0", "c8": "^12.0.0", "fast-check": "^4.10.2" }, diff --git a/ranges/test/alpha.test.js b/ranges/test/alpha.test.js index 662aa20..9745f7b 100644 --- a/ranges/test/alpha.test.js +++ b/ranges/test/alpha.test.js @@ -43,7 +43,7 @@ const schedule = { }, }; -// serves the made-up data above instead of core's snapshots +// serves the made-up data above instead of the snapshots const at = (now) => fixtures.at(now, { index, schedule }); describe('check to make sure that Alpha releases work as expected', async () => { diff --git a/ranges/test/bounds.test.js b/ranges/test/bounds.test.js index 24a7d81..8dbdfbe 100644 --- a/ranges/test/bounds.test.js +++ b/ranges/test/bounds.test.js @@ -5,7 +5,7 @@ const { describe, it } = require('node:test'); // the LTS and security bounds of lts/maintenance and lts/active come from the // newest and oldest lines in them, even when those lines have none. the -// expected values come from core's index.json and schedule.json snapshots. +// expected values come from the index.json and schedule.json snapshots. describe('generateRanges', () => { it('should take the newest bounds of "lts/active" from "lts/maintenance" when "lts/latest" is empty', async () => { // Arrange diff --git a/ranges/test/emptyFields.test.js b/ranges/test/emptyFields.test.js index 6c19dc5..6bb96f4 100644 --- a/ranges/test/emptyFields.test.js +++ b/ranges/test/emptyFields.test.js @@ -3,8 +3,8 @@ const ranges = require('../index'); const { at } = require('./fixtures'); const { describe, it } = require('node:test'); -// core's snapshot time, when the Current line hasn't had a security release yet -const snapshotNow = require('../../core/test/data/static/now.json'); +// the snapshot time, when the Current line hasn't had a security release yet +const snapshotNow = require('@nodevu/fixtures/data/now.json'); describe('fields without a value should be undefined, not "undefined"', async () => { it('should leave out the security fields of a Current line without security releases', async () => { diff --git a/ranges/test/fixtures.js b/ranges/test/fixtures.js index 927660e..5ba2be4 100644 --- a/ranges/test/fixtures.js +++ b/ranges/test/fixtures.js @@ -1,34 +1,6 @@ -const index = require('../../core/test/data/static/index.json'); -const schedule = require('../../core/test/data/static/schedule.json'); - -// the default date for tests. it's picked so that current, active LTS, -// maintenance LTS and end of life each have a release line, and each of those -// lines already has a security release, so no range or security field is -// empty. -const now = '2025-09-01'; +const { at } = require('@nodevu/fixtures'); // every version in index.json, like v22.19.0 const versionPattern = /^v\d+\.\d+\.\d+$/; -// returns options for `ranges` that serve `data` (core's index.json and -// schedule.json snapshots by default) instead of fetching them, only including -// releases that would be out by `now`, like the real index.json. any other URL -// throws, so tests never touch the network. -function at(date = now, data = { index, schedule }) { - return { - now: date, - fetch: async (url) => { - if (url.endsWith('/schedule.json')) { - return Response.json(data.schedule); - } - - if (url.endsWith('/index.json')) { - return Response.json(data.index.filter((entry) => entry.date <= date)); - } - - throw new Error(`No fixture data for ${url}`); - }, - }; -} - module.exports = { at, versionPattern }; diff --git a/ranges/test/properties.test.js b/ranges/test/properties.test.js index 95ade92..dcfd71e 100644 --- a/ranges/test/properties.test.js +++ b/ranges/test/properties.test.js @@ -3,8 +3,8 @@ const aliases = require('@nodevu/aliases'); const fc = require('fast-check'); const ranges = require('../index'); const { at } = require('./fixtures'); -const index = require('../../core/test/data/static/index.json'); -const schedule = require('../../core/test/data/static/schedule.json'); +const index = require('@nodevu/fixtures/data/index.json'); +const schedule = require('@nodevu/fixtures/data/schedule.json'); const { describe, it } = require('node:test'); // an LTS bound from @nodevu/core, a version without a `v`, like 20.9.0 diff --git a/static/package.json b/static/package.json index cf11914..4383565 100644 --- a/static/package.json +++ b/static/package.json @@ -40,6 +40,7 @@ "homepage": "https://github.com/cutenode/nodevu#readme", "devDependencies": { "@nodevu/core": "^1.1.0", + "@nodevu/fixtures": "^0.0.0", "c8": "^12.0.0" }, "types": "types/index.d.ts", diff --git a/static/test/test.build.js b/static/test/test.build.js index d5f631e..a3b3620 100644 --- a/static/test/test.build.js +++ b/static/test/test.build.js @@ -6,8 +6,8 @@ const { join } = require('node:path'); const { describe, it } = require('node:test'); const { promisify } = require('node:util'); const nodevu = require('@nodevu/core'); -const fixtures = require('../../core/util/dev/fixtures'); -const now = require('../../core/test/data/static/now.json'); +const fixtures = require('@nodevu/fixtures'); +const now = require('@nodevu/fixtures/data/now.json'); // a full diff of every release takes node minutes to build, so failures say // this instead @@ -84,7 +84,7 @@ function toJSON(data) { } // runs a build script the way `npm run build -w static` does, from a copy of -// static/, with core's snapshot data served instead of nodejs.org and the +// static/, with the snapshot data served instead of nodejs.org and the // clock stopped at the time the snapshot was taken. returns what it wrote. async function runBuild(name) { const cwd = join(await mkdtemp(join(tmpdir(), 'nodevu-')), 'static'); @@ -93,7 +93,7 @@ async function runBuild(name) { const setup = [ // core reads the time through luxon, which calls Date.now() `Date.now = () => ${Date.parse(now)};`, - `globalThis.fetch = require(${JSON.stringify(require.resolve('../../core/util/dev/fixtures'))}).at(${JSON.stringify(now)}).fetch;`, + `globalThis.fetch = require(${JSON.stringify(require.resolve('@nodevu/fixtures'))}).at(${JSON.stringify(now)}).fetch;`, ].join(' '); await promisify(execFile)( diff --git a/translate/package.json b/translate/package.json index b13f588..470229b 100644 --- a/translate/package.json +++ b/translate/package.json @@ -30,6 +30,7 @@ }, "devDependencies": { "@nodevu/aliases": "^1.1.0", + "@nodevu/fixtures": "^0.0.0", "@nodevu/ranges": "^1.0.0", "c8": "^12.0.0", "fast-check": "^4.10.2" diff --git a/translate/test/examples.test.js b/translate/test/examples.test.js index 0938e27..1df1cd7 100644 --- a/translate/test/examples.test.js +++ b/translate/test/examples.test.js @@ -6,8 +6,8 @@ const { promisify } = require('node:util'); describe('examples/default.js', async () => { it('should print the ranges for each Package Support term', async () => { // Arrange - // serve core's snapshot data instead of fetching from nodejs.org - const setup = `globalThis.fetch = require(${JSON.stringify(require.resolve('../../ranges/test/fixtures'))}).at().fetch;`; + // serve the snapshot data instead of fetching from nodejs.org + const setup = `globalThis.fetch = require(${JSON.stringify(require.resolve('@nodevu/fixtures'))}).at().fetch;`; const example = JSON.stringify(require.resolve('../examples/default')); // Act diff --git a/translate/test/ranges.test.js b/translate/test/ranges.test.js index 1e902f2..7e30a5b 100644 --- a/translate/test/ranges.test.js +++ b/translate/test/ranges.test.js @@ -2,9 +2,9 @@ const assert = require('node:assert'); const translate = require('../index'); const ranges = require('@nodevu/ranges'); const { describe, it } = require('node:test'); -const { at } = require('../../ranges/test/fixtures'); +const { at } = require('@nodevu/fixtures'); -// serve core's snapshot data at a fixed date so results don't depend on +// serve the snapshot data at a fixed date so results don't depend on // nodejs.org or today's date const options = at(); From 7064689d8026eea81b82119102ebaca038482587 Mon Sep 17 00:00:00 2001 From: botandbang <337765928+botandbang@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:59:46 -0500 Subject: [PATCH 4/4] build(lint): add an oxlint rule against relative paths into other packages tools/oxlint/nodevu/ is the repo's own oxlint JS plugin. Its no-cross-package-paths rule reports require(), require.resolve(), import and export paths, written out in full, that resolve into a different package's folder than the file's own. oxlint's no-restricted-imports only checks ESM imports, so it can't do this for CommonJS. npm run lint:rules tests the rule with oxlint's RuleTester and runs as part of npm run lint. The rule found translate/examples/default.js requiring ../../ranges, which now requires @nodevu/ranges. --- .oxlintrc.json | 4 +- README.md | 2 +- package.json | 5 +- tools/oxlint/nodevu/index.mjs | 118 ++++++++++++++++++ .../test/no-cross-package-paths.test.mjs | 103 +++++++++++++++ translate/examples/default.js | 2 +- 6 files changed, 229 insertions(+), 5 deletions(-) create mode 100644 tools/oxlint/nodevu/index.mjs create mode 100644 tools/oxlint/nodevu/test/no-cross-package-paths.test.mjs diff --git a/.oxlintrc.json b/.oxlintrc.json index 007569d..3abc443 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -16,11 +16,13 @@ "*/types/**" ], "jsPlugins": [ - { "name": "anti-slop", "specifier": "./tools/oxlint/anti-slop/index.ts" } + { "name": "anti-slop", "specifier": "./tools/oxlint/anti-slop/index.ts" }, + { "name": "nodevu", "specifier": "./tools/oxlint/nodevu/index.mjs" } ], "rules": { "no-unused-vars": ["warn", { "ignoreRestSiblings": true }], "oxc/no-accumulating-spread": "error", + "nodevu/no-cross-package-paths": "error", "anti-slop/no-array-filter-map": "error", "anti-slop/no-reduce-accumulator-copy": "error", "anti-slop/no-chained-type-assertions": "error", diff --git a/README.md b/README.md index 131b7da..f8ca9cc 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ nodevu is composed of a set of modules: Installing needs npm 11.17 or newer, which ships with Node.js 24 and later. `.npmrc` skips packages published in the last 3 days and blocks git and URL dependencies. npm only runs the dependency install scripts that `allowScripts` in `package.json` approves, and `npm approve-scripts ` adds one pinned to its installed version. - `npm test` and `npm run coverage` run every module's tests. The tests share the release data snapshot and its helpers through `fixtures/`, a private workspace that isn't published. `npm run test:update -w fixtures` refreshes the snapshot. -- `npm run lint` checks the whole repo with oxlint and oxfmt, and checks with [docov](https://github.com/cutenode/docov) that everything the modules export has JSDoc. `docov.jsonc` sets what it scans. `npm run lint:harden` tests `tools/harden/` and runs [@lavamoat/harden](https://github.com/LavaMoat/LavaMoat/tree/main/packages/harden) to check that `.npmrc` and `package.json` keep every hardening setting, except the ones `tools/harden/decisions.json` turns off. `npm run knip` runs [knip](https://knip.dev) to find unused files, exports and dependencies. `knip.jsonc` lists what it skips and why. `npm run lint` runs both. +- `npm run lint` checks the whole repo with oxlint and oxfmt, and checks with [docov](https://github.com/cutenode/docov) that everything the modules export has JSDoc. `docov.jsonc` sets what it scans. `npm run lint:harden` tests `tools/harden/` and runs [@lavamoat/harden](https://github.com/LavaMoat/LavaMoat/tree/main/packages/harden) to check that `.npmrc` and `package.json` keep every hardening setting, except the ones `tools/harden/decisions.json` turns off. `tools/oxlint/nodevu/` holds the repo's own oxlint rule, which stops code from reaching into another package's folder by relative path, and `npm run lint:rules` tests it. `npm run knip` runs [knip](https://knip.dev) to find unused files, exports and dependencies. `knip.jsonc` lists what it skips and why. `npm run lint` runs all of these. - `npm run types` type-checks the JSDoc and writes each module's TypeScript declarations to its `types/` folder. Commit them: CI fails when they're out of date. - `npm run docs:dev` builds the docs from `docs/pages/` with [doc-kit](https://doc-kit.nodejs.org/) and serves them locally. `npm run docs` only builds them, into `out/`. diff --git a/package.json b/package.json index 4835ffa..fa5b5ca 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "comprehensive node.js version tooling", "scripts": { - "lint": "oxlint && oxfmt --check && docov && npm run lint:harden && npm run knip", + "lint": "oxlint && oxfmt --check && docov && npm run lint:harden && npm run lint:rules && npm run knip", "lint:write": "oxlint --fix && oxfmt", "lint:harden": "node --test tools/harden/test/*.test.mjs && harden check -p npm -l strict --json | node tools/harden/check.mjs", "coverage": "npm run coverage --ws --if-present", @@ -12,7 +12,8 @@ "docs": "doc-kit generate", "docs:dev": "doc-kit generate && sirv out --dev", "types": "tsc -b", - "knip": "knip --treat-config-hints-as-errors" + "knip": "knip --treat-config-hints-as-errors", + "lint:rules": "node --test tools/oxlint/nodevu/test/*.test.mjs" }, "repository": { "type": "git", diff --git a/tools/oxlint/nodevu/index.mjs b/tools/oxlint/nodevu/index.mjs new file mode 100644 index 0000000..b054416 --- /dev/null +++ b/tools/oxlint/nodevu/index.mjs @@ -0,0 +1,118 @@ +import { existsSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; + +import { definePlugin, defineRule } from '@oxlint/plugins'; + +/** @type {Map} */ +const packageDirs = new Map(); + +/** + * The folder of the package `path` is in: the nearest one holding a + * package.json, which is a workspace or the repo root. + * @param {string} path + * @returns {string} + */ +function packageDir(path) { + const cached = packageDirs.get(path); + + if (cached !== undefined) { + return cached; + } + + const parent = dirname(path); + const isPackage = existsSync(join(path, 'package.json')) || parent === path; + const dir = isPackage ? path : packageDir(parent); + packageDirs.set(path, dir); + + return dir; +} + +/** + * The module path an argument holds, when it's written out in full. Literals + * that aren't strings come back as strings that are never relative paths. + * @param {import('@oxlint/plugins').ESTree.Node | null | undefined} node + * @returns {string | null} + */ +function staticPath(node) { + if (node?.type === 'Literal') { + return String(node.value); + } + + if (node?.type === 'TemplateLiteral' && node.expressions.length === 0) { + return node.quasis[0].value.cooked; + } + + return null; +} + +/** + * Whether `callee` is `require` or `require.resolve`. + * @param {import('@oxlint/plugins').ESTree.Node} callee + * @returns {boolean} + */ +function isRequire(callee) { + if (callee.type === 'Identifier') { + return callee.name === 'require'; + } + + return ( + callee.type === 'MemberExpression' && + !callee.computed && + callee.object.type === 'Identifier' && + callee.object.name === 'require' && + callee.property.type === 'Identifier' && + callee.property.name === 'resolve' + ); +} + +const noCrossPackagePaths = defineRule({ + meta: { + type: 'problem', + docs: { + description: + "Disallow relative paths into another package's folder, so each package only depends on what it lists.", + }, + messages: { + crossPackage: + "'{{path}}' is in another package's folder. Depend on that package and use its name.", + }, + }, + create(context) { + const fromPackage = packageDir(dirname(context.filename)); + + /** @param {import('@oxlint/plugins').ESTree.Node | null | undefined} node */ + function check(node) { + const path = staticPath(node); + + const isRelative = path !== null && /^\.\.?(\/|$)/.test(path); + + if (!isRelative) { + return; + } + + const target = resolve(dirname(context.filename), path); + + if (packageDir(target) !== fromPackage) { + context.report({ node, messageId: 'crossPackage', data: { path } }); + } + } + + return { + ImportDeclaration: (node) => check(node.source), + ExportAllDeclaration: (node) => check(node.source), + ExportNamedDeclaration: (node) => check(node.source), + ImportExpression: (node) => check(node.source), + CallExpression(node) { + if (isRequire(node.callee)) { + check(node.arguments[0]); + } + }, + }; + }, +}); + +/** nodevu's own oxlint rules. */ +export default definePlugin({ + meta: { name: 'nodevu' }, + rules: { 'no-cross-package-paths': noCrossPackagePaths }, +}); diff --git a/tools/oxlint/nodevu/test/no-cross-package-paths.test.mjs b/tools/oxlint/nodevu/test/no-cross-package-paths.test.mjs new file mode 100644 index 0000000..80e8c3f --- /dev/null +++ b/tools/oxlint/nodevu/test/no-cross-package-paths.test.mjs @@ -0,0 +1,103 @@ +import { describe, it } from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { RuleTester } from 'oxlint/plugins-dev'; + +import plugin from '../index.mjs'; + +RuleTester.describe = describe; + +RuleTester.it = it; + +// the rule looks for package.json files on disk, so the test files sit in the +// repo's real folders +const root = fileURLToPath(new URL('../../../../', import.meta.url)); + +const rangesTest = `${root}ranges/test/example.test.js`; + +const translateTest = `${root}translate/test/example.test.mjs`; + +const rootTool = `${root}tools/example.mjs`; + +const tester = new RuleTester({ languageOptions: { sourceType: 'commonjs' } }); + +tester.run('no-cross-package-paths', plugin.rules['no-cross-package-paths'], { + valid: [ + { code: "require('../index');", filename: rangesTest }, + { code: "require('./fixtures');", filename: rangesTest }, + { code: "require.resolve('../index');", filename: rangesTest }, + { code: "require('@nodevu/fixtures');", filename: rangesTest }, + { code: "require('node:path');", filename: rangesTest }, + // only paths written out in full can be checked + { code: 'require(`../../core/${name}`);', filename: rangesTest }, + { code: 'require(path);', filename: rangesTest }, + { code: 'require(5);', filename: rangesTest }, + { code: "resolve('../../core');", filename: rangesTest }, + { code: "require.cache('../../core');", filename: rangesTest }, + { + code: "import ranges from '../index.js';", + filename: translateTest, + languageOptions: { sourceType: 'module' }, + }, + { + code: "import { check } from './harden/check.mjs';", + filename: rootTool, + languageOptions: { sourceType: 'module' }, + }, + // outside any package, the search stops at the filesystem root + { code: "require('./example');", filename: '/example.js' }, + ], + invalid: [ + { + code: "require('../../core/util/prod/optionsParser');", + filename: `${root}fetchindex/test/test.js`, + errors: [{ messageId: 'crossPackage' }], + }, + { + code: "require('../../core');", + filename: rangesTest, + errors: [{ messageId: 'crossPackage' }], + }, + { + code: "require.resolve('../../ranges/test/fixtures');", + filename: `${root}translate/test/examples.test.js`, + errors: [{ messageId: 'crossPackage' }], + }, + { + code: 'require(`../../core/index.js`);', + filename: rangesTest, + errors: [{ messageId: 'crossPackage' }], + }, + { + code: "import ranges from '../../ranges/index.js';", + filename: translateTest, + languageOptions: { sourceType: 'module' }, + errors: [{ messageId: 'crossPackage' }], + }, + { + code: "export * from '../../ranges/index.js';", + filename: translateTest, + languageOptions: { sourceType: 'module' }, + errors: [{ messageId: 'crossPackage' }], + }, + { + code: "export { at } from '../../ranges/test/fixtures.js';", + filename: translateTest, + languageOptions: { sourceType: 'module' }, + errors: [{ messageId: 'crossPackage' }], + }, + { + code: "await import('../../ranges/index.js');", + filename: translateTest, + languageOptions: { sourceType: 'module' }, + errors: [{ messageId: 'crossPackage' }], + }, + // the repo root is a package of its own + { + code: "import core from '../core/index.js';", + filename: rootTool, + languageOptions: { sourceType: 'module' }, + errors: [{ messageId: 'crossPackage' }], + }, + ], +}); diff --git a/translate/examples/default.js b/translate/examples/default.js index 87bec24..e7e2f4a 100644 --- a/translate/examples/default.js +++ b/translate/examples/default.js @@ -1,4 +1,4 @@ -const ranges = require('../../ranges'); // would normally be "const ranges = require('@nodevu/ranges')" in your code +const ranges = require('@nodevu/ranges'); const translate = require('../index'); // would normally be "const translate = require('@nodevu/translate')" in your code async function getTranslatedData() {