mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 22:16:38 +00:00
fix(update): isolate updater test dependencies
This commit is contained in:
+119
-38
@@ -26,15 +26,81 @@ export interface UpdateOptions {
|
|||||||
beta?: boolean;
|
beta?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version (from centralized utility)
|
type TargetTag = 'latest' | 'dev';
|
||||||
const CCS_VERSION = getVersion();
|
|
||||||
|
type UpdateCheckResult =
|
||||||
|
| {
|
||||||
|
status: 'update_available';
|
||||||
|
current?: string;
|
||||||
|
latest?: string;
|
||||||
|
message?: string;
|
||||||
|
reason?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
status: 'check_failed';
|
||||||
|
message?: string;
|
||||||
|
latest?: string;
|
||||||
|
current?: string;
|
||||||
|
reason?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
status: 'no_update';
|
||||||
|
reason?: string;
|
||||||
|
latest?: string;
|
||||||
|
current?: string;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface UpdateCommandDeps {
|
||||||
|
initUI: typeof initUI;
|
||||||
|
getVersion: typeof getVersion;
|
||||||
|
detectCurrentInstall: typeof detectCurrentInstall;
|
||||||
|
buildPackageManagerEnv: typeof buildPackageManagerEnv;
|
||||||
|
formatManualUpdateCommand: typeof formatManualUpdateCommand;
|
||||||
|
readInstalledPackageState: typeof readInstalledPackageState;
|
||||||
|
compareVersionsWithPrerelease: typeof compareVersionsWithPrerelease;
|
||||||
|
checkForUpdates: (
|
||||||
|
currentVersion: string,
|
||||||
|
interactive: boolean,
|
||||||
|
channel: 'npm' | 'direct',
|
||||||
|
targetTag: TargetTag
|
||||||
|
) => Promise<UpdateCheckResult>;
|
||||||
|
spawn: typeof spawn;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCheckForUpdates(
|
||||||
|
currentVersion: string,
|
||||||
|
interactive: boolean,
|
||||||
|
channel: 'npm' | 'direct',
|
||||||
|
targetTag: TargetTag
|
||||||
|
): Promise<UpdateCheckResult> {
|
||||||
|
const { checkForUpdates } = await import('../utils/update-checker');
|
||||||
|
return checkForUpdates(
|
||||||
|
currentVersion,
|
||||||
|
interactive,
|
||||||
|
channel,
|
||||||
|
targetTag
|
||||||
|
) as Promise<UpdateCheckResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultDeps: UpdateCommandDeps = {
|
||||||
|
initUI,
|
||||||
|
getVersion,
|
||||||
|
detectCurrentInstall,
|
||||||
|
buildPackageManagerEnv,
|
||||||
|
formatManualUpdateCommand,
|
||||||
|
readInstalledPackageState,
|
||||||
|
compareVersionsWithPrerelease,
|
||||||
|
checkForUpdates: loadCheckForUpdates,
|
||||||
|
spawn,
|
||||||
|
};
|
||||||
|
|
||||||
async function resolveTargetVersion(
|
async function resolveTargetVersion(
|
||||||
currentVersion: string,
|
currentVersion: string,
|
||||||
targetTag: 'latest' | 'dev'
|
targetTag: TargetTag,
|
||||||
|
deps: UpdateCommandDeps
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
const { checkForUpdates } = await import('../utils/update-checker');
|
const result = await deps.checkForUpdates(currentVersion, true, 'npm', targetTag);
|
||||||
const result = await checkForUpdates(currentVersion, true, 'npm', targetTag);
|
|
||||||
|
|
||||||
if (result.status === 'update_available' && result.latest) {
|
if (result.status === 'update_available' && result.latest) {
|
||||||
return result.latest;
|
return result.latest;
|
||||||
@@ -47,15 +113,16 @@ async function resolveTargetVersion(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function handleUpdateCommand(
|
||||||
* Handle the update command
|
options: UpdateOptions = {},
|
||||||
* Checks for updates and installs the latest version
|
injectedDeps: Partial<UpdateCommandDeps> = {}
|
||||||
*/
|
): Promise<void> {
|
||||||
export async function handleUpdateCommand(options: UpdateOptions = {}): Promise<void> {
|
const deps = { ...defaultDeps, ...injectedDeps };
|
||||||
await initUI();
|
await deps.initUI();
|
||||||
const { force = false, beta = false } = options;
|
const { force = false, beta = false } = options;
|
||||||
const targetTag = beta ? 'dev' : 'latest';
|
const targetTag: TargetTag = beta ? 'dev' : 'latest';
|
||||||
const currentInstall = detectCurrentInstall();
|
const currentInstall = deps.detectCurrentInstall();
|
||||||
|
const currentVersion = deps.getVersion();
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(header('Checking for updates...'));
|
console.log(header('Checking for updates...'));
|
||||||
@@ -65,16 +132,20 @@ export async function handleUpdateCommand(options: UpdateOptions = {}): Promise<
|
|||||||
if (force) {
|
if (force) {
|
||||||
console.log(info(`Force reinstall from @${targetTag} channel...`));
|
console.log(info(`Force reinstall from @${targetTag} channel...`));
|
||||||
console.log('');
|
console.log('');
|
||||||
const expectedVersion = await resolveTargetVersion(CCS_VERSION, targetTag);
|
const expectedVersion = await resolveTargetVersion(currentVersion, targetTag, deps);
|
||||||
await performNpmUpdate(currentInstall, targetTag, true, expectedVersion);
|
await performNpmUpdate(currentInstall, targetTag, true, expectedVersion, deps);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { checkForUpdates } = await import('../utils/update-checker');
|
const updateResult = await deps.checkForUpdates(currentVersion, true, 'npm', targetTag);
|
||||||
const updateResult = await checkForUpdates(CCS_VERSION, true, 'npm', targetTag);
|
|
||||||
|
|
||||||
if (updateResult.status === 'check_failed') {
|
if (updateResult.status === 'check_failed') {
|
||||||
handleCheckFailed(updateResult.message ?? 'Update check failed', targetTag, currentInstall);
|
handleCheckFailed(
|
||||||
|
updateResult.message ?? 'Update check failed',
|
||||||
|
targetTag,
|
||||||
|
currentInstall,
|
||||||
|
deps
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +162,7 @@ export async function handleUpdateCommand(options: UpdateOptions = {}): Promise<
|
|||||||
const isDowngrade =
|
const isDowngrade =
|
||||||
updateResult.latest &&
|
updateResult.latest &&
|
||||||
updateResult.current &&
|
updateResult.current &&
|
||||||
compareVersionsWithPrerelease(updateResult.latest, updateResult.current) < 0;
|
deps.compareVersionsWithPrerelease(updateResult.latest, updateResult.current) < 0;
|
||||||
|
|
||||||
// This happens when stable user requests @dev but @dev base is older
|
// This happens when stable user requests @dev but @dev base is older
|
||||||
if (isDowngrade && beta) {
|
if (isDowngrade && beta) {
|
||||||
@@ -115,7 +186,7 @@ export async function handleUpdateCommand(options: UpdateOptions = {}): Promise<
|
|||||||
console.log('');
|
console.log('');
|
||||||
}
|
}
|
||||||
|
|
||||||
await performNpmUpdate(currentInstall, targetTag, false, updateResult.latest);
|
await performNpmUpdate(currentInstall, targetTag, false, updateResult.latest, deps);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,7 +195,8 @@ export async function handleUpdateCommand(options: UpdateOptions = {}): Promise<
|
|||||||
function handleCheckFailed(
|
function handleCheckFailed(
|
||||||
message: string,
|
message: string,
|
||||||
targetTag: string = 'latest',
|
targetTag: string = 'latest',
|
||||||
currentInstall: CurrentInstall = detectCurrentInstall()
|
currentInstall: CurrentInstall = defaultDeps.detectCurrentInstall(),
|
||||||
|
deps: UpdateCommandDeps = defaultDeps
|
||||||
): void {
|
): void {
|
||||||
console.log(fail(message));
|
console.log(fail(message));
|
||||||
console.log('');
|
console.log('');
|
||||||
@@ -135,7 +207,7 @@ function handleCheckFailed(
|
|||||||
console.log('');
|
console.log('');
|
||||||
console.log('Try again later or update manually:');
|
console.log('Try again later or update manually:');
|
||||||
|
|
||||||
console.log(color(` ${formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
|
console.log(color(` ${deps.formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
|
||||||
console.log('');
|
console.log('');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -172,9 +244,10 @@ function verifyCurrentInstallVersion(
|
|||||||
targetTag: string,
|
targetTag: string,
|
||||||
expectedVersion?: string,
|
expectedVersion?: string,
|
||||||
previousState?: InstalledPackageState,
|
previousState?: InstalledPackageState,
|
||||||
isReinstall: boolean = false
|
isReinstall: boolean = false,
|
||||||
|
deps: UpdateCommandDeps = defaultDeps
|
||||||
): void {
|
): void {
|
||||||
const nextState = readInstalledPackageState(currentInstall);
|
const nextState = deps.readInstalledPackageState(currentInstall);
|
||||||
const installedVersion = nextState.version;
|
const installedVersion = nextState.version;
|
||||||
if (!installedVersion) {
|
if (!installedVersion) {
|
||||||
console.log('');
|
console.log('');
|
||||||
@@ -183,7 +256,7 @@ function verifyCurrentInstallVersion(
|
|||||||
);
|
);
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Current install remains ambiguous. Re-run manually:');
|
console.log('Current install remains ambiguous. Re-run manually:');
|
||||||
console.log(color(` ${formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
|
console.log(color(` ${deps.formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
|
||||||
console.log('');
|
console.log('');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -199,7 +272,7 @@ function verifyCurrentInstallVersion(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const comparison = compareVersionsWithPrerelease(installedVersion, expectedVersion);
|
const comparison = deps.compareVersionsWithPrerelease(installedVersion, expectedVersion);
|
||||||
if (comparison < 0) {
|
if (comparison < 0) {
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(
|
console.log(
|
||||||
@@ -216,7 +289,9 @@ function verifyCurrentInstallVersion(
|
|||||||
}
|
}
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Re-run manually against the current install:');
|
console.log('Re-run manually against the current install:');
|
||||||
console.log(color(` ${formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
|
console.log(
|
||||||
|
color(` ${deps.formatManualUpdateCommand(targetTag, currentInstall)}`, 'command')
|
||||||
|
);
|
||||||
console.log('');
|
console.log('');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -236,7 +311,7 @@ function verifyCurrentInstallVersion(
|
|||||||
);
|
);
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Re-run manually against the current install:');
|
console.log('Re-run manually against the current install:');
|
||||||
console.log(color(` ${formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
|
console.log(color(` ${deps.formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
|
||||||
console.log('');
|
console.log('');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -246,15 +321,16 @@ async function performNpmUpdate(
|
|||||||
currentInstall: CurrentInstall,
|
currentInstall: CurrentInstall,
|
||||||
targetTag: string = 'latest',
|
targetTag: string = 'latest',
|
||||||
isReinstall: boolean = false,
|
isReinstall: boolean = false,
|
||||||
expectedVersion?: string
|
expectedVersion?: string,
|
||||||
|
deps: UpdateCommandDeps = defaultDeps
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const packageManager = currentInstall.manager;
|
const packageManager = currentInstall.manager;
|
||||||
let updateCommand: string;
|
let updateCommand: string;
|
||||||
let updateArgs: string[];
|
let updateArgs: string[];
|
||||||
let cacheCommand: string | null;
|
let cacheCommand: string | null;
|
||||||
let cacheArgs: string[] | null;
|
let cacheArgs: string[] | null;
|
||||||
const childEnv = buildPackageManagerEnv(currentInstall);
|
const childEnv = deps.buildPackageManagerEnv(currentInstall);
|
||||||
const previousState = readInstalledPackageState(currentInstall);
|
const previousState = deps.readInstalledPackageState(currentInstall);
|
||||||
|
|
||||||
switch (packageManager) {
|
switch (packageManager) {
|
||||||
case 'npm':
|
case 'npm':
|
||||||
@@ -300,12 +376,12 @@ async function performNpmUpdate(
|
|||||||
// Also suppress Node deprecation warnings that may come from package managers
|
// Also suppress Node deprecation warnings that may come from package managers
|
||||||
// Pipe stderr on Windows to filter npm cleanup warnings (EPERM on native modules)
|
// Pipe stderr on Windows to filter npm cleanup warnings (EPERM on native modules)
|
||||||
const child = isWindows
|
const child = isWindows
|
||||||
? spawn(`${updateCommand} ${updateArgs.join(' ')}`, [], {
|
? deps.spawn(`${updateCommand} ${updateArgs.join(' ')}`, [], {
|
||||||
stdio: ['inherit', 'inherit', 'pipe'],
|
stdio: ['inherit', 'inherit', 'pipe'],
|
||||||
shell: true,
|
shell: true,
|
||||||
env: { ...childEnv, NODE_NO_WARNINGS: '1' },
|
env: { ...childEnv, NODE_NO_WARNINGS: '1' },
|
||||||
})
|
})
|
||||||
: spawn(updateCommand, updateArgs, { stdio: 'inherit', env: childEnv });
|
: deps.spawn(updateCommand, updateArgs, { stdio: 'inherit', env: childEnv });
|
||||||
|
|
||||||
// On Windows, filter stderr to hide npm cleanup warnings (EPERM on bcrypt.node etc.)
|
// On Windows, filter stderr to hide npm cleanup warnings (EPERM on bcrypt.node etc.)
|
||||||
// These warnings are cosmetic - update succeeds despite file locking by antivirus/indexing
|
// These warnings are cosmetic - update succeeds despite file locking by antivirus/indexing
|
||||||
@@ -339,7 +415,8 @@ async function performNpmUpdate(
|
|||||||
targetTag,
|
targetTag,
|
||||||
expectedVersion,
|
expectedVersion,
|
||||||
previousState,
|
previousState,
|
||||||
isReinstall
|
isReinstall,
|
||||||
|
deps
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.log('');
|
console.log('');
|
||||||
@@ -353,7 +430,9 @@ async function performNpmUpdate(
|
|||||||
console.log(fail(`${isReinstall ? 'Reinstall' : 'Update'} failed`));
|
console.log(fail(`${isReinstall ? 'Reinstall' : 'Update'} failed`));
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Try manually:');
|
console.log('Try manually:');
|
||||||
console.log(color(` ${formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
|
console.log(
|
||||||
|
color(` ${deps.formatManualUpdateCommand(targetTag, currentInstall)}`, 'command')
|
||||||
|
);
|
||||||
console.log('');
|
console.log('');
|
||||||
}
|
}
|
||||||
process.exit(code || 0);
|
process.exit(code || 0);
|
||||||
@@ -364,7 +443,9 @@ async function performNpmUpdate(
|
|||||||
console.log(fail(`Failed to run ${packageManager} ${isReinstall ? 'reinstall' : 'update'}`));
|
console.log(fail(`Failed to run ${packageManager} ${isReinstall ? 'reinstall' : 'update'}`));
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Try manually:');
|
console.log('Try manually:');
|
||||||
console.log(color(` ${formatManualUpdateCommand(targetTag, currentInstall)}`, 'command'));
|
console.log(
|
||||||
|
color(` ${deps.formatManualUpdateCommand(targetTag, currentInstall)}`, 'command')
|
||||||
|
);
|
||||||
console.log('');
|
console.log('');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
@@ -383,12 +464,12 @@ async function performNpmUpdate(
|
|||||||
console.log(info(stepMessage));
|
console.log(info(stepMessage));
|
||||||
// On Windows, use shell with full command string to avoid deprecation warning
|
// On Windows, use shell with full command string to avoid deprecation warning
|
||||||
const cacheChild = isWindows
|
const cacheChild = isWindows
|
||||||
? spawn(`${cacheCommand} ${cacheArgs.join(' ')}`, [], {
|
? deps.spawn(`${cacheCommand} ${cacheArgs.join(' ')}`, [], {
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
shell: true,
|
shell: true,
|
||||||
env: { ...childEnv, NODE_NO_WARNINGS: '1' },
|
env: { ...childEnv, NODE_NO_WARNINGS: '1' },
|
||||||
})
|
})
|
||||||
: spawn(cacheCommand, cacheArgs, { stdio: 'inherit', env: childEnv });
|
: deps.spawn(cacheCommand, cacheArgs, { stdio: 'inherit', env: childEnv });
|
||||||
|
|
||||||
cacheChild.on('exit', (code) => {
|
cacheChild.on('exit', (code) => {
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
|
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
||||||
|
import { handleUpdateCommand, type UpdateCommandDeps } from '../../../src/commands/update-command';
|
||||||
|
|
||||||
let logLines: string[] = [];
|
let logLines: string[] = [];
|
||||||
let spawnCalls: Array<{ command: string; args: string[]; env?: NodeJS.ProcessEnv }> = [];
|
let spawnCalls: Array<{ command: string; args: string[]; env?: NodeJS.ProcessEnv }> = [];
|
||||||
let exitCodes: number[] = [];
|
|
||||||
let stateReads = 0;
|
|
||||||
let originalConsoleLog: typeof console.log;
|
let originalConsoleLog: typeof console.log;
|
||||||
let originalProcessExit: typeof process.exit;
|
let originalProcessExit: typeof process.exit;
|
||||||
let updateCheckResult:
|
|
||||||
|
type InstalledState = {
|
||||||
|
version: string | null;
|
||||||
|
packageJsonMtimeMs: number | null;
|
||||||
|
scriptMtimeMs: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Scenario = {
|
||||||
|
beforeState: InstalledState;
|
||||||
|
afterState: InstalledState;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UpdateCheckResult =
|
||||||
| { status: 'update_available'; current: string; latest: string }
|
| { status: 'update_available'; current: string; latest: string }
|
||||||
| { status: 'no_update' }
|
| { status: 'no_update' }
|
||||||
| { status: 'check_failed'; message: string };
|
| { status: 'check_failed'; message: string };
|
||||||
let currentInstallOverride = installDescriptor();
|
|
||||||
|
|
||||||
type Scenario = {
|
|
||||||
beforeState: {
|
|
||||||
version: string | null;
|
|
||||||
packageJsonMtimeMs: number | null;
|
|
||||||
scriptMtimeMs: number | null;
|
|
||||||
};
|
|
||||||
afterState: {
|
|
||||||
version: string | null;
|
|
||||||
packageJsonMtimeMs: number | null;
|
|
||||||
scriptMtimeMs: number | null;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let scenario: Scenario;
|
let scenario: Scenario;
|
||||||
|
let updateCheckResult: UpdateCheckResult;
|
||||||
|
let currentInstallOverride: ReturnType<typeof installDescriptor>;
|
||||||
|
let stateReads = 0;
|
||||||
|
|
||||||
function installDescriptor() {
|
function installDescriptor() {
|
||||||
return {
|
return {
|
||||||
@@ -38,67 +38,10 @@ function installDescriptor() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
function createDeps(): UpdateCommandDeps {
|
||||||
logLines = [];
|
return {
|
||||||
spawnCalls = [];
|
|
||||||
exitCodes = [];
|
|
||||||
stateReads = 0;
|
|
||||||
scenario = {
|
|
||||||
beforeState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
|
||||||
afterState: { version: '7.67.0-dev.9', packageJsonMtimeMs: 200, scriptMtimeMs: 200 },
|
|
||||||
};
|
|
||||||
updateCheckResult = {
|
|
||||||
status: 'update_available',
|
|
||||||
current: '7.67.0-dev.5',
|
|
||||||
latest: '7.67.0-dev.9',
|
|
||||||
};
|
|
||||||
currentInstallOverride = installDescriptor();
|
|
||||||
|
|
||||||
originalConsoleLog = console.log;
|
|
||||||
originalProcessExit = process.exit;
|
|
||||||
|
|
||||||
console.log = (...args: unknown[]) => {
|
|
||||||
logLines.push(args.map(String).join(' '));
|
|
||||||
};
|
|
||||||
|
|
||||||
process.exit = ((code?: number) => {
|
|
||||||
exitCodes.push(code ?? 0);
|
|
||||||
throw new Error(`process.exit(${code ?? 0})`);
|
|
||||||
}) as typeof process.exit;
|
|
||||||
|
|
||||||
mock.module('child_process', () => ({
|
|
||||||
spawn: (command: string, args: string[], options?: { env?: NodeJS.ProcessEnv }) => {
|
|
||||||
spawnCalls.push({ command, args, env: options?.env });
|
|
||||||
return {
|
|
||||||
on: (event: string, callback: (code?: number) => void) => {
|
|
||||||
if (event === 'exit') {
|
|
||||||
callback(0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
mock.module('../../../src/utils/ui', () => ({
|
|
||||||
initUI: async () => {},
|
initUI: async () => {},
|
||||||
header: (value: string) => value,
|
|
||||||
ok: (value: string) => `[OK] ${value}`,
|
|
||||||
fail: (value: string) => `[X] ${value}`,
|
|
||||||
warn: (value: string) => `[!] ${value}`,
|
|
||||||
info: (value: string) => `[i] ${value}`,
|
|
||||||
color: (value: string) => value,
|
|
||||||
}));
|
|
||||||
|
|
||||||
mock.module('../../../src/utils/version', () => ({
|
|
||||||
getVersion: () => '7.67.0-dev.5',
|
getVersion: () => '7.67.0-dev.5',
|
||||||
}));
|
|
||||||
|
|
||||||
mock.module('../../../src/utils/update-checker', () => ({
|
|
||||||
compareVersionsWithPrerelease: (left: string, right: string) => left.localeCompare(right),
|
|
||||||
checkForUpdates: async () => updateCheckResult,
|
|
||||||
}));
|
|
||||||
|
|
||||||
mock.module('../../../src/utils/package-manager-detector', () => ({
|
|
||||||
detectCurrentInstall: () => currentInstallOverride,
|
detectCurrentInstall: () => currentInstallOverride,
|
||||||
buildPackageManagerEnv: () => {
|
buildPackageManagerEnv: () => {
|
||||||
if (currentInstallOverride.manager === 'npm') {
|
if (currentInstallOverride.manager === 'npm') {
|
||||||
@@ -138,27 +81,59 @@ beforeEach(() => {
|
|||||||
stateReads += 1;
|
stateReads += 1;
|
||||||
return stateReads === 1 ? scenario.beforeState : scenario.afterState;
|
return stateReads === 1 ? scenario.beforeState : scenario.afterState;
|
||||||
},
|
},
|
||||||
}));
|
compareVersionsWithPrerelease: (left: string, right: string) => left.localeCompare(right),
|
||||||
|
checkForUpdates: async () => updateCheckResult,
|
||||||
|
spawn: ((command: string, args: string[], options?: { env?: NodeJS.ProcessEnv }) => {
|
||||||
|
spawnCalls.push({ command, args, env: options?.env });
|
||||||
|
return {
|
||||||
|
stderr: undefined,
|
||||||
|
on: (event: string, callback: (code?: number) => void) => {
|
||||||
|
if (event === 'exit') {
|
||||||
|
callback(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}) as typeof UpdateCommandDeps.prototype.spawn,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
logLines = [];
|
||||||
|
spawnCalls = [];
|
||||||
|
stateReads = 0;
|
||||||
|
scenario = {
|
||||||
|
beforeState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
||||||
|
afterState: { version: '7.67.0-dev.9', packageJsonMtimeMs: 200, scriptMtimeMs: 200 },
|
||||||
|
};
|
||||||
|
updateCheckResult = {
|
||||||
|
status: 'update_available',
|
||||||
|
current: '7.67.0-dev.5',
|
||||||
|
latest: '7.67.0-dev.9',
|
||||||
|
};
|
||||||
|
currentInstallOverride = installDescriptor();
|
||||||
|
|
||||||
|
originalConsoleLog = console.log;
|
||||||
|
originalProcessExit = process.exit;
|
||||||
|
|
||||||
|
console.log = (...args: unknown[]) => {
|
||||||
|
logLines.push(args.map(String).join(' '));
|
||||||
|
};
|
||||||
|
|
||||||
|
process.exit = ((code?: number) => {
|
||||||
|
throw new Error(`process.exit(${code ?? 0})`);
|
||||||
|
}) as typeof process.exit;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
process.exit = originalProcessExit;
|
process.exit = originalProcessExit;
|
||||||
mock.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadHandleUpdateCommand() {
|
|
||||||
const mod = await import(
|
|
||||||
`../../../src/commands/update-command?test=${Date.now()}-${Math.random()}`
|
|
||||||
);
|
|
||||||
return mod.handleUpdateCommand;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('update-command current install handling', () => {
|
describe('update-command current install handling', () => {
|
||||||
it('updates through the current install manager and prefix', async () => {
|
it('updates through the current install manager and prefix', async () => {
|
||||||
const handleUpdateCommand = await loadHandleUpdateCommand();
|
await expect(handleUpdateCommand({ beta: true }, createDeps())).rejects.toThrow(
|
||||||
|
'process.exit(0)'
|
||||||
await expect(handleUpdateCommand({ beta: true })).rejects.toThrow('process.exit(0)');
|
);
|
||||||
|
|
||||||
const installCall = spawnCalls.find((call) => call.args.includes('install'));
|
const installCall = spawnCalls.find((call) => call.args.includes('install'));
|
||||||
|
|
||||||
@@ -172,9 +147,10 @@ describe('update-command current install handling', () => {
|
|||||||
beforeState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
beforeState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
||||||
afterState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
afterState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
||||||
};
|
};
|
||||||
const handleUpdateCommand = await loadHandleUpdateCommand();
|
|
||||||
|
|
||||||
await expect(handleUpdateCommand({ beta: true })).rejects.toThrow('process.exit(1)');
|
await expect(handleUpdateCommand({ beta: true }, createDeps())).rejects.toThrow(
|
||||||
|
'process.exit(1)'
|
||||||
|
);
|
||||||
|
|
||||||
expect(logLines.join('\n')).toContain('outside the current installation');
|
expect(logLines.join('\n')).toContain('outside the current installation');
|
||||||
expect(logLines.join('\n')).toContain(
|
expect(logLines.join('\n')).toContain(
|
||||||
@@ -187,9 +163,8 @@ describe('update-command current install handling', () => {
|
|||||||
beforeState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
beforeState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
||||||
afterState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
afterState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
||||||
};
|
};
|
||||||
const handleUpdateCommand = await loadHandleUpdateCommand();
|
|
||||||
|
|
||||||
await expect(handleUpdateCommand({ force: true, beta: true })).rejects.toThrow(
|
await expect(handleUpdateCommand({ force: true, beta: true }, createDeps())).rejects.toThrow(
|
||||||
'process.exit(1)'
|
'process.exit(1)'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -202,9 +177,8 @@ describe('update-command current install handling', () => {
|
|||||||
afterState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
afterState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
||||||
};
|
};
|
||||||
updateCheckResult = { status: 'no_update' };
|
updateCheckResult = { status: 'no_update' };
|
||||||
const handleUpdateCommand = await loadHandleUpdateCommand();
|
|
||||||
|
|
||||||
await expect(handleUpdateCommand({ force: true, beta: true })).rejects.toThrow(
|
await expect(handleUpdateCommand({ force: true, beta: true }, createDeps())).rejects.toThrow(
|
||||||
'process.exit(1)'
|
'process.exit(1)'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -217,9 +191,8 @@ describe('update-command current install handling', () => {
|
|||||||
afterState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
afterState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
||||||
};
|
};
|
||||||
updateCheckResult = { status: 'check_failed', message: 'network' };
|
updateCheckResult = { status: 'check_failed', message: 'network' };
|
||||||
const handleUpdateCommand = await loadHandleUpdateCommand();
|
|
||||||
|
|
||||||
await expect(handleUpdateCommand({ force: true, beta: true })).rejects.toThrow(
|
await expect(handleUpdateCommand({ force: true, beta: true }, createDeps())).rejects.toThrow(
|
||||||
'process.exit(1)'
|
'process.exit(1)'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -231,9 +204,10 @@ describe('update-command current install handling', () => {
|
|||||||
beforeState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
beforeState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 100, scriptMtimeMs: 100 },
|
||||||
afterState: { version: '7.67.1-dev.0', packageJsonMtimeMs: 200, scriptMtimeMs: 200 },
|
afterState: { version: '7.67.1-dev.0', packageJsonMtimeMs: 200, scriptMtimeMs: 200 },
|
||||||
};
|
};
|
||||||
const handleUpdateCommand = await loadHandleUpdateCommand();
|
|
||||||
|
|
||||||
await expect(handleUpdateCommand({ beta: true })).rejects.toThrow('process.exit(0)');
|
await expect(handleUpdateCommand({ beta: true }, createDeps())).rejects.toThrow(
|
||||||
|
'process.exit(0)'
|
||||||
|
);
|
||||||
|
|
||||||
expect(logLines.join('\n')).not.toContain('outside the current installation');
|
expect(logLines.join('\n')).not.toContain('outside the current installation');
|
||||||
});
|
});
|
||||||
@@ -244,9 +218,8 @@ describe('update-command current install handling', () => {
|
|||||||
afterState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 200, scriptMtimeMs: 200 },
|
afterState: { version: '7.67.0-dev.5', packageJsonMtimeMs: 200, scriptMtimeMs: 200 },
|
||||||
};
|
};
|
||||||
updateCheckResult = { status: 'no_update' };
|
updateCheckResult = { status: 'no_update' };
|
||||||
const handleUpdateCommand = await loadHandleUpdateCommand();
|
|
||||||
|
|
||||||
await expect(handleUpdateCommand({ force: true, beta: true })).rejects.toThrow(
|
await expect(handleUpdateCommand({ force: true, beta: true }, createDeps())).rejects.toThrow(
|
||||||
'process.exit(0)'
|
'process.exit(0)'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -268,14 +241,15 @@ describe('update-command current install handling', () => {
|
|||||||
prefix: envValue,
|
prefix: envValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateCommand = await loadHandleUpdateCommand();
|
await expect(handleUpdateCommand({ beta: true }, createDeps())).rejects.toThrow(
|
||||||
|
'process.exit(0)'
|
||||||
await expect(handleUpdateCommand({ beta: true })).rejects.toThrow('process.exit(0)');
|
);
|
||||||
|
|
||||||
const updateCall = spawnCalls.find(
|
const updateCall = spawnCalls.find(
|
||||||
(call) =>
|
(call) =>
|
||||||
call.command === manager && call.args.some((arg) => arg.includes('@kaitranntt/ccs@dev'))
|
call.command === manager && call.args.some((arg) => arg.includes('@kaitranntt/ccs@dev'))
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(updateCall?.args).toContain(expectedArg);
|
expect(updateCall?.args).toContain(expectedArg);
|
||||||
expect(updateCall?.env?.[envKey]).toBe(envValue);
|
expect(updateCall?.env?.[envKey]).toBe(envValue);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user