Spaces:
Running
Running
| import { inspectDependencyInstallation } from './dependency-installation.mjs'; | |
| import assert from 'node:assert/strict'; | |
| import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; | |
| import { tmpdir } from 'node:os'; | |
| import { join } from 'node:path'; | |
| import { describe, it } from 'node:test'; | |
| const ROOT_LOCKFILE = { | |
| name: 'fixture', | |
| lockfileVersion: 3, | |
| packages: { | |
| '': { | |
| name: 'fixture', | |
| dependencies: { demo: '1.0.0' }, | |
| devDependencies: { '@scope/tool': '2.0.0' } | |
| }, | |
| 'node_modules/demo': { version: '1.0.0' }, | |
| 'node_modules/@scope/tool': { version: '2.0.0' } | |
| } | |
| }; | |
| const LOCAL_LINK_ROOT_LOCKFILE = { | |
| name: 'fixture', | |
| lockfileVersion: 3, | |
| packages: { | |
| '': { | |
| name: 'fixture', | |
| dependencies: { demo: 'file:vendor/demo' } | |
| }, | |
| 'node_modules/demo': { | |
| resolved: 'vendor/demo', | |
| link: true | |
| }, | |
| 'vendor/demo': { | |
| name: 'demo', | |
| version: '1.0.0' | |
| } | |
| } | |
| }; | |
| async function createFixture({ rootLockfile = ROOT_LOCKFILE, hiddenLockfile, manifests = {} } = {}) { | |
| const root = await mkdtemp(join(tmpdir(), 'gipc-dependency-installation-')); | |
| await mkdir(join(root, 'node_modules'), { recursive: true }); | |
| await writeFile(join(root, 'package-lock.json'), JSON.stringify(rootLockfile)); | |
| if (hiddenLockfile !== undefined) { | |
| await writeFile(join(root, 'node_modules', '.package-lock.json'), JSON.stringify(hiddenLockfile)); | |
| } | |
| for (const [name, manifest] of Object.entries(manifests)) { | |
| const packageDirectory = join(root, 'node_modules', name); | |
| await mkdir(packageDirectory, { recursive: true }); | |
| await writeFile(join(packageDirectory, 'package.json'), JSON.stringify(manifest)); | |
| } | |
| return root; | |
| } | |
| function buildHiddenLockfile(packages = ROOT_LOCKFILE.packages) { | |
| return { | |
| lockfileVersion: 3, | |
| packages: Object.fromEntries(Object.entries(packages).filter(([path]) => path !== '')) | |
| }; | |
| } | |
| describe('dependency installation inspection', () => { | |
| it('detects an interrupted install when only the hidden lockfile remains', async () => { | |
| const root = await createFixture({ hiddenLockfile: buildHiddenLockfile() }); | |
| try { | |
| const state = inspectDependencyInstallation(root); | |
| assert.equal(state.ok, false); | |
| assert.equal(state.reason, 'direct_package_missing'); | |
| assert.deepEqual(state.missingPackages, ['@scope/tool', 'demo']); | |
| } finally { | |
| await rm(root, { force: true, recursive: true }); | |
| } | |
| }); | |
| it('detects a missing hidden lockfile', async () => { | |
| const root = await createFixture(); | |
| try { | |
| const state = inspectDependencyInstallation(root); | |
| assert.equal(state.ok, false); | |
| assert.equal(state.reason, 'hidden_lockfile_missing'); | |
| assert.deepEqual(state.missingPackages, []); | |
| } finally { | |
| await rm(root, { force: true, recursive: true }); | |
| } | |
| }); | |
| it('detects a direct dependency version mismatch', async () => { | |
| const root = await createFixture({ | |
| hiddenLockfile: buildHiddenLockfile(), | |
| manifests: { | |
| demo: { name: 'demo', version: '1.1.0' }, | |
| '@scope/tool': { name: '@scope/tool', version: '2.0.0' } | |
| } | |
| }); | |
| try { | |
| const state = inspectDependencyInstallation(root); | |
| assert.equal(state.ok, false); | |
| assert.equal(state.reason, 'direct_package_version_mismatch'); | |
| assert.deepEqual(state.versionMismatches, [{ name: 'demo', expected: '1.0.0', actual: '1.1.0' }]); | |
| } finally { | |
| await rm(root, { force: true, recursive: true }); | |
| } | |
| }); | |
| it('accepts direct dependencies matching both lockfiles', async () => { | |
| const root = await createFixture({ | |
| hiddenLockfile: buildHiddenLockfile(), | |
| manifests: { | |
| demo: { name: 'demo', version: '1.0.0' }, | |
| '@scope/tool': { name: '@scope/tool', version: '2.0.0' } | |
| } | |
| }); | |
| try { | |
| const state = inspectDependencyInstallation(root); | |
| assert.equal(state.ok, true); | |
| assert.deepEqual(state.directDependencies, ['@scope/tool', 'demo']); | |
| assert.equal(state.reason, undefined); | |
| } finally { | |
| await rm(root, { force: true, recursive: true }); | |
| } | |
| }); | |
| it('accepts a local file dependency whose link target matches both lockfiles', async () => { | |
| const root = await createFixture({ | |
| rootLockfile: LOCAL_LINK_ROOT_LOCKFILE, | |
| hiddenLockfile: buildHiddenLockfile(LOCAL_LINK_ROOT_LOCKFILE.packages), | |
| manifests: { | |
| demo: { name: 'demo', version: '1.0.0' } | |
| } | |
| }); | |
| try { | |
| const state = inspectDependencyInstallation(root); | |
| assert.equal(state.ok, true); | |
| assert.deepEqual(state.directDependencies, ['demo']); | |
| } finally { | |
| await rm(root, { force: true, recursive: true }); | |
| } | |
| }); | |
| it('detects a local file dependency version mismatch in the hidden lockfile', async () => { | |
| const hiddenLockfile = buildHiddenLockfile({ | |
| ...LOCAL_LINK_ROOT_LOCKFILE.packages, | |
| 'vendor/demo': { name: 'demo', version: '1.1.0' } | |
| }); | |
| const root = await createFixture({ | |
| rootLockfile: LOCAL_LINK_ROOT_LOCKFILE, | |
| hiddenLockfile, | |
| manifests: { | |
| demo: { name: 'demo', version: '1.0.0' } | |
| } | |
| }); | |
| try { | |
| const state = inspectDependencyInstallation(root); | |
| assert.equal(state.ok, false); | |
| assert.equal(state.reason, 'hidden_lockfile_package_mismatch'); | |
| assert.deepEqual(state.hiddenLockMismatches, [{ name: 'demo', expected: '1.0.0', actual: '1.1.0' }]); | |
| } finally { | |
| await rm(root, { force: true, recursive: true }); | |
| } | |
| }); | |
| }); | |