UNPKG

@yolkai/nx-schematics

Version:

Extensible Dev Tools for Monorepos: Schematics

493 lines (483 loc) 22.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const schematics_1 = require("@angular-devkit/schematics"); const literals_1 = require("@angular-devkit/core/src/utils/literals"); const tasks_1 = require("@angular-devkit/schematics/tasks"); const nx_workspace_1 = require("@yolkai/nx-workspace"); const nx_workspace_2 = require("@yolkai/nx-workspace"); const nx_workspace_3 = require("@yolkai/nx-workspace"); const nx_workspace_4 = require("@yolkai/nx-workspace"); const nx_workspace_5 = require("@yolkai/nx-workspace"); function createKarma(host, project) { const offset = nx_workspace_4.offsetFromRoot(project.root); nx_workspace_1.createOrUpdate(host, `${project.root}/karma.conf.js`, ` // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html module.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], client: { clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, '${offset}coverage'), reports: ['html', 'lcovonly'], fixWebpackSourcePaths: true }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false }); }; `); } function createProtractor(host, project) { nx_workspace_1.createOrUpdate(host, `${project.root}/protractor.conf.js`, ` // Protractor configuration file, see link for more information // https://github.com/angular/protractor/blob/master/lib/config.ts const { SpecReporter } = require('jasmine-spec-reporter'); exports.config = { allScriptsTimeout: 11000, specs: [ './src/**/*.e2e-spec.ts' ], capabilities: { 'browserName': 'chrome' }, directConnect: true, baseUrl: 'http://localhost:4200/', framework: 'jasmine', jasmineNodeOpts: { showColors: true, defaultTimeoutInterval: 30000, print: function() {} }, onPrepare() { require('ts-node').register({ project: require('path').join(__dirname, './tsconfig.e2e.json') }); jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); } }; `); } function createTestTs(host, project) { if (project.projectType === 'library') { nx_workspace_1.createOrUpdate(host, `${project.sourceRoot}/test.ts`, ` // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'core-js/es7/reflect'; import 'zone.js/dist/zone'; import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\\.spec\\.ts$/); // And load the modules. context.keys().map(context); `); } else { nx_workspace_1.createOrUpdate(host, `${project.sourceRoot}/test.ts`, ` // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); `); } return host; } function createBrowserlist(host, project) { nx_workspace_1.createOrUpdate(host, `${project.root}/browserslist`, literals_1.stripIndents ` # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers # For additional information regarding the format and rule options, please see: # https://github.com/browserslist/browserslist#queries # For IE 9-11 support, please uncomment the last line of the file and adjust as needed > 0.5% last 2 versions Firefox ESR not dead # IE 9-11 `); } function createTsconfigSpecJson(host, project) { const files = ['src/test.ts']; const offset = nx_workspace_4.offsetFromRoot(project.root); const compilerOptions = { outDir: `${offset}dist/out-tsc/${project.root}`, types: ['jasmine', 'node'] }; if (project.projectType === 'application') { files.push('src/polyfills.ts'); compilerOptions['module'] = 'commonjs'; } nx_workspace_1.createOrUpdate(host, `${project.root}/tsconfig.spec.json`, nx_workspace_2.serializeJson({ extends: `${offset}tsconfig.json`, compilerOptions, files, include: ['**/*.spec.ts', '**/*.d.ts'] })); } function createTslintJson(host, project) { const offset = nx_workspace_4.offsetFromRoot(project.root); nx_workspace_1.createOrUpdate(host, `${project.root}/tslint.json`, nx_workspace_2.serializeJson({ extends: `${offset}tslint.json`, rules: { 'directive-selector': [true, 'attribute', project.prefix, 'camelCase'], 'component-selector': [true, 'element', project.prefix, 'kebab-case'] } })); } function createTsconfigLibJson(host, project) { const offset = nx_workspace_4.offsetFromRoot(project.root); nx_workspace_1.createOrUpdate(host, `${project.root}/tsconfig.lib.json`, nx_workspace_2.serializeJson({ extends: `${offset}tsconfig.json`, compilerOptions: { outDir: `${offset}out-tsc/${project.root}`, target: 'es2015', module: 'es2015', moduleResolution: 'node', declaration: true, sourceMap: true, inlineSources: true, emitDecoratorMetadata: true, experimentalDecorators: true, importHelpers: true, types: [], lib: ['dom', 'es2015'] }, angularCompilerOptions: { annotateForClosureCompiler: true, skipTemplateCodegen: true, strictMetadataEmit: true, fullTemplateTypeCheck: true, strictInjectionParameters: true, flatModuleId: 'AUTOGENERATED', flatModuleOutFile: 'AUTOGENERATED' }, exclude: ['src/test.ts', '**/*.spec.ts'] })); } function createAdditionalFiles(host) { const workspaceJson = nx_workspace_1.readJsonInTree(host, 'angular.json'); Object.entries(workspaceJson.projects).forEach(([key, project]) => { if (project.architect.test) { createTsconfigSpecJson(host, project); createKarma(host, project); createTestTs(host, project); } if (project.projectType === 'application' && !project.architect.e2e) { createBrowserlist(host, project); createTslintJson(host, project); } if (project.projectType === 'application' && project.architect.e2e) { createProtractor(host, project); } if (project.projectType === 'library') { createTsconfigLibJson(host, project); createTslintJson(host, project); } }); return host; } function moveE2eTests(host, context) { const workspaceJson = nx_workspace_1.readJsonInTree(host, 'angular.json'); Object.entries(workspaceJson.projects).forEach(([key, p]) => { if (p.projectType === 'application' && !p.architect.e2e) { nx_workspace_2.renameSync(`${p.root}/e2e`, `${p.root}-e2e/src`, err => { if (!err) { context.logger.info(`Moved ${p.root}/e2e to ${p.root}-e2e/src`); return; } context.logger.info(`We could not find e2e tests for the ${key} project.`); context.logger.info(`Ignore this if there are no e2e tests for ${key} project.`); context.logger.warn(err.message); context.logger.warn(`If there are e2e tests for the ${key} project, we recommend manually moving them to ${p.root}-e2e/src.`); }); } }); } function deleteUnneededFiles(host) { try { host.delete('karma.conf.js'); host.delete('protractor.conf.js'); host.delete('tsconfig.spec.json'); host.delete('test.js'); } catch (e) { } return host; } function patchLibIndexFiles(host, context) { const workspaceJson = nx_workspace_1.readJsonInTree(host, 'angular.json'); Object.entries(workspaceJson.projects).forEach(([key, p]) => { if (p.projectType === 'library') { try { // TODO: incorporate this into fileutils.renameSync nx_workspace_2.renameSync(p.sourceRoot, `${p.root}/lib`, err => { if (err) { context.logger.warn(`We could not resolve the "sourceRoot" of the ${key} library.`); throw err; } nx_workspace_2.renameSync(`${p.root}/lib`, `${p.sourceRoot}/lib`, err => { // This should never error context.logger.info(`Moved ${p.sourceRoot} to ${p.sourceRoot}/lib`); context.logger.warn('Deep imports may have been affected. Always try to import from the index of the lib.'); }); }); const npmScope = nx_workspace_1.readJsonInTree(host, 'nx.json').npmScope; host = nx_workspace_1.updateJsonInTree('tsconfig.json', json => { json.compilerOptions.paths = json.compilerOptions.paths || {}; json.compilerOptions.paths[`@${npmScope}/${p.root.replace('libs/', '')}`] = [`${p.sourceRoot}/index.ts`]; return json; })(host, context); const content = host.read(`${p.root}/index.ts`).toString(); host.create(`${p.sourceRoot}/index.ts`, content.replace(new RegExp('/src/', 'g'), '/lib/')); host.delete(`${p.root}/index.ts`); } catch (e) { context.logger.warn(`Nx failed to successfully update '${p.root}'.`); context.logger.warn(`Error message: ${e.message}`); context.logger.warn(`Please update the library manually.`); } } }); return host; } const updatePackageJson = nx_workspace_1.updateJsonInTree('package.json', json => { json.dependencies = json.dependencies || {}; json.devDependencies = json.devDependencies || {}; json.scripts = Object.assign({}, json.scripts, { 'affected:test': './node_modules/.bin/nx affected:test' }); json.dependencies = Object.assign({}, json.dependencies, { // Migrating Angular Dependencies because ng update @angular/core doesn't work well right now '@angular/animations': '6.0.1', '@angular/common': '6.0.1', '@angular/compiler': '6.0.1', '@angular/core': '6.0.1', '@angular/forms': '6.0.1', '@angular/platform-browser': '6.0.1', '@angular/platform-browser-dynamic': '6.0.1', '@angular/router': '6.0.1', rxjs: '6.0.0', 'rxjs-compat': '6.0.0', 'zone.js': '^0.8.26', 'core-js': '^2.5.4', // End Angular Versions '@ngrx/effects': '6.0.1', '@ngrx/router-store': '6.0.1', '@ngrx/store': '6.0.1', '@ngrx/store-devtools': '6.0.1', '@yolkai/nx': '6.0.2' }); json.devDependencies = Object.assign({}, json.devDependencies, { // Migrating Angular Dependencies because ng update @angular/core doesn't work well right now '@angular/compiler-cli': '6.0.1', '@angular/language-service': '6.0.1', // End Angular Versions typescript: '2.7.2', 'jasmine-marbles': '0.3.1', '@types/jasmine': '~2.8.6', '@types/jasminewd2': '~2.0.3', '@types/node': '~8.9.4', codelyzer: '~4.2.1', 'jasmine-core': '~2.99.1', 'jasmine-spec-reporter': '~4.2.1', karma: '~2.0.0', 'karma-chrome-launcher': '~2.2.0', 'karma-coverage-istanbul-reporter': '~1.4.2', 'karma-jasmine': '~1.1.0', 'karma-jasmine-html-reporter': '^0.2.2', protractor: '~5.3.0', 'ts-node': '~5.0.1', tslint: '~5.9.1', prettier: '1.10.2' }); if (json.dependencies['@angular/http']) { json.dependencies['@angular/http'] = '6.0.1'; } if (json.dependencies['@angular/platform-server']) { json.dependencies['@angular/platform-server'] = '6.0.1'; } if (json.dependencies['@angular/service-worker']) { json.dependencies['@angular/service-worker'] = '6.0.1'; } if (json.dependencies['@angular/platform-webworker']) { json.dependencies['@angular/platform-webworker'] = '6.0.1'; } if (json.dependencies['@angular/platform-webworker-dynamic']) { json.dependencies['@angular/platform-webworker-dynamic'] = '6.0.1'; } if (json.dependencies['@angular/upgrade']) { json.dependencies['@angular/upgrade'] = '6.0.1'; } return json; }); function createDefaultAppTsConfig(host, project) { const offset = nx_workspace_4.offsetFromRoot(project.root); const defaultAppTsConfig = { extends: `${offset}tsconfig.json`, compilerOptions: { outDir: `${offset}dist/out-tsc/${project.root}`, module: 'es2015' }, include: ['**/*.ts'], exclude: ['src/test.ts', '**/*.spec.ts'] }; nx_workspace_1.createOrUpdate(host, `${project.root}/tsconfig.app.json`, nx_workspace_2.serializeJson(defaultAppTsConfig)); } function createDefaultE2eTsConfig(host, project) { const offset = nx_workspace_4.offsetFromRoot(project.root); const defaultE2eTsConfig = { extends: `${offset}tsconfig.json`, compilerOptions: { outDir: `${offset}dist/out-tsc/${project.root}`, module: 'commonjs', target: 'es5', types: ['jasmine', 'jasminewd2', 'node'] } }; nx_workspace_1.createOrUpdate(host, `${project.root}/tsconfig.e2e.json`, nx_workspace_2.serializeJson(defaultE2eTsConfig)); } function updateTsConfigs(host) { const workspaceJson = nx_workspace_1.readJsonInTree(host, 'angular.json'); Object.entries(workspaceJson.projects).forEach(([key, project]) => { if (project.architect.build && project.architect.build.options.main.startsWith('apps')) { const offset = nx_workspace_4.offsetFromRoot(project.root); const originalTsConfigPath = `${project.root}/src/tsconfig.app.json`; if (host.exists(originalTsConfigPath)) { const tsConfig = nx_workspace_1.readJsonInTree(host, originalTsConfigPath); if (!tsConfig.exclude.includes('src/test.ts')) { tsConfig.exclude.push('src/test.ts'); } nx_workspace_1.createOrUpdate(host, `${project.root}/tsconfig.app.json`, nx_workspace_2.serializeJson(Object.assign({}, tsConfig, { extends: `${offset}tsconfig.json`, compilerOptions: Object.assign({}, tsConfig.compilerOptions, { outDir: `${offset}dist/out-tsc/${project.root}` }), include: tsConfig.include.map((include) => { if (include.startsWith('../../../')) { include = include.substring(3); } if (include.includes('/libs/') && include.endsWith('index.ts')) { include = include.replace('index.ts', 'src/index.ts'); } return include; }) }))); host.delete(`${project.root}/src/tsconfig.app.json`); } else { createDefaultAppTsConfig(host, project); } } if (project.architect.e2e) { const offset = nx_workspace_4.offsetFromRoot(project.root); if (host.exists(`${project.root}/src/tsconfig.e2e.json`)) { const tsConfig = nx_workspace_1.readJsonInTree(host, `${project.root}/src/tsconfig.e2e.json`); tsConfig.extends = `${offset}tsconfig.json`; tsConfig.compilerOptions = Object.assign({}, tsConfig.compilerOptions, { outDir: `${offset}dist/out-tsc/${project.root}` }); delete tsConfig.include; delete tsConfig.exclude; nx_workspace_1.createOrUpdate(host, `${project.root}/tsconfig.e2e.json`, nx_workspace_2.serializeJson(tsConfig)); host.delete(`${project.root}/src/tsconfig.e2e.json`); } else { createDefaultE2eTsConfig(host, project); } } }); return host; } const updateworkspaceJson = nx_workspace_1.updateWorkspaceInTree(json => { json.newProjectRoot = ''; json.cli = Object.assign({}, json.cli, { defaultCollection: '@yolkai/nx-schematics' }); delete json.projects.$workspaceRoot; delete json.projects['$workspaceRoot-e2e']; const prefix = json.schematics['@yolkai/nx-schematics:component'].prefix; delete json.schematics; json.defaultProject = pathToName(json.defaultProject); const projects = {}; Object.entries(json.projects).forEach(([key, project]) => { const type = !project.architect.build ? 'e2e' : project.architect.build.options.main.startsWith('apps') ? 'application' : 'library'; if (type !== 'e2e') { project.projectType = type; } switch (type) { case 'application': project.prefix = prefix; project.architect.build.options.tsConfig = `${project.root}/tsconfig.app.json`; project.architect.test.options.karmaConfig = `${project.root}/karma.conf.js`; project.architect.test.options.tsConfig = `${project.root}/tsconfig.spec.json`; project.architect.test.options.main = `${project.sourceRoot}/test.ts`; project.architect.lint.options.tsConfig = [ `${project.root}/tsconfig.app.json`, `${project.root}/tsconfig.spec.json` ]; project.architect.serve.options.browserTarget = nx_workspace_3.serializeTarget(parseAndNormalizeTarget(project.architect.serve.options.browserTarget)); project.architect.serve.configurations.production.browserTarget = nx_workspace_3.serializeTarget(parseAndNormalizeTarget(project.architect.serve.configurations.production.browserTarget)); project.architect['extract-i18n'].options.browserTarget = nx_workspace_3.serializeTarget(parseAndNormalizeTarget(project.architect['extract-i18n'].options.browserTarget)); projects[pathToName(key)] = project; break; case 'library': project.prefix = prefix; project.projectType = 'library'; project.architect.test.options.karmaConfig = `${project.root}/karma.conf.js`; project.architect.test.options.tsConfig = `${project.root}/tsconfig.spec.json`; project.architect.test.options.main = `${project.sourceRoot}/test.ts`; project.architect.lint.options.tsConfig = [ `${project.root}/tsconfig.lib.json`, `${project.root}/tsconfig.spec.json` ]; delete project.architect.build; delete project.architect.serve; delete project.architect['extract-i18n']; projects[pathToName(key)] = project; break; case 'e2e': const appProjectKey = nx_workspace_3.parseTarget(project.architect.e2e.options.devServerTarget).project; const appProject = json.projects[appProjectKey]; if (appProject.projectType === 'library') { break; } project.root = `${appProject.root}-e2e`; project.sourceRoot = `${project.root}/src`; project.prefix = prefix; project.architect.e2e.options.protractorConfig = `${project.root}/protractor.conf.js`; project.architect.lint.options.tsConfig = [ `${project.root}/tsconfig.e2e.json` ]; project.architect.e2e.options.devServerTarget = nx_workspace_3.serializeTarget(parseAndNormalizeTarget(project.architect.e2e.options.devServerTarget)); projects[pathToName(key)] = project; break; } }); json.projects = projects; return json; }); function addInstallTask(host, context) { context.addTask(new tasks_1.NodePackageInstallTask()); } function checkCli6Upgraded(host) { if (!host.exists('angular.json') && host.exists('.angular-cli.json')) { throw new Error('Please install the latest version and run ng update @angular/cli first'); } } function parseAndNormalizeTarget(s) { const r = nx_workspace_3.parseTarget(s); return Object.assign({}, r, { project: pathToName(r.project) }); } function pathToName(s) { return s.replace(new RegExp('/', 'g'), '-'); } function default_1() { return schematics_1.chain([ checkCli6Upgraded, updatePackageJson, updateworkspaceJson, moveE2eTests, updateTsConfigs, createAdditionalFiles, deleteUnneededFiles, patchLibIndexFiles, addInstallTask, nx_workspace_5.formatFiles() ]); } exports.default = default_1;