UNPKG

@nrwl/workspace

Version:

The Workspace plugin contains executors and generators that are useful for any Nx workspace. It should be present in every Nx workspace and other plugins build on it.

548 lines • 21.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.applyWithSkipExisting = exports.renameDirSyncInTree = exports.renameSyncInTree = exports.replaceNodeValue = exports.insertImport = exports.createOrUpdate = exports.getProjectConfig = exports.updatePackageJsonDependencies = exports.addDepsToPackageJson = exports.readWorkspace = exports.updateNxJsonInTree = exports.appsDir = exports.libsDir = exports.readNxJsonInTree = exports.updateWorkspaceInTree = exports.updateJsonInTree = exports.allFilesInDirInHost = exports.readJsonInTree = exports.insert = exports.addGlobal = exports.getImport = exports.addIncludeToTsConfig = exports.offset = exports.findClass = exports.addMethod = exports.addParameterToConstructor = exports.ReplaceChange = exports.RemoveChange = exports.InsertChange = exports.NoopChange = exports.getSourceNodes = exports.findNodes = exports.sortObjectByKeys = void 0; const devkit_1 = require("@nrwl/devkit"); const cli_config_utils_1 = require("./cli-config-utils"); const add_install_task_1 = require("./rules/add-install-task"); const typescript_1 = require("nx/src/utils/typescript"); const get_source_nodes_1 = require("../utilities/typescript/get-source-nodes"); const typescript_2 = require("../utilities/typescript"); let tsModule; function nodesByPosition(first, second) { return first.getStart() - second.getStart(); } function insertAfterLastOccurrence(nodes, toInsert, file, fallbackPos, syntaxKind) { // sort() has a side effect, so make a copy so that we won't overwrite the parent's object. let lastItem = [...nodes].sort(nodesByPosition).pop(); if (!lastItem) { throw new Error(); } if (syntaxKind) { lastItem = (0, typescript_1.findNodes)(lastItem, syntaxKind).sort(nodesByPosition).pop(); } if (!lastItem && fallbackPos == undefined) { throw new Error(`tried to insert ${toInsert} as first occurrence with no fallback position`); } const lastItemPosition = lastItem ? lastItem.getEnd() : fallbackPos; return new InsertChange(file, lastItemPosition, toInsert); } function sortObjectByKeys(obj) { return Object.keys(obj) .sort() .reduce((result, key) => { return Object.assign(Object.assign({}, result), { [key]: obj[key] }); }, {}); } exports.sortObjectByKeys = sortObjectByKeys; var find_nodes_1 = require("../utilities/typescript/find-nodes"); // TODO(v16): Remove this Object.defineProperty(exports, "findNodes", { enumerable: true, get: function () { return find_nodes_1.findNodes; } }); var get_source_nodes_2 = require("../utilities/typescript/get-source-nodes"); Object.defineProperty(exports, "getSourceNodes", { enumerable: true, get: function () { return get_source_nodes_2.getSourceNodes; } }); class NoopChange { constructor() { this.type = 'noop'; this.description = 'No operation.'; this.order = Infinity; this.path = null; } apply() { return Promise.resolve(); } } exports.NoopChange = NoopChange; class InsertChange { constructor(path, pos, toAdd) { this.path = path; this.pos = pos; this.toAdd = toAdd; this.type = 'insert'; if (pos < 0) { throw new Error('Negative positions are invalid'); } this.description = `Inserted ${toAdd} into position ${pos} of ${path}`; this.order = pos; } apply(host) { return host.read(this.path).then((content) => { const prefix = content.substring(0, this.pos); const suffix = content.substring(this.pos); return host.write(this.path, `${prefix}${this.toAdd}${suffix}`); }); } } exports.InsertChange = InsertChange; class RemoveChange { constructor(path, pos, toRemove) { this.path = path; this.pos = pos; this.toRemove = toRemove; this.type = 'remove'; if (pos < 0) { throw new Error('Negative positions are invalid'); } this.description = `Removed ${toRemove} into position ${pos} of ${path}`; this.order = pos; } apply(host) { return host.read(this.path).then((content) => { const prefix = content.substring(0, this.pos); const suffix = content.substring(this.pos + this.toRemove.length); return host.write(this.path, `${prefix}${suffix}`); }); } } exports.RemoveChange = RemoveChange; class ReplaceChange { constructor(path, pos, oldText, newText) { this.path = path; this.pos = pos; this.oldText = oldText; this.newText = newText; this.type = 'replace'; if (pos < 0) { throw new Error('Negative positions are invalid'); } this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`; this.order = pos; } apply(host) { return host.read(this.path).then((content) => { const prefix = content.substring(0, this.pos); const suffix = content.substring(this.pos + this.oldText.length); const text = content.substring(this.pos, this.pos + this.oldText.length); if (text !== this.oldText) { return Promise.reject(new Error(`Invalid replace: "${text}" != "${this.oldText}".`)); } return host.write(this.path, `${prefix}${this.newText}${suffix}`); }); } } exports.ReplaceChange = ReplaceChange; function addParameterToConstructor(source, modulePath, opts) { if (!tsModule) { tsModule = (0, typescript_2.ensureTypescript)(); } const clazz = findClass(source, opts.className); const constructor = clazz.members.filter((m) => m.kind === tsModule.SyntaxKind.Constructor)[0]; if (constructor) { throw new Error('Should be tested'); } else { const methodHeader = `constructor(${opts.param})`; return addMethod(source, modulePath, { className: opts.className, methodHeader, body: null, }); } } exports.addParameterToConstructor = addParameterToConstructor; function addMethod(source, modulePath, opts) { const clazz = findClass(source, opts.className); const body = opts.body ? ` ${opts.methodHeader} { ${offset(opts.body, 1, false)} } ` : ` ${opts.methodHeader} {} `; return [new InsertChange(modulePath, clazz.end - 1, offset(body, 1, true))]; } exports.addMethod = addMethod; function findClass(source, className, silent = false) { if (!tsModule) { tsModule = (0, typescript_2.ensureTypescript)(); } const nodes = (0, get_source_nodes_1.getSourceNodes)(source); const clazz = (nodes.filter((n) => n.kind === tsModule.SyntaxKind.ClassDeclaration && n.name.text === className)[0]); if (!clazz && !silent) { throw new Error(`Cannot find class '${className}'`); } return clazz; } exports.findClass = findClass; function offset(text, numberOfTabs, wrap) { const lines = text .trim() .split('\n') .map((line) => { let tabs = ''; for (let c = 0; c < numberOfTabs; ++c) { tabs += ' '; } return `${tabs}${line}`; }) .join('\n'); return wrap ? `\n${lines}\n` : lines; } exports.offset = offset; function addIncludeToTsConfig(tsConfigPath, source, include) { const includeKeywordPos = source.text.indexOf('"include":'); if (includeKeywordPos > -1) { const includeArrayEndPos = source.text.indexOf(']', includeKeywordPos); return [new InsertChange(tsConfigPath, includeArrayEndPos, include)]; } else { return []; } } exports.addIncludeToTsConfig = addIncludeToTsConfig; function getImport(source, predicate) { if (!tsModule) { tsModule = (0, typescript_2.ensureTypescript)(); } const allImports = (0, typescript_1.findNodes)(source, tsModule.SyntaxKind.ImportDeclaration); const matching = allImports.filter((i) => predicate(i.moduleSpecifier.getText())); return matching.map((i) => { const moduleSpec = i.moduleSpecifier .getText() .substring(1, i.moduleSpecifier.getText().length - 1); const t = i.importClause.namedBindings.getText(); const bindings = t .replace('{', '') .replace('}', '') .split(',') .map((q) => q.trim()); return { moduleSpec, bindings }; }); } exports.getImport = getImport; function addGlobal(source, modulePath, statement) { if (!tsModule) { tsModule = (0, typescript_2.ensureTypescript)(); } const allImports = (0, typescript_1.findNodes)(source, tsModule.SyntaxKind.ImportDeclaration); if (allImports.length > 0) { const lastImport = allImports[allImports.length - 1]; return [ new InsertChange(modulePath, lastImport.end + 1, `\n${statement}\n`), ]; } else { return [new InsertChange(modulePath, 0, `${statement}\n`)]; } } exports.addGlobal = addGlobal; function insert(host, modulePath, changes) { if (changes.length < 1) { return; } // sort changes so that the highest pos goes first const orderedChanges = changes.sort((a, b) => b.order - a.order); const recorder = host.beginUpdate(modulePath); for (const change of orderedChanges) { if (change.type == 'insert') { recorder.insertLeft(change.pos, change.toAdd); } else if (change.type == 'remove') { recorder.remove(change.pos - 1, change.toRemove.length + 1); } else if (change.type == 'replace') { recorder.remove(change.pos, change.oldText.length); recorder.insertLeft(change.pos, change.newText); } else if (change.type === 'noop') { // do nothing } else { throw new Error(`Unexpected Change '${change.constructor.name}'`); } } host.commitUpdate(recorder); } exports.insert = insert; /** * This method is specifically for reading JSON files in a Tree * @param host The host tree * @param path The path to the JSON file * @returns The JSON data in the file. */ function readJsonInTree(host, path) { if (!host.exists(path)) { throw new Error(`Cannot find ${path}`); } try { return (0, devkit_1.parseJson)(host.read(path).toString('utf-8')); } catch (e) { throw new Error(`Cannot parse ${path}: ${e.message}`); } } exports.readJsonInTree = readJsonInTree; function allFilesInDirInHost(host, path, options = { recursive: true }) { const { join } = require('@angular-devkit/core'); const dir = host.getDir(path); const res = []; dir.subfiles.forEach((p) => { res.push(join(path, p)); }); if (!options.recursive) { return res; } dir.subdirs.forEach((p) => { res.push(...allFilesInDirInHost(host, join(path, p))); }); return res; } exports.allFilesInDirInHost = allFilesInDirInHost; /** * This method is specifically for updating JSON in a Tree * @param path Path of JSON file in the Tree * @param callback Manipulation of the JSON data * @returns A rule which updates a JSON file file in a Tree */ function updateJsonInTree(path, callback) { return (host, context) => { if (!host.exists(path)) { host.create(path, (0, devkit_1.serializeJson)(callback({}, context))); return host; } host.overwrite(path, (0, devkit_1.serializeJson)(callback(readJsonInTree(host, path), context))); return host; }; } exports.updateJsonInTree = updateJsonInTree; function updateWorkspaceInTree(callback) { return (host, context = undefined) => { const path = (0, cli_config_utils_1.getWorkspacePath)(host); host.overwrite(path, (0, devkit_1.serializeJson)(callback(readJsonInTree(host, path), context, host))); return host; }; } exports.updateWorkspaceInTree = updateWorkspaceInTree; function readNxJsonInTree(host) { return readJsonInTree(host, 'nx.json'); } exports.readNxJsonInTree = readNxJsonInTree; function libsDir(host) { var _a, _b; const json = readJsonInTree(host, 'nx.json'); return (_b = (_a = json === null || json === void 0 ? void 0 : json.workspaceLayout) === null || _a === void 0 ? void 0 : _a.libsDir) !== null && _b !== void 0 ? _b : 'libs'; } exports.libsDir = libsDir; function appsDir(host) { var _a, _b; const json = readJsonInTree(host, 'nx.json'); return (_b = (_a = json === null || json === void 0 ? void 0 : json.workspaceLayout) === null || _a === void 0 ? void 0 : _a.appsDir) !== null && _b !== void 0 ? _b : 'apps'; } exports.appsDir = appsDir; function updateNxJsonInTree(callback) { return (host, context) => { host.overwrite('nx.json', (0, devkit_1.serializeJson)(callback(readJsonInTree(host, 'nx.json'), context))); return host; }; } exports.updateNxJsonInTree = updateNxJsonInTree; function readWorkspace(host) { const path = (0, cli_config_utils_1.getWorkspacePath)(host); return readJsonInTree(host, path); } exports.readWorkspace = readWorkspace; /** * verifies whether the given packageJson dependencies require an update * given the deps & devDeps passed in */ function requiresAddingOfPackages(packageJsonFile, deps, devDeps) { let needsDepsUpdate = false; let needsDevDepsUpdate = false; packageJsonFile.dependencies = packageJsonFile.dependencies || {}; packageJsonFile.devDependencies = packageJsonFile.devDependencies || {}; if (Object.keys(deps).length > 0) { needsDepsUpdate = Object.keys(deps).some((entry) => !packageJsonFile.dependencies[entry]); } if (Object.keys(devDeps).length > 0) { needsDevDepsUpdate = Object.keys(devDeps).some((entry) => !packageJsonFile.devDependencies[entry]); } return needsDepsUpdate || needsDevDepsUpdate; } /** * Updates the package.json given the passed deps and/or devDeps. Only updates * if the packages are not yet present * * @param deps the package.json dependencies to add * @param devDeps the package.json devDependencies to add * @param addInstall default `true`; set to false to avoid installs */ function addDepsToPackageJson(deps, devDeps, addInstall = true) { const { chain, noop } = require('@angular-devkit/schematics'); return (host, context) => { const currentPackageJson = readJsonInTree(host, 'package.json'); if (requiresAddingOfPackages(currentPackageJson, deps, devDeps)) { return chain([ updateJsonInTree('package.json', (json, context) => { json.dependencies = Object.assign(Object.assign(Object.assign({}, (json.dependencies || {})), deps), (json.dependencies || {})); json.devDependencies = Object.assign(Object.assign(Object.assign({}, (json.devDependencies || {})), devDeps), (json.devDependencies || {})); json.dependencies = sortObjectByKeys(json.dependencies); json.devDependencies = sortObjectByKeys(json.devDependencies); return json; }), (0, add_install_task_1.addInstallTask)({ skipInstall: !addInstall, }), ]); } else { return noop(); } }; } exports.addDepsToPackageJson = addDepsToPackageJson; function updatePackageJsonDependencies(deps, devDeps, addInstall = true) { const { chain } = require('@angular-devkit/schematics'); return chain([ updateJsonInTree('package.json', (json, context) => { json.dependencies = Object.assign(Object.assign({}, (json.dependencies || {})), deps); json.devDependencies = Object.assign(Object.assign({}, (json.devDependencies || {})), devDeps); json.dependencies = sortObjectByKeys(json.dependencies); json.devDependencies = sortObjectByKeys(json.devDependencies); return json; }), (0, add_install_task_1.addInstallTask)({ skipInstall: !addInstall, }), ]); } exports.updatePackageJsonDependencies = updatePackageJsonDependencies; function getProjectConfig(host, name) { const workspaceJson = readJsonInTree(host, (0, cli_config_utils_1.getWorkspacePath)(host)); const projectConfig = workspaceJson.projects[name]; if (!projectConfig) { throw new Error(`Cannot find project '${name}'`); } else if (typeof projectConfig === 'string') { return readJsonInTree(host, projectConfig); } else { return projectConfig; } } exports.getProjectConfig = getProjectConfig; function createOrUpdate(host, path, content) { if (host.exists(path)) { host.overwrite(path, content); } else { host.create(path, content); } } exports.createOrUpdate = createOrUpdate; function insertImport(source, fileToEdit, symbolName, fileName, isDefault = false) { if (!tsModule) { tsModule = (0, typescript_2.ensureTypescript)(); } const rootNode = source; const allImports = (0, typescript_1.findNodes)(rootNode, tsModule.SyntaxKind.ImportDeclaration); // get nodes that map to import statements from the file fileName const relevantImports = allImports.filter((node) => { // StringLiteral of the ImportDeclaration is the import file (fileName in this case). const importFiles = node .getChildren() .filter((child) => child.kind === tsModule.SyntaxKind.StringLiteral) .map((n) => n.text); return importFiles.filter((file) => file === fileName).length === 1; }); if (relevantImports.length > 0) { let importsAsterisk = false; // imports from import file const imports = []; relevantImports.forEach((n) => { Array.prototype.push.apply(imports, (0, typescript_1.findNodes)(n, tsModule.SyntaxKind.Identifier)); if ((0, typescript_1.findNodes)(n, tsModule.SyntaxKind.AsteriskToken).length > 0) { importsAsterisk = true; } }); // if imports * from fileName, don't add symbolName if (importsAsterisk) { return new NoopChange(); } const importTextNodes = imports.filter((n) => n.text === symbolName); // insert import if it's not there if (importTextNodes.length === 0) { const fallbackPos = (0, typescript_1.findNodes)(relevantImports[0], tsModule.SyntaxKind.CloseBraceToken)[0].getStart() || (0, typescript_1.findNodes)(relevantImports[0], tsModule.SyntaxKind.FromKeyword)[0].getStart(); return insertAfterLastOccurrence(imports, `, ${symbolName}`, fileToEdit, fallbackPos); } return new NoopChange(); } // no such import declaration exists const useStrict = (0, typescript_1.findNodes)(rootNode, tsModule.SyntaxKind.StringLiteral).filter((n) => n.text === 'use strict'); let fallbackPos = 0; if (useStrict.length > 0) { fallbackPos = useStrict[0].end; } const open = isDefault ? '' : '{ '; const close = isDefault ? '' : ' }'; // if there are no imports or 'use strict' statement, insert import at beginning of file const insertAtBeginning = allImports.length === 0 && useStrict.length === 0; const separator = insertAtBeginning ? '' : ';\n'; const toInsert = `${separator}import ${open}${symbolName}${close}` + ` from '${fileName}'${insertAtBeginning ? ';\n' : ''}`; return insertAfterLastOccurrence(allImports, toInsert, fileToEdit, fallbackPos, tsModule.SyntaxKind.StringLiteral); } exports.insertImport = insertImport; function replaceNodeValue(host, modulePath, node, content) { insert(host, modulePath, [ new ReplaceChange(modulePath, node.getStart(node.getSourceFile()), node.getFullText(), content), ]); } exports.replaceNodeValue = replaceNodeValue; function renameSyncInTree(tree, from, to, cb) { if (!tree.exists(from)) { cb(`Path: ${from} does not exist`); } else if (tree.exists(to)) { cb(`Path: ${to} already exists`); } else { renameFile(tree, from, to); cb(null); } } exports.renameSyncInTree = renameSyncInTree; function renameDirSyncInTree(tree, from, to, cb) { const dir = tree.getDir(from); if (!dirExists(dir)) { cb(`Path: ${from} does not exist`); return; } dir.visit((path) => { const destination = path.replace(from, to); renameFile(tree, path, destination); }); cb(null); } exports.renameDirSyncInTree = renameDirSyncInTree; function dirExists(dir) { return dir.subdirs.length + dir.subfiles.length !== 0; } function renameFile(tree, from, to) { const buffer = tree.read(from); if (!buffer) { return; } tree.create(to, buffer); tree.delete(from); } /** * Applies a template merge but skips for already existing entries */ function applyWithSkipExisting(source, rules) { const { mergeWith, apply, forEach } = require('@angular-devkit/schematics'); return (tree, _context) => { const rule = mergeWith(apply(source, [ ...rules, forEach((fileEntry) => { if (tree.exists(fileEntry.path)) { return null; } return fileEntry; }), ])); return rule(tree, _context); }; } exports.applyWithSkipExisting = applyWithSkipExisting; //# sourceMappingURL=ast-utils.js.map