Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions Packages/src/Cli~/src/__tests__/cli-update.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
type SpawnArgs = [string, string[], Record<string, unknown>?];

const mockSpawn = jest.fn<unknown, SpawnArgs>();

jest.mock('child_process', () => ({
spawn: (...args: SpawnArgs): unknown => mockSpawn(...args),
}));

jest.mock(
'launch-unity',
() => ({
orchestrateLaunch: jest.fn(),
}),
{ virtual: true },
);

import { getInstalledVersion, updateCli } from '../cli.js';

type CloseHandler = (code: number | null) => void;
type ErrorHandler = (error: Error) => void;
type DataHandler = (chunk: Buffer) => void;

interface MockChildProcess {
stdout: {
on: jest.Mock<void, [string, DataHandler]>;
};
on: jest.Mock<void, [string, CloseHandler | ErrorHandler]>;
emitStdout: (chunk: string) => void;
emitClose: (code: number | null) => void;
emitError: (error: Error) => void;
}

function createMockChildProcess(): MockChildProcess {
let closeHandler: CloseHandler | undefined;
let errorHandler: ErrorHandler | undefined;
let dataHandler: DataHandler | undefined;

return {
stdout: {
on: jest.fn((event: string, handler: DataHandler) => {
if (event === 'data') {
dataHandler = handler;
}
}),
},
on: jest.fn((event: string, handler: CloseHandler | ErrorHandler) => {
if (event === 'close') {
closeHandler = handler as CloseHandler;
}

if (event === 'error') {
errorHandler = handler as ErrorHandler;
}
}),
emitStdout: (chunk: string): void => {
dataHandler?.(Buffer.from(chunk));
},
emitClose: (code: number | null): void => {
closeHandler?.(code);
},
emitError: (error: Error): void => {
errorHandler?.(error);
},
};
}

describe('CLI update npm invocation', () => {
const expectedNpmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';

beforeEach(() => {
mockSpawn.mockReset();
jest.spyOn(console, 'log').mockImplementation(() => {});
});

afterEach(() => {
jest.restoreAllMocks();
});

it('gets installed version without enabling shell mode', () => {
const child = createMockChildProcess();
const callback = jest.fn();
mockSpawn.mockReturnValue(child);

getInstalledVersion(callback);

expect(mockSpawn).toHaveBeenCalledWith(expectedNpmCommand, [
'list',
'-g',
'uloop-cli',
'--json',
]);
expect(mockSpawn.mock.calls[0]).toHaveLength(2);

child.emitStdout(JSON.stringify({ dependencies: { 'uloop-cli': { version: '1.8.0' } } }));
child.emitClose(0);

expect(callback).toHaveBeenCalledWith('1.8.0');
});

it('updates the CLI without enabling shell mode', () => {
const updateChild = createMockChildProcess();
const listChild = createMockChildProcess();
mockSpawn.mockReturnValueOnce(updateChild).mockReturnValueOnce(listChild);

updateCli();

expect(mockSpawn).toHaveBeenNthCalledWith(
1,
expectedNpmCommand,
['install', '-g', 'uloop-cli@latest'],
{ stdio: 'inherit' },
);
const installOptions = mockSpawn.mock.calls[0]?.[2];
expect(installOptions?.['shell']).toBeUndefined();

updateChild.emitClose(0);

expect(mockSpawn).toHaveBeenNthCalledWith(2, expectedNpmCommand, [
'list',
'-g',
'uloop-cli',
'--json',
]);
expect(mockSpawn.mock.calls[1]).toHaveLength(2);

listChild.emitStdout(JSON.stringify({ dependencies: { 'uloop-cli': { version: '1.7.1' } } }));
listChild.emitClose(0);
});
});
13 changes: 6 additions & 7 deletions Packages/src/Cli~/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,11 +597,9 @@ compdef _uloop uloop`;
/**
* Get the currently installed version of uloop-cli from npm.
*/
function getInstalledVersion(callback: (version: string | null) => void): void {
export function getInstalledVersion(callback: (version: string | null) => void): void {
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const child = spawn(npmCommand, ['list', '-g', 'uloop-cli', '--json'], {
shell: true,
});
const child = spawn(npmCommand, ['list', '-g', 'uloop-cli', '--json']);

let stdout = '';
child.stdout.on('data', (data: Buffer) => {
Expand Down Expand Up @@ -656,14 +654,13 @@ function getInstalledVersion(callback: (version: string | null) => void): void {
/**
* Update uloop CLI to the latest version using npm.
*/
function updateCli(): void {
export function updateCli(): void {
const previousVersion = VERSION;
console.log('Updating uloop-cli to the latest version...');

const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const child = spawn(npmCommand, ['install', '-g', 'uloop-cli@latest'], {
stdio: 'inherit',
shell: true,
});

child.on('close', (code) => {
Expand Down Expand Up @@ -1032,4 +1029,6 @@ async function main(): Promise<void> {
program.parse();
}

void main();
if (process.env.JEST_WORKER_ID === undefined) {
void main();
}
2 changes: 1 addition & 1 deletion Packages/src/Cli~/src/project-root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function getUnitySettingsCandidatePaths(dirPath: string): string[] {
}

export function hasUloopInstalled(dirPath: string): boolean {
return getUnitySettingsCandidatePaths(dirPath).some(path => existsSync(path));
return getUnitySettingsCandidatePaths(dirPath).some((path) => existsSync(path));
}

function isUnityProjectWithUloop(dirPath: string): boolean {
Expand Down