diff --git a/e2e/workspace/src/workspace-lib.test.ts b/e2e/workspace/src/workspace-lib.test.ts new file mode 100644 index 0000000000000..7da4b1b9c5660 --- /dev/null +++ b/e2e/workspace/src/workspace-lib.test.ts @@ -0,0 +1,101 @@ +import { + checkFilesExist, + newProject, + runCLI, + runCLIAsync, + uniq, + updateFile, +} from '@nrwl/e2e/utils'; + +let proj: string; + +beforeAll(() => { + proj = newProject(); +}); + +describe('@nrwl/workspace:library', () => { + it('should be able to be created', () => { + const libName = uniq('mylib'); + const dirName = uniq('dir'); + + runCLI(`generate @nrwl/workspace:lib ${libName} --directory ${dirName}`); + + checkFilesExist( + `libs/${dirName}/${libName}/src/index.ts`, + `libs/${dirName}/${libName}/README.md` + ); + }); + + describe('linting', () => { + it('should support eslint', () => { + const libName = uniq('mylib'); + + runCLI(`generate @nrwl/workspace:lib ${libName}`); + + const result = runCLI(`lint ${libName}`); + + expect(result).toContain(`Linting "${libName}"...`); + expect(result).toContain('All files pass linting.'); + }); + + it('should support tslint', () => { + const libName = uniq('mylib'); + + runCLI(`generate @nrwl/workspace:lib ${libName} --linter tslint`); + + const result = runCLI(`lint ${libName}`); + + expect(result).toContain(`Linting "${libName}"...`); + expect(result).toContain('All files pass linting.'); + }); + }); + + describe('unit testing', () => { + it('should support jest', async () => { + const libName = uniq('mylib'); + + runCLI(`generate @nrwl/workspace:lib ${libName} --linter tslint`); + + const { stderr: result } = await runCLIAsync(`test ${libName}`); + + expect(result).toContain(`Test Suites: 1 passed, 1 total`); + expect(result).toContain('Tests: 1 passed, 1 total'); + }); + }); + + it('should be able to use and be used by other libs', () => { + const consumerLib = uniq('consumer'); + const producerLib = uniq('producer'); + + runCLI(`generate @nrwl/workspace:lib ${consumerLib}`); + runCLI(`generate @nrwl/workspace:lib ${producerLib}`); + + updateFile( + `libs/${producerLib}/src/lib/${producerLib}.ts`, + 'export const a = 0;' + ); + + updateFile( + `libs/${consumerLib}/src/lib/${consumerLib}.ts`, + ` + import { a } from '@${proj}/${producerLib}'; + + export function ${consumerLib}() { + return a + 1; + }` + ); + updateFile( + `libs/${consumerLib}/src/lib/${consumerLib}.spec.ts`, + ` + import { ${consumerLib} } from './${consumerLib}'; + + describe('', () => { + it('should return 1', () => { + expect(${consumerLib}()).toEqual(1); + }); + });` + ); + + runCLI(`test ${consumerLib}`); + }); +}); diff --git a/packages/linter/src/generators/init/init.ts b/packages/linter/src/generators/init/init.ts index aeed31217b32b..142e4cfb9ac12 100644 --- a/packages/linter/src/generators/init/init.ts +++ b/packages/linter/src/generators/init/init.ts @@ -10,6 +10,7 @@ import { eslintVersion, typescriptESLintVersion, tslintVersion, + buildAngularVersion, } from '../../utils/versions'; import { Linter } from '../utils/linter'; @@ -160,6 +161,7 @@ function initTsLint(tree: Tree) { {}, { tslint: tslintVersion, + '@angular-devkit/build-angular': buildAngularVersion, } ); } diff --git a/packages/linter/src/utils/versions.ts b/packages/linter/src/utils/versions.ts index 54f55516eb056..08dc18d46c2cc 100644 --- a/packages/linter/src/utils/versions.ts +++ b/packages/linter/src/utils/versions.ts @@ -1,6 +1,7 @@ export const nxVersion = '*'; export const tslintVersion = '~6.1.0'; +export const buildAngularVersion = '~0.1100.1'; export const typescriptESLintVersion = '4.3.0'; export const eslintVersion = '7.10.0'; diff --git a/packages/tao/src/commands/ngcli-adapter.ts b/packages/tao/src/commands/ngcli-adapter.ts index fe0d874d196b4..53d942525cff8 100644 --- a/packages/tao/src/commands/ngcli-adapter.ts +++ b/packages/tao/src/commands/ngcli-adapter.ts @@ -13,20 +13,6 @@ import { } from '@angular-devkit/core'; import * as chalk from 'chalk'; import { createConsoleLogger, NodeJsSyncHost } from '@angular-devkit/core/node'; -import { RunOptions } from './run'; -import { - FileSystemCollectionDescription, - FileSystemSchematicDescription, - NodeModulesEngineHost, - NodeWorkflow, - validateOptionsWithSchema, -} from '@angular-devkit/schematics/tools'; -import { - DryRunEvent, - formats, - Schematic, - TaskExecutor, -} from '@angular-devkit/schematics'; import * as fs from 'fs'; import { readFileSync } from 'fs'; import { detectPackageManager } from '@nrwl/tao/src/shared/package-manager'; @@ -37,16 +23,13 @@ import { toOldFormatOrNull, workspaceConfigName, } from '@nrwl/tao/src/shared/workspace'; -import { BaseWorkflow } from '@angular-devkit/schematics/src/workflow'; -import { NodePackageName } from '@angular-devkit/schematics/tasks/package-manager/options'; -import { BuiltinTaskExecutor } from '@angular-devkit/schematics/tasks/node'; -import { dirname, extname, join, resolve } from 'path'; +import * as path from 'path'; +import { dirname, extname, resolve } from 'path'; import * as stripJsonComments from 'strip-json-comments'; import { FileBuffer } from '@angular-devkit/core/src/virtual-fs/host/interface'; import { Observable, of } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; import { NX_ERROR, NX_PREFIX } from '../shared/logger'; -import * as path from 'path'; export async function scheduleTarget( root: string, @@ -87,17 +70,22 @@ function createWorkflow( root: string, opts: any ) { + const NodeWorkflow = require('@angular-devkit/schematics/tools').NodeWorkflow; const workflow = new NodeWorkflow(fsHost, { force: opts.force, dryRun: opts.dryRun, packageManager: detectPackageManager(), root: normalize(root), - registry: new schema.CoreSchemaRegistry(formats.standardFormats), + registry: new schema.CoreSchemaRegistry( + require('@angular-devkit/schematics').formats.standardFormats + ), resolvePaths: [process.cwd(), root], }); workflow.registry.addPostTransform(schema.transforms.addUndefinedDefaults); workflow.engineHost.registerOptionsTransform( - validateOptionsWithSchema(workflow.registry) + require('@angular-devkit/schematics/tools').validateOptionsWithSchema( + workflow.registry + ) ); return workflow; } @@ -117,7 +105,7 @@ async function createRecorder( logger: logging.Logger ) { const actualConfigName = await host.workspaceConfigName(); - return (event: DryRunEvent) => { + return (event: import('@angular-devkit/schematics').DryRunEvent) => { let eventPath = event.path.startsWith('/') ? event.path.substr(1) : event.path; @@ -156,12 +144,12 @@ async function createRecorder( async function runSchematic( host: NxScopedHost, root: string, - workflow: NodeWorkflow, + workflow: import('@angular-devkit/schematics/tools').NodeWorkflow, logger: logging.Logger, opts: GenerateOptions, - schematic: Schematic< - FileSystemCollectionDescription, - FileSystemSchematicDescription + schematic: import('@angular-devkit/schematics').Schematic< + import('@angular-devkit/schematics/tools').FileSystemCollectionDescription, + import('@angular-devkit/schematics/tools').FileSystemSchematicDescription >, printDryRunMessage = true, recorder: any = null @@ -194,95 +182,6 @@ async function runSchematic( return { status: 0, loggingQueue: record.loggingQueue }; } -class MigrationEngineHost extends NodeModulesEngineHost { - private nodeInstallLogPrinted = false; - - constructor(logger: logging.Logger) { - super(); - - // Overwrite the original CLI node package executor with a new one that does basically nothing - // since nx migrate doesn't do npm installs by itself - // (https://github.com/angular/angular-cli/blob/5df776780deadb6be5048b3ab006a5d3383650dc/packages/angular_devkit/schematics/tools/workflow/node-workflow.ts#L41) - this.registerTaskExecutor({ - name: NodePackageName, - create: () => - Promise.resolve(() => { - return new Promise((res) => { - if (!this.nodeInstallLogPrinted) { - logger.warn( - `An installation of node_modules has been required. Make sure to run it after the migration` - ); - this.nodeInstallLogPrinted = true; - } - - res(); - }); - }), - }); - - this.registerTaskExecutor(BuiltinTaskExecutor.RunSchematic); - } - - protected _resolveCollectionPath(name: string): string { - let collectionPath: string | undefined = undefined; - - if (name.startsWith('.') || name.startsWith('/')) { - name = resolve(name); - } - - if (extname(name)) { - collectionPath = require.resolve(name); - } else { - let packageJsonPath; - try { - packageJsonPath = require.resolve(path.join(name, 'package.json'), { - paths: [process.cwd()], - }); - } catch (e) { - // workaround for a bug in node 12 - packageJsonPath = require.resolve( - path.join(process.cwd(), name, 'package.json') - ); - } - - // eslint-disable-next-line @typescript-eslint/no-var-requires - const packageJson = require(packageJsonPath); - let pkgJsonSchematics = packageJson['nx-migrations']; - if (!pkgJsonSchematics) { - pkgJsonSchematics = packageJson['ng-update']; - if (!pkgJsonSchematics) { - throw new Error(`Could not find migrations in package: "${name}"`); - } - } - if (typeof pkgJsonSchematics != 'string') { - pkgJsonSchematics = pkgJsonSchematics.migrations; - } - collectionPath = resolve(dirname(packageJsonPath), pkgJsonSchematics); - } - - try { - if (collectionPath) { - JSON.parse(stripJsonComments(readFileSync(collectionPath).toString())); - return collectionPath; - } - } catch (e) { - throw new Error(`Invalid migration file in package: "${name}"`); - } - throw new Error(`Collection cannot be resolved: "${name}"`); - } -} - -class MigrationsWorkflow extends BaseWorkflow { - constructor(host: virtualFs.Host, logger: logging.Logger) { - super({ - host, - engineHost: new MigrationEngineHost(logger), - force: true, - dryRun: false, - }); - } -} - export class NxScopedHost extends virtualFs.ScopedHost { constructor(root: Path) { super(new NodeJsSyncHost(), root); @@ -536,6 +435,107 @@ export async function runMigration( schematic: string, isVerbose: boolean ) { + const NodeModulesEngineHost = require('@angular-devkit/schematics/tools') + .NodeModulesEngineHost; + + class MigrationEngineHost extends NodeModulesEngineHost { + private nodeInstallLogPrinted = false; + + constructor(logger: logging.Logger) { + super(); + + // Overwrite the original CLI node package executor with a new one that does basically nothing + // since nx migrate doesn't do npm installs by itself + // (https://github.com/angular/angular-cli/blob/5df776780deadb6be5048b3ab006a5d3383650dc/packages/angular_devkit/schematics/tools/workflow/node-workflow.ts#L41) + this.registerTaskExecutor({ + name: require('@angular-devkit/schematics/tasks/package-manager/options') + .NodePackageName, + create: () => + Promise.resolve( + () => { + return new Promise((res) => { + if (!this.nodeInstallLogPrinted) { + logger.warn( + `An installation of node_modules has been required. Make sure to run it after the migration` + ); + this.nodeInstallLogPrinted = true; + } + + res(); + }); + } + ), + }); + + this.registerTaskExecutor( + require('@angular-devkit/schematics/tasks/node').BuiltinTaskExecutor + .RunSchematic + ); + } + + protected _resolveCollectionPath(name: string): string { + let collectionPath: string | undefined = undefined; + + if (name.startsWith('.') || name.startsWith('/')) { + name = resolve(name); + } + + if (extname(name)) { + collectionPath = require.resolve(name); + } else { + let packageJsonPath; + try { + packageJsonPath = require.resolve(path.join(name, 'package.json'), { + paths: [process.cwd()], + }); + } catch (e) { + // workaround for a bug in node 12 + packageJsonPath = require.resolve( + path.join(process.cwd(), name, 'package.json') + ); + } + + // eslint-disable-next-line @typescript-eslint/no-var-requires + const packageJson = require(packageJsonPath); + let pkgJsonSchematics = packageJson['nx-migrations']; + if (!pkgJsonSchematics) { + pkgJsonSchematics = packageJson['ng-update']; + if (!pkgJsonSchematics) { + throw new Error(`Could not find migrations in package: "${name}"`); + } + } + if (typeof pkgJsonSchematics != 'string') { + pkgJsonSchematics = pkgJsonSchematics.migrations; + } + collectionPath = resolve(dirname(packageJsonPath), pkgJsonSchematics); + } + + try { + if (collectionPath) { + JSON.parse( + stripJsonComments(readFileSync(collectionPath).toString()) + ); + return collectionPath; + } + } catch (e) { + throw new Error(`Invalid migration file in package: "${name}"`); + } + throw new Error(`Collection cannot be resolved: "${name}"`); + } + } + + const { BaseWorkflow } = require('@angular-devkit/schematics/src/workflow'); + class MigrationsWorkflow extends BaseWorkflow { + constructor(host: virtualFs.Host, logger: logging.Logger) { + super({ + host, + engineHost: new MigrationEngineHost(logger) as any, + force: true, + dryRun: false, + }); + } + } + const logger = getLogger(isVerbose); const host = new NxScopedHostForMigrations(normalize(root)); const workflow = new MigrationsWorkflow(host, logger as any); @@ -599,7 +599,9 @@ export function wrapAngularDevkitSchematic( } as any; emptyLogger.createChild = () => emptyLogger; - const recorder = (event: DryRunEvent) => { + const recorder = ( + event: import('@angular-devkit/schematics').DryRunEvent + ) => { let eventPath = event.path.startsWith('/') ? event.path.substr(1) : event.path; diff --git a/packages/workspace/src/generators/library/library.spec.ts b/packages/workspace/src/generators/library/library.spec.ts index 396e1f58f560f..4c13054c93552 100644 --- a/packages/workspace/src/generators/library/library.spec.ts +++ b/packages/workspace/src/generators/library/library.spec.ts @@ -34,12 +34,6 @@ describe('lib', () => { expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); expect(workspaceJson.projects['my-lib'].architect.build).toBeUndefined(); - expect(workspaceJson.projects['my-lib'].architect.lint).toEqual({ - builder: '@nrwl/linter:eslint', - options: { - lintFilePatterns: ['libs/my-lib/**/*.ts'], - }, - }); }); it('should update nx.json', async () => { @@ -269,16 +263,129 @@ describe('lib', () => { }, ]); }); + }); - it('should create a local .eslintrc.json', async () => { - await libraryGenerator(tree, { - ...defaultOptions, - name: 'myLib', - directory: 'myDir', + describe('--linter', () => { + describe('eslint', () => { + it('should add eslint dependencies', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + }); + + const packageJson = readJson(tree, 'package.json'); + expect(packageJson.devDependencies['eslint']).toBeDefined(); + expect(packageJson.devDependencies['@nrwl/linter']).toBeDefined(); + expect( + packageJson.devDependencies['@nrwl/eslint-plugin-nx'] + ).toBeDefined(); + }); + + describe('not nested', () => { + it('should update workspace.json', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + }); + + const workspaceJson = readJson(tree, 'workspace.json'); + expect(workspaceJson.projects['my-lib'].architect.lint).toEqual({ + builder: '@nrwl/linter:eslint', + options: { + lintFilePatterns: ['libs/my-lib/**/*.ts'], + }, + }); + }); }); - const lint = readJson(tree, 'libs/my-dir/my-lib/.eslintrc.json'); - expect(lint.extends).toEqual(['../../../.eslintrc.json']); + describe('nested', () => { + it('should update workspace.json', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + }); + + const workspaceJson = readJson(tree, 'workspace.json'); + expect( + workspaceJson.projects['my-dir-my-lib'].architect.lint + ).toEqual({ + builder: '@nrwl/linter:eslint', + options: { + lintFilePatterns: ['libs/my-dir/my-lib/**/*.ts'], + }, + }); + }); + + it('should create a local .eslintrc.json', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + }); + + const lint = readJson(tree, 'libs/my-dir/my-lib/.eslintrc.json'); + expect(lint.extends).toEqual(['../../../.eslintrc.json']); + }); + }); + }); + + describe('tslint', () => { + it('should add tslint dependencies', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + linter: 'tslint', + }); + + const packageJson = readJson(tree, 'package.json'); + expect(packageJson.devDependencies['tslint']).toBeDefined(); + expect( + packageJson.devDependencies['@angular-devkit/build-angular'] + ).toBeDefined(); + }); + + it('should update workspace.json', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + linter: 'tslint', + }); + + const workspaceJson = readJson(tree, 'workspace.json'); + expect(workspaceJson.projects['my-dir-my-lib'].architect.lint).toEqual({ + builder: '@angular-devkit/build-angular:tslint', + options: { + exclude: ['**/node_modules/**', '!libs/my-dir/my-lib/**/*'], + tsConfig: [ + 'libs/my-dir/my-lib/tsconfig.lib.json', + 'libs/my-dir/my-lib/tsconfig.spec.json', + ], + }, + }); + }); + + it('should create a local tslint.json', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + linter: 'tslint', + }); + const tslintJson = readJson(tree, 'libs/my-dir/my-lib/tslint.json'); + expect(tslintJson).toMatchInlineSnapshot(` + Object { + "extends": "../../../tslint.json", + "linterOptions": Object { + "exclude": Array [ + "!**/*", + ], + }, + "rules": Object {}, + } + `); + }); }); }); diff --git a/packages/workspace/src/generators/library/library.ts b/packages/workspace/src/generators/library/library.ts index 521b782f2e570..61b9d0b1888b3 100644 --- a/packages/workspace/src/generators/library/library.ts +++ b/packages/workspace/src/generators/library/library.ts @@ -42,6 +42,9 @@ function addLint(tree: Tree, options: NormalizedSchema) { project: options.name, linter: options.linter, skipFormat: true, + tsConfigPaths: [ + joinPathFragments(options.projectRoot, 'tsconfig.lib.json'), + ], eslintFilePatterns: [ `${options.projectRoot}/**/*.${options.js ? 'js' : 'ts'}`, ], @@ -77,11 +80,7 @@ function updateRootTsConfig(host: Tree, options: NormalizedSchema) { } c.paths[options.importPath] = [ - joinPathFragments( - options.projectRoot, - './src', - 'index.' + (options.js ? 'js' : 'ts') - ), + join(options.projectRoot, './src', 'index.' + (options.js ? 'js' : 'ts')), ]; return json; diff --git a/packages/workspace/src/generators/new/__snapshots__/new.spec.ts.snap b/packages/workspace/src/generators/new/__snapshots__/new.spec.ts.snap index 896fc5062546e..271f65f4dcb04 100644 --- a/packages/workspace/src/generators/new/__snapshots__/new.spec.ts.snap +++ b/packages/workspace/src/generators/new/__snapshots__/new.spec.ts.snap @@ -11,10 +11,8 @@ Object { "@nrwl/workspace": "*", "@types/node": "12.12.38", "dotenv": "6.2.0", - "eslint": "7.10.0", "prettier": "2.2.1", "ts-node": "~9.1.1", - "tslint": "~6.1.0", "typescript": "~4.0.3", }, "license": "MIT", @@ -56,10 +54,8 @@ Object { "@nrwl/workspace": "*", "@types/node": "12.12.38", "dotenv": "6.2.0", - "eslint": "7.10.0", "prettier": "2.2.1", "ts-node": "~9.1.1", - "tslint": "~6.1.0", "typescript": "~4.0.3", }, "license": "MIT", @@ -102,10 +98,8 @@ Object { "@nrwl/workspace": "*", "@types/node": "12.12.38", "dotenv": "6.2.0", - "eslint": "7.10.0", "prettier": "2.2.1", "ts-node": "~9.1.1", - "tslint": "~6.1.0", "typescript": "~4.0.3", }, "license": "MIT", diff --git a/packages/workspace/src/generators/workspace/files/package.json__tmpl__ b/packages/workspace/src/generators/workspace/files/package.json__tmpl__ index 8af4e779568a4..9aa914d00438f 100644 --- a/packages/workspace/src/generators/workspace/files/package.json__tmpl__ +++ b/packages/workspace/src/generators/workspace/files/package.json__tmpl__ @@ -39,8 +39,6 @@ "@types/node": "12.12.38", "dotenv": "6.2.0", "ts-node": "~9.1.1", - "tslint": "~6.1.0", - "eslint": "<%= eslintVersion %>", "typescript": "<%= typescriptVersion %>", "prettier": "<%= prettierVersion %>" } diff --git a/packages/workspace/src/generators/workspace/workspace.ts b/packages/workspace/src/generators/workspace/workspace.ts index 1812b1ff7da43..16c563eace61b 100644 --- a/packages/workspace/src/generators/workspace/workspace.ts +++ b/packages/workspace/src/generators/workspace/workspace.ts @@ -10,7 +10,6 @@ import { import { Schema } from './schema'; import { angularCliVersion, - eslintVersion, nxVersion, prettierVersion, typescriptVersion, @@ -62,7 +61,6 @@ function createFiles(host: Tree, options: Schema) { nxCli: false, typescriptVersion, prettierVersion, - eslintVersion, // angular cli is used only when workspace schematics is added to angular cli angularCliVersion, ...(options as object), diff --git a/packages/workspace/src/utils/lint.ts b/packages/workspace/src/utils/lint.ts index b7738d52e51ea..c9dcff190fa92 100644 --- a/packages/workspace/src/utils/lint.ts +++ b/packages/workspace/src/utils/lint.ts @@ -11,6 +11,8 @@ import { typescriptESLintVersion, eslintConfigPrettierVersion, nxVersion, + tslintVersion, + buildAngularVersion, } from './versions'; import { offsetFromRoot } from '@nrwl/devkit'; @@ -88,6 +90,16 @@ export function addLintFiles( } }); + chainedCommands.push( + addDepsToPackageJson( + {}, + { + tslint: tslintVersion, + '@angular-devkit/build-angular': buildAngularVersion, + } + ) + ); + return chain(chainedCommands); } diff --git a/packages/workspace/src/utils/versions.ts b/packages/workspace/src/utils/versions.ts index 8365705371b67..dc693beb6a4b6 100644 --- a/packages/workspace/src/utils/versions.ts +++ b/packages/workspace/src/utils/versions.ts @@ -1,8 +1,10 @@ export const nxVersion = '*'; export const angularCliVersion = '~11.0.0'; +export const buildAngularVersion = '~0.1100.1'; export const typescriptVersion = '~4.0.3'; export const prettierVersion = '2.2.1'; export const typescriptESLintVersion = '4.3.0'; +export const tslintVersion = '~6.1.0'; export const eslintVersion = '7.10.0'; export const eslintConfigPrettierVersion = '6.0.0';