Skip to content

Commit 7ebcbb8

Browse files
authored
fix: regression with migrate command
1 parent 8892926 commit 7ebcbb8

9 files changed

Lines changed: 252 additions & 65 deletions

File tree

packages/migrate/src/index.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import chalk from 'chalk';
2-
import diff from 'diff';
2+
import { Change, diffLines } from 'diff';
33
import fs from 'fs';
44
import inquirer from 'inquirer';
55
import Listr from 'listr';
@@ -90,9 +90,9 @@ function runMigration(currentConfigPath: string, outputConfigPath: string): Prom
9090
.run()
9191
.then((ctx: Node): void | Promise<void> => {
9292
const result: string = ctx.ast.toSource(recastOptions);
93-
const diffOutput: diff.Change[] = diff.diffLines(ctx.source, result);
93+
const diffOutput: Change[] = diffLines(ctx.source, result);
9494

95-
diffOutput.forEach((diffLine: diff.Change): void => {
95+
diffOutput.forEach((diffLine: Change): void => {
9696
if (diffLine.added) {
9797
process.stdout.write(chalk.green(`+ ${diffLine.value}`));
9898
} else if (diffLine.removed) {
@@ -133,15 +133,11 @@ function runMigration(currentConfigPath: string, outputConfigPath: string): Prom
133133
return;
134134
}
135135

136-
runPrettier(outputConfigPath, result, (err: object): void => {
137-
if (err) {
138-
throw err;
139-
}
140-
});
136+
runPrettier(outputConfigPath, result);
141137

142138
if (answer.confirmValidation) {
143-
const outputPath = await import(outputConfigPath);
144-
const webpackOptionsValidationErrors: string[] = validate(outputPath);
139+
const outputConfig = (await import(outputConfigPath)).default;
140+
const webpackOptionsValidationErrors: string[] = validate(outputConfig);
145141

146142
if (webpackOptionsValidationErrors.length) {
147143
console.error(chalk.red("\n✖ Your configuration validation wasn't successful \n"));
@@ -198,7 +194,7 @@ export default function migrate(...args: string[]): void | Promise<void> {
198194
])
199195
.then((ans: { confirmPath: boolean }): void | Promise<void> => {
200196
if (!ans.confirmPath) {
201-
console.error(chalk.red('✖ ︎Migration aborted due no output path'));
197+
console.error(chalk.red('✖ ︎Migration aborted due to no output path'));
202198
return;
203199
}
204200
outputConfigPath = path.resolve(process.cwd(), filePaths[0]);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
3+
import fs from 'fs';
4+
import path from 'path';
5+
//eslint-disable-next-line node/no-extraneous-import
6+
import rimraf from 'rimraf';
7+
import { runPrettier } from '../src/run-prettier';
8+
9+
const outputPath = path.join(__dirname, 'test-assets');
10+
const outputFile = path.join(outputPath, 'test.js');
11+
const stdoutSpy = jest.spyOn(process.stdout, 'write');
12+
13+
describe('runPrettier', () => {
14+
beforeEach(() => {
15+
rimraf.sync(outputPath);
16+
fs.mkdirSync(outputPath);
17+
stdoutSpy.mockClear();
18+
});
19+
20+
afterAll(() => {
21+
rimraf.sync(outputPath);
22+
});
23+
24+
it('should run prettier on JS string and write file', () => {
25+
runPrettier(outputFile, 'console.log("1");console.log("2");');
26+
expect(fs.existsSync(outputFile)).toBeTruthy();
27+
const data = fs.readFileSync(outputFile, 'utf8');
28+
expect(data).toContain("console.log('1');\n");
29+
30+
expect(stdoutSpy.mock.calls.length).toEqual(0);
31+
});
32+
33+
it('prettier should fail on invalid JS, with file still written', () => {
34+
runPrettier(outputFile, '"');
35+
expect(fs.existsSync(outputFile)).toBeTruthy();
36+
const data = fs.readFileSync(outputFile, 'utf8');
37+
expect(data).toContain('"');
38+
39+
expect(stdoutSpy.mock.calls.length).toEqual(1);
40+
expect(stdoutSpy.mock.calls[0][0]).toContain('WARNING: Could not apply prettier');
41+
});
42+
});

packages/utils/src/run-prettier.ts

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,27 @@ import prettier from 'prettier';
88
*
99
* @param {String} outputPath - Path to write the config to
1010
* @param {Node} source - AST to write at the given path
11-
* @param {Function} cb - executes a callback after execution if supplied
12-
* @returns {Void} Writes a file at given location and prints messages accordingly
11+
* @returns {Void} Writes a file at given location
1312
*/
1413

15-
export function runPrettier(outputPath: string, source: string, cb?: Function): void {
16-
function validateConfig(): void | Function {
17-
let prettySource: string;
18-
let error: object;
19-
try {
20-
prettySource = prettier.format(source, {
21-
filepath: outputPath,
22-
parser: 'babel',
23-
singleQuote: true,
24-
tabWidth: 1,
25-
useTabs: true,
26-
});
27-
} catch (err) {
28-
process.stdout.write(
29-
`\n${chalk.yellow(
30-
`WARNING: Could not apply prettier to ${outputPath}` + ' due validation error, but the file has been created\n',
31-
)}`,
32-
);
33-
prettySource = source;
34-
error = err;
35-
}
36-
if (cb) {
37-
return cb(error);
38-
}
39-
return fs.writeFileSync(outputPath, prettySource, 'utf8');
14+
export function runPrettier(outputPath: string, source: string): void {
15+
let prettySource: string = source;
16+
try {
17+
prettySource = prettier.format(source, {
18+
filepath: outputPath,
19+
parser: 'babel',
20+
singleQuote: true,
21+
tabWidth: 1,
22+
useTabs: true,
23+
});
24+
} catch (err) {
25+
process.stdout.write(
26+
`\n${chalk.yellow(
27+
`WARNING: Could not apply prettier to ${outputPath}` + ' due validation error, but the file has been created\n',
28+
)}`,
29+
);
30+
prettySource = source;
4031
}
41-
return fs.writeFile(outputPath, source, 'utf8', validateConfig);
32+
33+
return fs.writeFileSync(outputPath, prettySource, 'utf8');
4234
}

test/init/generator/init-inquirer.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('init', () => {
2222
});
2323

2424
it('should scaffold when given answers', async () => {
25-
const { stdout } = await runPromptWithAnswers(genPath, ['init'], ['N', ENTER, ENTER, ENTER, ENTER, ENTER, ENTER, ENTER]);
25+
const { stdout } = await runPromptWithAnswers(genPath, ['init'], [`N${ENTER}`, ENTER, ENTER, ENTER, ENTER, ENTER, ENTER]);
2626

2727
expect(stdout).toBeTruthy();
2828
expect(stdout).toContain(firstPrompt);

test/loader/loader.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('loader command', () => {
3131
});
3232

3333
it('should scaffold loader template with a given name', async () => {
34-
const { stdout } = await runPromptWithAnswers(__dirname, ['loader'], [loaderName, ENTER]);
34+
const { stdout } = await runPromptWithAnswers(__dirname, ['loader'], [`${loaderName}${ENTER}`]);
3535

3636
expect(stdout).toContain(firstPrompt);
3737

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/* eslint-disable */
2+
3+
module.exports = {
4+
output: {
5+
badOption: true,
6+
},
7+
};
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
'use strict';
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
const rimraf = require('rimraf');
6+
const { run, runAndGetWatchProc, runPromptWithAnswers } = require('../../utils/test-utils');
7+
8+
const ENTER = '\x0D';
9+
const outputDir = 'test-assets';
10+
const outputPath = path.join(__dirname, outputDir);
11+
const outputFile = `${outputDir}/updated-webpack.config.js`;
12+
const outputFilePath = path.join(__dirname, outputFile);
13+
14+
describe('migrate command', () => {
15+
beforeEach(() => {
16+
rimraf.sync(outputPath);
17+
fs.mkdirSync(outputPath);
18+
});
19+
20+
afterAll(() => {
21+
rimraf.sync(outputPath);
22+
});
23+
24+
it('should warn if the source config file is not specified', () => {
25+
const { stderr } = run(__dirname, ['migrate'], false);
26+
expect(stderr).toContain('Please specify a path to your webpack config');
27+
});
28+
29+
it('should prompt accordingly if an output path is not specified', () => {
30+
const { stdout } = run(__dirname, ['migrate', 'webpack.config.js'], false);
31+
expect(stdout).toContain('? Migration output path not specified');
32+
});
33+
34+
it('should throw an error if the user refused to overwrite the source file and no output path is provided', async () => {
35+
const { stderr } = await runAndGetWatchProc(__dirname, ['migrate', 'webpack.config.js'], false, 'n');
36+
expect(stderr).toBe('✖ ︎Migration aborted due to no output path');
37+
});
38+
39+
it('should prompt for config validation when an output path is provided', async () => {
40+
const { stdout } = await runAndGetWatchProc(__dirname, ['migrate', 'webpack.config.js', outputFile], false, 'y');
41+
// should show the diff of the config file
42+
expect(stdout).toContain('rules: [');
43+
expect(stdout).toContain('? Do you want to validate your configuration?');
44+
});
45+
46+
it('should generate an updated config file when an output path is provided', async () => {
47+
const { stdout, stderr } = await runPromptWithAnswers(
48+
__dirname,
49+
['migrate', 'webpack.config.js', outputFile],
50+
[ENTER, ENTER],
51+
true,
52+
);
53+
expect(stdout).toContain('? Do you want to validate your configuration?');
54+
// should show the diff of the config file
55+
expect(stdout).toContain('rules: [');
56+
expect(stderr).toBeFalsy();
57+
58+
expect(fs.existsSync(outputFilePath)).toBeTruthy();
59+
// the output file should be a valid config file
60+
const config = require(outputFilePath);
61+
expect(config.module.rules).toEqual([
62+
{
63+
test: /\.js$/,
64+
exclude: /node_modules/,
65+
66+
use: [
67+
{
68+
loader: 'babel-loader',
69+
70+
options: {
71+
presets: ['@babel/preset-env'],
72+
},
73+
},
74+
],
75+
},
76+
]);
77+
});
78+
79+
it('should generate an updated config file and warn of an invalid webpack config', async () => {
80+
const { stdout, stderr } = await runPromptWithAnswers(__dirname, ['migrate', 'bad-webpack.config.js', outputFile], [ENTER, ENTER]);
81+
expect(stdout).toContain('? Do you want to validate your configuration?');
82+
expect(stderr).toContain("configuration.output has an unknown property 'badOption'");
83+
84+
expect(fs.existsSync(outputFilePath)).toBeTruthy();
85+
});
86+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* eslint-disable */
2+
const path = require('path');
3+
4+
module.exports = {
5+
entry: {
6+
index: './src/index.js',
7+
vendor: './src/vendor.js',
8+
},
9+
10+
output: {
11+
filename: '[name].[chunkhash].js',
12+
chunkFilename: '[name].[chunkhash].js',
13+
path: path.resolve(__dirname, 'dist'),
14+
},
15+
16+
optimization: {
17+
minimize: true
18+
},
19+
20+
module: {
21+
loaders: [
22+
{
23+
test: /\.js$/,
24+
exclude: /node_modules/,
25+
loader: 'babel',
26+
query: {
27+
presets: ['@babel/preset-env'],
28+
},
29+
},
30+
],
31+
},
32+
};

0 commit comments

Comments
 (0)