Skip to content

Commit 1d35c81

Browse files
committed
✨ Presets are now supported by the CLI
1 parent 24969d2 commit 1d35c81

File tree

2 files changed

+197
-35
lines changed

2 files changed

+197
-35
lines changed

packages/cli/src/main.spec.ts

Lines changed: 130 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,16 @@ jest.mock('live-plugin-manager', () => ({
1212
'19.0.0': `${codemodName}/path/to/19.js`,
1313
'20.0.0': `${codemodName}/path/to/20.js`,
1414
},
15+
presets: {
16+
'update-formatting': `${codemodName}/path/to/update-formatting.js`,
17+
'update-imports': `${codemodName}/path/to/update-imports.js`,
18+
},
1519
},
1620
}),
1721
uninstallAll: () => Promise.resolve(),
1822
}),
1923
}));
2024

21-
// jest.mock('fs-extra', () => ({
22-
// readdir: () =>
23-
// Promise.resolve([
24-
// '18.0.0',
25-
// '19.0.0',
26-
// '20.0.0',
27-
// 'codeshift.config.js',
28-
// 'index.ts',
29-
// ]),
30-
// }));
31-
3225
// @ts-ignore
3326
import * as jscodeshift from 'jscodeshift/src/Runner';
3427

@@ -242,6 +235,25 @@ describe('main', () => {
242235
expect.any(Object),
243236
);
244237
});
238+
it('should run multiple transforms of the same package', async () => {
239+
await main([mockPath], {
240+
packages: '@myscope/mylib@20.0.0@19.0.0',
241+
parser: 'babel',
242+
extensions: 'js',
243+
});
244+
245+
expect(jscodeshift.run).toHaveBeenCalledTimes(2);
246+
expect(jscodeshift.run).toHaveBeenCalledWith(
247+
'@codeshift/mod-myscope__mylib/path/to/19.js',
248+
expect.any(Array),
249+
expect.any(Object),
250+
);
251+
expect(jscodeshift.run).toHaveBeenCalledWith(
252+
'@codeshift/mod-myscope__mylib/path/to/20.js',
253+
expect.any(Array),
254+
expect.any(Object),
255+
);
256+
});
245257

246258
it('should handle empty package transforms', async () => {
247259
await main([mockPath], {
@@ -270,7 +282,24 @@ describe('main', () => {
270282
} catch (error) {
271283
// @ts-ignore
272284
expect(error.message).toMatch(
273-
'Invalid version provided to the --packages flag. Package mylib@NOT_SEMVER is missing version. Please try: "@[scope]/[package]@[version]" for example @mylib/avatar@10.0.0',
285+
'Invalid version provided to the --packages flag. Unable to resolve version "NOT_SEMVER" for package "mylib". Please try: "[scope]/[package]@[version]" for example @mylib/mypackage@10.0.0',
286+
);
287+
}
288+
});
289+
290+
it('should throw when transform is not found', async () => {
291+
expect.assertions(1);
292+
293+
try {
294+
await main([mockPath], {
295+
packages: 'mylib@120.0.0',
296+
parser: 'babel',
297+
extensions: 'js',
298+
});
299+
} catch (error) {
300+
// @ts-ignore
301+
expect(error.message).toMatch(
302+
'Invalid version provided to the --packages flag. Unable to resolve version "120.0.0" for package "mylib"',
274303
);
275304
}
276305
});
@@ -299,4 +328,93 @@ describe('main', () => {
299328
);
300329
});
301330
});
331+
332+
describe('when running presets with the -p flag', () => {
333+
it('should run single preset', async () => {
334+
await main([mockPath], {
335+
packages: 'mylib#update-formatting',
336+
parser: 'babel',
337+
extensions: 'js',
338+
});
339+
340+
expect(jscodeshift.run).toHaveBeenCalledTimes(1);
341+
expect(jscodeshift.run).toHaveBeenCalledWith(
342+
'@codeshift/mod-mylib/path/to/update-formatting.js',
343+
expect.arrayContaining([mockPath]),
344+
expect.objectContaining({
345+
parser: 'babel',
346+
extensions: 'js',
347+
}),
348+
);
349+
});
350+
351+
it('should run multiple presets', async () => {
352+
await main([mockPath], {
353+
packages: 'mylib#update-formatting,mylib#update-imports',
354+
parser: 'babel',
355+
extensions: 'js',
356+
});
357+
358+
expect(jscodeshift.run).toHaveBeenCalledTimes(2);
359+
expect(jscodeshift.run).toHaveBeenCalledWith(
360+
'@codeshift/mod-mylib/path/to/update-formatting.js',
361+
expect.arrayContaining([mockPath]),
362+
expect.objectContaining({
363+
parser: 'babel',
364+
extensions: 'js',
365+
}),
366+
);
367+
expect(jscodeshift.run).toHaveBeenCalledWith(
368+
'@codeshift/mod-mylib/path/to/update-imports.js',
369+
expect.arrayContaining([mockPath]),
370+
expect.objectContaining({
371+
parser: 'babel',
372+
extensions: 'js',
373+
}),
374+
);
375+
});
376+
377+
it('should run multiple presets of the same package', async () => {
378+
await main([mockPath], {
379+
packages: 'mylib#update-formatting#update-imports',
380+
parser: 'babel',
381+
extensions: 'js',
382+
});
383+
384+
expect(jscodeshift.run).toHaveBeenCalledTimes(2);
385+
expect(jscodeshift.run).toHaveBeenCalledWith(
386+
'@codeshift/mod-mylib/path/to/update-formatting.js',
387+
expect.arrayContaining([mockPath]),
388+
expect.objectContaining({
389+
parser: 'babel',
390+
extensions: 'js',
391+
}),
392+
);
393+
expect(jscodeshift.run).toHaveBeenCalledWith(
394+
'@codeshift/mod-mylib/path/to/update-imports.js',
395+
expect.arrayContaining([mockPath]),
396+
expect.objectContaining({
397+
parser: 'babel',
398+
extensions: 'js',
399+
}),
400+
);
401+
});
402+
403+
it('should throw when preset is not found', async () => {
404+
expect.assertions(1);
405+
406+
try {
407+
await main([mockPath], {
408+
packages: 'mylib#does-not-exist',
409+
parser: 'babel',
410+
extensions: 'js',
411+
});
412+
} catch (error) {
413+
// @ts-ignore
414+
expect(error.message).toMatch(
415+
'Invalid preset provided to the --packages flag. Unable to resolve preset "does-not-exist" for package "mylib"',
416+
);
417+
}
418+
});
419+
});
302420
});

packages/cli/src/main.ts

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,33 +32,77 @@ export default async function main(paths: string[], flags: Flags) {
3232
const pkgs = flags.packages.split(',').filter(pkg => !!pkg);
3333

3434
for (const pkg of pkgs) {
35-
const pkgSplit = pkg.split('@').filter(str => !!str);
36-
const name = pkgSplit[0].replace('/', '__');
37-
const baseVersion = semver.valid(
38-
semver.coerce(pkgSplit[pkgSplit.length - 1]),
39-
);
40-
41-
if (!baseVersion) {
42-
throw new InvalidUserInputError(
43-
`Invalid version provided to the --packages flag. Package ${pkg} is missing version. Please try: "@[scope]/[package]@[version]" for example @mylib/avatar@10.0.0`,
44-
);
45-
}
35+
const pkgName = pkg
36+
.split(/[@#]/)
37+
.filter(str => !!str)[0]
38+
.replace('/', '__');
39+
const codemodName = `@codeshift/mod-${pkgName}`;
4640

47-
const codemodName = `@codeshift/mod-${name}`;
4841
await packageManager.install(codemodName);
4942
const { default: codeshiftConfig } = packageManager.require(codemodName);
5043

51-
Object.entries(codeshiftConfig.transforms as Record<string, string>)
52-
.filter(
53-
([key]) =>
54-
semver.valid(key) && semver.satisfies(key, `>=${baseVersion}`),
55-
)
56-
.filter(([key]) => {
57-
if (flags.sequence) return true;
58-
59-
return baseVersion && semver.eq(key, baseVersion);
60-
})
61-
.forEach(([, path]) => transforms.push(path));
44+
const codemodIds = pkg.split(/(?=[@#])/).filter(str => !!str);
45+
codemodIds.shift();
46+
47+
const transformIds = codemodIds
48+
.filter(id => id.startsWith('@'))
49+
.map(id => id.substring(1))
50+
.sort((idA, idB) => {
51+
if (semver.lt(idA, idB)) return -1;
52+
if (semver.gt(idA, idB)) return 1;
53+
return 0;
54+
});
55+
56+
const presetIds = codemodIds
57+
.filter(id => id.startsWith('#'))
58+
.map(id => id.substring(1));
59+
60+
// Validate transforms/presets
61+
transformIds.forEach(id => {
62+
if (!semver.valid(semver.coerce(id.substring(1)))) {
63+
throw new InvalidUserInputError(
64+
`Invalid version provided to the --packages flag. Unable to resolve version "${id}" for package "${pkgName}". Please try: "[scope]/[package]@[version]" for example @mylib/mypackage@10.0.0`,
65+
);
66+
}
67+
68+
if (!codeshiftConfig.transforms[id]) {
69+
throw new InvalidUserInputError(
70+
`Invalid version provided to the --packages flag. Unable to resolve version "${id}" for package "${pkgName}"`,
71+
);
72+
}
73+
});
74+
75+
presetIds.forEach(id => {
76+
if (!codeshiftConfig.presets[id]) {
77+
throw new InvalidUserInputError(
78+
`Invalid preset provided to the --packages flag. Unable to resolve preset "${id}" for package "${pkgName}""`,
79+
);
80+
}
81+
});
82+
83+
// Get transform file paths
84+
if (flags.sequence) {
85+
Object.entries(codeshiftConfig.transforms as Record<string, string>)
86+
.filter(([key]) => semver.satisfies(key, `>=${transformIds[0]}`))
87+
.forEach(([, path]) => transforms.push(path));
88+
} else {
89+
Object.entries(
90+
codeshiftConfig.transforms as Record<string, string>,
91+
).forEach(([id, path]) => {
92+
if (transformIds.includes(id)) {
93+
transforms.push(path);
94+
}
95+
});
96+
}
97+
98+
// Get preset file paths
99+
Object.entries(codeshiftConfig.presets as Record<string, string>).forEach(
100+
([id, path]) => {
101+
if (presetIds.includes(id)) {
102+
transforms.push(path);
103+
}
104+
},
105+
);
62106
}
63107
}
64108

0 commit comments

Comments
 (0)