Skip to content
Open
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
152 changes: 152 additions & 0 deletions utils/src/dep-manager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { execSync } from 'node:child_process';
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import assert from 'node:assert/strict';
import { describe, it, beforeEach, afterEach } from 'node:test';
import { spawnPromisified } from './spawn-promisified.ts';
import { detectPackageManager, removeDependency } from './dep-manager.ts';

const PACKAGE_NAME = '@augustinmauroy/vec3';

const haveNpmOnMachine = (): boolean => {
try {
execSync('npm --version', { stdio: 'ignore' });
return true;
} catch {
return false;
}
};

const haveYarnOnMachine = (): boolean => {
try {
execSync('yarn --version', { stdio: 'ignore' });
return true;
} catch {
return false;
}
};

const havePnpmOnMachine = (): boolean => {
try {
execSync('pnpm --version', { stdio: 'ignore' });
return true;
} catch {
return false;
}
};

describe('dep-manager utilities', () => {
let tempDir: string;

beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'dep-manager-test-'));
process.chdir(tempDir);
});

afterEach(async () => {
await rm(tempDir, { recursive: true, force: true });
});

it('should work with npm', { skip: !haveNpmOnMachine() }, async () => {
await spawnPromisified('npm', ['init', '-y'], { cwd: tempDir });

await spawnPromisified('npm', ['install', PACKAGE_NAME], { cwd: tempDir });

const detectedManager = detectPackageManager();
assert.strictEqual(detectedManager, 'npm');

const packageJsonPath = join(tempDir, 'package.json');
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));

assert(
packageJson.dependencies[PACKAGE_NAME],
`${PACKAGE_NAME} should be in dependencies`,
);

await removeDependency(PACKAGE_NAME, 'ignore');

const updatedPackageJson = JSON.parse(
await readFile(packageJsonPath, 'utf-8'),
);
assert(
!updatedPackageJson.dependencies ||
!updatedPackageJson.dependencies[PACKAGE_NAME],
`${PACKAGE_NAME} should not be in dependencies`,
);
});

it('should work with yarn', { skip: !haveYarnOnMachine() }, async () => {
await spawnPromisified('yarn', ['init', '-y'], { cwd: tempDir });

await spawnPromisified('yarn', ['add', PACKAGE_NAME], { cwd: tempDir });

// list all file in the tempDir for debugging
const files = await import('node:fs').then((fs) =>
fs.promises.readdir(tempDir),
);
console.log('Files in tempDir:', files);

const detectedManager = detectPackageManager();
assert.strictEqual(detectedManager, 'yarn');

const packageJsonPath = join(tempDir, 'package.json');
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));

assert(
packageJson.dependencies[PACKAGE_NAME],
`${PACKAGE_NAME} should be in dependencies`,
);

await removeDependency(PACKAGE_NAME, 'ignore');

const updatedPackageJson = JSON.parse(
await readFile(packageJsonPath, 'utf-8'),
);
assert(
!updatedPackageJson.dependencies ||
!updatedPackageJson.dependencies[PACKAGE_NAME],
`${PACKAGE_NAME} should not be in dependencies`,
);
});

it('should work with pnpm', { skip: !havePnpmOnMachine() }, async () => {
await spawnPromisified('pnpm', ['init'], { cwd: tempDir });

// Run pnpm install to generate the pnpm-lock.yaml file
await spawnPromisified('pnpm', ['install'], { cwd: tempDir });

await spawnPromisified('pnpm', ['add', PACKAGE_NAME], { cwd: tempDir });

const detectedManager = detectPackageManager();
assert.strictEqual(detectedManager, 'pnpm');

const packageJsonPath = join(tempDir, 'package.json');
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));

assert(
packageJson.dependencies[PACKAGE_NAME],
`${PACKAGE_NAME} should be in dependencies`,
);

await removeDependency(PACKAGE_NAME, 'ignore');

const updatedPackageJson = JSON.parse(
await readFile(packageJsonPath, 'utf-8'),
);
assert(
!updatedPackageJson.dependencies ||
!updatedPackageJson.dependencies[PACKAGE_NAME],
`${PACKAGE_NAME} should not be in dependencies`,
);
});

it('should default to npm if no lock file is present', async () => {
await spawnPromisified('npm', ['init', '-y'], { cwd: tempDir });

await rm(join(tempDir, 'package-lock.json'), { force: true });

const detectedManager = detectPackageManager();
assert.strictEqual(detectedManager, 'npm');
});
});
44 changes: 44 additions & 0 deletions utils/src/dep-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @fileoverview these utilties need codemod capabilities https://docs.codemod.com/jssg/security
*/
import { accessSync } from 'node:fs';
import { execSync } from 'node:child_process';
import type { StdioOptions } from 'node:child_process';

export const detectPackageManager = (): 'npm' | 'yarn' | 'pnpm' => {
try {
console.log('Checking for yarn.lock file');
accessSync('yarn.lock');
console.log('Detected yarn.lock file');
return 'yarn';
} catch {}

try {
accessSync('pnpm-lock.yaml');
return 'pnpm';
} catch {}

return 'npm';
};

export const removeDependency = (
dependency: string,
stdio: StdioOptions = 'inherit',
): void => {
const packageManager = detectPackageManager();
let command = '';

switch (packageManager) {
case 'npm':
command = `npm uninstall ${dependency}`;
break;
case 'yarn':
command = `yarn remove ${dependency}`;
break;
case 'pnpm':
command = `pnpm remove ${dependency}`;
break;
}

execSync(command, { stdio });
};
Loading