feat(core): generate inputs configuration for new workspaces (#11856)

This commit is contained in:
Jason Jean 2022-09-07 19:32:59 -07:00 committed by GitHub
parent c334277a93
commit 292f0c14b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 565 additions and 169 deletions

View File

@ -59,14 +59,14 @@ We can define a more precise configuration as follows:
{
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"prod": ["!{projectRoot}/**/*.spec.tsx"]
"production": ["!{projectRoot}/**/*.spec.tsx"]
},
"targetDefaults": {
"build": {
"inputs": ["prod", "^prod"]
"inputs": ["production", "^production"]
},
"test": {
"inputs": ["default", "^prod", "{workspaceRoot}/jest.config.ts"]
"inputs": ["default", "^production", "{workspaceRoot}/jest.config.ts"]
}
}
}

View File

@ -25,11 +25,11 @@ The following is an expanded version showing all options. Your `nx.json` will li
},
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"prod": ["!{projectRoot}/**/*.spec.tsx"]
"production": ["!{projectRoot}/**/*.spec.tsx"]
},
"targetDefaults": {
"build": {
"inputs": ["prod", "^prod"],
"inputs": ["production", "^production"],
"dependsOn": ["^build"]
}
},
@ -127,7 +127,7 @@ like this (which applies to every project):
"test": {
"inputs": [
"default",
"^prod"
"^production"
]
}
```
@ -137,7 +137,10 @@ And projects can define their prod fileset, without having to redefine the input
```json title="project.json"
{
"namedInputs": {
"prod": ["!{projectRoot}/**/*.test.js", "{workspacRoot}/jest.config.js"]
"production": [
"!{projectRoot}/**/*.test.js",
"{workspacRoot}/jest.config.js"
]
}
}
```

View File

@ -64,18 +64,18 @@ You can add Nx-specific configuration as follows:
"default": [
"{projectRoot}/**/*"
],
"prod": [
"production": [
"!{projectRoot}/**/*.spec.tsx"
]
},
"targets": {
"build": {
"inputs": ["prod", "^prod"],
"inputs": ["production", "^production"],
"outputs": ["dist/libs/mylib"],
"dependsOn": ["^build"]
},
"test": {
"inputs": ["default", "^prod"],
"inputs": ["default", "^production"],
"outputs": [],
"dependsOn": ["build"]
}
@ -94,19 +94,19 @@ You can add Nx-specific configuration as follows:
"projectType": "library",
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"prod": ["!{projectRoot}/**/*.spec.tsx"]
"production": ["!{projectRoot}/**/*.spec.tsx"]
},
"targets": {
"test": {
"executor": "@nrwl/jest:jest",
"inputs": ["default", "^prod"],
"inputs": ["default", "^production"],
"outputs": [],
"dependsOn": ["build"],
"options": {}
},
"build": {
"executor": "@nrwl/js:tsc",
"inputs": ["prod", "^prod"],
"inputs": ["production", "^production"],
"outputs": ["dist/libs/mylib"],
"dependsOn": ["^build"],
"options": {}
@ -154,8 +154,8 @@ _Named Inputs_
Examples:
- `inputs: ["prod"]`
- same as `inputs: [{input: "prod", projects: "self"}]`
- `inputs: ["production"]`
- same as `inputs: [{input: "production", projects: "self"}]`
Often the same glob will appear in many places (e.g., prod fileset will exclude spec files for all projects). Because
keeping them in sync is error-prone, we recommend defining named inputs, which you can then reference in all of those
@ -165,15 +165,15 @@ places.
Examples:
- `inputs: ["^prod"]`
- same as `inputs: [{input: "prod", projects: "dependencies"}]`
- `inputs: ["^production"]`
- same as `inputs: [{input: "production", projects: "dependencies"}]`
Similar to `dependsOn`, the "^" symbols means "dependencies". This is a very important idea, so let's illustrate it with
an example.
```
"test": {
"inputs": [ "default", "^prod" ]
"inputs": [ "default", "^production" ]
}
```

13
nx.json
View File

@ -30,19 +30,22 @@
},
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"prod": ["!{projectRoot}/**/*.spec.ts{,.snap}"]
"production": [
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)"
]
},
"targetDefaults": {
"build": {
"dependsOn": ["build-base"],
"inputs": ["prod", "^prod"]
"inputs": ["production", "^production"]
},
"build-base": {
"dependsOn": ["^build-base"],
"inputs": ["prod", "^prod"]
"inputs": ["production", "^production"]
},
"test": {
"inputs": ["default", "^prod", "{workspaceRoot}/jest.config.ts"]
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"]
},
"lint": {
"inputs": [
@ -54,7 +57,7 @@
"e2e": {
"inputs": [
"default",
"^prod",
"^production",
{
"env": "SELECTED_CLI"
},

View File

@ -1,12 +1,13 @@
import * as devkit from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { karmaGenerator } from './karma';
import { NxJsonConfiguration, readJson, updateJson } from '@nrwl/devkit';
describe('karma', () => {
let tree: devkit.Tree;
beforeEach(() => {
tree = createTreeWithEmptyV1Workspace();
tree = createTreeWithEmptyWorkspace();
});
it('should do nothing when karma is already installed and karma.conf.js exists', () => {
@ -57,4 +58,27 @@ describe('karma', () => {
expect(tree.exists('karma.conf.js')).toBeTruthy();
});
it('should add inputs for test targets', () => {
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.namedInputs ??= {};
json.namedInputs.production = ['default', '^production'];
return json;
});
karmaGenerator(tree, {});
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
expect(nxJson.namedInputs.production).toContain(
'!{projectRoot}/karma.conf.js'
);
expect(nxJson.namedInputs.production).toContain(
'!{projectRoot}/tsconfig.spec.json'
);
expect(nxJson.namedInputs.production).toContain(
'!{projectRoot}/**/*.spec.[jt]s'
);
expect(nxJson.targetDefaults.test).toEqual({
inputs: ['default', '^production', '{workspaceRoot}/karma.conf.js'],
});
});
});

View File

@ -4,6 +4,8 @@ import {
generateFiles,
joinPathFragments,
readJson,
readWorkspaceConfiguration,
updateWorkspaceConfiguration,
} from '@nrwl/devkit';
import {
jasmineCoreVersion,
@ -18,6 +20,39 @@ import {
} from '../../utils/versions';
import { GeneratorOptions } from './schema';
function addTestInputs(tree: Tree) {
const workspaceConfiguration = readWorkspaceConfiguration(tree);
const productionFileSet = workspaceConfiguration.namedInputs?.production;
if (productionFileSet) {
productionFileSet.push(
// Exclude spec files from production fileset
'!{projectRoot}/**/*.spec.[jt]s',
// Remove tsconfig.spec.json
'!{projectRoot}/tsconfig.spec.json',
// Remove karma.conf.js
'!{projectRoot}/karma.conf.js'
);
// Dedupe and set
workspaceConfiguration.namedInputs.production = Array.from(
new Set(productionFileSet)
);
}
// Test targets depend on all their project's sources + production sources of dependencies
workspaceConfiguration.targetDefaults ??= {};
workspaceConfiguration.targetDefaults.test ??= {};
workspaceConfiguration.targetDefaults.test.inputs ??= [
'default',
productionFileSet ? '^production' : '^default',
];
workspaceConfiguration.targetDefaults.test.inputs.push(
'{workspaceRoot}/karma.conf.js'
);
updateWorkspaceConfiguration(tree, workspaceConfiguration);
}
export function karmaGenerator(tree: Tree, options: GeneratorOptions) {
const packageJson = readJson(tree, 'package.json');
@ -25,11 +60,14 @@ export function karmaGenerator(tree: Tree, options: GeneratorOptions) {
generateFiles(tree, joinPathFragments(__dirname, 'files'), '.', {
tmpl: '',
});
addTestInputs(tree);
}
if (options.skipPackageJson || packageJson.devDependencies['karma']) {
return () => {};
}
return addDependenciesToPackageJson(
tree,
{},

View File

@ -176,6 +176,12 @@ Object {
"^build",
],
},
"lint": Object {
"inputs": Array [
"default",
"{workspaceRoot}/.eslintrc.json",
],
},
},
"tasksRunnerOptions": Object {
"default": Object {

View File

@ -1,5 +1,5 @@
import { readJson, Tree, updateJson } from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { NxJsonConfiguration, readJson, Tree, updateJson } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { cypressVersion } from '../../utils/versions';
import { cypressInitGenerator } from './init';
@ -8,7 +8,7 @@ describe('init', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyV1Workspace();
tree = createTreeWithEmptyWorkspace();
});
it('should add dependencies into `package.json` file', async () => {
@ -31,4 +31,20 @@ describe('init', () => {
expect(packageJson.dependencies['@nrwl/cypress']).toBeUndefined();
expect(packageJson.dependencies[existing]).toBeDefined();
});
it('should setup e2e target defaults', async () => {
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.namedInputs ??= {};
json.namedInputs.production = ['default'];
return json;
});
cypressInitGenerator(tree, {});
expect(
readJson<NxJsonConfiguration>(tree, 'nx.json').targetDefaults.e2e
).toEqual({
inputs: ['default', '^production'],
});
});
});

View File

@ -1,8 +1,10 @@
import {
addDependenciesToPackageJson,
convertNxGenerator,
readWorkspaceConfiguration,
removeDependenciesFromPackageJson,
Tree,
updateWorkspaceConfiguration,
} from '@nrwl/devkit';
import {
cypressVersion,
@ -11,11 +13,27 @@ import {
} from '../../utils/versions';
import { Schema } from './schema';
function updateDependencies(host: Tree) {
removeDependenciesFromPackageJson(host, ['@nrwl/cypress'], []);
function setupE2ETargetDefaults(tree: Tree) {
const workspaceConfiguration = readWorkspaceConfiguration(tree);
// E2e targets depend on all their project's sources + production sources of dependencies
workspaceConfiguration.targetDefaults ??= {};
const productionFileSet = !!workspaceConfiguration.namedInputs?.production;
workspaceConfiguration.targetDefaults.e2e ??= {};
workspaceConfiguration.targetDefaults.e2e.inputs ??= [
'default',
productionFileSet ? '^production' : '^default',
];
updateWorkspaceConfiguration(tree, workspaceConfiguration);
}
function updateDependencies(tree: Tree) {
removeDependenciesFromPackageJson(tree, ['@nrwl/cypress'], []);
return addDependenciesToPackageJson(
host,
tree,
{},
{
['@nrwl/cypress']: nxVersion,
@ -25,8 +43,9 @@ function updateDependencies(host: Tree) {
);
}
export function cypressInitGenerator(host: Tree, options: Schema) {
return !options.skipPackageJson ? updateDependencies(host) : () => {};
export function cypressInitGenerator(tree: Tree, options: Schema) {
setupE2ETargetDefaults(tree);
return !options.skipPackageJson ? updateDependencies(tree) : () => {};
}
export default cypressInitGenerator;

View File

@ -1,5 +1,6 @@
export const nxPreset = {
testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'],
// This is one of the patterns that jest finds by default https://jestjs.io/docs/configuration#testmatch-arraystring
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
resolver: '@nrwl/jest/plugins/resolver',
moduleFileExtensions: ['ts', 'js', 'mjs', 'html'],
coverageReporters: ['html'],

View File

@ -1,4 +1,11 @@
import { readJson, stripIndents, Tree, writeJson } from '@nrwl/devkit';
import {
NxJsonConfiguration,
readJson,
stripIndents,
Tree,
updateJson,
writeJson,
} from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { jestInitGenerator } from './init';
@ -39,6 +46,61 @@ describe('jest', () => {
expect(tree.read('jest.config.ts', 'utf-8')).toEqual('test');
});
it('should add target defaults for test', async () => {
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.namedInputs ??= {};
json.namedInputs.production = ['default'];
return json;
});
jestInitGenerator(tree, {});
const productionFileSet = readJson<NxJsonConfiguration>(tree, 'nx.json')
.namedInputs.production;
const testDefaults = readJson<NxJsonConfiguration>(tree, 'nx.json')
.targetDefaults.test;
expect(productionFileSet).toContain(
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)'
);
expect(productionFileSet).toContain('!{projectRoot}/tsconfig.spec.json');
expect(productionFileSet).toContain('!{projectRoot}/jest.config.[jt]s');
expect(testDefaults).toEqual({
inputs: ['default', '^production', '{workspaceRoot}/jest.preset.js'],
});
});
it('should not alter target defaults if jest.preset.js already exists', async () => {
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.namedInputs ??= {};
json.namedInputs.production = ['default', '^production'];
return json;
});
jestInitGenerator(tree, {});
let nxJson: NxJsonConfiguration;
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.namedInputs.production = [
'default',
'^production',
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
'!{projectRoot}/**/*.md',
];
json.targetDefaults.test = {
inputs: [
'default',
'^production',
'{workspaceRoot}/jest.preset.js',
'{workspaceRoot}/testSetup.ts',
],
};
nxJson = json;
return json;
});
jestInitGenerator(tree, {});
expect(readJson<NxJsonConfiguration>(tree, 'nx.json')).toEqual(nxJson);
});
it('should add dependencies', async () => {
jestInitGenerator(tree, {});
const packageJson = readJson(tree, 'package.json');

View File

@ -2,10 +2,12 @@ import {
addDependenciesToPackageJson,
convertNxGenerator,
GeneratorCallback,
readWorkspaceConfiguration,
removeDependenciesFromPackageJson,
stripIndents,
Tree,
updateJson,
updateWorkspaceConfiguration,
} from '@nrwl/devkit';
import {
babelJestVersion,
@ -26,9 +28,9 @@ const schemaDefaults = {
js: false,
} as const;
function createJestConfig(host: Tree, js: boolean = false) {
function createJestConfig(tree: Tree, js: boolean = false) {
// if the root ts config already exists then don't make a js one or vice versa
if (!host.exists('jest.config.ts') && !host.exists('jest.config.js')) {
if (!tree.exists('jest.config.ts') && !tree.exists('jest.config.js')) {
const contents = js
? stripIndents`
const { getJestProjects } = require('@nrwl/jest');
@ -42,21 +44,57 @@ function createJestConfig(host: Tree, js: boolean = false) {
export default {
projects: getJestProjects()
};`;
host.write(`jest.config.${js ? 'js' : 'ts'}`, contents);
tree.write(`jest.config.${js ? 'js' : 'ts'}`, contents);
}
if (!host.exists('jest.preset.js')) {
if (!tree.exists('jest.preset.js')) {
// preset is always js file.
host.write(
tree.write(
`jest.preset.js`,
`
const nxPreset = require('@nrwl/jest/preset').default;
module.exports = { ...nxPreset }`
);
addTestInputs(tree);
}
}
function addTestInputs(tree: Tree) {
const workspaceConfiguration = readWorkspaceConfiguration(tree);
const productionFileSet = workspaceConfiguration.namedInputs?.production;
if (productionFileSet) {
// This is one of the patterns in the default jest patterns
productionFileSet.push(
// Remove spec, test, and snapshots from the production fileset
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
// Remove tsconfig.spec.json
'!{projectRoot}/tsconfig.spec.json',
// Remove jest.config.js/ts
'!{projectRoot}/jest.config.[jt]s'
);
// Dedupe and set
workspaceConfiguration.namedInputs.production = Array.from(
new Set(productionFileSet)
);
}
// Test targets depend on all their project's sources + production sources of dependencies
workspaceConfiguration.targetDefaults ??= {};
workspaceConfiguration.targetDefaults.test ??= {};
workspaceConfiguration.targetDefaults.test.inputs ??= [
'default',
productionFileSet ? '^production' : '^default',
];
workspaceConfiguration.targetDefaults.test.inputs.push(
'{workspaceRoot}/jest.preset.js'
);
updateWorkspaceConfiguration(tree, workspaceConfiguration);
}
function updateDependencies(tree: Tree, options: NormalizedSchema) {
const dependencies = {
tslib: tslibVersion,

View File

@ -1,13 +1,13 @@
import { Linter } from '../utils/linter';
import { Tree } from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { readJson, Tree } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { lintInitGenerator } from './init';
describe('@nrwl/linter:init', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyV1Workspace();
tree = createTreeWithEmptyWorkspace();
});
describe('--linter', () => {
@ -20,6 +20,16 @@ describe('@nrwl/linter:init', () => {
expect(tree.read('.eslintrc.json', 'utf-8')).toMatchSnapshot();
});
it('should add the root eslint config to the lint targetDefaults for lint', async () => {
await lintInitGenerator(tree, {
linter: Linter.EsLint,
});
expect(readJson(tree, 'nx.json').targetDefaults.lint).toEqual({
inputs: ['default', '{workspaceRoot}/.eslintrc.json'],
});
});
it('should not generate the global eslint config if it already exist', async () => {
tree.write('.eslintrc.js', '{}');

View File

@ -1,8 +1,10 @@
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
import {
addDependenciesToPackageJson,
readWorkspaceConfiguration,
removeDependenciesFromPackageJson,
updateJson,
updateWorkspaceConfiguration,
writeJson,
} from '@nrwl/devkit';
import {
@ -15,7 +17,7 @@ import {
} from '../../utils/versions';
import { Linter } from '../utils/linter';
import { containsEslint } from '../utils/eslint-file';
import { findEslintFile } from '../utils/eslint-file';
import { ESLint } from 'eslint';
export interface LinterInitOptions {
@ -180,8 +182,31 @@ function initTsLint(tree: Tree, options: LinterInitOptions): GeneratorCallback {
: () => {};
}
function addTargetDefaults(tree: Tree) {
const workspaceConfiguration = readWorkspaceConfiguration(tree);
const productionFileSet = workspaceConfiguration.namedInputs?.production;
if (productionFileSet) {
// Remove .eslintrc.json
productionFileSet.push('!{projectRoot}/.eslintrc.json');
// Dedupe and set
workspaceConfiguration.namedInputs.production = Array.from(
new Set(productionFileSet)
);
}
workspaceConfiguration.targetDefaults ??= {};
workspaceConfiguration.targetDefaults.lint ??= {};
workspaceConfiguration.targetDefaults.lint.inputs ??= [
'default',
`{workspaceRoot}/.eslintrc.json`,
];
updateWorkspaceConfiguration(tree, workspaceConfiguration);
}
function initEsLint(tree: Tree, options: LinterInitOptions): GeneratorCallback {
if (containsEslint(tree)) {
if (findEslintFile(tree)) {
return () => {};
}
@ -194,6 +219,7 @@ function initEsLint(tree: Tree, options: LinterInitOptions): GeneratorCallback {
'.eslintrc.json',
getGlobalEsLintConfiguration(options.unitTestRunner)
);
addTargetDefaults(tree);
if (tree.exists('.vscode/extensions.json')) {
updateJson(tree, '.vscode/extensions.json', (json) => {

View File

@ -42,8 +42,11 @@ function createEsLintConfiguration(
projectConfig: ProjectConfiguration,
setParserOptionsProject: boolean
) {
const eslintConfig = findEslintFile(tree);
writeJson(tree, join(projectConfig.root, `.eslintrc.json`), {
extends: [`${offsetFromRoot(projectConfig.root)}${findEslintFile(tree)}`],
extends: eslintConfig
? [`${offsetFromRoot(projectConfig.root)}${eslintConfig}`]
: undefined,
// Include project files to be linted since the global one excludes all files.
ignorePatterns: ['!**/*'],
overrides: [

View File

@ -1,4 +1,4 @@
import { containsEslint, findEslintFile } from './eslint-file';
import { findEslintFile } from './eslint-file';
import { Tree } from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
@ -10,30 +10,9 @@ describe('@nrwl/linter:eslint-file', () => {
tree = createTreeWithEmptyV1Workspace();
});
describe('containsEslint', () => {
it('should return false when calling containsEslint without a eslint config', () => {
expect(containsEslint(tree)).toBe(false);
});
it('should return true when calling containsEslint with a .eslintrc.json config', () => {
tree.write('.eslintrc.json', '{}');
expect(containsEslint(tree)).toBe(true);
});
it('should return true when calling containsEslint with a .eslintrc.js config', () => {
tree.write('.eslintrc.js', '{}');
expect(containsEslint(tree)).toBe(true);
});
it('should return false when calling containsEslint witn an incorrect eslint file name', () => {
tree.write('.eslintrc.yaml', '{}');
expect(containsEslint(tree)).toBe(false);
});
});
describe('findEslintFile', () => {
it('should return default name when calling findEslintFile when no eslint is found', () => {
expect(findEslintFile(tree)).toBe('.eslintrc.json');
it('should return null when calling findEslintFile when no eslint is found', () => {
expect(findEslintFile(tree)).toBe(null);
});
it('should return the name of the eslint config when calling findEslintFile', () => {
@ -49,7 +28,7 @@ describe('@nrwl/linter:eslint-file', () => {
it('should return default name when calling findEslintFile when no eslint is found', () => {
tree.write('.eslintrc.yaml', '{}');
expect(findEslintFile(tree)).toBe('.eslintrc.json');
expect(findEslintFile(tree)).toBe(null);
});
});
});

View File

@ -2,21 +2,11 @@ import type { Tree } from '@nrwl/devkit';
const eslintFileList = ['.eslintrc.json', '.eslintrc.js'];
export function containsEslint(tree: Tree): boolean {
for (const file of eslintFileList) {
if (tree.exists(file)) {
return true;
}
}
return false;
}
export function findEslintFile(tree: Tree): string {
export function findEslintFile(tree: Tree): string | null {
for (const file of eslintFileList) {
if (tree.exists(file)) {
return file;
}
}
// Default file
return '.eslintrc.json';
return null;
}

View File

@ -1,12 +1,12 @@
import { Tree } from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { NxJsonConfiguration, readJson, Tree } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { lintWorkspaceRuleGenerator } from './workspace-rule';
describe('@nrwl/linter:workspace-rule', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyV1Workspace();
tree = createTreeWithEmptyWorkspace();
});
it('should generate the required files', async () => {

View File

@ -1,10 +1,12 @@
import {
addProjectConfiguration,
NxJsonConfiguration,
readJson,
readProjectConfiguration,
Tree,
updateJson,
} from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import {
lintWorkspaceRulesProjectGenerator,
WORKSPACE_RULES_PROJECT_NAME,
@ -14,22 +16,23 @@ describe('@nrwl/linter:workspace-rules-project', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyV1Workspace();
tree = createTreeWithEmptyWorkspace();
});
it('should update implicitDependencies in nx.json', async () => {
expect(
readJson(tree, 'nx.json').implicitDependencies
).toMatchInlineSnapshot(`undefined`);
it('should add lint project files to lint inputs', async () => {
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.targetDefaults = {
lint: {
inputs: ['default', '{workspaceRoot}/.eslintrc.json'],
},
};
return json;
});
await lintWorkspaceRulesProjectGenerator(tree);
expect(readJson(tree, 'nx.json').implicitDependencies)
.toMatchInlineSnapshot(`
Object {
"tools/eslint-rules/**/*": "*",
}
`);
expect(
readJson<NxJsonConfiguration>(tree, 'nx.json').targetDefaults.lint.inputs
).toContain('{workspaceRoot}/tools/eslint-rules/**/*');
});
it('should generate the required files', async () => {
@ -72,6 +75,7 @@ describe('@nrwl/linter:workspace-rules-project', () => {
expect(readProjectConfiguration(tree, WORKSPACE_RULES_PROJECT_NAME))
.toMatchInlineSnapshot(`
Object {
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"root": "tools/eslint-rules",
"sourceRoot": "tools/eslint-rules",
"targets": Object {

View File

@ -47,13 +47,14 @@ export async function lintWorkspaceRulesProjectGenerator(tree: Tree) {
* TODO: Explore writing a ProjectGraph plugin to make this more surgical.
*/
const workspaceConfig = readWorkspaceConfiguration(tree);
updateWorkspaceConfiguration(tree, {
...workspaceConfig,
implicitDependencies: {
...workspaceConfig.implicitDependencies,
[`${WORKSPACE_PLUGIN_DIR}/**/*`]: '*',
},
});
if (workspaceConfig.targetDefaults?.lint?.inputs) {
workspaceConfig.targetDefaults.lint.inputs.push(
`{workspaceRoot}/${WORKSPACE_PLUGIN_DIR}/**/*`
);
updateWorkspaceConfiguration(tree, workspaceConfig);
}
// Add jest to the project and return installation task
const installTask = await jestProjectGenerator(tree, {

View File

@ -1,4 +1,4 @@
import { logger } from '@nrwl/devkit';
import { NxJsonConfiguration } from '@nrwl/devkit';
import { Tree, readJson, updateJson } from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { reactNativeInitGenerator } from './init';
@ -38,11 +38,23 @@ describe('init', () => {
describe('babel config', () => {
it('should create babel config if not present', async () => {
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.namedInputs = {
sharedGlobals: ['{workspaceRoot}/exiting-file.json'],
};
return json;
});
await reactNativeInitGenerator(tree, {
unitTestRunner: 'none',
e2eTestRunner: 'none',
});
expect(tree.exists('babel.config.json')).toBe(true);
const sharedGloabls = readJson<NxJsonConfiguration>(tree, 'nx.json')
.namedInputs.sharedGlobals;
expect(sharedGloabls).toContain('{workspaceRoot}/exiting-file.json');
expect(sharedGloabls).toContain('{workspaceRoot}/babel.config.json');
});
it('should not overwrite existing babel config', async () => {

View File

@ -1,4 +1,9 @@
import { Tree, writeJson } from '@nrwl/devkit';
import {
readWorkspaceConfiguration,
Tree,
updateWorkspaceConfiguration,
writeJson,
} from '@nrwl/devkit';
export function initRootBabelConfig(tree: Tree) {
if (tree.exists('/babel.config.json') || tree.exists('/babel.config.js')) {
@ -8,4 +13,13 @@ export function initRootBabelConfig(tree: Tree) {
writeJson(tree, '/babel.config.json', {
babelrcRoots: ['*'], // Make sure .babelrc files other than root can be loaded in a monorepo
});
const workspaceConfiguration = readWorkspaceConfiguration(tree);
if (workspaceConfiguration.namedInputs?.sharedGlobals) {
workspaceConfiguration.namedInputs.sharedGlobals.push(
'{workspaceRoot}/babel.config.json'
);
}
updateWorkspaceConfiguration(tree, workspaceConfiguration);
}

View File

@ -39,7 +39,46 @@ Object {
"affected": Object {
"defaultBase": "main",
},
"namedInputs": Object {
"production": Array [
"default",
"!{projectRoot}/.eslintrc.json",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.[jt]s",
"!{projectRoot}/.storybook/**/*",
"!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)",
],
},
"npmScope": "proj",
"targetDefaults": Object {
"build-storybook": Object {
"inputs": Array [
"default",
"^production",
"{workspaceRoot}/.storybook/**/*",
],
},
"e2e": Object {
"inputs": Array [
"default",
"^production",
],
},
"lint": Object {
"inputs": Array [
"default",
"{workspaceRoot}/.eslintrc.json",
],
},
"test": Object {
"inputs": Array [
"default",
"^production",
"{workspaceRoot}/jest.preset.js",
],
},
},
"tasksRunnerOptions": Object {
"default": Object {
"options": Object {

View File

@ -1,11 +1,12 @@
import {
NxJsonConfiguration,
readJson,
readProjectConfiguration,
Tree,
updateJson,
writeJson,
} from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { Linter } from '@nrwl/linter';
import { libraryGenerator } from '@nrwl/workspace/generators';
@ -18,7 +19,13 @@ describe('@nrwl/storybook:configuration', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyV1Workspace();
tree = createTreeWithEmptyWorkspace();
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.namedInputs = {
production: ['default'],
};
return json;
});
await libraryGenerator(tree, {
name: 'test-ui-lib',
standaloneConfig: false,
@ -425,7 +432,7 @@ describe('@nrwl/storybook:configuration', () => {
describe('for js Storybook configurations', () => {
let tree: Tree;
beforeAll(async () => {
tree = createTreeWithEmptyV1Workspace();
tree = createTreeWithEmptyWorkspace();
writeJson(tree, 'workspace.json', workspaceConfiguration);
writeJson(tree, 'apps/nxapp/tsconfig.json', {});
writeJson(tree, 'apps/reapp/tsconfig.json', {});
@ -523,7 +530,7 @@ describe('@nrwl/storybook:configuration', () => {
describe('for TypeScript Storybook configurations', () => {
let tree: Tree;
beforeAll(async () => {
tree = createTreeWithEmptyV1Workspace();
tree = createTreeWithEmptyWorkspace();
writeJson(tree, 'workspace.json', workspaceConfiguration);
writeJson(tree, 'apps/nxapp/tsconfig.json', {});
writeJson(tree, 'apps/reapp/tsconfig.json', {});

View File

@ -5,10 +5,12 @@ import {
offsetFromRoot,
readJson,
readProjectConfiguration,
readWorkspaceConfiguration,
toJS,
Tree,
updateJson,
updateProjectConfiguration,
updateWorkspaceConfiguration,
writeJson,
} from '@nrwl/devkit';
import { Linter } from '@nrwl/linter';
@ -263,6 +265,31 @@ export function createRootStorybookDir(
rootTsConfigPath: getRootTsConfigPathInTree(tree),
});
const workspaceConfiguration = readWorkspaceConfiguration(tree);
const hasProductionFileset = !!workspaceConfiguration.namedInputs?.production;
if (hasProductionFileset) {
workspaceConfiguration.namedInputs.production.push(
'!{projectRoot}/.storybook/**/*'
);
workspaceConfiguration.namedInputs.production.push(
'!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)'
);
}
workspaceConfiguration.targetDefaults ??= {};
workspaceConfiguration.targetDefaults['build-storybook'] ??= {};
workspaceConfiguration.targetDefaults['build-storybook'].inputs ??= [
'default',
hasProductionFileset ? '^production' : '^default',
];
workspaceConfiguration.targetDefaults['build-storybook'].inputs.push(
'{workspaceRoot}/.storybook/**/*'
);
updateWorkspaceConfiguration(tree, workspaceConfiguration);
if (js) {
toJS(tree);
}

View File

@ -5,7 +5,7 @@ import {
Tree,
updateJson,
} from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { nxVersion } from '../../utils/versions';
@ -15,7 +15,7 @@ describe('init', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyV1Workspace();
tree = createTreeWithEmptyWorkspace();
});
it('should add web dependencies', async () => {
@ -48,10 +48,22 @@ describe('init', () => {
describe('babel config', () => {
it('should create babel config if not present', async () => {
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.namedInputs = {
sharedGlobals: ['{workspaceRoot}/exiting-file.json'],
};
return json;
});
await webInitGenerator(tree, {
unitTestRunner: 'none',
});
expect(tree.exists('babel.config.json')).toBe(true);
const sharedGloabls = readJson<NxJsonConfiguration>(tree, 'nx.json')
.namedInputs.sharedGlobals;
expect(sharedGloabls).toContain('{workspaceRoot}/exiting-file.json');
expect(sharedGloabls).toContain('{workspaceRoot}/babel.config.json');
});
it('should not overwrite existing babel config', async () => {

View File

@ -4,8 +4,10 @@ import {
convertNxGenerator,
formatFiles,
GeneratorCallback,
readWorkspaceConfiguration,
removeDependenciesFromPackageJson,
Tree,
updateWorkspaceConfiguration,
writeJson,
} from '@nrwl/devkit';
import { jestInitGenerator } from '@nrwl/jest';
@ -42,6 +44,15 @@ function initRootBabelConfig(tree: Tree) {
writeJson(tree, '/babel.config.json', {
babelrcRoots: ['*'], // Make sure .babelrc files other than root can be loaded in a monorepo
});
const workspaceConfiguration = readWorkspaceConfiguration(tree);
if (workspaceConfiguration.namedInputs?.sharedGlobals) {
workspaceConfiguration.namedInputs.sharedGlobals.push(
'{workspaceRoot}/babel.config.json'
);
}
updateWorkspaceConfiguration(tree, workspaceConfiguration);
}
export async function webInitGenerator(tree: Tree, schema: Schema) {

View File

@ -75,12 +75,15 @@ Object {
"affected": Object {
"defaultBase": "main",
},
"implicitDependencies": Object {
".eslintrc.json": "*",
"package.json": Object {
"dependencies": "*",
"devDependencies": "*",
},
"namedInputs": Object {
"default": Array [
"{projectRoot}/**/*",
"sharedGlobals",
],
"production": Array [
"default",
],
"sharedGlobals": Array [],
},
"npmScope": "npmScope",
"targetDefaults": Object {
@ -88,6 +91,10 @@ Object {
"dependsOn": Array [
"^build",
],
"inputs": Array [
"production",
"^production",
],
},
},
"tasksRunnerOptions": Object {

View File

@ -1,32 +0,0 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"npmScope": "<%= npmScope %>",
"affected": {
"defaultBase": "<%= defaultBase %>"
},
<% if (packageManager && cli === 'angular') { -%>
"cli": {
"packageManager": "<%=packageManager%>"
},
<% } -%>
"implicitDependencies": {
"package.json": {
"dependencies": "*",
"devDependencies": "*"
},
".eslintrc.json": "*"
},
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": ["build", "lint", "test", "e2e"]
}
}
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"]
}
}
}

View File

@ -1,3 +1,5 @@
import { PackageManager } from '@nrwl/devkit';
export interface Schema {
name: string;
directory: string;
@ -9,5 +11,5 @@ export interface Schema {
cli: 'nx' | 'angular';
preset: string;
defaultBase: string;
packageManager?: string;
packageManager?: PackageManager;
}

View File

@ -45,13 +45,6 @@ describe('@nrwl/workspace:workspace', () => {
affected: {
defaultBase: 'main',
},
implicitDependencies: {
'package.json': {
dependencies: '*',
devDependencies: '*',
},
'.eslintrc.json': '*',
},
tasksRunnerOptions: {
default: {
runner: 'nx/tasks-runners/default',
@ -60,11 +53,6 @@ describe('@nrwl/workspace:workspace', () => {
},
},
},
targetDefaults: {
build: {
dependsOn: ['^build'],
},
},
});
const validateNxJson = ajv.compile(nxSchema);
expect(validateNxJson(nxJson)).toEqual(true);
@ -79,6 +67,43 @@ describe('@nrwl/workspace:workspace', () => {
expect(validateWorkspaceJson(workspaceJson)).toEqual(true);
});
it('should setup named inputs and target defaults for non-empty presets', async () => {
await workspaceGenerator(tree, {
name: 'proj',
directory: 'proj',
cli: 'nx',
preset: Preset.React,
defaultBase: 'main',
});
const nxJson = readJson<NxJsonConfiguration>(tree, '/proj/nx.json');
expect(nxJson).toEqual({
$schema: './node_modules/nx/schemas/nx-schema.json',
npmScope: 'proj',
affected: {
defaultBase: 'main',
},
tasksRunnerOptions: {
default: {
runner: 'nx/tasks-runners/default',
options: {
cacheableOperations: ['build', 'lint', 'test', 'e2e'],
},
},
},
namedInputs: {
default: ['{projectRoot}/**/*', 'sharedGlobals'],
production: ['default'],
sharedGlobals: [],
},
targetDefaults: {
build: {
dependsOn: ['^build'],
inputs: ['production', '^production'],
},
},
});
});
it('should create a prettierrc file', async () => {
await workspaceGenerator(tree, {
name: 'proj',

View File

@ -8,6 +8,7 @@ import {
formatFiles,
getPackageManagerVersion,
PackageManager,
NxJsonConfiguration,
} from '@nrwl/devkit';
import { Schema } from './schema';
import {
@ -59,8 +60,54 @@ function createAppsAndLibsFolders(host: Tree, options: Schema) {
}
}
function createNxJson(
host: Tree,
{ directory, npmScope, cli, packageManager, defaultBase, preset }: Schema
) {
const nxJson: NxJsonConfiguration & { $schema: string } = {
$schema: './node_modules/nx/schemas/nx-schema.json',
npmScope: npmScope,
affected: {
defaultBase,
},
tasksRunnerOptions: {
default: {
runner: 'nx/tasks-runners/default',
options: {
cacheableOperations: ['build', 'lint', 'test', 'e2e'],
},
},
},
};
if (
preset !== Preset.Core &&
preset !== Preset.NPM &&
preset !== Preset.Empty
) {
nxJson.namedInputs = {
default: ['{projectRoot}/**/*', 'sharedGlobals'],
production: ['default'],
sharedGlobals: [],
};
nxJson.targetDefaults = {
build: {
dependsOn: ['^build'],
inputs: ['production', '^production'],
},
};
}
if (packageManager && cli === 'angular') {
nxJson.cli = {
packageManager: packageManager,
};
}
writeJson<NxJsonConfiguration>(host, join(directory, 'nx.json'), nxJson);
}
function createFiles(host: Tree, options: Schema) {
const npmScope = options.npmScope ?? options.name;
const formattedNames = names(options.name);
generateFiles(host, pathJoin(__dirname, './files'), options.directory, {
formattedNames,
@ -75,7 +122,6 @@ function createFiles(host: Tree, options: Schema) {
angularCliVersion,
...(options as object),
nxVersion,
npmScope,
packageManager: options.packageManager,
});
}
@ -157,6 +203,7 @@ export async function workspaceGenerator(host: Tree, options: Schema) {
}
options = normalizeOptions(options);
createFiles(host, options);
createNxJson(host, options);
createPrettierrc(host, options);
if (options.cli === 'angular') {
decorateAngularClI(host, options);
@ -193,6 +240,7 @@ function addPropertyWithStableKeys(obj: any, key: string, value: string) {
function normalizeOptions(options: Schema) {
let defaultBase = options.defaultBase || deduceDefaultBase();
return {
npmScope: options.name,
...options,
defaultBase,
};

View File

@ -1,7 +1,7 @@
#!/usr/bin/env node
import * as yargs from 'yargs';
import { execSync } from 'child_process';
import { readFileSync, writeFileSync } from 'fs';
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { URL } from 'url';
import { join } from 'path';
@ -75,7 +75,8 @@ function hideFromGitIndex(uncommittedFiles: string[]) {
const uncommittedFiles = execSync('git diff --name-only --relative HEAD .')
.toString()
.split('\n')
.filter((i) => i.length > 0);
.filter((i) => i.length > 0)
.filter((f) => existsSync(f));
const unhideFromGitIndex = hideFromGitIndex(uncommittedFiles);
process.on('exit', unhideFromGitIndex);