feat(nx-plugin): add new package for creating nx plugins

This commit is contained in:
Jonathan Cammisuli 2020-01-21 15:17:40 -05:00 committed by Victor Savkin
parent ca69622ebe
commit fe98e29e4b
71 changed files with 2327 additions and 17 deletions

View File

@ -21,6 +21,7 @@ module.exports = {
{ name: 'docs', description: 'anything related to docs infrastructure' }, { name: 'docs', description: 'anything related to docs infrastructure' },
{ name: 'nextjs', description: 'anything Next specific' }, { name: 'nextjs', description: 'anything Next specific' },
{ name: 'node', description: 'anything Node specific' }, { name: 'node', description: 'anything Node specific' },
{ name: 'nx-plugin', description: 'anything Nx Plugin specific' },
{ name: 'react', description: 'anything React specific' }, { name: 'react', description: 'anything React specific' },
{ name: 'storybook', description: 'anything Storybook specific' }, { name: 'storybook', description: 'anything Storybook specific' },
{ {

View File

@ -0,0 +1,25 @@
# e2e
Creates and runs an e2e for a Nx Plugin
Builder properties can be configured in angular.json when defining the builder, or when invoking it.
## Properties
### jestConfig
Type: `string`
Jest config file
### target
Type: `string`
the target Nx Plugin project and build
### tsSpecConfig
Type: `string`
Spec tsconfig file

View File

@ -0,0 +1,91 @@
# plugin
Create a Nx Plugin
## Usage
```bash
ng generate plugin ...
```
By default, Nx will search for `plugin` in the default collection provisioned in `angular.json`.
You can specify the collection explicitly as follows:
```bash
ng g @nrwl/nx-plugin:plugin ...
```
Show what will be generated without writing to disk:
```bash
ng g plugin ... --dry-run
```
### Examples
Generate libs/plugins/my-plugin:
```bash
ng g plugin my-plugin --directory=plugins
```
## Options
### directory
Alias(es): d
Type: `string`
A directory where the plugin is placed
### linter
Default: `tslint`
Type: `string`
Possible values: `eslint`, `tslint`
The tool to use for running lint checks.
### name
Type: `string`
Plugin name
### skipFormat
Default: `false`
Type: `boolean`
Skip formatting files
### skipTsConfig
Default: `false`
Type: `boolean`
Do not update tsconfig.json for development experience.
### tags
Alias(es): t
Type: `string`
Add tags to the library (used for linting)
### unitTestRunner
Default: `jest`
Type: `string`
Possible values: `jest`, `none`
Test runner to use for unit tests

View File

@ -6,6 +6,7 @@
"nest", "nest",
"next", "next",
"node", "node",
"nx-plugin",
"storybook", "storybook",
"web", "web",
"workspace" "workspace"

View File

@ -7,6 +7,7 @@
"nest", "nest",
"next", "next",
"node", "node",
"nx-plugin",
"react", "react",
"storybook", "storybook",
"web", "web",

View File

@ -0,0 +1,26 @@
# e2e
Creates and runs an e2e for a Nx Plugin
Builder properties can be configured in workspace.json when defining the builder, or when invoking it.
Read more about how to use builders and the CLI here: https://nx.dev/react/guides/cli.
## Properties
### jestConfig
Type: `string`
Jest config file
### target
Type: `string`
the target Nx Plugin project and build
### tsSpecConfig
Type: `string`
Spec tsconfig file

View File

@ -0,0 +1,91 @@
# plugin
Create a Nx Plugin
## Usage
```bash
nx generate plugin ...
```
By default, Nx will search for `plugin` in the default collection provisioned in `workspace.json`.
You can specify the collection explicitly as follows:
```bash
nx g @nrwl/nx-plugin:plugin ...
```
Show what will be generated without writing to disk:
```bash
nx g plugin ... --dry-run
```
### Examples
Generate libs/plugins/my-plugin:
```bash
nx g plugin my-plugin --directory=plugins
```
## Options
### directory
Alias(es): d
Type: `string`
A directory where the plugin is placed
### linter
Default: `tslint`
Type: `string`
Possible values: `eslint`, `tslint`
The tool to use for running lint checks.
### name
Type: `string`
Plugin name
### skipFormat
Default: `false`
Type: `boolean`
Skip formatting files
### skipTsConfig
Default: `false`
Type: `boolean`
Do not update tsconfig.json for development experience.
### tags
Alias(es): t
Type: `string`
Add tags to the library (used for linting)
### unitTestRunner
Default: `jest`
Type: `string`
Possible values: `jest`, `none`
Test runner to use for unit tests

View File

@ -6,6 +6,7 @@
"nest", "nest",
"next", "next",
"node", "node",
"nx-plugin",
"storybook", "storybook",
"web", "web",
"workspace" "workspace"

View File

@ -7,6 +7,7 @@
"nest", "nest",
"next", "next",
"node", "node",
"nx-plugin",
"react", "react",
"storybook", "storybook",
"web", "web",

View File

@ -0,0 +1,26 @@
# e2e
Creates and runs an e2e for a Nx Plugin
Builder properties can be configured in workspace.json when defining the builder, or when invoking it.
Read more about how to use builders and the CLI here: https://nx.dev/web/guides/cli.
## Properties
### jestConfig
Type: `string`
Jest config file
### target
Type: `string`
the target Nx Plugin project and build
### tsSpecConfig
Type: `string`
Spec tsconfig file

View File

@ -0,0 +1,91 @@
# plugin
Create a Nx Plugin
## Usage
```bash
nx generate plugin ...
```
By default, Nx will search for `plugin` in the default collection provisioned in `workspace.json`.
You can specify the collection explicitly as follows:
```bash
nx g @nrwl/nx-plugin:plugin ...
```
Show what will be generated without writing to disk:
```bash
nx g plugin ... --dry-run
```
### Examples
Generate libs/plugins/my-plugin:
```bash
nx g plugin my-plugin --directory=plugins
```
## Options
### directory
Alias(es): d
Type: `string`
A directory where the plugin is placed
### linter
Default: `tslint`
Type: `string`
Possible values: `eslint`, `tslint`
The tool to use for running lint checks.
### name
Type: `string`
Plugin name
### skipFormat
Default: `false`
Type: `boolean`
Skip formatting files
### skipTsConfig
Default: `false`
Type: `boolean`
Do not update tsconfig.json for development experience.
### tags
Alias(es): t
Type: `string`
Add tags to the library (used for linting)
### unitTestRunner
Default: `jest`
Type: `string`
Possible values: `jest`, `none`
Test runner to use for unit tests

View File

@ -6,6 +6,7 @@
"nest", "nest",
"next", "next",
"node", "node",
"nx-plugin",
"storybook", "storybook",
"web", "web",
"workspace" "workspace"

View File

@ -7,6 +7,7 @@
"nest", "nest",
"next", "next",
"node", "node",
"nx-plugin",
"react", "react",
"storybook", "storybook",
"web", "web",

97
e2e/nx-plugin.test.ts Normal file
View File

@ -0,0 +1,97 @@
import {
forEachCli,
ensureProject,
uniq,
runCLI,
updateFile,
expectTestsPass,
runCLIAsync,
checkFilesExist,
readJson,
workspaceConfigName
} from './utils';
forEachCli(currentCLIName => {
const linter = currentCLIName === 'angular' ? 'tslint' : 'eslint';
describe('Nx Plugin', () => {
it('should be able to generate a Nx Plugin ', async done => {
ensureProject();
const plugin = uniq('plugin');
runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter}`);
const lintResults = runCLI(`lint ${plugin}`);
expect(lintResults).toContain('All files pass linting.');
expectTestsPass(await runCLIAsync(`test ${plugin}`));
const buildResults = runCLI(`build ${plugin}`);
expect(buildResults).toContain('Done compiling TypeScript files');
checkFilesExist(
`dist/libs/${plugin}/package.json`,
`dist/libs/${plugin}/collection.json`,
`dist/libs/${plugin}/builders.json`,
`dist/libs/${plugin}/src/index.js`,
`dist/libs/${plugin}/src/schematics/${plugin}/schema.json`,
`dist/libs/${plugin}/src/schematics/${plugin}/schema.d.ts`,
`dist/libs/${plugin}/src/schematics/${plugin}/schematic.js`,
`dist/libs/${plugin}/src/schematics/${plugin}/files/src/index.ts.template`,
`dist/libs/${plugin}/src/builders/${plugin}/builder.js`,
`dist/libs/${plugin}/src/builders/${plugin}/schema.d.ts`,
`dist/libs/${plugin}/src/builders/${plugin}/schema.json`
);
const nxJson = readJson('nx.json');
expect(nxJson).toMatchObject({
projects: expect.objectContaining({
[plugin]: {
tags: []
},
[`${plugin}-e2e`]: {
tags: [],
implicitDependencies: [`${plugin}`]
}
})
});
done();
}, 45000);
it(`should run the plugin's e2e tests`, async done => {
ensureProject();
const plugin = uniq('plugin');
runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter}`);
const results = await runCLIAsync(`e2e ${plugin}-e2e`);
expect(results.stdout).toContain('Compiling TypeScript files');
expectTestsPass(results);
done();
}, 75000);
describe('--directory', () => {
it('should create a plugin in the specified directory', () => {
ensureProject();
const plugin = uniq('plugin');
runCLI(
`generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter} --directory subdir`
);
checkFilesExist(`libs/subdir/${plugin}/package.json`);
const workspace = readJson(workspaceConfigName());
expect(workspace.projects[`subdir-${plugin}`]).toBeTruthy();
expect(workspace.projects[`subdir-${plugin}`].root).toBe(
`libs/subdir/${plugin}`
);
expect(workspace.projects[`subdir-${plugin}-e2e`]).toBeTruthy();
}, 45000);
});
describe('--tags', () => {
it('should add tags to nx.json', async () => {
ensureProject();
const plugin = uniq('plugin');
runCLI(
`generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter} --tags=e2etag,e2ePackage`
);
const nxJson = readJson('nx.json');
expect(nxJson.projects[plugin].tags).toEqual(['e2etag', 'e2ePackage']);
}, 45000);
});
});
});

View File

@ -76,6 +76,7 @@
"@testing-library/react": "9.4.0", "@testing-library/react": "9.4.0",
"@types/express": "4.17.0", "@types/express": "4.17.0",
"@types/fast-levenshtein": "^0.0.1", "@types/fast-levenshtein": "^0.0.1",
"@types/fs-extra": "7.0.0",
"@types/jasmine": "~2.8.6", "@types/jasmine": "~2.8.6",
"@types/jasminewd2": "~2.0.3", "@types/jasminewd2": "~2.0.3",
"@types/jest": "24.0.9", "@types/jest": "24.0.9",

View File

@ -104,6 +104,18 @@ describe('NodeCompileBuilder', () => {
fakeEventEmitter.emit('exit', 0); fakeEventEmitter.emit('exit', 0);
}); });
it('should have the output path in the BuilderOutput', done => {
runNodePackageBuilder(testOptions, context).subscribe({
next: value => {
expect(value.outputPath).toEqual(testOptions.outputPath);
},
complete: () => {
done();
}
});
fakeEventEmitter.emit('exit', 0);
});
describe('Asset copying', () => { describe('Asset copying', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();

View File

@ -11,7 +11,7 @@ import { copy, removeSync } from 'fs-extra';
import * as glob from 'glob'; import * as glob from 'glob';
import { basename, dirname, join, normalize, relative } from 'path'; import { basename, dirname, join, normalize, relative } from 'path';
import { Observable, Subscriber } from 'rxjs'; import { Observable, Subscriber } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators'; import { switchMap, tap, map } from 'rxjs/operators';
import * as treeKill from 'tree-kill'; import * as treeKill from 'tree-kill';
export interface NodePackageBuilderOptions extends JsonObject { export interface NodePackageBuilderOptions extends JsonObject {
@ -26,6 +26,7 @@ export interface NodePackageBuilderOptions extends JsonObject {
interface NormalizedBuilderOptions extends NodePackageBuilderOptions { interface NormalizedBuilderOptions extends NodePackageBuilderOptions {
files: Array<FileInputOutput>; files: Array<FileInputOutput>;
normalizedOutputPath: string;
relativeMainFileOutput: string; relativeMainFileOutput: string;
} }
@ -51,6 +52,12 @@ export function runNodePackageBuilder(
}), }),
switchMap(() => { switchMap(() => {
return copyAssetFiles(normalizedOptions, context); return copyAssetFiles(normalizedOptions, context);
}),
map(value => {
return {
...value,
outputPath: normalizedOptions.outputPath
};
}) })
); );
} }
@ -109,7 +116,8 @@ function normalizeOptions(
return { return {
...options, ...options,
files, files,
relativeMainFileOutput relativeMainFileOutput,
normalizedOutputPath: join(context.workspaceRoot, options.outputPath)
}; };
} }
@ -122,7 +130,7 @@ function compileTypeScriptFiles(
killProcess(context); killProcess(context);
} }
// Cleaning the /dist folder // Cleaning the /dist folder
removeSync(join(context.workspaceRoot, options.outputPath)); removeSync(options.normalizedOutputPath);
return Observable.create((subscriber: Subscriber<BuilderOutput>) => { return Observable.create((subscriber: Subscriber<BuilderOutput>) => {
try { try {
@ -130,7 +138,7 @@ function compileTypeScriptFiles(
'-p', '-p',
join(context.workspaceRoot, options.tsConfig), join(context.workspaceRoot, options.tsConfig),
'--outDir', '--outDir',
join(context.workspaceRoot, options.outputPath) options.normalizedOutputPath
]; ];
if (options.sourceMap) { if (options.sourceMap) {

View File

@ -0,0 +1,248 @@
#!/usr/bin/env node
// we can import from '@nrwl/workspace' because it will require typescript
import { output } from '@nrwl/workspace/src/utils/output';
import { dirSync } from 'tmp';
import { writeFileSync } from 'fs-extra';
import * as path from 'path';
import { execSync } from 'child_process';
import * as inquirer from 'inquirer';
import yargsParser = require('yargs-parser');
const tsVersion = 'TYPESCRIPT_VERSION';
const cliVersion = 'NX_VERSION';
const nxVersion = 'NX_VERSION';
const prettierVersion = 'PRETTIER_VERSION';
const parsedArgs = yargsParser(process.argv, {
string: ['pluginName'],
alias: {
pluginName: 'plugin-name'
},
boolean: ['help']
});
if (parsedArgs.help) {
showHelp();
process.exit(0);
}
const packageManager = determinePackageManager();
determineWorkspaceName(parsedArgs).then(workspaceName => {
return determinPluginName(parsedArgs).then(pluginName => {
const tmpDir = createSandbox(packageManager);
createWorkspace(tmpDir, packageManager, parsedArgs, workspaceName);
createNxPlugin(workspaceName, pluginName);
commitChanges(workspaceName);
showNxWarning(workspaceName);
});
});
function createSandbox(packageManager: string) {
console.log(`Creating a sandbox with Nx...`);
const tmpDir = dirSync().name;
writeFileSync(
path.join(tmpDir, 'package.json'),
JSON.stringify({
dependencies: {
'@nrwl/workspace': nxVersion,
'@nrwl/tao': cliVersion,
typescript: tsVersion,
prettier: prettierVersion
},
license: 'MIT'
})
);
execSync(`${packageManager} install --silent`, {
cwd: tmpDir,
stdio: [0, 1, 2]
});
return tmpDir;
}
function createWorkspace(
tmpDir: string,
packageManager: string,
parsedArgs: any,
name: string
) {
const args = [
name,
...process.argv.slice(parsedArgs._[2] ? 3 : 2).map(a => `"${a}"`)
].join(' ');
console.log(`new ${args} --preset=empty --collection=@nrwl/workspace`);
execSync(
`"${path.join(
tmpDir,
'node_modules',
'.bin',
'tao'
)}" new ${args} --preset=empty --collection=@nrwl/workspace`,
{
stdio: [0, 1, 2]
}
);
execSync(`${packageManager} add -D @nrwl/nx-plugin@${nxVersion}`, {
cwd: name,
stdio: [0, 1, 2]
});
}
function createNxPlugin(workspaceName, pluginName) {
console.log(`nx generate @nrwl/nx-plugin:plugin ${pluginName}`);
execSync(
`node ./node_modules/@nrwl/cli/bin/nx.js generate @nrwl/nx-plugin:plugin ${pluginName}`,
{
cwd: workspaceName,
stdio: [0, 1, 2]
}
);
}
function commitChanges(workspaceName) {
execSync('git add .', {
cwd: workspaceName,
stdio: 'ignore'
});
execSync('git commit --amend --no-edit', {
cwd: workspaceName,
stdio: 'ignore'
});
}
function showNxWarning(workspaceName: string) {
try {
const pathToRunNxCommand = path.resolve(process.cwd(), workspaceName);
execSync('nx --version', {
cwd: pathToRunNxCommand,
stdio: ['ignore', 'ignore', 'ignore']
});
} catch (e) {
// no nx found
output.addVerticalSeparator();
output.note({
title: `Nx CLI is not installed globally.`,
bodyLines: [
`This means that you might have to use "yarn nx" or "npm nx" to execute commands in the workspace.`,
`Run "yarn global add @nrwl/cli" or "npm install -g @nrwl/cli" to be able to execute command directly.`
]
});
}
}
// Move to @nrwl/workspace package?
function determinePackageManager() {
let packageManager = getPackageManagerFromAngularCLI();
if (packageManager === 'npm' || isPackageManagerInstalled(packageManager)) {
return packageManager;
}
if (isPackageManagerInstalled('yarn')) {
return 'yarn';
}
if (isPackageManagerInstalled('pnpm')) {
return 'pnpm';
}
return 'npm';
}
function getPackageManagerFromAngularCLI(): string {
// If you have Angular CLI installed, read Angular CLI config.
// If it isn't installed, default to 'yarn'.
try {
return execSync('ng config -g cli.packageManager', {
stdio: ['ignore', 'pipe', 'ignore'],
timeout: 500
})
.toString()
.trim();
} catch (e) {
return 'yarn';
}
}
function isPackageManagerInstalled(packageManager: string) {
let isInstalled = false;
try {
execSync(`${packageManager} --version`, {
stdio: ['ignore', 'ignore', 'ignore']
});
isInstalled = true;
} catch (e) {
/* do nothing */
}
return isInstalled;
}
function determineWorkspaceName(parsedArgs: any): Promise<string> {
const workspaceName: string = parsedArgs._[2];
if (workspaceName) {
return Promise.resolve(workspaceName);
}
return inquirer
.prompt([
{
name: 'WorkspaceName',
message: `Workspace name (e.g., org name) `,
type: 'string'
}
])
.then(a => {
if (!a.WorkspaceName) {
output.error({
title: 'Invalid workspace name',
bodyLines: [`Workspace name cannot be empty`]
});
process.exit(1);
}
return a.WorkspaceName;
});
}
function determinPluginName(parsedArgs) {
if (parsedArgs.pluginName) {
return Promise.resolve(parsedArgs.pluginName);
}
return inquirer
.prompt([
{
name: 'PluginName',
message: `Plugin name `,
type: 'string'
}
])
.then(a => {
if (!a.PluginName) {
output.error({
title: 'Invalid name',
bodyLines: [`Name cannot be empty`]
});
process.exit(1);
}
return a.PluginName;
});
}
function showHelp() {
console.log(`
Usage: <name> [options]
Create a new Nx workspace
Args:
name workspace name
Options:
pluginName the name of the plugin to be created
`);
}

View File

@ -0,0 +1,10 @@
{
"$schema": "@angular-devkit/architect/src/builders-schema.json",
"builders": {
"e2e": {
"implementation": "./src/builders/e2e/e2e.impl",
"schema": "./src/builders/e2e/schema.json",
"description": "Creates and runs an e2e for a Nx Plugin"
}
}
}

View File

@ -0,0 +1,18 @@
{
"name": "Nx Plugin",
"version": "0.1",
"extends": ["@nrwl/workspace"],
"schematics": {
"plugin": {
"factory": "./src/schematics/plugin/plugin",
"schema": "./src/schematics/plugin/schema.json",
"description": "Create a Nx Plugin"
},
"e2e-project": {
"factory": "./src/schematics/e2e-project/e2e",
"schema": "./src/schematics/e2e-project/schema.json",
"description": "Create a e2e application for a Nx Plugin",
"hidden": true
}
}
}

View File

View File

@ -0,0 +1,3 @@
{
"schematics": {}
}

View File

@ -0,0 +1,49 @@
{
"name": "@nrwl/nx-plugin",
"version": "0.0.1",
"description": "Node Plugin for Nx",
"repository": {
"type": "git",
"url": "git+https://github.com/nrwl/nx.git"
},
"keywords": [
"Monorepo",
"Node",
"Nest",
"Jest",
"Cypress",
"CLI"
],
"main": "./index.js",
"types": "./index.d.ts",
"bin": "./bin/create.js",
"author": "Nrwl",
"license": "MIT",
"bugs": {
"url": "https://github.com/nrwl/nx/issues"
},
"homepage": "https://nx.dev",
"schematics": "./collection.json",
"builders": "./builders.json",
"ng-update": {
"requirements": {},
"migrations": "./migrations.json"
},
"peerDependencies": {
"@nrwl/workspace": "*"
},
"dependencies": {
"@nrwl/node": "*",
"@nrwl/linter": "*",
"@nrwl/workspace": "*",
"@nrwl/tao": "*",
"@angular-devkit/architect": "0.803.23",
"@angular-devkit/core": "8.3.23",
"@angular-devkit/schematics": "8.3.23",
"fs-extra": "7.0.1",
"tmp": "0.0.33",
"yargs-parser": "10.0.0",
"yargs": "^11.0.0",
"inquirer": "^6.3.1"
}
}

View File

@ -0,0 +1,64 @@
import { NxPluginE2EBuilderOptions, runNxPluginE2EBuilder } from './e2e.impl';
import { MockBuilderContext } from '@nrwl/workspace/testing';
import { getMockContext } from '../../utils/testing';
import * as devkitArchitect from '@angular-devkit/architect';
import { of } from 'rxjs';
describe('NxPluginE2EBuilder', () => {
let testOptions: NxPluginE2EBuilderOptions;
let context: MockBuilderContext;
let scheduleTargetAndForgetSpy: jest.SpyInstance;
let contextBuilderSpy: jest.SpyInstance;
beforeEach(async () => {
context = await getMockContext();
context.addTarget(
{ project: 'plugin-e2e', target: 'build' },
'@nrwl/nx-plugin:e2e'
);
testOptions = {
jestConfig: 'apps/plugin-e2e/jest.config.js',
tsSpecConfig: 'apps/plugin-e2e/tsconfig.spec.js',
target: 'plugin:build'
};
scheduleTargetAndForgetSpy = jest
.spyOn(devkitArchitect, 'scheduleTargetAndForget')
.mockImplementation((context, options) => {
debugger;
return of({ success: true });
});
contextBuilderSpy = jest
.spyOn(context, 'scheduleBuilder')
.mockImplementation((name, overrides) => {
console.log('hello');
return new Promise((res, rej) => {
res({
result: of({ success: true }).toPromise(),
id: 1,
info: {
builderName: 'builder',
description: '',
optionSchema: {}
},
output: of({ success: true }),
progress: of({} as any),
stop: jest.fn()
});
});
});
});
it('should build the plugin and run the test', async () => {
await runNxPluginE2EBuilder(testOptions, context).toPromise();
expect(scheduleTargetAndForgetSpy).toHaveBeenCalledWith(context, {
project: 'plugin',
target: 'build'
});
expect(contextBuilderSpy).toHaveBeenCalledWith('@nrwl/jest:jest', {
tsConfig: testOptions.tsSpecConfig,
jestConfig: testOptions.jestConfig,
watch: false
});
});
});

View File

@ -0,0 +1,37 @@
import {
BuilderContext,
createBuilder,
scheduleTargetAndForget,
targetFromTargetString
} from '@angular-devkit/architect';
import { switchMap, concatMap } from 'rxjs/operators';
import { Schema } from './schema';
import { from } from 'rxjs';
try {
require('dotenv').config();
} catch (e) {}
export interface NxPluginE2EBuilderOptions extends Schema {}
export default createBuilder(runNxPluginE2EBuilder);
export function runNxPluginE2EBuilder(
options: NxPluginE2EBuilderOptions,
context: BuilderContext
) {
return buildTarget(context, options.target).pipe(
switchMap(() => {
return from(
context.scheduleBuilder('@nrwl/jest:jest', {
tsConfig: options.tsSpecConfig,
jestConfig: options.jestConfig,
watch: false
})
).pipe(concatMap(run => run.output));
})
);
}
function buildTarget(context: BuilderContext, target: string) {
return scheduleTargetAndForget(context, targetFromTargetString(target));
}

View File

@ -0,0 +1,7 @@
import { JsonObject } from '@angular-devkit/core';
export interface Schema extends JsonObject {
target: string;
jestConfig: string;
tsSpecConfig: string;
}

View File

@ -0,0 +1,20 @@
{
"title": "Nx Plugin Playground Target",
"description": "Creates a playground for a Nx Plugin",
"type": "object",
"properties": {
"target": {
"type": "string",
"description": "the target Nx Plugin project and build"
},
"jestConfig": {
"type": "string",
"description": "Jest config file"
},
"tsSpecConfig": {
"type": "string",
"description": "Spec tsconfig file"
}
},
"required": ["target", "jestConfig", "tsSpecConfig"]
}

View File

@ -0,0 +1,130 @@
import { Tree } from '@angular-devkit/schematics';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import {
updateWorkspace,
readJsonInTree,
readWorkspace,
getWorkspace
} from '@nrwl/workspace';
import { runSchematic } from '../../utils/testing';
describe('NxPlugin e2e-project', () => {
let appTree: Tree;
beforeEach(() => {
appTree = createEmptyWorkspace(Tree.empty());
// add a plugin project to the workspace for validations
updateWorkspace(workspace => {
workspace.projects.add({ name: 'my-plugin', root: 'libs/my-plugin' });
})(appTree, null);
});
it('should validate the plugin name', async () => {
await expect(
runSchematic(
'e2e-project',
{
pluginName: 'my-plugin',
pluginOutputPath: `dist/libs/my-plugin`,
npmPackageName: '@proj/my-plugin'
},
appTree
)
).resolves.not.toThrow();
await expect(
runSchematic(
'e2e-project',
{
pluginName: 'my-nonexistentplugin',
pluginOutputPath: `dist/libs/my-nonexistentplugin`,
npmPackageName: '@proj/my-nonexistentplugin'
},
appTree
)
).rejects.toThrow();
});
it('should add files related to e2e', async () => {
const tree = await runSchematic(
'e2e-project',
{
pluginName: 'my-plugin',
pluginOutputPath: `dist/libs/my-plugin`,
npmPackageName: '@proj/my-plugin'
},
appTree
);
expect(tree.exists('apps/my-plugin-e2e/tsconfig.json')).toBeTruthy();
expect(
tree.exists('apps/my-plugin-e2e/tests/my-plugin.test.ts')
).toBeTruthy();
});
it('should update the nxJson', async () => {
const tree = await runSchematic(
'e2e-project',
{
pluginName: 'my-plugin',
pluginOutputPath: `dist/libs/my-plugin`,
npmPackageName: '@proj/my-plugin'
},
appTree
);
expect(JSON.parse(tree.readContent('nx.json'))).toMatchObject({
projects: {
'my-plugin-e2e': {
tags: [],
implicitDependencies: ['my-plugin']
}
}
});
});
it('should update the workspace', async () => {
const tree = await runSchematic(
'e2e-project',
{
pluginName: 'my-plugin',
pluginOutputPath: `dist/libs/my-plugin`,
npmPackageName: '@proj/my-plugin'
},
appTree
);
const workspace = await getWorkspace(tree);
const project = workspace.projects.get('my-plugin-e2e');
expect(project).toBeTruthy();
expect(project.root).toEqual('apps/my-plugin-e2e');
expect(project.targets.get('e2e')).toBeTruthy();
expect(project.targets.get('e2e')).toMatchObject({
builder: '@nrwl/nx-plugin:e2e',
options: expect.objectContaining({
target: 'my-plugin:build',
npmPackageName: '@proj/my-plugin',
pluginOutputPath: 'dist/libs/my-plugin'
})
});
});
it('should add jest support', async () => {
const tree = await runSchematic(
'e2e-project',
{
pluginName: 'my-plugin',
pluginOutputPath: `dist/libs/my-plugin`,
npmPackageName: '@proj/my-plugin'
},
appTree
);
const workspace = await getWorkspace(tree);
const project = workspace.projects.get('my-plugin-e2e');
expect(project.targets.get('e2e')).toMatchObject({
options: expect.objectContaining({
tsSpecConfig: 'apps/my-plugin-e2e/tsconfig.spec.json',
jestConfig: 'apps/my-plugin-e2e/jest.config.js'
})
});
expect(tree.exists('apps/my-plugin-e2e/tsconfig.spec.json')).toBeTruthy();
expect(tree.exists('apps/my-plugin-e2e/jest.config.js')).toBeTruthy();
});
});

View File

@ -0,0 +1,143 @@
import { normalize } from '@angular-devkit/core';
import { WorkspaceDefinition } from '@angular-devkit/core/src/workspace';
import {
apply,
chain,
externalSchematic,
mergeWith,
move,
Rule,
SchematicContext,
SchematicsException,
template,
Tree,
url
} from '@angular-devkit/schematics';
import {
addProjectToNxJsonInTree,
getWorkspace,
offsetFromRoot,
readNxJsonInTree,
toPropertyName,
updateWorkspace
} from '@nrwl/workspace';
import { join } from 'path';
import { Schema } from './schema';
export interface NxPluginE2ESchema extends Schema {
projectRoot: string;
projectName: string;
pluginPropertyName: string;
npmScope: string;
}
export default function(options: Schema): Rule {
return async (host: Tree, context: SchematicContext) => {
const workspace = await getWorkspace(host);
validatePlugin(workspace, options.pluginName);
const normalizedOptions = normalizeOptions(host, options);
return chain([
updateFiles(normalizedOptions),
updateNxJson(normalizedOptions),
updateWorkspaceJson(normalizedOptions),
addJest(normalizedOptions)
]);
};
}
function validatePlugin(workspace: WorkspaceDefinition, pluginName: string) {
const project = workspace.projects.get(pluginName);
if (!project) {
throw new SchematicsException(
`Project name "${pluginName}" doesn't not exist.`
);
}
}
function normalizeOptions(host: Tree, options: Schema): NxPluginE2ESchema {
const projectName = `${options.pluginName}-e2e`;
const projectRoot = join(normalize('apps'), projectName);
const npmScope = readNxJsonInTree(host).npmScope;
const pluginPropertyName = toPropertyName(options.pluginName);
return {
...options,
projectName,
pluginPropertyName,
projectRoot,
npmScope
};
}
function updateNxJson(options: NxPluginE2ESchema): Rule {
return addProjectToNxJsonInTree(options.projectName, {
tags: [],
implicitDependencies: [options.pluginName]
});
}
function updateWorkspaceJson(options: NxPluginE2ESchema): Rule {
return chain([
async (host, context) => {
const workspace = await getWorkspace(host);
workspace.projects.add({
name: options.projectName,
root: options.projectRoot,
projectType: 'application',
sourceRoot: `${options.projectRoot}/src`,
targets: {
e2e: {
builder: '@nrwl/nx-plugin:e2e',
options: {
target: `${options.pluginName}:build`,
npmPackageName: options.npmPackageName,
pluginOutputPath: options.pluginOutputPath
}
}
}
});
return updateWorkspace(workspace);
}
]);
}
function updateFiles(options: NxPluginE2ESchema): Rule {
return mergeWith(
apply(url('./files'), [
template({
tmpl: '',
...options,
offsetFromRoot: offsetFromRoot(options.projectRoot)
}),
move(options.projectRoot)
])
);
}
function addJest(options: NxPluginE2ESchema): Rule {
return chain([
externalSchematic('@nrwl/jest', 'jest-project', {
project: options.projectName,
setupFile: 'none',
supportTsx: false,
skipSerializers: true
}),
async (host, context) => {
const workspace = await getWorkspace(host);
const project = workspace.projects.get(options.projectName);
const testOptions = project.targets.get('test').options;
const e2eOptions = project.targets.get('e2e').options;
project.targets.get('e2e').options = {
...e2eOptions,
...{
jestConfig: testOptions.jestConfig,
tsSpecConfig: testOptions.tsConfig
}
};
// remove the jest build target
project.targets.delete('test');
return updateWorkspace(workspace);
}
]);
}

View File

@ -0,0 +1,44 @@
import {
checkFilesExist,
ensureNxProject,
readJson,
runNxCommandAsync,
uniq,
} from '@nrwl/nx-plugin/testing';
describe('<%= pluginName %> e2e', () => {
it('should create <%= pluginName %>', async (done) => {
const plugin = uniq('<%= pluginName %>');
ensureNxProject('<%= npmPackageName %>', '<%= pluginOutputPath %>');
await runNxCommandAsync(`generate <%=npmPackageName%>:<%= pluginPropertyName %> ${plugin}`);
const result = await runNxCommandAsync(`build ${plugin}`);
expect(result.stdout).toContain('Builder ran');
done();
})
describe('--directory', () => {
it('should create src in the specified directory', async (done) => {
const plugin = uniq('<%= pluginName %>');
ensureNxProject('<%= npmPackageName %>', '<%= pluginOutputPath %>');
await runNxCommandAsync(
`generate <%=npmPackageName%>:<%= pluginPropertyName %> ${plugin} --directory subdir`
);
expect(() => checkFilesExist(`libs/subdir/${plugin}/src/index.ts`)).not.toThrow();
done();
});
});
describe('--tags', () => {
it('should add tags to nx.json', async (done) => {
const plugin = uniq('<%= pluginName %>');
ensureNxProject('<%= npmPackageName %>', '<%= pluginOutputPath %>');
await runNxCommandAsync(
`generate <%=npmPackageName%>:<%= pluginPropertyName %> ${plugin} --tags e2etag,e2ePackage`
);
const nxJson = readJson('nx.json');
expect(nxJson.projects[plugin].tags).toEqual(['e2etag', 'e2ePackage']);
done();
});
});
})

View File

@ -0,0 +1,9 @@
{
"extends": "<%= offsetFromRoot %>tsconfig.json",
"compilerOptions": {
"types": [
"node"
]
},
"include": ["**/*.ts"]
}

View File

@ -0,0 +1,9 @@
import { Linter } from '@nrwl/workspace';
export interface Schema {
pluginName: string;
npmPackageName: string;
pluginOutputPath: string;
jestConfig: string;
tsSpecConfig: string;
}

View File

@ -0,0 +1,28 @@
{
"id": "NxPluginE2E",
"title": "Create an e2e app for a Nx Plugin",
"type": "object",
"properties": {
"pluginName": {
"type": "string",
"description": "the name of the pluging to be tested"
},
"npmPackageName": {
"type": "string",
"description": "the name of the package that would be published to NPM"
},
"pluginOutputPath": {
"type": "string",
"description": "the output path of the plugin after it builds"
},
"jestConfig": {
"type": "string",
"description": "Jest config file"
},
"tsSpecConfig": {
"type": "string",
"description": "Spec tsconfig file"
}
},
"required": ["pluginName", "npmPackageName"]
}

View File

@ -0,0 +1,10 @@
{
"$schema": "<%= offsetFromRoot %>node_modules/@angular-devkit/architect/src/builders-schema.json",
"builders": {
"build": {
"implementation": "./src/builders/<%= fileName %>/builder",
"schema": "./src/builders/<%= fileName %>/schema.json",
"description": "<%= name %> builder"
}
}
}

View File

@ -0,0 +1,12 @@
{
"$schema": "<%= offsetFromRoot %>node_modules/@angular-devkit/schematics/collection-schema.json",
"name": "<%= name %>",
"version": "0.0.1",
"schematics": {
"<%= propertyName %>": {
"factory": "./src/schematics/<%= fileName %>/schematic",
"schema": "./src/schematics/<%= fileName %>/schema.json",
"description": "<%= name %> schematic"
}
}
}

View File

@ -0,0 +1,7 @@
{
"name": "<%= npmPackageName %>",
"version": "0.0.1",
"main": "src/index.js",
"schematics": "./collection.json",
"builders": "./builders.json",
}

View File

@ -0,0 +1,40 @@
import { Architect } from '@angular-devkit/architect';
import { TestingArchitectHost } from '@angular-devkit/architect/testing';
import { schema } from '@angular-devkit/core';
import { join } from 'path';
import { <%= className %>BuilderSchema } from './schema';
const options: <%= className %>BuilderSchema = {};
describe('Command Runner Builder', () => {
let architect: Architect;
let architectHost: TestingArchitectHost;
beforeEach(async () => {
const registry = new schema.CoreSchemaRegistry();
registry.addPostTransform(schema.transforms.addUndefinedDefaults);
architectHost = new TestingArchitectHost('/root', '/root');
architect = new Architect(architectHost, registry);
// This will either take a Node package name, or a path to the directory
// for the package.json file.
await architectHost.addBuilderFromPackage(join(__dirname, '../../..'));
});
it('can run', async () => {
// A "run" can have multiple outputs, and contains progress information.
const run = await architect.scheduleBuilder('@<%= npmScope %>/<%= fileName %>:build', options); // We pass the logger for checking later.
// The "result" member (of type BuilderOutput) is the next output.
const output = await run.result;
// Stop the builder from running. This stops Architect from keeping
// the builder-associated states in memory, since builders keep waiting
// to be scheduled.
await run.stop();
// Expect that it succeeded.
expect(output.success).toBe(true);
});
});

View File

@ -0,0 +1,19 @@
import {
BuilderContext,
BuilderOutput,
createBuilder
} from '@angular-devkit/architect';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { <%= className %>BuilderSchema } from './schema';
export function runBuilder(
options: <%= className %>BuilderSchema,
context: BuilderContext
): Observable<BuilderOutput> {
return of({ success: true }).pipe(tap(() => {
context.logger.info("Builder ran for <%= name %>");
}));
}
export default createBuilder(runBuilder);

View File

@ -0,0 +1,3 @@
import { JsonObject } from '@angular-devkit/core';
export interface <%= className %>BuilderSchema extends JsonObject {}

View File

@ -0,0 +1,9 @@
{
"$schema": "https://json-schema.org/draft-07/schema",
"$id": "https://json-schema.org/draft-07/schema",
"title": "<%= className %> builder",
"description": "",
"type": "object",
"properties": {},
"required": []
}

View File

@ -0,0 +1,5 @@
export interface <%= className %>SchematicSchema {
name: string;
tags?: string;
directory?: string;
}

View File

@ -0,0 +1,28 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"id": "<%= className %>",
"title": "",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What name would you like to use?"
},
"tags": {
"type": "string",
"description": "Add tags to the project (used for linting)",
"alias": "t"
},
"directory": {
"type": "string",
"description": "A directory where the project is placed",
"alias": "d"
}
},
"required": ["name"]
}

View File

@ -0,0 +1,29 @@
import { Tree } from '@angular-devkit/schematics';
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { join } from 'path'
import { <%= className %>SchematicSchema } from './schema';
describe('<%= name %> schematic', () => {
let appTree: Tree;
const options: <%= className %>SchematicSchema = { name: 'test' };
const testRunner = new SchematicTestRunner(
'@<%= npmScope %>/<%= name %>',
join(__dirname, '../../../collection.json')
);
beforeEach(() => {
appTree = createEmptyWorkspace(Tree.empty());
});
it('should run successfully', async () => {
await expect(testRunner.runSchematicAsync(
'<%= propertyName %>',
options,
appTree
).toPromise()
).resolves.not.toThrowError();
})
});

View File

@ -0,0 +1,85 @@
import {
apply,
applyTemplates,
chain,
mergeWith,
move,
Rule,
url
} from '@angular-devkit/schematics';
import {
addProjectToNxJsonInTree,
names,
offsetFromRoot,
projectRootDir,
ProjectType,
toFileName,
updateWorkspace
} from '@nrwl/workspace';
import { <%= className %>SchematicSchema } from './schema';
/**
* Depending on your needs, you can change this to either `Library` or `Application`
*/
const projectType = ProjectType.Library
interface NormalizedSchema extends <%= className %>SchematicSchema {
projectName: string;
projectRoot: string;
projectDirectory: string;
parsedTags: string[]
}
function normalizeOptions(options: <%= className %>SchematicSchema): NormalizedSchema {
const name = toFileName(options.name);
const projectDirectory = options.directory
? `${toFileName(options.directory)}/${name}`
: name;
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
const projectRoot = `${projectRootDir(projectType)}/${projectDirectory}`;
const parsedTags = options.tags
? options.tags.split(',').map(s => s.trim())
: [];
return {
...options,
projectName,
projectRoot,
projectDirectory,
parsedTags
};
}
function addFiles(options: NormalizedSchema): Rule {
return mergeWith(
apply(url(`./files`), [
applyTemplates({
...options,
...names(options.name),
offsetFromRoot: offsetFromRoot(options.projectRoot)
}),
move(options.projectRoot)
])
)
}
export default function(options: <%= className %>SchematicSchema): Rule {
const normalizedOptions = normalizeOptions(options);
return chain([
updateWorkspace(workspace => {
workspace.projects.add({
name: normalizedOptions.projectName,
root: normalizedOptions.projectRoot,
sourceRoot: `${normalizedOptions.projectRoot}/src`,
projectType
}).targets.add({
name: 'build',
builder: '<%= npmPackageName %>:build'
})
}),
addProjectToNxJsonInTree(normalizedOptions.projectName, { tags: normalizedOptions.parsedTags }),
addFiles(normalizedOptions)
]);
}

View File

@ -0,0 +1,139 @@
import * as ngSchematics from '@angular-devkit/schematics';
import { readJsonInTree, readWorkspace } from '@nrwl/workspace';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { runSchematic } from '../../utils/testing';
describe('NxPlugin plugin', () => {
let appTree: ngSchematics.Tree;
beforeEach(() => {
appTree = createEmptyWorkspace(ngSchematics.Tree.empty());
});
it('should update the workspace.json file', async () => {
const tree = await runSchematic('plugin', { name: 'myPlugin' }, appTree);
const workspace = await readWorkspace(tree);
const project = workspace.projects['my-plugin'];
expect(project.root).toEqual('libs/my-plugin');
expect(project.architect.build).toEqual({
builder: '@nrwl/node:package',
options: {
outputPath: 'dist/libs/my-plugin',
tsConfig: 'libs/my-plugin/tsconfig.lib.json',
packageJson: 'libs/my-plugin/package.json',
main: 'libs/my-plugin/src/index.ts',
assets: [
'libs/my-plugin/*.md',
{
input: './libs/my-plugin/src',
glob: '**/*.!(ts)',
output: './src'
},
{
input: './libs/my-plugin',
glob: 'collection.json',
output: '.'
},
{
input: './libs/my-plugin',
glob: 'builders.json',
output: '.'
}
]
}
});
expect(project.architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint',
options: {
exclude: ['**/node_modules/**', '!libs/my-plugin/**'],
tsConfig: [
'libs/my-plugin/tsconfig.lib.json',
'libs/my-plugin/tsconfig.spec.json'
]
}
});
expect(project.architect.test).toEqual({
builder: '@nrwl/jest:jest',
options: {
jestConfig: 'libs/my-plugin/jest.config.js',
tsConfig: 'libs/my-plugin/tsconfig.spec.json'
}
});
});
it('should update the tsconfig.lib.json file', async () => {
const tree = await runSchematic('plugin', { name: 'myPlugin' }, appTree);
const tsLibConfig = readJsonInTree(
tree,
'libs/my-plugin/tsconfig.lib.json'
);
expect(tsLibConfig.compilerOptions.rootDir).toEqual('.');
});
it('should create schematic and builder files', async () => {
const tree = await runSchematic('plugin', { name: 'myPlugin' }, appTree);
expect(tree.exists('libs/my-plugin/collection.json')).toBeTruthy();
expect(tree.exists('libs/my-plugin/builders.json')).toBeTruthy();
expect(
tree.exists('libs/my-plugin/src/schematics/my-plugin/schema.d.ts')
).toBeTruthy();
expect(
tree.exists('libs/my-plugin/src/schematics/my-plugin/schematic.ts')
).toBeTruthy();
expect(
tree.exists('libs/my-plugin/src/schematics/my-plugin/schematic.spec.ts')
).toBeTruthy();
expect(
tree.exists('libs/my-plugin/src/schematics/my-plugin/schema.json')
).toBeTruthy();
expect(
tree.exists('libs/my-plugin/src/schematics/my-plugin/schema.d.ts')
).toBeTruthy();
expect(
tree.exists(
'libs/my-plugin/src/schematics/my-plugin/files/src/index.ts.template'
)
).toBeTruthy();
expect(
tree.readContent(
'libs/my-plugin/src/schematics/my-plugin/files/src/index.ts.template'
)
).toContain('const variable = "<%= projectName %>";');
expect(
tree.exists('libs/my-plugin/src/builders/my-plugin/builder.ts')
).toBeTruthy();
expect(
tree.exists('libs/my-plugin/src/builders/my-plugin/builder.spec.ts')
).toBeTruthy();
expect(
tree.exists('libs/my-plugin/src/builders/my-plugin/schema.json')
).toBeTruthy();
expect(
tree.exists('libs/my-plugin/src/builders/my-plugin/schema.d.ts')
).toBeTruthy();
});
it('should call the @nrwl/node:lib schematic', async () => {
const externalSchematicSpy = jest.spyOn(ngSchematics, 'externalSchematic');
await runSchematic('plugin', { name: 'myPlugin' }, appTree);
expect(externalSchematicSpy).toBeCalledWith(
'@nrwl/node',
'lib',
expect.objectContaining({
publishable: true
})
);
});
it('should call the @nrwl/nx-plugin:e2e schematic', async () => {
const schematicSpy = jest.spyOn(ngSchematics, 'schematic');
const tree = await runSchematic('plugin', { name: 'myPlugin' }, appTree);
expect(schematicSpy).toBeCalledWith(
'e2e-project',
expect.objectContaining({
pluginName: 'my-plugin',
pluginOutputPath: `dist/libs/my-plugin`,
npmPackageName: '@proj/my-plugin'
})
);
});
});

View File

@ -0,0 +1,165 @@
import { JsonArray, normalize, Path } from '@angular-devkit/core';
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import {
apply,
chain,
externalSchematic,
MergeStrategy,
mergeWith,
move,
Rule,
schematic,
SchematicContext,
template,
Tree,
url
} from '@angular-devkit/schematics';
import {
formatFiles,
getProjectConfig,
names,
offsetFromRoot,
readNxJsonInTree,
toFileName,
updateJsonInTree,
updateWorkspace
} from '@nrwl/workspace';
import { allFilesInDirInHost } from '@nrwl/workspace/src/utils/ast-utils';
import { Schema } from './schema';
export interface NormalizedSchema extends Schema {
name: string;
fileName: string;
projectRoot: Path;
projectDirectory: string;
parsedTags: string[];
npmScope: string;
npmPackageName: string;
fileTemplate: string;
}
export default function(schema: NormalizedSchema): Rule {
return (host: Tree, context: SchematicContext) => {
const options = normalizeOptions(host, schema);
return chain([
externalSchematic('@nrwl/node', 'lib', {
...schema,
publishable: true
}),
addFiles(options),
updateWorkspaceJson(options),
updateTsConfig(options),
schematic('e2e-project', {
pluginName: options.name,
pluginOutputPath: `dist/libs/${options.projectDirectory}`,
npmPackageName: options.npmPackageName
}),
formatFiles(options)
]);
};
}
function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
const nxJson = readNxJsonInTree(host);
const npmScope = nxJson.npmScope;
const name = toFileName(options.name);
const projectDirectory = options.directory
? `${toFileName(options.directory)}/${name}`
: name;
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
const fileName = projectName;
const projectRoot = normalize(`libs/${projectDirectory}`);
const parsedTags = options.tags
? options.tags.split(',').map(s => s.trim())
: [];
const npmPackageName = `@${npmScope}/${name}`;
const fileTemplate = getFileTemplate();
const normalized: NormalizedSchema = {
...options,
fileName,
npmScope,
name: projectName,
projectRoot,
projectDirectory,
parsedTags,
npmPackageName,
fileTemplate
};
return normalized;
}
function addFiles(options: NormalizedSchema): Rule {
return chain([
host => {
allFilesInDirInHost(
host,
normalize(`${options.projectRoot}/src/lib`)
).forEach(file => {
host.delete(file);
});
return host;
},
mergeWith(
apply(url(`./files/plugin`), [
template({
...options,
...names(options.name),
tmpl: '',
offsetFromRoot: offsetFromRoot(options.projectRoot)
}),
move(options.projectRoot)
]),
MergeStrategy.Overwrite
)
]);
}
function updateWorkspaceJson(options: NormalizedSchema): Rule {
return updateWorkspace(workspace => {
const targets = workspace.projects.get(options.name).targets;
const build = targets.get('build');
if (build) {
(build.options.assets as JsonArray).push(
...[
{
input: `./${options.projectRoot}/src`,
glob: '**/*.!(ts)',
output: './src'
},
{
input: `./${options.projectRoot}`,
glob: 'collection.json',
output: '.'
},
{
input: `./${options.projectRoot}`,
glob: 'builders.json',
output: '.'
}
]
);
}
});
}
function updateTsConfig(options: NormalizedSchema): Rule {
return (host: Tree, context: SchematicContext) => {
const projectConfig = getProjectConfig(host, options.name);
return updateJsonInTree(`${projectConfig.root}/tsconfig.lib.json`, json => {
json.compilerOptions.rootDir = '.';
return json;
});
};
}
function getFileTemplate() {
return stripIndents`
const variable = "<%= projectName %>";
`;
}

View File

@ -0,0 +1,11 @@
import { Linter } from '@nrwl/workspace';
export interface Schema {
name: string;
directory?: string;
skipTsConfig: boolean;
skipFormat: boolean;
tags?: string;
unitTestRunner: 'jest' | 'none';
linter: Linter;
}

View File

@ -0,0 +1,56 @@
{
"$schema": "http://json-schema.org/schema",
"id": "NxPluginPlugin",
"title": "Create a Plugin for Nx",
"type": "object",
"examples": [
{
"command": "g plugin my-plugin --directory=plugins",
"description": "Generate libs/plugins/my-plugin"
}
],
"properties": {
"name": {
"type": "string",
"description": "Plugin name",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What name would you like to use for the plugin?"
},
"directory": {
"type": "string",
"description": "A directory where the plugin is placed",
"alias": "d"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "tslint"],
"default": "tslint"
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"description": "Test runner to use for unit tests",
"default": "jest"
},
"tags": {
"type": "string",
"description": "Add tags to the library (used for linting)",
"alias": "t"
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"skipTsConfig": {
"type": "boolean",
"default": false,
"description": "Do not update tsconfig.json for development experience."
}
},
"required": ["name"]
}

View File

@ -0,0 +1,46 @@
import { exec } from 'child_process';
import { tmpProjPath } from './paths';
/**
* Run a command asynchronously
* @param command
* @param opts
*/
export function runCommandAsync(
command: string,
opts = {
silenceError: false
}
): Promise<{ stdout: string; stderr: string }> {
return new Promise((resolve, reject) => {
exec(
command,
{
cwd: tmpProjPath()
},
(err, stdout, stderr) => {
if (!opts.silenceError && err) {
reject(err);
}
resolve({ stdout, stderr });
}
);
});
}
/**
* Run a nx command asynchronously
* @param command
* @param opts
*/
export function runNxCommandAsync(
command: string,
opts = {
silenceError: false
}
): Promise<{ stdout: string; stderr: string }> {
return runCommandAsync(
`node ./node_modules/@nrwl/cli/bin/nx.js ${command}`,
opts
);
}

View File

@ -0,0 +1,43 @@
import { execSync } from 'child_process';
import { tmpProjPath } from './paths';
/**
* Run a nx command
* @param command
* @param opts
*/
export function runNxCommand(
command?: string,
opts = {
silenceError: false
}
): string {
try {
return execSync(`node ./node_modules/@nrwl/cli/bin/nx.js ${command}`, {
cwd: tmpProjPath()
})
.toString()
.replace(
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
''
);
} catch (e) {
if (opts.silenceError) {
return e.stdout.toString();
} else {
console.log(e.stdout.toString(), e.stderr.toString());
throw e;
}
}
}
export function runCommand(command: string): string {
try {
return execSync(command, {
cwd: tmpProjPath(),
stdio: ['pipe', 'pipe', 'pipe']
}).toString();
} catch (e) {
return e.stdout.toString() + e.stderr.toString();
}
}

View File

@ -0,0 +1,5 @@
export * from './async-commands';
export * from './commands';
export * from './paths';
export * from './nx-project';
export * from './utils';

View File

@ -0,0 +1,71 @@
import { appRootPath } from '@nrwl/workspace/src/utils/app-root';
import { execSync } from 'child_process';
import { readFileSync, writeFileSync } from 'fs';
import { ensureDirSync } from 'fs-extra';
import { tmpProjPath } from './paths';
import { cleanup, copyNodeModules } from './utils';
function runNxNewCommand(args?: string, silent?: boolean) {
const localTmpDir = `./tmp/nx-e2e`;
return execSync(
`node ${require.resolve(
'@nrwl/tao'
)} new proj --no-interactive --skip-install --collection=@nrwl/workspace --npmScope=proj ${args ||
''}`,
{
cwd: localTmpDir,
...(silent && false ? { stdio: ['ignore', 'ignore', 'ignore'] } : {})
}
);
}
function patchPackageJsonForPlugin(npmPackageName: string, distPath: string) {
const p = JSON.parse(readFileSync(tmpProjPath('package.json')).toString());
p.devDependencies[npmPackageName] = `file:${appRootPath}/${distPath}`;
writeFileSync(tmpProjPath('package.json'), JSON.stringify(p, null, 2));
}
/**
* Generate a unique name for running CLI commands
* @param prefix
*/
export function uniq(prefix: string) {
return `${prefix}${Math.floor(Math.random() * 10000000)}`;
}
export function runYarnInstall(silent: boolean = true) {
const install = execSync('yarn install', {
cwd: tmpProjPath(),
...(silent ? { stdio: ['ignore', 'ignore', 'ignore'] } : {})
});
return install ? install.toString() : '';
}
/**
* Sets up a new project in the temporary project path
* for the currently selected CLI.
*/
export function newNxProject(
npmPackageName: string,
pluginDistPath: string
): void {
cleanup();
runNxNewCommand('', true);
patchPackageJsonForPlugin(npmPackageName, pluginDistPath);
runYarnInstall();
}
/**
* Ensures that a project has been setup
* in the temporary project path
*
* If one is not found, it creates a new project.
*/
export function ensureNxProject(
npmPackageName?: string,
pluginDistPath?: string
): void {
ensureDirSync(tmpProjPath());
newNxProject(npmPackageName, pluginDistPath);
copyNodeModules(['@nrwl']);
}

View File

@ -0,0 +1,11 @@
export function tmpProjPath(path?: string) {
return path
? `${process.cwd()}/tmp/nx-e2e/proj/${path}`
: `${process.cwd()}/tmp/nx-e2e/proj`;
}
export function tmpBackupProjPath(path?: string) {
return path
? `${process.cwd()}/tmp/nx-e2e/proj-backup/${path}`
: `${process.cwd()}/tmp/nx-e2e/proj-backup`;
}

View File

@ -0,0 +1,110 @@
import {
ensureDirSync,
readdirSync,
readFileSync,
removeSync,
renameSync,
statSync,
writeFileSync,
copySync
} from 'fs-extra';
import { dirname } from 'path';
import { tmpProjPath } from './paths';
/**
* Copies module folders from the working directory to the e2e directory
* @param modules a list of module names or scopes to copy
*/
export function copyNodeModules(modules: string[]) {
modules.forEach(module => {
removeSync(`${tmpProjPath()}/node_modules/${module}`);
copySync(
`./node_modules/${module}`,
`${tmpProjPath()}/node_modules/${module}`
);
});
}
/**
* Assert output from a asynchronous CLI command
* @param output: Output from an asynchronous command
*/
export function expectTestsPass(v: { stdout: string; stderr: string }) {
expect(v.stderr).toContain('Ran all test suites');
expect(v.stderr).not.toContain('fail');
}
export function updateFile(f: string, content: string | Function): void {
ensureDirSync(dirname(tmpProjPath(f)));
if (typeof content === 'string') {
writeFileSync(tmpProjPath(f), content);
} else {
writeFileSync(
tmpProjPath(f),
content(readFileSync(tmpProjPath(f)).toString())
);
}
}
export function renameFile(f: string, newPath: string): void {
ensureDirSync(dirname(tmpProjPath(newPath)));
renameSync(tmpProjPath(f), tmpProjPath(newPath));
}
export function checkFilesExist(...expectedFiles: string[]) {
expectedFiles.forEach(f => {
const ff = f.startsWith('/') ? f : tmpProjPath(f);
if (!exists(ff)) {
throw new Error(`File '${ff}' does not exist`);
}
});
}
export function listFiles(dirName: string) {
return readdirSync(tmpProjPath(dirName));
}
export function readJson(f: string): any {
return JSON.parse(readFile(f));
}
export function readFile(f: string) {
const ff = f.startsWith('/') ? f : tmpProjPath(f);
return readFileSync(ff).toString();
}
export function cleanup() {
removeSync(tmpProjPath());
}
export function rmDist() {
removeSync(`${tmpProjPath()}/dist`);
}
export function getCwd(): string {
return process.cwd();
}
export function directoryExists(filePath: string): boolean {
try {
return statSync(filePath).isDirectory();
} catch (err) {
return false;
}
}
export function fileExists(filePath: string): boolean {
try {
return statSync(filePath).isFile();
} catch (err) {
return false;
}
}
export function exists(filePath: string): boolean {
return directoryExists(filePath) || fileExists(filePath);
}
export function getSize(filePath: string): number {
return statSync(filePath).size;
}

View File

@ -0,0 +1,40 @@
/**
* Testing file for internal schematics
*/
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import { join } from 'path';
import { Tree } from '@angular-devkit/schematics';
import { TestingArchitectHost } from '@angular-devkit/architect/testing';
import { schema } from '@angular-devkit/core';
import { Architect } from '@angular-devkit/architect';
import { MockBuilderContext } from '@nrwl/workspace/testing';
const testRunner = new SchematicTestRunner(
'@nrwl/nx-plugin',
join(__dirname, '../../collection.json')
);
export function runSchematic<T>(schematicName: string, options: T, tree: Tree) {
return testRunner.runSchematicAsync(schematicName, options, tree).toPromise();
}
export async function getTestArchitect() {
const architectHost = new TestingArchitectHost('/root', '/root');
const registry = new schema.CoreSchemaRegistry();
registry.addPostTransform(schema.transforms.addUndefinedDefaults);
const architect = new Architect(architectHost, registry);
await architectHost.addBuilderFromPackage(join(__dirname, '../..'));
return [architect, architectHost] as [Architect, TestingArchitectHost];
}
export async function getMockContext() {
const [architect, architectHost] = await getTestArchitect();
const context = new MockBuilderContext(architect, architectHost);
await context.addBuilderFromPackage(join(__dirname, '../..'));
return context;
}

View File

@ -0,0 +1 @@
export const nxVersion = '*';

View File

@ -0,0 +1 @@
export * from './src/utils/testing-utils';

View File

@ -6,6 +6,7 @@ export {
names, names,
findModuleParent findModuleParent
} from './src/utils/name-utils'; } from './src/utils/name-utils';
export { ProjectType, projectRootDir } from './src/utils/project-type';
export { export {
serializeJson, serializeJson,
renameSync, renameSync,
@ -42,7 +43,10 @@ export {
getProjectGraphFromHost, getProjectGraphFromHost,
readWorkspace, readWorkspace,
renameSyncInTree, renameSyncInTree,
renameDirSyncInTree renameDirSyncInTree,
updateNxJsonInTree,
addProjectToNxJsonInTree,
readNxJsonInTree
} from './src/utils/ast-utils'; } from './src/utils/ast-utils';
export { export {

View File

@ -40,6 +40,7 @@
"@nrwl/nest", "@nrwl/nest",
"@nrwl/next", "@nrwl/next",
"@nrwl/node", "@nrwl/node",
"@nrwl/nx-plugin",
"@nrwl/react", "@nrwl/react",
"@nrwl/storybook", "@nrwl/storybook",
"@nrwl/web" "@nrwl/web"

View File

@ -20,6 +20,7 @@ import { toFileName, names } from '@nrwl/workspace';
import { formatFiles } from '@nrwl/workspace'; import { formatFiles } from '@nrwl/workspace';
import { offsetFromRoot } from '@nrwl/workspace'; import { offsetFromRoot } from '@nrwl/workspace';
import { generateProjectLint, addLintFiles } from '../../utils/lint'; import { generateProjectLint, addLintFiles } from '../../utils/lint';
import { addProjectToNxJsonInTree } from '../../utils/ast-utils';
export interface NormalizedSchema extends Schema { export interface NormalizedSchema extends Schema {
name: string; name: string;
@ -81,10 +82,7 @@ function createFiles(options: NormalizedSchema): Rule {
} }
function updateNxJson(options: NormalizedSchema): Rule { function updateNxJson(options: NormalizedSchema): Rule {
return updateJsonInTree<NxJson>('nx.json', json => { return addProjectToNxJsonInTree(options.name, { tags: options.parsedTags });
json.projects[options.name] = { tags: options.parsedTags };
return json;
});
} }
export default function(schema: Schema): Rule { export default function(schema: Schema): Rule {
@ -117,6 +115,8 @@ function normalizeOptions(options: Schema): NormalizedSchema {
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
const fileName = options.simpleModuleName ? name : projectName; const fileName = options.simpleModuleName ? name : projectName;
// const projectRoot = `libs/${projectDirectory}`;
const projectRoot = `libs/${projectDirectory}`; const projectRoot = `libs/${projectDirectory}`;
const parsedTags = options.tags const parsedTags = options.tags

View File

@ -23,7 +23,10 @@ import {
} from '../core/project-graph'; } from '../core/project-graph';
import { FileData } from '../core/file-utils'; import { FileData } from '../core/file-utils';
import { extname, join, normalize, Path } from '@angular-devkit/core'; import { extname, join, normalize, Path } from '@angular-devkit/core';
import { NxJson } from '@nrwl/workspace/src/core/shared-interfaces'; import {
NxJson,
NxJsonProjectConfig
} from '@nrwl/workspace/src/core/shared-interfaces';
function nodesByPosition(first: ts.Node, second: ts.Node): number { function nodesByPosition(first: ts.Node, second: ts.Node): number {
return first.getStart() - second.getStart(); return first.getStart() - second.getStart();
@ -497,6 +500,35 @@ export function updateWorkspaceInTree<T = any, O = T>(
}; };
} }
export function readNxJsonInTree(host: Tree) {
return readJsonInTree<NxJson>(host, 'nx.json');
}
export function updateNxJsonInTree(
callback: (json: NxJson, context: SchematicContext) => NxJson
): Rule {
return (host: Tree, context: SchematicContext): Tree => {
host.overwrite(
'nx.json',
serializeJson(callback(readJsonInTree(host, 'nx.json'), context))
);
return host;
};
}
export function addProjectToNxJsonInTree(
projectName: string,
options: NxJsonProjectConfig
): Rule {
const defaultOptions = {
tags: []
};
return updateNxJsonInTree(json => {
json.projects[projectName] = { ...defaultOptions, ...options };
return json;
});
}
export function readWorkspace(host: Tree): any { export function readWorkspace(host: Tree): any {
const path = getWorkspacePath(host); const path = getWorkspacePath(host);
return readJsonInTree(host, path); return readJsonInTree(host, path);

View File

@ -0,0 +1,12 @@
export enum ProjectType {
Application = 'application',
Library = 'library'
}
export function projectRootDir(projectType: ProjectType) {
if (projectType == ProjectType.Application) {
return 'apps';
} else if (projectType == ProjectType.Library) {
return 'libs';
}
}

View File

@ -29,6 +29,7 @@ rm -rf build/packages/angular/bundles/nrwl-angular-testing.umd.min.js.bak
rsync -a --exclude=*.ts packages/ build/packages rsync -a --exclude=*.ts packages/ build/packages
chmod +x build/packages/create-nx-workspace/bin/create-nx-workspace.js chmod +x build/packages/create-nx-workspace/bin/create-nx-workspace.js
chmod +x build/packages/nx-plugin/bin/create.js
chmod +x build/packages/cli/bin/nx.js chmod +x build/packages/cli/bin/nx.js
chmod +x build/packages/tao/index.js chmod +x build/packages/tao/index.js
@ -58,6 +59,7 @@ cp README.md build/packages/tao
cp README.md build/packages/eslint-plugin-nx cp README.md build/packages/eslint-plugin-nx
cp README.md build/packages/linter cp README.md build/packages/linter
cp README.md build/packages/bazel cp README.md build/packages/bazel
cp README.md build/packages/nx-plugin
cp LICENSE build/packages/builders cp LICENSE build/packages/builders
cp LICENSE build/packages/schematics cp LICENSE build/packages/schematics
@ -79,6 +81,7 @@ cp LICENSE build/packages/tao
cp LICENSE build/packages/eslint-plugin-nx cp LICENSE build/packages/eslint-plugin-nx
cp LICENSE build/packages/linter cp LICENSE build/packages/linter
cp LICENSE build/packages/bazel cp LICENSE build/packages/bazel
cp LICENSE build/packages/nx-plugin
echo "Nx libraries available at build/packages:" echo "Nx libraries available at build/packages:"
ls build/packages ls build/packages

View File

@ -6,7 +6,7 @@ const gitMessage = require('child_process')
.toString() .toString()
.trim(); .trim();
const matchCommit = /(chore|feat|fix|cleanup|docs)\((angular|bazel|core|docs|nextjs|node|react|storybook|testing|repo|misc)\):\s(([a-z0-9:\-\s])+)/g.test( const matchCommit = /(chore|feat|fix|cleanup|docs)\((angular|bazel|core|docs|nextjs|node|nx-plugin|react|storybook|testing|repo|misc)\):\s(([a-z0-9:\-\s])+)/g.test(
gitMessage gitMessage
); );
const matchRevert = /Revert/gi.test(gitMessage); const matchRevert = /Revert/gi.test(gitMessage);

View File

@ -12,6 +12,7 @@ export SELECTED_CLI=$1
jest --maxWorkers=1 ./build/e2e/ng-add.test.js && jest --maxWorkers=1 ./build/e2e/ng-add.test.js &&
jest --maxWorkers=1 ./build/e2e/ngrx.test.js && jest --maxWorkers=1 ./build/e2e/ngrx.test.js &&
jest --maxWorkers=1 ./build/e2e/node.test.js && jest --maxWorkers=1 ./build/e2e/node.test.js &&
jest --maxWorkers=1 ./build/e2e/nx-plugin.test.js &&
jest --maxWorkers=1 ./build/e2e/print-affected.test.js && jest --maxWorkers=1 ./build/e2e/print-affected.test.js &&
jest --maxWorkers=1 ./build/e2e/react.test.js && jest --maxWorkers=1 ./build/e2e/react.test.js &&
jest --maxWorkers=1 ./build/e2e/report.test.js && jest --maxWorkers=1 ./build/e2e/report.test.js &&

View File

@ -165,7 +165,8 @@ const options = {
'build/npm/cli/package.json', 'build/npm/cli/package.json',
'build/npm/tao/package.json', 'build/npm/tao/package.json',
'build/npm/eslint-plugin-nx/package.json', 'build/npm/eslint-plugin-nx/package.json',
'build/npm/linter/package.json' 'build/npm/linter/package.json',
'build/npm/nx-plugin/package.json'
], ],
increment: parsedVersion.version, increment: parsedVersion.version,
requireUpstream: false, requireUpstream: false,

View File

@ -18,25 +18,33 @@ cd build/packages
if [[ "$OSTYPE" == "darwin"* ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i "" "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,web,jest,node,express,nest,cypress,storybook,angular,workspace}/src/utils/versions.js sed -i "" "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,web,jest,node,express,nest,cypress,storybook,angular,workspace}/src/utils/versions.js
sed -i "" "s|\*|$NX_VERSION|g" {schematics,react,next,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,bazel,tao,eslint-plugin-nx,create-nx-workspace}/package.json sed -i "" "s|\*|$NX_VERSION|g" {schematics,react,next,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,bazel,tao,eslint-plugin-nx,create-nx-workspace,nx-plugin}/package.json
sed -i "" "s|NX_VERSION|$NX_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "" "s|NX_VERSION|$NX_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js
sed -i "" "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "" "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js
sed -i "" "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "" "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js
sed -i "" "s|PRETTIER_VERSION|$PRETTIER_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "" "s|PRETTIER_VERSION|$PRETTIER_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js
sed -i "" "s|NX_VERSION|$NX_VERSION|g" nx-plugin/bin/create.js
sed -i "" "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" nx-plugin/bin/create.js
sed -i "" "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" nx-plugin/bin/create.js
sed -i "" "s|PRETTIER_VERSION|$PRETTIER_VERSION|g" nx-plugin/bin/create.js
else else
sed -i "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,web,jest,node,express,nest,cypress,storybook,angular,workspace}/src/utils/versions.js sed -i "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,web,jest,node,express,nest,cypress,storybook,angular,workspace}/src/utils/versions.js
sed -i "s|\*|$NX_VERSION|g" {schematics,react,next,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,bazel,tao,eslint-plugin-nx,create-nx-workspace}/package.json sed -i "s|\*|$NX_VERSION|g" {schematics,react,next,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,bazel,tao,eslint-plugin-nx,create-nx-workspace,nx-plugin}/package.json
sed -i "s|NX_VERSION|$NX_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "s|NX_VERSION|$NX_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js
sed -i "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js
sed -i "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js
sed -i "s|PRETTIER_VERSION|$PRETTIER_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "s|PRETTIER_VERSION|$PRETTIER_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js
sed -i "s|NX_VERSION|$NX_VERSION|g" nx-plugin/bin/create.js
sed -i "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" nx-plugin/bin/create.js
sed -i "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" nx-plugin/bin/create.js
sed -i "s|PRETTIER_VERSION|$PRETTIER_VERSION|g" nx-plugin/bin/create.js
fi fi
if [[ $NX_VERSION == "*" ]]; then if [[ $NX_VERSION == "*" ]]; then
if [[ "$OSTYPE" == "darwin"* ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then
sed -E -i "" "s|\"@nrwl\/([^\"]+)\": \"\\*\"|\"@nrwl\/\1\": \"file:$PWD\/\1\"|" {schematics,jest,web,react,next,node,express,nest,cypress,storybook,angular,workspace,linter,bazel,cli,tao,eslint-plugin-nx,create-nx-workspace}/package.json sed -E -i "" "s|\"@nrwl\/([^\"]+)\": \"\\*\"|\"@nrwl\/\1\": \"file:$PWD\/\1\"|" {schematics,jest,web,react,next,node,express,nest,cypress,storybook,angular,workspace,linter,bazel,cli,tao,eslint-plugin-nx,create-nx-workspace,nx-plugin}/package.json
else else
echo $PWD echo $PWD
sed -E -i "s|\"@nrwl\/([^\"]+)\": \"\\*\"|\"@nrwl\/\1\": \"file:$PWD\/\1\"|" {schematics,jest,web,react,next,node,express,nest,cypress,storybook,angular,workspace,linter,bazel,cli,tao,eslint-plugin-nx,create-nx-workspace}/package.json sed -E -i "s|\"@nrwl\/([^\"]+)\": \"\\*\"|\"@nrwl\/\1\": \"file:$PWD\/\1\"|" {schematics,jest,web,react,next,node,express,nest,cypress,storybook,angular,workspace,linter,bazel,cli,tao,eslint-plugin-nx,create-nx-workspace,nx-plugin}/package.json
fi fi
fi fi

View File

@ -3,5 +3,5 @@
if [ -n "$1" ]; then if [ -n "$1" ]; then
jest --maxWorkers=1 ./build/packages/$1.spec.js jest --maxWorkers=1 ./build/packages/$1.spec.js
else else
jest --maxWorkers=1 ./build/packages/{schematics,bazel,builders,react,jest,web,node,express,nest,cypress,storybook,angular,workspace,tao,eslint-plugin-nx,next} --passWithNoTests jest --maxWorkers=1 ./build/packages/{schematics,bazel,builders,react,jest,web,node,express,nest,cypress,storybook,angular,workspace,tao,eslint-plugin-nx,next,nx-plugin} --passWithNoTests
fi fi

View File

@ -4167,6 +4167,13 @@
resolved "https://registry.yarnpkg.com/@types/fast-levenshtein/-/fast-levenshtein-0.0.1.tgz#3a3615cf173645c8fca58d051e4e32824e4bd286" resolved "https://registry.yarnpkg.com/@types/fast-levenshtein/-/fast-levenshtein-0.0.1.tgz#3a3615cf173645c8fca58d051e4e32824e4bd286"
integrity sha1-OjYVzxc2Rcj8pY0FHk4ygk5L0oY= integrity sha1-OjYVzxc2Rcj8pY0FHk4ygk5L0oY=
"@types/fs-extra@7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-7.0.0.tgz#9c4ad9e1339e7448a76698829def1f159c1b636c"
integrity sha512-ndoMMbGyuToTy4qB6Lex/inR98nPiNHacsgMPvy+zqMLgSxbt8VtWpDArpGp69h1fEDQHn1KB+9DWD++wgbwYA==
dependencies:
"@types/node" "*"
"@types/glob@^7.1.1": "@types/glob@^7.1.1":
version "7.1.1" version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"