Skip to content

Commit

Permalink
fix(generators): fix small issues with generators (#1385)
Browse files Browse the repository at this point in the history
  • Loading branch information
knagaitsev committed Apr 1, 2020
1 parent 7729d0b commit f62c60d
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 71 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ junit.xml
#typescript source maps
packages/**/*.map
*.tsbuildinfo

# temporary test files
test-assets/
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
"lint-staged": "^10.0.8",
"prettier": "1.19.1",
"readable-stream": "^3.6.0",
"rimraf": "^3.0.2",
"ts-jest": "^25.2.1",
"typedoc": "^0.17.0",
"typescript": "^3.8.3",
Expand Down
95 changes: 95 additions & 0 deletions packages/generators/__tests__/addon-generator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
jest.setMock('@webpack-cli/package-utils', {
getPackageManager: jest.fn(),
});

import fs from 'fs';
import path from 'path';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import { getPackageManager } from "@webpack-cli/package-utils";
import addonGenerator from '../src/addon-generator';

describe('addon generator', () => {
let gen, installMock, packageMock;
const genName = 'test-addon';
const testAssetsPath = path.join(__dirname, 'test-assets');
const genPath = path.join(testAssetsPath, genName);
// we want to test that the addon generator does not create a path
// like ./test-assets/test-addon/test-addon
// we call this unwanted path doubleGenPath
const doubleGenPath = path.join(genPath, genName);

beforeAll(() => {
rimraf.sync(testAssetsPath);
mkdirp.sync(genPath);
// set the working directory to here so that the addon directory is
// generated in ./test-assets/test-addon
process.chdir(genPath);
packageMock = getPackageManager as jest.Mock;
});

afterAll(() => {
rimraf.sync(testAssetsPath);
});

beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const Gen = addonGenerator([], '', [], [], () => {});
gen = new Gen(null, null);
gen.props = {
name: genName,
};
gen.scheduleInstallTask = jest.fn();
installMock = gen.scheduleInstallTask as jest.Mock;
});

it('schedules install using npm', () => {
packageMock.mockReturnValue('npm');

gen.install();
expect(installMock.mock.calls.length).toEqual(1);
expect(installMock.mock.calls[0]).toEqual([
'npm',
['webpack-defaults', 'bluebird'],
{
'save-dev': true,
},
]);
});

it('schedules install using yarn', () => {
packageMock.mockReturnValue('yarn');

gen.install();
expect(installMock.mock.calls.length).toEqual(1);
expect(installMock.mock.calls[0]).toEqual([
'yarn',
['webpack-defaults', 'bluebird'],
{
'dev': true,
},
]);
});

it('does not create new directory when current directory matches addon name', () => {
expect(fs.existsSync(genPath)).toBeTruthy();
gen.default();
expect(fs.existsSync(genPath)).toBeTruthy();
expect(fs.existsSync(doubleGenPath)).toBeFalsy();

// this needs to happen before the next test so that the
// working directory is changed before we create the new
// generator above
// this is switching the working directory as follows:
// ./test-assets/test-addon -> ./test-assets
process.chdir(testAssetsPath);
rimraf.sync(genPath);
});

it('creates a new directory for the generated addon', () => {
expect(fs.existsSync(genPath)).toBeFalsy();
gen.default();
expect(fs.existsSync(genPath)).toBeTruthy();
expect(fs.existsSync(doubleGenPath)).toBeFalsy();
});
});
148 changes: 143 additions & 5 deletions packages/generators/__tests__/init-generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,168 @@ import * as assert from 'yeoman-assert';
import { run } from 'yeoman-test';
import { join } from 'path';

jest.setTimeout(10000);

describe('init generator', () => {
it('generates a webpack project config', async () => {
it('generates a webpack config with default options', async () => {
const outputDir = await run(join(__dirname, '../src/init-generator.ts')).withPrompts({
multiEntries: false,
singularEntry: 'src/index',
outputDir: 'dist',
langType: 'No',
stylingType: 'No',
});

// Check that all the project files are generated with the correct name
const filePaths = ['package.json', 'README.md', 'src/index.js', 'sw.js'];
assert.file(filePaths.map(file => join(outputDir, file)));

// Check generated file contents
assert.fileContent(join(outputDir, 'package.json'), '"name": "my-webpack-project"');
assert.fileContent(join(outputDir, 'README.md'), 'Welcome to your new awesome project!');
assert.fileContent(join(outputDir, 'src', 'index.js'), 'console.log("Hello World from your main file!");');
assert.fileContent(join(outputDir, 'sw.js'), 'self.addEventListener(\'install\'');

const output = require(join(outputDir, '.yo-rc.json'));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const config = (Object.entries(output)[0][1] as any).configuration.config.webpackOptions;
// since default options were used, entry and output are not specified
// in the generated config file
expect(config.entry).toEqual(undefined);
expect(config.output).toEqual(undefined);
// there are no special loaders, so rules should be empty
expect(config.module.rules).toEqual([]);
});

it('generates a webpack config with custom entry and output', async () => {
const outputDir = await run(join(__dirname, '../src/init-generator.ts')).withPrompts({
multiEntries: false,
singularEntry: 'src/index2',
outputDir: 'dist2',
langType: 'No',
stylingType: 'No',
useExtractPlugin: 'main',
});

// Check that all the project files are generated with the correct name
const filePaths = ['package.json', 'README.md', 'src/index2.js'];
assert.file(filePaths.map(file => join(outputDir, file)));

// this file is only added if the default options are used
assert.noFile(join(outputDir, 'sw.js'));

// Check generated file contents
assert.fileContent(join(outputDir, 'package.json'), '"name": "my-webpack-project"');
assert.fileContent(join(outputDir, 'README.md'), 'Welcome to your new awesome project!');
assert.fileContent(join(outputDir, 'src', 'index2.js'), 'console.log("Hello World from your main file!");');

const output = require(join(outputDir, '.yo-rc.json'));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const config = (Object.entries(output)[0][1] as any).configuration.config.webpackOptions;
expect(config.entry).toEqual("'./src/index2.js'");
expect(config.output.path).toEqual("path.resolve(__dirname, 'dist2')");
}, 10000);
// there are no special loaders, so rules should be empty
expect(config.module.rules).toEqual([]);
});

it('generates a webpack config using CSS without mini-css-extract-plugin', async () => {
const outputDir = await run(join(__dirname, '../src/init-generator.ts')).withPrompts({
multiEntries: false,
singularEntry: 'src/index',
outputDir: 'dist',
langType: 'No',
stylingType: 'CSS',
useExtractPlugin: false,
});

// Check that all the project files are generated with the correct name
const filePaths = ['package.json', 'README.md', 'src/index.js'];
assert.file(filePaths.map(file => join(outputDir, file)));

const output = require(join(outputDir, '.yo-rc.json'));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const config = (Object.entries(output)[0][1] as any).configuration.config.webpackOptions;
expect(config.module.rules[0].test).toEqual('/.css$/');
expect(config.module.rules[0].use.length).toEqual(2);
expect(config.module.rules[0].use[0].loader).toEqual('"style-loader"');
expect(config.module.rules[0].use[1].loader).toEqual('"css-loader"');
});

it('generates a webpack config using CSS with mini-css-extract-plugin', async () => {
const outputDir = await run(join(__dirname, '../src/init-generator.ts')).withPrompts({
multiEntries: false,
singularEntry: 'src/index',
outputDir: 'dist',
langType: 'No',
stylingType: 'CSS',
useExtractPlugin: true,
cssBundleName: 'main',
});

// Check that all the project files are generated with the correct name
const filePaths = ['package.json', 'README.md', 'src/index.js'];
assert.file(filePaths.map(file => join(outputDir, file)));

const output = require(join(outputDir, '.yo-rc.json'));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const config = (Object.entries(output)[0][1] as any).configuration.config.webpackOptions;
expect(config.module.rules[0].test).toEqual('/.css$/');
expect(config.module.rules[0].use.length).toEqual(3);
expect(config.module.rules[0].use[0].loader).toEqual('MiniCssExtractPlugin.loader');
expect(config.module.rules[0].use[1].loader).toEqual('"style-loader"');
expect(config.module.rules[0].use[2].loader).toEqual('"css-loader"');
});

it('generates a webpack config with multiple entries', async () => {
const outputDir = await run(join(__dirname, '../src/init-generator.ts')).withPrompts({
multiEntries: true,
multipleEntries: 'test1, test2',
test1: 'dir1/test1',
test2: 'dir2/test2',
outputDir: 'dist',
langType: 'No',
stylingType: 'No',
});

// Check that all the project files are generated with the correct name
const filePaths = ['package.json', 'README.md', 'dir1/test1.js', 'dir2/test2.js'];
assert.file(filePaths.map(file => join(outputDir, file)));

// Check generated file contents
assert.fileContent(join(outputDir, 'dir1', 'test1.js'), 'console.log("Hello World from test1 main file!");');
assert.fileContent(join(outputDir, 'dir2', 'test2.js'), 'console.log("Hello World from test2 main file!");');

const output = require(join(outputDir, '.yo-rc.json'));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const config = (Object.entries(output)[0][1] as any).configuration.config.webpackOptions;
expect(config.entry).toEqual({
test1: "'./dir1/test1.js'",
test2: "'./dir2/test2.js'",
});
});

it('generates a webpack config that uses ES6', async () => {
const outputDir = await run(join(__dirname, '../src/init-generator.ts')).withPrompts({
multiEntries: false,
singularEntry: 'src/index',
outputDir: 'dist',
langType: 'ES6',
stylingType: 'No',
});

// Check that all the project files are generated with the correct name
const filePaths = ['package.json', 'README.md', 'src/index.js', '.babelrc'];
assert.file(filePaths.map(file => join(outputDir, file)));
});

it('generates a webpack config that uses Typescript', async () => {
const outputDir = await run(join(__dirname, '../src/init-generator.ts')).withPrompts({
multiEntries: false,
singularEntry: 'src/index',
outputDir: 'dist',
langType: 'Typescript',
stylingType: 'No',
});

// Check that all the project files are generated with the correct name
const filePaths = ['package.json', 'README.md', 'src/index.ts', 'tsconfig.json'];
assert.file(filePaths.map(file => join(outputDir, file)));
});
});
23 changes: 15 additions & 8 deletions packages/generators/src/addon-generator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import mkdirp from 'mkdirp';
import path from 'path';
import Generator from 'yeoman-generator';
import { getPackageManager } from "@webpack-cli/package-utils";
import { generatorCopy, generatorCopyTpl } from '@webpack-cli/utils';

/**
Expand Down Expand Up @@ -48,18 +49,20 @@ const addonGenerator = (
this.log(`
Your project must be inside a folder named ${this.props.name}
I will create this folder for you.
`);
mkdirp(this.props.name).catch((err: object): void => {
if (err) console.error('Failed to create directory', err);
});
`);
const pathToProjectDir: string = this.destinationPath(this.props.name);
try {
mkdirp.sync(pathToProjectDir);
} catch (err) {
console.error('Failed to create directory', err);
}
this.destinationRoot(pathToProjectDir);
}
}

public writing(): void {
const packageJsonTemplatePath = "../templates/addon-package.json.js";
this.fs.extendJSON(this.destinationPath("package.json"), require(packageJsonTemplatePath)(this.props.name));
this.fs.extendJSON(this.destinationPath("package.json"), require(packageJsonTemplatePath)(this.props.name));

this.copy = generatorCopy(this, templateDir);
this.copyTpl = generatorCopyTpl(this, templateDir, templateFn(this));
Expand All @@ -69,9 +72,13 @@ const addonGenerator = (
}

public install(): void {
this.npmInstall(['webpack-defaults', 'bluebird'], {
'save-dev': true,
});
const packager = getPackageManager();
const opts: {
dev?: boolean;
"save-dev"?: boolean;
} = packager === "yarn" ? { dev: true } : { "save-dev": true };

this.scheduleInstallTask(packager, ['webpack-defaults', 'bluebird'], opts);
}
};
};
Expand Down
Loading

0 comments on commit f62c60d

Please sign in to comment.