import assert from 'node:assert/strict' import fs from 'node:fs' import path from 'node:path' import { describe, test } from 'vitest' const REPO_ROOT = path.resolve(__dirname, '..') interface Manifest { engines?: { node?: string } } interface Lockfile { packages?: Record } function readJson(relativePath: string): T { return JSON.parse(fs.readFileSync(path.join(REPO_ROOT, relativePath), 'utf-8')) as T } function parseVersion(version: string): [number, number, number] { const [major = 0, minor = 0, patch = 0] = version.split('-', 1)[0].split('.').map(Number) return [major, minor, patch] } function compare(left: string, right: string): number { const have = parseVersion(left) const want = parseVersion(right) for (let index = 0; index < have.length; index += 1) { if (have[index] !== want[index]) { return have[index] - want[index] } } return 0 } function satisfiesClause(version: string, clause: string): boolean { assert.match(clause, /^(?:\^|>=|<=|>|<|=)?\d+(?:\.\d+){0,2}$/, `unsupported semver clause: ${clause}`) if (clause.startsWith('^')) { const bound = clause.slice(1) return parseVersion(version)[0] === parseVersion(bound)[0] && compare(version, bound) >= 0 } const match = clause.match(/^(>=|<=|>|<|=)?(.+)$/) assert.ok(match) const [, operator = '=', bound] = match const result = compare(version, bound) return operator === '>=' ? result >= 0 : operator === '<=' ? result <= 0 : operator === '>' ? result > 0 : operator === '<' ? result < 0 : result === 0 } function satisfiesRange(version: string, range: string): boolean { const alternatives = range.split('||').map(alternative => alternative.trim().split(/\s+/)) alternatives.flat().forEach(clause => satisfiesClause(version, clause)) return alternatives.some(clauses => clauses.every(clause => satisfiesClause(version, clause))) } const rootManifest = readJson('package.json') const desktopManifest = readJson('apps/desktop/package.json') const lockfile = readJson('package-lock.json') function nodeRange(manifest: Manifest, label: string): string { assert.ok(manifest.engines?.node, `${label} must declare engines.node`) return manifest.engines.node } describe('Node engine alignment', () => { const rootRange = nodeRange(rootManifest, 'root package.json') const desktopRange = nodeRange(desktopManifest, 'apps/desktop/package.json') test.each(['22.22.0', '22.23.1', '24.11.0', '24.18.2', '26.0.0'])('all workspace manifests accept supported Node %s', version => { assert.ok(satisfiesRange(version, rootRange)) assert.ok(satisfiesRange(version, desktopRange)) }) test.each(['22.21.1', '23.0.0', '24.0.0', '24.10.9', '25.2.1'])( 'all workspace manifests reject dependency-incompatible Node %s', version => { assert.ok(!satisfiesRange(version, rootRange)) assert.ok(!satisfiesRange(version, desktopRange)) } ) test('lockfile workspace mirrors match their manifests', () => { assert.equal(nodeRange(lockfile.packages?.[''] ?? {}, 'root lock entry'), rootRange) assert.equal(nodeRange(lockfile.packages?.['apps/desktop'] ?? {}, 'desktop lock entry'), desktopRange) }) test.each(['~22.22.0', '22.x', '>=26.0.0-rc.1'])( 'the alignment helper rejects unsupported semver clause %s instead of misclassifying it', clause => { assert.throws(() => satisfiesRange('26.0.0', clause), /unsupported semver clause/) } ) test('unsupported clauses are rejected even after a matching alternative', () => { assert.throws(() => satisfiesRange('26.0.0', '>=26.0.0 || ~28.0.0'), /unsupported semver clause/) }) })