import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import { test } from 'node:test'; import { runDependencyUpgrade } from './upgrade-dependencies.mjs'; test('runDependencyUpgrade updates TypeScript and Python manifests before regenerating lockfiles', async () => { const calls = []; const logs = []; const result = await runDependencyUpgrade({ rootDir: '/workspace/ktx', readFile: async (path) => { assert.equal(path, '/workspace/ktx/pnpm-workspace.yaml'); return 'packages: []\nminimumReleaseAge: 10080\n'; }, execFile: async (command, args, options) => { calls.push({ command, args, cwd: options.cwd }); return { stdout: '', stderr: '' }; }, log: (line) => logs.push(line), }); assert.equal(result.ok, true); assert.deepEqual( calls.map((call) => [call.command, call.args]), [ [ 'pnpm', [ 'dlx', 'npm-check-updates', '-u', '--deep', '--reject', 'fumadocs-core,fumadocs-ui', '--cooldown', '10080m', ], ], ['uvx', ['dependency-check-updates', '--manifest', 'pyproject.toml', '-u']], ['uvx', ['dependency-check-updates', '--manifest', 'python/ktx-sl/pyproject.toml', '-u']], ['uvx', ['dependency-check-updates', '--manifest', 'python/ktx-daemon/pyproject.toml', '-u']], ['pnpm', ['install']], ['uv', ['lock', '--upgrade']], ], ); assert.equal(calls.every((call) => call.cwd === '/workspace/ktx'), true); assert.equal(logs.some((line) => line.includes('PASS Python dependency constraints')), true); }); test('runDependencyUpgrade stops at the failed phase and prints a retry command', async () => { const calls = []; const logs = []; const result = await runDependencyUpgrade({ rootDir: '/workspace/ktx', readFile: async () => 'packages: []\n', execFile: async (command, args) => { calls.push({ command, args }); if (command === 'uvx' && args.includes('python/ktx-sl/pyproject.toml')) { const error = new Error('dependency-check-updates failed'); error.stdout = 'checking Python dependencies'; error.stderr = 'could not read pyproject.toml'; throw error; } return { stdout: '', stderr: '' }; }, log: (line) => logs.push(line), }); assert.equal(result.ok, false); assert.equal(result.failedPhase.name, 'Python dependency constraints: python/ktx-sl/pyproject.toml'); assert.equal(result.failedPhase.retry, 'uvx dependency-check-updates --manifest python/ktx-sl/pyproject.toml -u'); assert.deepEqual( calls.map((call) => [call.command, call.args]), [ ['pnpm', ['dlx', 'npm-check-updates', '-u', '--deep', '--reject', 'fumadocs-core,fumadocs-ui']], ['uvx', ['dependency-check-updates', '--manifest', 'pyproject.toml', '-u']], ['uvx', ['dependency-check-updates', '--manifest', 'python/ktx-sl/pyproject.toml', '-u']], ], ); assert.equal(logs.some((line) => line.includes('FAIL Python dependency constraints')), true); assert.equal(logs.some((line) => line.includes('could not read pyproject.toml')), true); assert.equal(logs.some((line) => line.includes('checking Python dependencies')), true); assert.equal( logs.some((line) => line.includes('Retry: uvx dependency-check-updates --manifest python/ktx-sl/pyproject.toml -u')), true, ); }); test('runDependencyUpgrade ignores missing pnpm minimum release age config', async () => { const calls = []; const result = await runDependencyUpgrade({ rootDir: '/workspace/ktx', readFile: async () => { throw Object.assign(new Error('missing'), { code: 'ENOENT' }); }, execFile: async (command, args) => { calls.push({ command, args }); return { stdout: '', stderr: '' }; }, log: () => undefined, }); assert.equal(result.ok, true); assert.deepEqual(calls[0], { command: 'pnpm', args: ['dlx', 'npm-check-updates', '-u', '--deep', '--reject', 'fumadocs-core,fumadocs-ui'], }); assert.equal( calls .filter((call) => call.command === 'uvx') .every((call) => call.args.includes('--manifest') && !call.args.includes('-d')), true, ); }); test('package scripts expose the full dependency upgrade command', async () => { const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')); assert.equal(packageJson.scripts['deps:upgrade'], 'node scripts/upgrade-dependencies.mjs'); });