UNPKG

@yolkai/nx-workspace

Version:

Extensible Dev Tools for Monorepos

408 lines (407 loc) 18.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const schematics_1 = require("@angular-devkit/schematics"); const path = require("path"); const path_1 = require("path"); const versions_1 = require("../../utils/versions"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const tasks_1 = require("@angular-devkit/schematics/tasks"); const nx_workspace_1 = require("@yolkai/nx-workspace"); const workspace_1 = require("../workspace/workspace"); const workspace_2 = require("../../utils/workspace"); function updatePackageJson() { return nx_workspace_1.updateJsonInTree('package.json', packageJson => { packageJson.scripts = packageJson.scripts || {}; packageJson.scripts = Object.assign({}, packageJson.scripts, { nx: 'nx', 'affected:apps': 'nx affected:apps', 'affected:libs': 'nx affected:libs', 'affected:build': 'nx affected:build', 'affected:e2e': 'nx affected:e2e', 'affected:test': 'nx affected:test', 'affected:lint': 'nx affected:lint', 'affected:dep-graph': 'nx affected:dep-graph', affected: 'nx affected', format: 'nx format:write', 'format:write': 'nx format:write', 'format:check': 'nx format:check', update: 'ng update @yolkai/nx-workspace', 'update:check': 'ng update', lint: 'nx workspace-lint && ng lint', 'dep-graph': 'nx dep-graph', 'workspace-schematic': 'nx workspace-schematic', help: 'nx help' }); packageJson.devDependencies = packageJson.devDependencies || {}; if (!packageJson.dependencies) { packageJson.dependencies = {}; } if (!packageJson.dependencies['@yolkai/nx-angular']) { packageJson.dependencies['@yolkai/nx-angular'] = versions_1.nxVersion; } if (!packageJson.devDependencies['@yolkai/nx-workspace']) { packageJson.devDependencies['@yolkai/nx-workspace'] = versions_1.nxVersion; } if (!packageJson.devDependencies['@angular/cli']) { packageJson.devDependencies['@angular/cli'] = versions_1.angularCliVersion; } if (!packageJson.devDependencies['prettier']) { packageJson.devDependencies['prettier'] = versions_1.prettierVersion; } return packageJson; }); } function convertPath(name, originalPath) { return `apps/${name}/${originalPath}`; } function updateAngularCLIJson(options) { return workspace_2.updateWorkspace(workspace => { const appName = workspace.extensions.defaultProject; const e2eName = appName + '-e2e'; const e2eRoot = path_1.join('apps', e2eName); workspace.extensions.newProjectRoot = ''; const defaultProject = workspace.projects.get(appName); const oldSourceRoot = defaultProject.sourceRoot; const newRoot = path_1.join('apps', appName); defaultProject.root = newRoot; defaultProject.sourceRoot = path_1.join(newRoot, 'src'); function convertBuildOptions(buildOptions) { buildOptions.outputPath = buildOptions.outputPath && path_1.join('dist', 'apps', appName); buildOptions.index = buildOptions.index && convertAsset(buildOptions.index); buildOptions.main = buildOptions.main && convertAsset(buildOptions.main); buildOptions.polyfills = buildOptions.polyfills && convertAsset(buildOptions.polyfills); buildOptions.tsConfig = buildOptions.tsConfig && path_1.join(newRoot, 'tsconfig.app.json'); buildOptions.assets = buildOptions.assets && buildOptions.assets.map(convertAsset); buildOptions.styles = buildOptions.styles && buildOptions.styles.map(convertAsset); buildOptions.scripts = buildOptions.scripts && buildOptions.scripts.map(convertAsset); buildOptions.fileReplacements = buildOptions.fileReplacements && buildOptions.fileReplacements.map(replacement => ({ replace: convertAsset(replacement.replace), with: convertAsset(replacement.with) })); } convertBuildOptions(defaultProject.targets.get('build').options); Object.values(defaultProject.targets.get('build').configurations).forEach(config => convertBuildOptions(config)); const testOptions = defaultProject.targets.get('test').options; testOptions.main = testOptions.main && convertAsset(testOptions.main); testOptions.polyfills = testOptions.polyfills && convertAsset(testOptions.polyfills); testOptions.tsConfig = path_1.join(newRoot, 'tsconfig.spec.json'); testOptions.karmaConfig = path_1.join(newRoot, 'karma.conf.js'); testOptions.assets = testOptions.assets && testOptions.assets.map(convertAsset); testOptions.styles = testOptions.styles && testOptions.styles.map(convertAsset); testOptions.scripts = testOptions.scripts && testOptions.scripts.map(convertAsset); const lintTarget = defaultProject.targets.get('lint'); lintTarget.options.tsConfig = [ path_1.join(newRoot, 'tsconfig.app.json'), path_1.join(newRoot, 'tsconfig.spec.json') ]; function convertServerOptions(serverOptions) { serverOptions.outputPath = serverOptions.outputPath && path.join('dist', 'apps', options.name + '-server'); serverOptions.main = serverOptions.main && convertAsset(serverOptions.main); serverOptions.tsConfig = serverOptions.tsConfig && path_1.join('apps', appName, 'tsconfig.server.json'); serverOptions.fileReplacements = serverOptions.fileReplacements && serverOptions.fileReplacements.map(replacement => ({ replace: convertAsset(replacement.replace), with: convertAsset(replacement.with) })); } if (defaultProject.targets.has('server')) { const serverOptions = defaultProject.targets.get('server').options; convertServerOptions(serverOptions); Object.values(defaultProject.targets.get('server').configurations).forEach(config => convertServerOptions(config)); } function convertAsset(asset) { if (typeof asset === 'string') { return asset.startsWith(oldSourceRoot) ? convertPath(appName, asset) : asset; } else { return Object.assign({}, asset, { input: asset.input && asset.input.startsWith(oldSourceRoot) ? convertPath(appName, asset.input) : asset.input }); } } if (defaultProject.targets.get('e2e')) { const e2eProject = workspace.projects.add({ name: e2eName, root: e2eRoot, projectType: 'application', targets: { e2e: defaultProject.targets.get('e2e') } }); e2eProject.targets.add({ name: 'lint', builder: '@angular-devkit/build-angular:tslint', options: Object.assign({}, lintTarget.options, { tsConfig: path_1.join(e2eRoot, 'tsconfig.json') }) }); e2eProject.targets.get('e2e').options.protractorConfig = path_1.join(e2eRoot, 'protractor.conf.js'); defaultProject.targets.delete('e2e'); } }); } function updateTsConfig(options) { return nx_workspace_1.updateJsonInTree('tsconfig.json', tsConfigJson => setUpCompilerOptions(tsConfigJson, options.npmScope, '')); } function updateTsConfigsJson(options) { return (host) => { const workspaceJson = nx_workspace_1.readJsonInTree(host, 'angular.json'); const app = workspaceJson.projects[options.name]; const e2eProject = getE2eProject(workspaceJson); const offset = '../../'; return schematics_1.chain([ nx_workspace_1.updateJsonInTree(app.architect.build.options.tsConfig, json => { json.extends = `${offset}tsconfig.json`; json.compilerOptions.outDir = `${offset}dist/out-tsc`; return json; }), nx_workspace_1.updateJsonInTree(app.architect.test.options.tsConfig, json => { json.extends = `${offset}tsconfig.json`; json.compilerOptions.outDir = `${offset}dist/out-tsc`; return json; }), app.architect.server ? nx_workspace_1.updateJsonInTree(app.architect.server.options.tsConfig, json => { json.compilerOptions.outDir = `${offset}dist/out-tsc`; return json; }) : schematics_1.noop(), !!e2eProject ? nx_workspace_1.updateJsonInTree(e2eProject.architect.lint.options.tsConfig, json => { json.extends = `${nx_workspace_1.offsetFromRoot(e2eProject.root)}tsconfig.json`; json.compilerOptions = Object.assign({}, json.compilerOptions, { outDir: `${nx_workspace_1.offsetFromRoot(e2eProject.root)}dist/out-tsc` }); return json; }) : schematics_1.noop() ]); }; } function updateTsLint() { return nx_workspace_1.updateJsonInTree('tslint.json', tslintJson => { [ 'no-trailing-whitespace', 'one-line', 'quotemark', 'typedef-whitespace', 'whitespace' ].forEach(key => { tslintJson[key] = undefined; }); tslintJson.rulesDirectory = tslintJson.rulesDirectory || []; tslintJson.rulesDirectory.push('node_modules/@yolkai/nx-workspace/src/tslint'); tslintJson.rules['nx-enforce-module-boundaries'] = [ true, { allow: [], depConstraints: [{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }] } ]; return tslintJson; }); } function updateProjectTsLint(options) { return (host) => { const workspaceJson = nx_workspace_1.readJsonInTree(host, nx_workspace_1.getWorkspacePath(host)); const app = workspaceJson.projects[options.name]; const offset = '../../'; if (host.exists(`${app.root}/tslint.json`)) { return nx_workspace_1.updateJsonInTree(`${app.root}/tslint.json`, json => { json.extends = `${offset}tslint.json`; return json; }); } return host; }; } function setUpCompilerOptions(tsconfig, npmScope, offset) { if (!tsconfig.compilerOptions.paths) { tsconfig.compilerOptions.paths = {}; } tsconfig.compilerOptions.baseUrl = '.'; tsconfig.compilerOptions.rootDir = '.'; return tsconfig; } function moveOutOfSrc(tree, appName, filePath, context) { const filename = !!filePath ? path.basename(filePath) : ''; const from = filePath; const to = path.join('apps', appName, filename); nx_workspace_1.renameSyncInTree(tree, from, to, err => { if (!context) { return; } else if (!err) { context.logger.info(`Renamed ${from} -> ${to}`); } else { context.logger.warn(err); } }); } function getE2eKey(workspaceJson) { return Object.keys(workspaceJson.projects).find(key => { return !!workspaceJson.projects[key].architect.e2e; }); } function getE2eProject(workspaceJson) { const key = getE2eKey(workspaceJson); if (key) { return workspaceJson.projects[key]; } else { return null; } } function moveExistingFiles(options) { return (host, context) => { const workspaceJson = nx_workspace_1.readJsonInTree(host, nx_workspace_1.getWorkspacePath(host)); const app = workspaceJson.projects[options.name]; const e2eApp = getE2eProject(workspaceJson); // No context is passed because it should not be required to have a browserslist moveOutOfSrc(host, options.name, 'browserslist'); moveOutOfSrc(host, options.name, app.architect.test.options.karmaConfig, context); moveOutOfSrc(host, options.name, app.architect.build.options.tsConfig, context); moveOutOfSrc(host, options.name, app.architect.test.options.tsConfig, context); if (app.architect.server) { moveOutOfSrc(host, options.name, app.architect.server.options.tsConfig, context); } const oldAppSourceRoot = app.sourceRoot; const newAppSourceRoot = path_1.join('apps', options.name, app.sourceRoot); nx_workspace_1.renameDirSyncInTree(host, oldAppSourceRoot, newAppSourceRoot, err => { if (!err) { context.logger.info(`Renamed ${oldAppSourceRoot} -> ${newAppSourceRoot}`); } else { context.logger.error(err); throw err; } }); if (e2eApp) { const oldE2eRoot = 'e2e'; const newE2eRoot = path_1.join('apps', getE2eKey(workspaceJson) + '-e2e'); nx_workspace_1.renameDirSyncInTree(host, oldE2eRoot, newE2eRoot, err => { if (!err) { context.logger.info(`Renamed ${oldE2eRoot} -> ${newE2eRoot}`); } else { context.logger.error(err); throw err; } }); } else { context.logger.warn('No e2e project was migrated because there was none declared in angular.json'); } return host; }; } function createAdditionalFiles(options) { return (host, _context) => { const workspaceJson = nx_workspace_1.readJsonInTree(host, 'angular.json'); host.create('nx.json', nx_workspace_1.serializeJson({ npmScope: options.npmScope, implicitDependencies: { 'angular.json': '*', 'package.json': '*', 'tsconfig.json': '*', 'tslint.json': '*', 'nx.json': '*' }, projects: { [options.name]: { tags: [] }, [getE2eKey(workspaceJson) + '-e2e']: { tags: [] } } })); host.create('libs/.gitkeep', ''); host = nx_workspace_1.updateJsonInTree('.vscode/extensions.json', (json) => { json.recommendations = json.recommendations || []; [ 'nrwl.angular-console', 'angular.ng-template', 'ms-vscode.vscode-typescript-tslint-plugin', 'esbenp.prettier-vscode' ].forEach(extension => { if (!json.recommendations.includes(extension)) { json.recommendations.push(extension); } }); return json; })(host, _context); // if the user does not already have a prettier configuration // of any kind, create one return rxjs_1.from(nx_workspace_1.resolveUserExistingPrettierConfig()).pipe(operators_1.tap(existingPrettierConfig => { if (!existingPrettierConfig) { host.create('.prettierrc', nx_workspace_1.serializeJson(workspace_1.DEFAULT_NRWL_PRETTIER_CONFIG)); } }), operators_1.mapTo(host)); }; } function checkCanConvertToWorkspace(options) { return (host, context) => { try { if (!host.exists('package.json')) { throw new Error('Cannot find package.json'); } if (!host.exists('angular.json')) { throw new Error('Cannot find angular.json'); } // TODO: This restriction should be lited const workspaceJson = nx_workspace_1.readJsonInTree(host, 'angular.json'); if (Object.keys(workspaceJson.projects).length > 2) { throw new Error('Can only convert projects with one app'); } const e2eKey = getE2eKey(workspaceJson); const e2eApp = getE2eProject(workspaceJson); if (e2eApp && !host.exists(e2eApp.architect.e2e.options.protractorConfig)) { context.logger.info(`Make sure the ${e2eKey}.architect.e2e.options.protractorConfig is valid or the ${e2eKey} project is removed from angular.json.`); throw new Error(`An e2e project was specified but ${e2eApp.architect.e2e.options.protractorConfig} could not be found.`); } return host; } catch (e) { context.logger.error(e.message); context.logger.error('Your workspace could not be converted into an Nx Workspace because of the above error.'); throw e; } }; } function addInstallTask(options) { return (host, context) => { if (!options.skipInstall) { context.addTask(new tasks_1.NodePackageInstallTask()); } return host; }; } function default_1(schema) { const options = Object.assign({}, schema, { npmScope: nx_workspace_1.toFileName(schema.npmScope || schema.name) }); const templateSource = schematics_1.apply(schematics_1.url('./files'), [ schematics_1.template({ tmpl: '' }) ]); return schematics_1.chain([ checkCanConvertToWorkspace(options), schematics_1.mergeWith(templateSource), moveExistingFiles(options), createAdditionalFiles(options), updatePackageJson(), updateAngularCLIJson(options), updateTsLint(), updateProjectTsLint(options), updateTsConfig(options), updateTsConfigsJson(options), addInstallTask(options) ]); } exports.default = default_1;