Files
ccs/tests/unit/utils/package-manager-detector.test.ts
T

245 lines
7.7 KiB
TypeScript

import { afterEach, describe, expect, it } from 'bun:test';
import { mkdtempSync, mkdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import {
buildPackageManagerEnv,
detectCurrentInstall,
formatManualUpdateCommand,
readInstalledPackageVersion,
} from '../../../src/utils/package-manager-detector';
const tempDirs: string[] = [];
function makeTempDir(prefix: string): string {
const dir = mkdtempSync(join(tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
function writePackage(root: string, version: string): void {
mkdirSync(root, { recursive: true });
writeFileSync(
join(root, 'package.json'),
JSON.stringify({ name: '@kaitranntt/ccs', version }, null, 2)
);
}
afterEach(() => {
while (tempDirs.length > 0) {
const dir = tempDirs.pop();
if (dir) {
rmSync(dir, { recursive: true, force: true });
}
}
});
describe('package-manager-detector', () => {
it('detects npm installs from the current binary path and keeps the custom prefix', () => {
const tempRoot = makeTempDir('ccs-install-detector-npm-');
const packageRoot = join(tempRoot, 'prefix', 'lib', 'node_modules', '@kaitranntt', 'ccs');
const scriptPath = join(packageRoot, 'dist', 'ccs.js');
writePackage(packageRoot, '7.67.0-dev.5');
const install = detectCurrentInstall(scriptPath);
expect(install.manager).toBe('npm');
expect(install.prefix).toBe(join(tempRoot, 'prefix'));
expect(install.packageRoot).toBe(packageRoot);
expect(readInstalledPackageVersion(install)).toBe('7.67.0-dev.5');
});
it('detects bun installs from the resolved package path', () => {
const tempRoot = makeTempDir('ccs-install-detector-bun-');
const packageRoot = join(
tempRoot,
'.bun',
'install',
'global',
'node_modules',
'@kaitranntt',
'ccs'
);
const scriptPath = join(packageRoot, 'dist', 'ccs.js');
writePackage(packageRoot, '7.67.0-dev.9');
const install = detectCurrentInstall(scriptPath);
expect(install.manager).toBe('bun');
expect(install.prefix).toBe(join(tempRoot, '.bun'));
expect(readInstalledPackageVersion(install)).toBe('7.67.0-dev.9');
});
it.if(process.platform !== 'win32')(
'detects bun installs from a POSIX symlinked ~/.bun/bin/ccs entrypoint',
() => {
const tempRoot = makeTempDir('ccs-install-detector-bun-symlink-');
const packageRoot = join(
tempRoot,
'.bun',
'install',
'global',
'node_modules',
'@kaitranntt',
'ccs'
);
const scriptPath = join(packageRoot, 'dist', 'ccs.js');
const symlinkPath = join(tempRoot, 'home', '.bun', 'bin', 'ccs');
writePackage(packageRoot, '7.67.0-dev.9');
mkdirSync(join(packageRoot, 'dist'), { recursive: true });
writeFileSync(scriptPath, '#!/usr/bin/env node\n');
mkdirSync(join(tempRoot, 'home', '.bun', 'bin'), { recursive: true });
symlinkSync(scriptPath, symlinkPath);
const install = detectCurrentInstall(symlinkPath);
const resolvedTempRoot = realpathSync(tempRoot);
const resolvedScriptPath = realpathSync(scriptPath);
expect(install.manager).toBe('bun');
expect(install.prefix).toBe(join(resolvedTempRoot, '.bun'));
expect(install.resolvedScriptPath).toBe(resolvedScriptPath);
expect(readInstalledPackageVersion(install)).toBe('7.67.0-dev.9');
}
);
it('detects custom bun install roots that still use install/global/node_modules', () => {
const tempRoot = makeTempDir('ccs-install-detector-custom-bun-');
const packageRoot = join(
tempRoot,
'custom-bun-root',
'install',
'global',
'node_modules',
'@kaitranntt',
'ccs'
);
writePackage(packageRoot, '7.67.0-dev.9');
const install = detectCurrentInstall(join(packageRoot, 'dist', 'ccs.js'));
expect(install.manager).toBe('bun');
expect(install.prefix).toBe(join(tempRoot, 'custom-bun-root'));
});
it('detects custom yarn global layouts', () => {
const tempRoot = makeTempDir('ccs-install-detector-custom-yarn-');
const packageRoot = join(
tempRoot,
'custom-yarn-root',
'global',
'node_modules',
'@kaitranntt',
'ccs'
);
writePackage(packageRoot, '7.67.0-dev.9');
const install = detectCurrentInstall(join(packageRoot, 'dist', 'ccs.js'));
expect(install.manager).toBe('yarn');
expect(install.prefix).toBe(join(tempRoot, 'custom-yarn-root'));
});
it('detects custom pnpm global layouts that include a store version segment', () => {
const tempRoot = makeTempDir('ccs-install-detector-custom-pnpm-');
const packageRoot = join(
tempRoot,
'custom-pnpm-root',
'global',
'5',
'.pnpm',
'@kaitranntt+ccs@7.67.0-dev.9',
'node_modules',
'@kaitranntt',
'ccs'
);
writePackage(packageRoot, '7.67.0-dev.9');
const install = detectCurrentInstall(join(packageRoot, 'dist', 'ccs.js'));
expect(install.manager).toBe('pnpm');
expect(install.prefix).toBe(join(tempRoot, 'custom-pnpm-root'));
});
it('detects pnpm global layouts without a visible .pnpm segment in the script path', () => {
const tempRoot = makeTempDir('ccs-install-detector-pnpm-global-flat-');
const packageRoot = join(
tempRoot,
'custom-pnpm-root',
'global',
'5',
'node_modules',
'@kaitranntt',
'ccs'
);
writePackage(packageRoot, '7.67.0-dev.9');
const install = detectCurrentInstall(join(packageRoot, 'dist', 'ccs.js'));
expect(install.manager).toBe('pnpm');
expect(install.prefix).toBe(join(tempRoot, 'custom-pnpm-root'));
});
it('detects Windows npm globals without a lib directory', () => {
const install = detectCurrentInstall(
'C:/Program Files/node-prefix/node_modules/@kaitranntt/ccs/dist/ccs.js'
);
expect(install.manager).toBe('npm');
expect(install.prefix).toBe('C:/Program Files/node-prefix');
});
it('formats manual npm update commands with the current prefix', () => {
const install = {
manager: 'npm' as const,
scriptPath: '/tmp/prefix/bin/ccs',
resolvedScriptPath: '/tmp/prefix/lib/node_modules/@kaitranntt/ccs/dist/ccs.js',
packageRoot: '/tmp/prefix/lib/node_modules/@kaitranntt/ccs',
prefix: '/tmp/prefix',
detectionSource: 'path' as const,
};
expect(formatManualUpdateCommand('dev', install)).toBe(
'NPM_CONFIG_PREFIX=/tmp/prefix npm install -g @kaitranntt/ccs@dev'
);
});
it('formats Windows-safe manual npm update commands for prefixes with spaces', () => {
const install = {
manager: 'npm' as const,
scriptPath: 'C:/Tools/CCS/ccs.cmd',
resolvedScriptPath: 'C:/Program Files/CCS/lib/node_modules/@kaitranntt/ccs/dist/ccs.js',
packageRoot: 'C:/Program Files/CCS/lib/node_modules/@kaitranntt/ccs',
prefix: 'C:/Program Files/CCS',
detectionSource: 'path' as const,
};
expect(formatManualUpdateCommand('dev', install, 'win32')).toBe(
`powershell -NoProfile -Command "$env:NPM_CONFIG_PREFIX='C:/Program Files/CCS'; npm install -g @kaitranntt/ccs@dev"`
);
});
it('builds manager-specific env overrides for the current install', () => {
const install = {
manager: 'npm' as const,
scriptPath: '/tmp/prefix/bin/ccs',
resolvedScriptPath: '/tmp/prefix/lib/node_modules/@kaitranntt/ccs/dist/ccs.js',
packageRoot: '/tmp/prefix/lib/node_modules/@kaitranntt/ccs',
prefix: '/tmp/prefix',
detectionSource: 'path' as const,
};
const env = buildPackageManagerEnv(install, { PATH: '/usr/bin' });
expect(env.PATH).toBe('/usr/bin');
expect(env.npm_config_prefix).toBe('/tmp/prefix');
expect(env.NPM_CONFIG_PREFIX).toBe('/tmp/prefix');
});
});