Skip to content
Closed
Show file tree
Hide file tree
Changes from 11 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
13 changes: 9 additions & 4 deletions src/commands/migrate.meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ export const meta = {
name: 'migrate',
description: 'Migrate from a package to a more performant alternative.',
args: {
'dry-run': {
all: {
type: 'boolean',
default: false,
description: `Don't apply any fixes, only show what would change.`
description: 'Run all available migrations'
},
interactive: {
'dry-run': {
type: 'boolean',
default: false,
description: 'Run in interactive mode.'
description: `Don't apply any fixes, only show what would change.`
},
include: {
type: 'string',
default: '**/*.{ts,js}',
description: 'Files to migrate'
},
interactive: {
type: 'boolean',
default: false,
description: 'Run in interactive mode.'
}
}
} as const;
10 changes: 9 additions & 1 deletion src/commands/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export async function run(ctx: CommandContext<typeof meta.args>) {
const dryRun = ctx.values['dry-run'] === true;
const interactive = ctx.values.interactive === true;
const include = ctx.values.include;
const all = ctx.values.all === true;
const fileSystem = new LocalFileSystem(process.cwd());
const packageJson = await getPackageJson(fileSystem);

Expand All @@ -37,6 +38,13 @@ export async function run(ctx: CommandContext<typeof meta.args>) {
.map((rep) => rep.from)
);

// If --all flag is used, add all available migrations
if (all) {
for (const target of fixableReplacementsTargets) {
targetModules.push(target);
}
}

if (interactive) {
const additionalTargets = await prompts.autocompleteMultiselect({
message: 'Select packages to migrate',
Expand All @@ -62,7 +70,7 @@ export async function run(ctx: CommandContext<typeof meta.args>) {

if (targetModules.length === 0) {
prompts.cancel(
'Error: Please specify a package to migrate. For example, `migrate chalk`'
'Error: Please specify a package to migrate. For example, `migrate chalk` or use `--all` to migrate all available packages'
);
return;
}
Expand Down
141 changes: 141 additions & 0 deletions src/test/__snapshots__/migrate.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`migrate command > should actually modify files when not using --dry-run 1`] = `
"e18e (cli <version>)

┌ Migrating packages...
│
│ Targets: chalk
│
◇ /private{cwd}/lib/main.js...
│
│ loading /private{cwd}/lib/main.js
│ loading /private{cwd}/lib/main.js
│ migrating chalk to picocolors
│ loading /private{cwd}/lib/main.js
│ migrating chalk to picocolors
│ writing /private{cwd}/lib/main.js
│
◆ /private{cwd}/lib/main.js (1 migrated)
│
└ Migration complete.

"
`;

exports[`migrate command > should actually modify files when not using --dry-run 2`] = `
"import * as pc from 'picocolors';

console.log(pc.cyan('I am _so_ cyan'));
"
`;

exports[`migrate command > should handle --all flag correctly 1`] = `
"e18e (cli <version>)

┌ Migrating packages...
│
│ Targets: chalk
│
◇ /private{cwd}/lib/main.js...
│
│ loading /private{cwd}/lib/main.js
│ loading /private{cwd}/lib/main.js
│ migrating chalk to picocolors
│ loading /private{cwd}/lib/main.js
│ migrating chalk to picocolors
│ writing /private{cwd}/lib/main.js
│
◆ /private{cwd}/lib/main.js (1 migrated)
│
└ Migration complete.

"
`;

exports[`migrate command > should handle --all flag correctly 2`] = `
"import chalk from 'chalk';

console.log(chalk.cyan('I am _so_ cyan'));
"
`;

exports[`migrate command > should handle custom include pattern 1`] = `
"e18e (cli <version>)

┌ Migrating packages...
│
│ Targets: chalk
│
◇ /private{cwd}/lib/main.js...
│
│ loading /private{cwd}/lib/main.js
│ loading /private{cwd}/lib/main.js
│ migrating chalk to picocolors
│ loading /private{cwd}/lib/main.js
│ migrating chalk to picocolors
│ writing /private{cwd}/lib/main.js
│
◆ /private{cwd}/lib/main.js (1 migrated)
│
└ Migration complete.

"
`;

exports[`migrate command > should handle custom include pattern 2`] = `
"import chalk from 'chalk';

console.log(chalk.cyan('I am _so_ cyan'));
"
`;

exports[`migrate command > should handle interactive mode 1`] = `
"e18e (cli <version>)

┌ Migrating packages...
[?25l│
◆ Select packages to migrate

│ Search: _
│ ◼ chalk
│ ↑/↓ to navigate • Space: select • Enter: confirm • Type: to search
└"
`;

exports[`migrate command > should handle interactive mode 2`] = `
"import chalk from 'chalk';

console.log(chalk.cyan('I am _so_ cyan'));
"
`;

exports[`migrate command > should handle specific package migration 1`] = `
"e18e (cli <version>)

┌ Migrating packages...
│
│ Targets: chalk
│
◇ /private{cwd}/lib/main.js...
│
│ loading /private{cwd}/lib/main.js
│ loading /private{cwd}/lib/main.js
│ migrating chalk to picocolors
│ loading /private{cwd}/lib/main.js
│ migrating chalk to picocolors
│ writing /private{cwd}/lib/main.js
│
◆ /private{cwd}/lib/main.js (1 migrated)
│
└ Migration complete.

"
`;

exports[`migrate command > should handle specific package migration 2`] = `
"import chalk from 'chalk';

console.log(chalk.cyan('I am _so_ cyan'));
"
`;
40 changes: 9 additions & 31 deletions src/test/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {describe, it, expect, beforeAll, afterAll} from 'vitest';
import {spawn} from 'node:child_process';
import path from 'node:path';
import fs from 'node:fs/promises';
import {createTempDir, cleanupTempDir, createTestPackage} from './utils.js';
import {
createTempDir,
cleanupTempDir,
createTestPackage,
runCliProcess,
stripVersion
} from './utils.js';
import {pack as packAsTarball} from '@publint/pack';

let mockTarballPath: string;
let tempDir: string;
const stripVersion = (str: string): string =>
str.replace(
new RegExp(/\(cli v\d+\.\d+\.\d+(?:-\S+)?\)/, 'g'),
'(cli <version>)'
);

beforeAll(async () => {
// Create a temporary directory for the test package
Expand Down Expand Up @@ -59,28 +59,6 @@ afterAll(async () => {
await cleanupTempDir(tempDir);
});

function runCliProcess(
args: string[],
cwd?: string
): Promise<{stdout: string; stderr: string; code: number | null}> {
return new Promise((resolve) => {
const cliPath = path.resolve(__dirname, '../../lib/cli.js');
const proc = spawn('node', [cliPath, ...args], {
env: process.env,
cwd: cwd || process.cwd()
});
let stdout = '';
let stderr = '';
proc.stdout.on('data', (data) => (stdout += data.toString()));
proc.stderr.on('data', (data) => (stderr += data.toString()));
proc.on('error', (err) => {
stderr += String(err);
resolve({stdout, stderr, code: 1});
});
proc.on('close', (code) => resolve({stdout, stderr, code}));
});
}

describe('CLI', () => {
it('should run successfully with default options', async () => {
const {stdout, stderr, code} = await runCliProcess(
Expand All @@ -91,7 +69,7 @@ describe('CLI', () => {
console.error('CLI Error:', stderr);
}
expect(code).toBe(0);
expect(stripVersion(stdout)).toMatchSnapshot();
expect(stripVersion(stdout, process.cwd())).toMatchSnapshot();
expect(stderr).toBe('');
});

Expand All @@ -101,7 +79,7 @@ describe('CLI', () => {
tempDir
);
expect(code).toBe(0);
expect(stripVersion(stdout)).toMatchSnapshot();
expect(stripVersion(stdout, process.cwd())).toMatchSnapshot();
expect(stderr).toMatchSnapshot();
});
});
109 changes: 109 additions & 0 deletions src/test/migrate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {describe, it, expect, beforeEach, afterEach} from 'vitest';
import path from 'node:path';
import fs from 'node:fs/promises';
import {
createTempDir,
cleanupTempDir,
runCliProcess,
stripVersion
} from './utils.js';

describe('migrate command', () => {
let tempDir: string;

beforeEach(async () => {
// Create a temporary directory for each test
tempDir = await createTempDir();

// Copy the basic-chalk fixture to the temp dir
const fixturePath = path.join(process.cwd(), 'test/fixtures/basic-chalk');
await fs.cp(fixturePath, tempDir, {recursive: true});
});

afterEach(async () => {
// Clean up the temporary directory after each test
await cleanupTempDir(tempDir);
});
it('should handle --all flag correctly', async () => {
const {stdout, stderr, code} = await runCliProcess(
['migrate', '--all', '--dry-run'],
tempDir
);

expect(code).toBe(0);
expect(stripVersion(stdout, tempDir)).toMatchSnapshot();
expect(stderr).toBe('');

// Check that the file was NOT modified in dry-run mode
const mainJsPath = path.join(tempDir, 'lib/main.js');
const fileContent = await fs.readFile(mainJsPath, 'utf-8');
expect(fileContent).toMatchSnapshot();
});

it('should handle specific package migration', async () => {
const {stdout, stderr, code} = await runCliProcess(
['migrate', 'chalk', '--dry-run'],
tempDir
);

expect(code).toBe(0);
expect(stripVersion(stdout, tempDir)).toMatchSnapshot();
expect(stderr).toBe('');

// Check that the file was NOT modified in dry-run mode
const mainJsPath = path.join(tempDir, 'lib/main.js');
const fileContent = await fs.readFile(mainJsPath, 'utf-8');
expect(fileContent).toMatchSnapshot();
});

it('should handle interactive mode', async () => {
// Test interactive mode by providing input to the prompt
// Press Enter to accept the default selection
const {stdout, stderr, code} = await runCliProcess(
['migrate', '--all', '--interactive', '--dry-run'],
tempDir,
'\n' // Press Enter to accept default
);

expect(code).toBe(0);
expect(stripVersion(stdout, tempDir)).toMatchSnapshot();
expect(stderr).toBe('');

// Check that the file was NOT modified in dry-run mode
const mainJsPath = path.join(tempDir, 'lib/main.js');
const fileContent = await fs.readFile(mainJsPath, 'utf-8');
expect(fileContent).toMatchSnapshot();
});

it('should handle custom include pattern', async () => {
const {stdout, stderr, code} = await runCliProcess(
['migrate', 'chalk', '--include', '**/*.js', '--dry-run'],
tempDir
);

expect(code).toBe(0);
expect(stripVersion(stdout, tempDir)).toMatchSnapshot();
expect(stderr).toBe('');

// Check that the file was NOT modified in dry-run mode
const mainJsPath = path.join(tempDir, 'lib/main.js');
const fileContent = await fs.readFile(mainJsPath, 'utf-8');
expect(fileContent).toMatchSnapshot();
});

it('should actually modify files when not using --dry-run', async () => {
const {stdout, stderr, code} = await runCliProcess(
['migrate', 'chalk'],
tempDir
);

expect(code).toBe(0);
expect(stripVersion(stdout, tempDir)).toMatchSnapshot();
expect(stderr).toBe('');

// Check that the file was actually modified (chalk import should be replaced with picocolors)
const mainJsPath = path.join(tempDir, 'lib/main.js');
const fileContent = await fs.readFile(mainJsPath, 'utf-8');
expect(fileContent).toMatchSnapshot();
});
});
Loading
Loading