├── .cache └── nxdeps.json ├── .gitattributes ├── .gitignore ├── .nxignore ├── README.md ├── core ├── app-root.js ├── build-project-graph.js ├── default-tasks-runner.js ├── devkit.js ├── file-map-utils.js ├── git-hash.js ├── implicit-project-dependencies.js ├── life-cycle.js ├── npm-packages.js ├── nx-deps-cache.js ├── output.js ├── package.json ├── path.js ├── project-graph-builder.js ├── project-graph.js ├── project.json ├── run-command.js ├── run-one.js ├── static-run-one-terminal-output-life-cycle.js ├── strip-source-code.js ├── target-project-locator.js ├── type.js ├── typescript-import-locator.js ├── typescript.js ├── workspace-projects.js └── workspace.js ├── demo ├── args.html ├── args2.html ├── index.html └── lodash.js ├── hash ├── README.md └── hash.js ├── nx.js ├── nx.json ├── package-lock.json ├── package.json ├── screenshots ├── forkProcessPipeOutputCapture.png └── pipelines.png ├── socket ├── README.md ├── client.js ├── daemon.log ├── package.json ├── server.js └── start.js ├── test ├── README.md ├── child │ ├── index.js │ ├── package.json │ └── project.json ├── cli.js ├── nx.js ├── package.json └── run-script.js ├── tsconfig.base.json └── workspace.json /.cache/nxdeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5.0", 3 | "deps": { 4 | "chalk": "^5.0.0", 5 | "fast-glob": "^3.2.11", 6 | "fs-extra": "^10.0.0", 7 | "ignore": "^5.2.0", 8 | "jsonc-parser": "^3.0.0", 9 | "typescript": "^4.5.5", 10 | "yargs-parser": "^21.0.0" 11 | }, 12 | "pathMappings": { 13 | "@stores/demoapp2": [ 14 | "libs/demoapp2/src/index.ts" 15 | ] 16 | }, 17 | "nxJsonPlugins": [], 18 | "nodes": { 19 | "core": { 20 | "name": "core", 21 | "type": "lib", 22 | "data": { 23 | "root": "core", 24 | "targets": { 25 | "start": { 26 | "executor": "@nrwl/workspace:run-script", 27 | "options": { 28 | "script": "start" 29 | } 30 | }, 31 | "build": { 32 | "executor": "@nrwl/js:tsc", 33 | "outputs": [ 34 | "{options.outputPath}" 35 | ], 36 | "options": { 37 | "outputPath": "dist/apps/demoapp", 38 | "main": "apps/demoapp/src/index.ts", 39 | "tsConfig": "apps/demoapp/tsconfig.app.json", 40 | "assets": [ 41 | "apps/demoapp/*.md" 42 | ] 43 | } 44 | }, 45 | "serve": { 46 | "executor": "@nrwl/js:node", 47 | "options": { 48 | "buildTarget": "simple:test" 49 | } 50 | }, 51 | "make": { 52 | "executor": "@nrwl/workspace:run-commands", 53 | "options": { 54 | "commands": [ 55 | { 56 | "command": "node ./index.js" 57 | } 58 | ] 59 | } 60 | } 61 | }, 62 | "tags": [], 63 | "files": [ 64 | { 65 | "file": "core/app-root.js", 66 | "hash": "1be820df9be13a7179255eb294b74f722823b7f4" 67 | }, 68 | { 69 | "file": "core/build-project-graph.js", 70 | "hash": "eb62d2ec5acce1df107b51f30f651bade96b339e" 71 | }, 72 | { 73 | "file": "core/default-tasks-runner.js", 74 | "hash": "998ef3aa23f7febb2c2646710d81dcfeaecb5c76" 75 | }, 76 | { 77 | "file": "core/devkit.js", 78 | "hash": "ae007cb6bd1a576f7be636421fa94bd9a3b10bee", 79 | "deps": [ 80 | "npm:jsonc-parser" 81 | ] 82 | }, 83 | { 84 | "file": "core/file-map-utils.js", 85 | "hash": "1b818887244da60821731491b2032f1a15bb1df3" 86 | }, 87 | { 88 | "file": "core/git-hash.js", 89 | "hash": "482ab0d9fcee3b2aee1368dbd7ad48985b55cef3", 90 | "deps": [ 91 | "npm:fs-extra", 92 | "npm:ignore" 93 | ] 94 | }, 95 | { 96 | "file": "core/implicit-project-dependencies.js", 97 | "hash": "8870f764818de8c7876b150fad88060fe42166fd" 98 | }, 99 | { 100 | "file": "core/life-cycle.js", 101 | "hash": "9240bcff4ba7d7b128b2779d33e452774ae86eb9" 102 | }, 103 | { 104 | "file": "core/npm-packages.js", 105 | "hash": "824f31fb0a8f09cf8799e56a59d8203a0875621b" 106 | }, 107 | { 108 | "file": "core/nx-deps-cache.js", 109 | "hash": "9c8dd0fb92d8aa82fa506653a362a401fb09989a" 110 | }, 111 | { 112 | "file": "core/output.js", 113 | "hash": "2b76962aa3ba7dbcbc740d0138a4318372f1ebe6", 114 | "deps": [ 115 | "npm:chalk" 116 | ] 117 | }, 118 | { 119 | "file": "core/package.json", 120 | "hash": "07975e4f25e084cb1873a85f932409555ff0978f" 121 | }, 122 | { 123 | "file": "core/path.js", 124 | "hash": "cd684ee874efb21223f60e3472c29c515cb3764a" 125 | }, 126 | { 127 | "file": "core/project-graph-builder.js", 128 | "hash": "47a3bbaa03a0c7fc9cc3bed8332683ca2802888b" 129 | }, 130 | { 131 | "file": "core/project-graph.js", 132 | "hash": "b60550af287192cb6b711e869de03e643395b308" 133 | }, 134 | { 135 | "file": "core/project.json", 136 | "hash": "1922a42ef51e68e8d4e64dee09e05607866ade38" 137 | }, 138 | { 139 | "file": "core/run-command.js", 140 | "hash": "40e225c97aa0ab3440b3f2f5357fcfdb5a57cc38" 141 | }, 142 | { 143 | "file": "core/run-one.js", 144 | "hash": "7ed7a4dfeee5a2513d2eb5fb981b2ec003e01558" 145 | }, 146 | { 147 | "file": "core/static-run-one-terminal-output-life-cycle.js", 148 | "hash": "909c770aa0fd22cab87b042a7b32d08d84ca3708" 149 | }, 150 | { 151 | "file": "core/strip-source-code.js", 152 | "hash": "69d10edf8801a03f0ae84bb275694fd4e57a1b4e", 153 | "deps": [ 154 | "npm:typescript" 155 | ] 156 | }, 157 | { 158 | "file": "core/target-project-locator.js", 159 | "hash": "891bef6a0e695b6526166a926023c2997d4bf210" 160 | }, 161 | { 162 | "file": "core/type.js", 163 | "hash": "cc5beafb4f85afc0ef43e318fe129c83b56bbd59" 164 | }, 165 | { 166 | "file": "core/typescript-import-locator.js", 167 | "hash": "5ab83d3d05c716cd32ce98619bf4daab82f301e5", 168 | "deps": [ 169 | "npm:typescript" 170 | ] 171 | }, 172 | { 173 | "file": "core/typescript.js", 174 | "hash": "538ff7139a4f23d9204268dc9bfbfe28b505e325", 175 | "deps": [ 176 | "npm:typescript" 177 | ] 178 | }, 179 | { 180 | "file": "core/workspace-projects.js", 181 | "hash": "6af9de87be7524bb367d0e645a70b39a29905743", 182 | "deps": [ 183 | "npm:fs-extra", 184 | "npm:fast-glob" 185 | ] 186 | }, 187 | { 188 | "file": "core/workspace.js", 189 | "hash": "57985926e32391f7a26afa7a7cad8e26df5ec081" 190 | } 191 | ] 192 | } 193 | } 194 | }, 195 | "externalNodes": { 196 | "npm:chalk": { 197 | "type": "npm", 198 | "name": "npm:chalk", 199 | "data": { 200 | "version": "^5.0.0", 201 | "packageName": "chalk" 202 | } 203 | }, 204 | "npm:fast-glob": { 205 | "type": "npm", 206 | "name": "npm:fast-glob", 207 | "data": { 208 | "version": "^3.2.11", 209 | "packageName": "fast-glob" 210 | } 211 | }, 212 | "npm:fs-extra": { 213 | "type": "npm", 214 | "name": "npm:fs-extra", 215 | "data": { 216 | "version": "^10.0.0", 217 | "packageName": "fs-extra" 218 | } 219 | }, 220 | "npm:ignore": { 221 | "type": "npm", 222 | "name": "npm:ignore", 223 | "data": { 224 | "version": "^5.2.0", 225 | "packageName": "ignore" 226 | } 227 | }, 228 | "npm:jsonc-parser": { 229 | "type": "npm", 230 | "name": "npm:jsonc-parser", 231 | "data": { 232 | "version": "^3.0.0", 233 | "packageName": "jsonc-parser" 234 | } 235 | }, 236 | "npm:typescript": { 237 | "type": "npm", 238 | "name": "npm:typescript", 239 | "data": { 240 | "version": "^4.5.5", 241 | "packageName": "typescript" 242 | } 243 | }, 244 | "npm:yargs-parser": { 245 | "type": "npm", 246 | "name": "npm:yargs-parser", 247 | "data": { 248 | "version": "^21.0.0", 249 | "packageName": "yargs-parser" 250 | } 251 | } 252 | }, 253 | "dependencies": { 254 | "core": [ 255 | { 256 | "source": "core", 257 | "target": "npm:jsonc-parser", 258 | "type": "static" 259 | }, 260 | { 261 | "source": "core", 262 | "target": "npm:fs-extra", 263 | "type": "static" 264 | }, 265 | { 266 | "source": "core", 267 | "target": "npm:ignore", 268 | "type": "static" 269 | }, 270 | { 271 | "source": "core", 272 | "target": "npm:chalk", 273 | "type": "static" 274 | }, 275 | { 276 | "source": "core", 277 | "target": "npm:typescript", 278 | "type": "static" 279 | }, 280 | { 281 | "source": "core", 282 | "target": "npm:fast-glob", 283 | "type": "static" 284 | } 285 | ] 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.nxignore: -------------------------------------------------------------------------------- 1 | workspace.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nx架构图 2 | 3 | https://app.diagrams.net/#G1kgAmZj_n_qzBESU8OR43jjQ7ls1ORkbW 4 | 5 | # 源码解读 6 | https://github.com/Wscats/nx/issues/1 7 | 8 | # 源码实现 9 | https://github.com/Wscats/monorepo-tutorial/tree/main/test 10 | -------------------------------------------------------------------------------- /core/app-root.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | function pathInner(dir) { 4 | if (process.env.NX_WORKSPACE_ROOT_PATH) 5 | return process.env.NX_WORKSPACE_ROOT_PATH; 6 | if (path.dirname(dir) === dir) 7 | return process.cwd(); 8 | if (fileExists(path.join(dir, 'workspace.json')) || 9 | fileExists(path.join(dir, 'nx.json')) || 10 | fileExists(path.join(dir, 'angular.json'))) { 11 | return dir; 12 | } 13 | else { 14 | return pathInner(path.dirname(dir)); 15 | } 16 | } 17 | function fileExists(filePath) { 18 | try { 19 | return (0, fs.statSync)(filePath).isFile(); 20 | } 21 | catch (err) { 22 | return false; 23 | } 24 | } 25 | 26 | module.exports = { 27 | appRootPath: pathInner(__dirname), 28 | fileExists: fileExists 29 | } 30 | -------------------------------------------------------------------------------- /core/build-project-graph.js: -------------------------------------------------------------------------------- 1 | const { TypeScriptImportLocator } = require('./typescript-import-locator'); 2 | const { TargetProjectLocator } = require('./target-project-locator'); 3 | const { joinPathFragments } = require('./path') 4 | const { parseJson } = require("./devkit"); 5 | 6 | function buildExplicitTypeScriptDependencies( 7 | workspace, 8 | graph, 9 | filesToProcess 10 | ) { 11 | const importLocator = new TypeScriptImportLocator(); 12 | const targetProjectLocator = new TargetProjectLocator( 13 | graph.nodes, 14 | graph.externalNodes 15 | ); 16 | const res = []; 17 | Object.keys(filesToProcess).forEach((source) => { 18 | Object.values(filesToProcess[source]).forEach((f) => { 19 | importLocator.fromFile( 20 | f.file, 21 | (importExpr, filePath, type) => { 22 | const target = targetProjectLocator.findProjectWithImport( 23 | importExpr, 24 | f.file, 25 | workspace.npmScope 26 | ); 27 | if (target) { 28 | res.push({ 29 | sourceProjectName: source, 30 | targetProjectName: target, 31 | sourceProjectFile: f.file, 32 | }); 33 | } 34 | } 35 | ); 36 | }); 37 | }); 38 | return res; 39 | } 40 | 41 | function processPackageJson( 42 | sourceProject, 43 | fileName, 44 | graph, 45 | collectedDeps, 46 | packageNameMap 47 | ) { 48 | try { 49 | const deps = readDeps(parseJson(defaultFileRead(fileName))); 50 | // the name matches the import path 51 | deps.forEach((d) => { 52 | // package.json refers to another project in the monorepo 53 | if (packageNameMap[d]) { 54 | collectedDeps.push({ 55 | sourceProjectName: sourceProject, 56 | targetProjectName: packageNameMap[d], 57 | sourceProjectFile: fileName, 58 | }); 59 | } else if (graph.externalNodes[`npm:${d}`]) { 60 | collectedDeps.push({ 61 | sourceProjectName: sourceProject, 62 | targetProjectName: `npm:${d}`, 63 | sourceProjectFile: fileName, 64 | }); 65 | } 66 | }); 67 | } catch (e) { 68 | if (process.env.NX_VERBOSE_LOGGING === 'true') { 69 | console.log(e); 70 | } 71 | } 72 | } 73 | 74 | function readDeps(packageJsonDeps) { 75 | // ?? 76 | return [ 77 | ...Object.keys(packageJsonDeps && packageJsonDeps.dependencies || {}), 78 | ...Object.keys(packageJsonDeps && packageJsonDeps.devDependencies || {}), 79 | ...Object.keys(packageJsonDeps && packageJsonDeps.peerDependencies || {}), 80 | ]; 81 | } 82 | 83 | function buildExplicitPackageJsonDependencies( 84 | workspace, 85 | graph, 86 | filesToProcess 87 | ) { 88 | const res = []; 89 | let packageNameMap = undefined; 90 | Object.keys(filesToProcess).forEach((source) => { 91 | Object.values(filesToProcess[source]).forEach((f) => { 92 | if (isPackageJsonAtProjectRoot(graph.nodes, f.file)) { 93 | // we only create the package name map once and only if a package.json file changes 94 | packageNameMap = packageNameMap || createPackageNameMap(workspace); 95 | processPackageJson(source, f.file, graph, res, packageNameMap); 96 | } 97 | }); 98 | }); 99 | return res; 100 | } 101 | 102 | function defaultFileRead(filePath) { 103 | return fs.readFileSync(path.join(appRootPath, filePath), 'utf-8'); 104 | } 105 | 106 | function createPackageNameMap(w) { 107 | const res = {}; 108 | for (let projectName of Object.keys(w.projects)) { 109 | try { 110 | const packageJson = parseJson( 111 | defaultFileRead(path.join(w.projects[projectName].root, 'package.json')) 112 | ); 113 | res[packageJson.name || `@${w.npmScope}/${projectName}`] = projectName; 114 | } catch (e) { } 115 | } 116 | return res; 117 | } 118 | 119 | function isPackageJsonAtProjectRoot( 120 | nodes, 121 | fileName 122 | ) { 123 | return Object.values(nodes).find( 124 | (projectNode) => 125 | (projectNode.type === 'lib' || projectNode.type === 'app') && 126 | joinPathFragments(projectNode.data.root, 'package.json') === fileName 127 | ); 128 | } 129 | 130 | function buildExplicitTypescriptAndPackageJsonDependencies( 131 | jsPluginConfig, 132 | workspace, 133 | projectGraph, 134 | filesToProcess 135 | ) { 136 | let res = []; 137 | if ( 138 | jsPluginConfig.analyzeSourceFiles === undefined || 139 | jsPluginConfig.analyzeSourceFiles === true 140 | ) { 141 | res = res.concat( 142 | buildExplicitTypeScriptDependencies( 143 | workspace, 144 | projectGraph, 145 | filesToProcess 146 | ) 147 | ); 148 | } 149 | if ( 150 | jsPluginConfig.analyzePackageJson === undefined || 151 | jsPluginConfig.analyzePackageJson === true 152 | ) { 153 | res = res.concat( 154 | buildExplicitPackageJsonDependencies( 155 | workspace, 156 | projectGraph, 157 | filesToProcess 158 | ) 159 | ); 160 | } 161 | return res; 162 | } 163 | 164 | function jsPluginConfig(nxJson) { 165 | if ( 166 | nxJson && 167 | nxJson && 168 | nxJson.pluginsConfig && 169 | nxJson.pluginsConfig['@nrwl/js'] 170 | ) { 171 | return nxJson && nxJson.pluginsConfig['@nrwl/js']; 172 | } else { 173 | return {}; 174 | } 175 | } 176 | 177 | function buildExplicitDependencies( 178 | jsPluginConfig, 179 | ctx, 180 | builder 181 | ) { 182 | let totalNumOfFilesToProcess = totalNumberOfFilesToProcess(ctx); 183 | // using workers has an overhead, so we only do it when the number of 184 | // files we need to process is >= 100 and there are more than 2 CPUs 185 | // to be able to use at least 2 workers (1 worker per CPU and 186 | // 1 CPU for the main thread) 187 | if (totalNumOfFilesToProcess < 100 || getNumberOfWorkers() <= 2) { 188 | return buildExplicitDependenciesWithoutWorkers( 189 | jsPluginConfig, 190 | ctx, 191 | builder 192 | ); 193 | } else { 194 | // return buildExplicitDependenciesUsingWorkers( 195 | // jsPluginConfig, 196 | // ctx, 197 | // totalNumOfFilesToProcess, 198 | // builder 199 | // ); 200 | } 201 | } 202 | 203 | function buildExplicitDependenciesWithoutWorkers( 204 | jsPluginConfig, 205 | ctx, 206 | builder 207 | ) { 208 | buildExplicitTypescriptAndPackageJsonDependencies( 209 | jsPluginConfig, 210 | ctx.workspace, 211 | builder.graph, 212 | ctx.filesToProcess 213 | ).forEach((r) => { 214 | builder.addExplicitDependency( 215 | r.sourceProjectName, 216 | r.sourceProjectFile, 217 | r.targetProjectName 218 | ); 219 | }); 220 | } 221 | 222 | function getNumberOfWorkers() { 223 | return process.env.NX_PROJECT_GRAPH_MAX_WORKERS 224 | ? +process.env.NX_PROJECT_GRAPH_MAX_WORKERS 225 | : os.cpus().length - 1; 226 | } 227 | 228 | function totalNumberOfFilesToProcess(ctx) { 229 | let totalNumOfFilesToProcess = 0; 230 | Object.values(ctx.filesToProcess).forEach( 231 | (t) => (totalNumOfFilesToProcess += t.length) 232 | ); 233 | return totalNumOfFilesToProcess; 234 | } 235 | 236 | module.exports = { buildExplicitDependencies, jsPluginConfig } -------------------------------------------------------------------------------- /core/default-tasks-runner.js: -------------------------------------------------------------------------------- 1 | // const task_orchestrator_1 = require("./task-orchestrator"); 2 | // const task_graph_creator_1 = require("./task-graph-creator"); 3 | // const hasher_1 = require("../core/hasher/hasher"); 4 | const defaultTasksRunner = async (tasks, options, context) => { 5 | if (options['parallel'] === 'false' || 6 | options['parallel'] === false) { 7 | options['parallel'] = 1; 8 | } 9 | else if (options['parallel'] === 'true' || 10 | options['parallel'] === true) { 11 | options['parallel'] = Number(options['maxParallel'] || 3); 12 | } 13 | else if (options.parallel === undefined) { 14 | options.parallel = Number(options['maxParallel'] || 3); 15 | } 16 | options.lifeCycle.startCommand(); 17 | try { 18 | // return await runAllTasks(tasks, options, context); 19 | } 20 | catch (e) { 21 | console.error('Unexpected error:'); 22 | console.error(e); 23 | process.exit(1); 24 | } 25 | finally { 26 | options.lifeCycle.endCommand(); 27 | } 28 | }; 29 | async function runAllTasks(tasks, options, context) { 30 | var _a; 31 | return () => { 32 | // const defaultTargetDependencies = (_a = context.nxJson.targetDependencies) !== null && _a !== void 0 ? _a : {}; 33 | // const taskGraphCreator = new task_graph_creator_1.TaskGraphCreator(context.projectGraph, defaultTargetDependencies); 34 | // const taskGraph = taskGraphCreator.createTaskGraph(tasks); 35 | // perf_hooks_1.performance.mark('task-graph-created'); 36 | // perf_hooks_1.performance.measure('nx-prep-work', 'init-local', 'task-graph-created'); 37 | // perf_hooks_1.performance.measure('graph-creation', 'command-execution-begins', 'task-graph-created'); 38 | // const hasher = new hasher_1.Hasher(context.projectGraph, context.nxJson, options); 39 | // const orchestrator = new task_orchestrator_1.TaskOrchestrator(hasher, context.initiatingProject, context.projectGraph, taskGraph, options); 40 | // return orchestrator.run(); 41 | }; 42 | } 43 | 44 | module.exports = defaultTasksRunner; -------------------------------------------------------------------------------- /core/devkit.js: -------------------------------------------------------------------------------- 1 | const jsonc_parser = require("jsonc-parser"); 2 | /** 3 | * Parses the given JSON string and returns the object the JSON content represents. 4 | * By default javascript-style comments are allowed. 5 | * 6 | * @param input JSON content as string 7 | * @param options JSON parse options 8 | * @returns Object the JSON content represents 9 | */ 10 | function parseJson(input, options) { 11 | try { 12 | if ((options === null || options === void 0 ? void 0 : options.disallowComments) === true || 13 | (options === null || options === void 0 ? void 0 : options.expectComments) !== true) { 14 | return JSON.parse(input); 15 | } 16 | } 17 | catch (error) { 18 | if ((options === null || options === void 0 ? void 0 : options.disallowComments) === true) { 19 | throw error; 20 | } 21 | } 22 | const errors = []; 23 | const result = jsonc_parser.parse(input, errors); 24 | if (errors.length > 0) { 25 | const { error, offset } = errors[0]; 26 | throw new Error(`${jsonc_parser.printParseErrorCode(error)} in JSON at position ${offset}`); 27 | } 28 | return result; 29 | } 30 | 31 | module.exports = { parseJson } -------------------------------------------------------------------------------- /core/file-map-utils.js: -------------------------------------------------------------------------------- 1 | const { dirname } = require("path"); 2 | 3 | function createProjectRootMappings( 4 | workspaceJson, 5 | projectFileMap 6 | ) { 7 | const projectRootMappings = new Map(); 8 | for (const projectName of Object.keys(workspaceJson.projects)) { 9 | if (!projectFileMap[projectName]) { 10 | projectFileMap[projectName] = []; 11 | } 12 | const root = workspaceJson.projects[projectName].root; 13 | projectRootMappings.set( 14 | root.endsWith('/') ? root.substring(0, root.length - 1) : root, 15 | projectFileMap[projectName] 16 | ); 17 | } 18 | return projectRootMappings; 19 | } 20 | 21 | function findMatchingProjectFiles( 22 | projectRootMappings, 23 | file 24 | ) { 25 | for ( 26 | let currentPath = dirname(file); 27 | currentPath != dirname(currentPath); 28 | currentPath = dirname(currentPath) 29 | ) { 30 | const p = projectRootMappings.get(currentPath); 31 | if (p) { 32 | return p; 33 | } 34 | } 35 | return null; 36 | } 37 | 38 | function createProjectFileMap( 39 | workspaceJson, 40 | allWorkspaceFiles 41 | ) { 42 | const projectFileMap = {}; 43 | const projectRootMappings = createProjectRootMappings( 44 | workspaceJson, 45 | projectFileMap 46 | ); 47 | for (const f of allWorkspaceFiles) { 48 | const matchingProjectFiles = findMatchingProjectFiles( 49 | projectRootMappings, 50 | f.file 51 | ); 52 | if (matchingProjectFiles) { 53 | matchingProjectFiles.push(f); 54 | } 55 | } 56 | return { projectFileMap, allWorkspaceFiles }; 57 | } 58 | 59 | function updateProjectFileMap( 60 | workspaceJson, 61 | projectFileMap, 62 | allWorkspaceFiles, 63 | updatedFiles, 64 | deletedFiles 65 | ) { 66 | const projectRootMappings = createProjectRootMappings( 67 | workspaceJson, 68 | projectFileMap 69 | ); 70 | 71 | for (const f of updatedFiles.keys()) { 72 | const matchingProjectFiles = findMatchingProjectFiles( 73 | projectRootMappings, 74 | f 75 | ); 76 | if (matchingProjectFiles) { 77 | const fileData = matchingProjectFiles.find((t) => t.file === f); 78 | if (fileData) { 79 | fileData.hash = updatedFiles.get(f); 80 | } else { 81 | matchingProjectFiles.push({ 82 | file: f, 83 | hash: updatedFiles.get(f), 84 | }); 85 | } 86 | } 87 | 88 | const fileData = allWorkspaceFiles.find((t) => t.file === f); 89 | if (fileData) { 90 | fileData.hash = updatedFiles.get(f); 91 | } else { 92 | allWorkspaceFiles.push({ 93 | file: f, 94 | hash: updatedFiles.get(f), 95 | }); 96 | } 97 | } 98 | 99 | for (const f of deletedFiles) { 100 | const matchingProjectFiles = findMatchingProjectFiles( 101 | projectRootMappings, 102 | f 103 | ); 104 | if (matchingProjectFiles) { 105 | const index = matchingProjectFiles.findIndex((t) => t.file === f); 106 | if (index > -1) { 107 | matchingProjectFiles.splice(index, 1); 108 | } 109 | } 110 | const index = allWorkspaceFiles.findIndex((t) => t.file === f); 111 | if (index > -1) { 112 | allWorkspaceFiles.splice(index, 1); 113 | } 114 | } 115 | return { projectFileMap, allWorkspaceFiles }; 116 | } 117 | 118 | module.exports = { createProjectFileMap, updateProjectFileMap } -------------------------------------------------------------------------------- /core/git-hash.js: -------------------------------------------------------------------------------- 1 | const { statSync, existsSync, readFileSync } = require('fs-extra'); 2 | const { spawn } = require('child_process'); 3 | const path = require("path"); 4 | const ignore = require("ignore"); 5 | function removeWindowsDriveLetter(osSpecificPath) { 6 | return osSpecificPath.replace(/^[A-Z]:/, ''); 7 | } 8 | 9 | /** 10 | * Coverts an os specific path to a unix style path 11 | */ 12 | function normalizePath(osSpecificPath) { 13 | return removeWindowsDriveLetter(osSpecificPath).split('\\').join('/'); 14 | } 15 | 16 | /** 17 | * Normalized path fragments and joins them 18 | */ 19 | function joinPathFragments(...fragments) { 20 | return normalizePath(path.join(...fragments)); 21 | } 22 | 23 | function fileExists(filePath) { 24 | try { 25 | return statSync(filePath).isFile(); 26 | } catch (err) { 27 | return false; 28 | } 29 | } 30 | 31 | async function getGitHashForFiles( 32 | potentialFilesToHash, 33 | path 34 | ) { 35 | const filesToHash = []; 36 | const deleted = []; 37 | for (let f of potentialFilesToHash) { 38 | if (fileExists(joinPathFragments(path, f))) { 39 | filesToHash.push(f); 40 | } else { 41 | deleted.push(f); 42 | } 43 | } 44 | 45 | const res = new Map(); 46 | if (filesToHash.length) { 47 | const hashStdout = await spawnProcess( 48 | 'git', 49 | ['hash-object', ...filesToHash], 50 | path 51 | ); 52 | const hashes = hashStdout.split('\n').filter((s) => !!s); 53 | if (hashes.length !== filesToHash.length) { 54 | throw new Error( 55 | `Passed ${filesToHash.length} file paths to Git to hash, but received ${hashes.length} hashes.` 56 | ); 57 | } 58 | for (let i = 0; i < hashes.length; i++) { 59 | const hash = hashes[i]; 60 | const filePath = filesToHash[i]; 61 | res.set(filePath, hash); 62 | } 63 | } 64 | return { hashes: res, deleted }; 65 | } 66 | 67 | async function spawnProcess(command, args, cwd) { 68 | const cp = spawn(command, args, { 69 | windowsHide: true, 70 | shell: false, 71 | cwd, 72 | }); 73 | let s = ''; 74 | for await (const data of cp.stdout) { 75 | s += data; 76 | } 77 | return s; 78 | } 79 | 80 | async function getStagedFiles(path) { 81 | const staged = await spawnProcess( 82 | 'git', 83 | ['ls-files', '-s', '-z', '--exclude-standard', '.'], 84 | path 85 | ); 86 | const res = new Map(); 87 | for (let line of staged.split('\0')) { 88 | if (!line) continue; 89 | const [_, hash, __, ...fileParts] = line.split(/\s/); 90 | const fileName = fileParts.join(' '); 91 | res.set(fileName, hash); 92 | } 93 | return res; 94 | } 95 | 96 | async function getUnstagedFiles(path) { 97 | const unstaged = await spawnProcess( 98 | 'git', 99 | ['ls-files', '-m', '-z', '--exclude-standard', '.'], 100 | path 101 | ); 102 | const lines = unstaged.split('\0').filter((f) => !!f); 103 | return getGitHashForFiles(lines, path); 104 | } 105 | 106 | async function getUntrackedFiles(path) { 107 | const untracked = await spawnProcess( 108 | 'git', 109 | ['ls-files', '--other', '-z', '--exclude-standard', '.'], 110 | path 111 | ); 112 | const lines = untracked.split('\0').filter((f) => !!f); 113 | return getGitHashForFiles(lines, path); 114 | } 115 | 116 | async function getFileHashes(path) { 117 | const [staged, unstaged, untracked] = await Promise.all([ 118 | getStagedFiles(path), 119 | getUnstagedFiles(path), 120 | getUntrackedFiles(path), 121 | ]); 122 | 123 | unstaged.hashes.forEach((hash, filename) => { 124 | staged.set(filename, hash); 125 | }); 126 | 127 | unstaged.deleted.forEach((filename) => { 128 | staged.delete(filename); 129 | }); 130 | 131 | untracked.hashes.forEach((hash, filename) => { 132 | staged.set(filename, hash); 133 | }); 134 | 135 | return { allFiles: staged }; 136 | } 137 | 138 | function getIgnoredGlobs(appRootPath) { 139 | if (existsSync(`${appRootPath}/.nxignore`)) { 140 | const ig = ignore(); 141 | ig.add(readFileSync(`${appRootPath}/.nxignore`, 'utf-8')); 142 | return ig; 143 | } else { 144 | return { ignores: (file) => false }; 145 | } 146 | } 147 | 148 | module.exports = { getFileHashes, getIgnoredGlobs } -------------------------------------------------------------------------------- /core/implicit-project-dependencies.js: -------------------------------------------------------------------------------- 1 | function buildImplicitProjectDependencies(ctx, builder) { 2 | Object.keys(ctx.workspace.projects).forEach((source) => { 3 | const p = ctx.workspace.projects[source]; 4 | if (p.implicitDependencies && p.implicitDependencies.length > 0) { 5 | p.implicitDependencies.forEach((target) => { 6 | if (target.startsWith('!')) { 7 | builder.removeDependency(source, target.substr(1)); 8 | } 9 | else { 10 | builder.addImplicitDependency(source, target); 11 | } 12 | }); 13 | } 14 | }); 15 | } 16 | 17 | module.exports = { buildImplicitProjectDependencies } -------------------------------------------------------------------------------- /core/life-cycle.js: -------------------------------------------------------------------------------- 1 | class CompositeLifeCycle { 2 | constructor(lifeCycles) { 3 | this.lifeCycles = lifeCycles; 4 | } 5 | startCommand() { 6 | for (let l of this.lifeCycles) { 7 | if (l.startCommand) { 8 | l.startCommand(); 9 | } 10 | } 11 | } 12 | endCommand() { 13 | for (let l of this.lifeCycles) { 14 | if (l.endCommand) { 15 | l.endCommand(); 16 | } 17 | } 18 | } 19 | scheduleTask(task) { 20 | for (let l of this.lifeCycles) { 21 | if (l.scheduleTask) { 22 | l.scheduleTask(task); 23 | } 24 | } 25 | } 26 | startTask(task) { 27 | for (let l of this.lifeCycles) { 28 | if (l.startTask) { 29 | l.startTask(task); 30 | } 31 | } 32 | } 33 | endTask(task, code) { 34 | for (let l of this.lifeCycles) { 35 | if (l.endTask) { 36 | l.endTask(task, code); 37 | } 38 | } 39 | } 40 | startTasks(tasks, metadata) { 41 | for (let l of this.lifeCycles) { 42 | if (l.startTasks) { 43 | l.startTasks(tasks, metadata); 44 | } 45 | else if (l.startTask) { 46 | tasks.forEach((t) => l.startTask(t)); 47 | } 48 | } 49 | } 50 | endTasks(taskResults, metadata) { 51 | for (let l of this.lifeCycles) { 52 | if (l.endTasks) { 53 | l.endTasks(taskResults, metadata); 54 | } 55 | else if (l.endTask) { 56 | taskResults.forEach((t) => l.endTask(t.task, t.code)); 57 | } 58 | } 59 | } 60 | printTaskTerminalOutput(task, status, output) { 61 | for (let l of this.lifeCycles) { 62 | if (l.printTaskTerminalOutput) { 63 | l.printTaskTerminalOutput(task, status, output); 64 | } 65 | } 66 | } 67 | } 68 | 69 | module.exports = { CompositeLifeCycle } -------------------------------------------------------------------------------- /core/npm-packages.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | function readJsonFile(path) { 4 | const content = fs.readFileSync(path, 'utf-8'); 5 | return JSON.parse(content); 6 | } 7 | 8 | function buildNpmPackageNodes(builder, appRootPath) { 9 | const packageJson = readJsonFile(path.join(appRootPath, 'package.json')); 10 | const deps = { 11 | ...packageJson.dependencies, 12 | ...packageJson.devDependencies, 13 | }; 14 | Object.keys(deps).forEach((d) => { 15 | builder.addExternalNode({ 16 | type: 'npm', 17 | name: `npm:${d}`, 18 | data: { 19 | version: deps[d], 20 | packageName: d, 21 | }, 22 | }); 23 | }); 24 | } 25 | 26 | module.exports = { buildNpmPackageNodes } -------------------------------------------------------------------------------- /core/nx-deps-cache.js: -------------------------------------------------------------------------------- 1 | const { writeFileSync } = require('fs'); 2 | function createCache( 3 | nxJson, 4 | packageJsonDeps, 5 | projectGraph, 6 | tsConfig 7 | ) { 8 | const nxJsonPlugins = (nxJson.plugins || []).map((p) => ({ 9 | name: p, 10 | version: packageJsonDeps[p], 11 | })); 12 | const newValue = { 13 | version: projectGraph.version || '5.0', 14 | deps: packageJsonDeps, 15 | // compilerOptions may not exist, especially for repos converted through add-nx-to-monorepo 16 | pathMappings: tsConfig && tsConfig.compilerOptions && tsConfig.compilerOptions.paths || {}, 17 | nxJsonPlugins, 18 | nodes: projectGraph.nodes, 19 | externalNodes: projectGraph.externalNodes, 20 | dependencies: projectGraph.dependencies, 21 | }; 22 | return newValue; 23 | } 24 | 25 | /** 26 | * Serializes the given data to a JSON string. 27 | * By default the JSON string is formatted with a 2 space intendation to be easy readable. 28 | * 29 | * @param input Object which should be serialized to JSON 30 | * @param options JSON serialize options 31 | * @returns the formatted JSON representation of the object 32 | */ 33 | function serializeJson( 34 | input, 35 | options 36 | ) { 37 | return JSON.stringify(input, null, options && options.spaces || 2) + '\n'; 38 | } 39 | 40 | /** 41 | * Serializes the given data to JSON and writes it to a file. 42 | * 43 | * @param path A path to a file. 44 | * @param data data which should be serialized to JSON and written to the file 45 | * @param options JSON serialize options 46 | */ 47 | function writeJsonFile( 48 | path, 49 | data, 50 | options 51 | ) { 52 | const serializedJson = serializeJson(data, options); 53 | const content = options && options.appendNewLine 54 | ? `${serializedJson}\n` 55 | : serializedJson; 56 | writeFileSync(path, content, { encoding: 'utf-8' }); 57 | } 58 | 59 | function writeCache(cache) { 60 | writeJsonFile('.cache/nxdeps.json', cache); 61 | } 62 | 63 | module.exports = { createCache, writeCache } 64 | -------------------------------------------------------------------------------- /core/output.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | const os = require("os"); 3 | 4 | class CLIOutput { 5 | constructor() { 6 | this.X_PADDING = ' '; 7 | /** 8 | * Expose some color and other utility functions so that other parts of the codebase that need 9 | * more fine-grained control of message bodies are still using a centralized 10 | * implementation. 11 | */ 12 | this.colors = { 13 | gray: chalk.gray, 14 | green: chalk.green, 15 | red: chalk.red, 16 | cyan: chalk.cyan, 17 | white: chalk.white, 18 | }; 19 | this.bold = chalk.bold; 20 | this.underline = chalk.underline; 21 | this.dim = chalk.dim; 22 | } 23 | /** 24 | * Longer dash character which forms more of a continuous line when place side to side 25 | * with itself, unlike the standard dash character 26 | */ 27 | get VERTICAL_SEPARATOR() { 28 | let divider = ''; 29 | for (let i = 0; i < process.stdout.columns - this.X_PADDING.length * 2; i++) { 30 | divider += '\u2014'; 31 | } 32 | return divider; 33 | } 34 | writeToStdOut(str) { 35 | process.stdout.write(str); 36 | } 37 | writeOutputTitle({ color, title, }) { 38 | this.writeToStdOut(` ${this.applyNxPrefix(color, title)}${os.EOL}`); 39 | } 40 | writeOptionalOutputBody(bodyLines) { 41 | if (!bodyLines) { 42 | return; 43 | } 44 | this.addNewline(); 45 | bodyLines.forEach((bodyLine) => this.writeToStdOut(` ${bodyLine}${os.EOL}`)); 46 | } 47 | applyNxPrefix(color = 'cyan', text) { 48 | let nxPrefix = ''; 49 | if (chalk[color]) { 50 | nxPrefix = `${chalk[color]('>')} ${chalk.reset.inverse.bold[color](' NX ')}`; 51 | } 52 | else { 53 | nxPrefix = `${chalk.keyword(color)('>')} ${chalk.reset.inverse.bold.keyword(color)(' NX ')}`; 54 | } 55 | return `${nxPrefix} ${text}`; 56 | } 57 | addNewline() { 58 | this.writeToStdOut(os.EOL); 59 | } 60 | addVerticalSeparator(color = 'gray') { 61 | this.addNewline(); 62 | this.addVerticalSeparatorWithoutNewLines(color); 63 | this.addNewline(); 64 | } 65 | addVerticalSeparatorWithoutNewLines(color = 'gray') { 66 | this.writeToStdOut(`${this.X_PADDING}${chalk.dim[color](this.VERTICAL_SEPARATOR)}${os.EOL}`); 67 | } 68 | error({ title, slug, bodyLines }) { 69 | this.addNewline(); 70 | this.writeOutputTitle({ 71 | color: 'red', 72 | title: chalk.red(title), 73 | }); 74 | this.writeOptionalOutputBody(bodyLines); 75 | /** 76 | * Optional slug to be used in an Nx error message redirect URL 77 | */ 78 | if (slug && typeof slug === 'string') { 79 | this.addNewline(); 80 | this.writeToStdOut(`${chalk.grey(' Learn more about this error: ')}https://errors.nx.dev/${slug}${os.EOL}`); 81 | } 82 | this.addNewline(); 83 | } 84 | warn({ title, slug, bodyLines }) { 85 | this.addNewline(); 86 | this.writeOutputTitle({ 87 | color: 'yellow', 88 | title: chalk.yellow(title), 89 | }); 90 | this.writeOptionalOutputBody(bodyLines); 91 | /** 92 | * Optional slug to be used in an Nx warning message redirect URL 93 | */ 94 | if (slug && typeof slug === 'string') { 95 | this.addNewline(); 96 | this.writeToStdOut(`${chalk.grey(' Learn more about this warning: ')}https://errors.nx.dev/${slug}\n`); 97 | } 98 | this.addNewline(); 99 | } 100 | note({ title, bodyLines }) { 101 | this.addNewline(); 102 | this.writeOutputTitle({ 103 | color: 'orange', 104 | title: chalk.keyword('orange')(title), 105 | }); 106 | this.writeOptionalOutputBody(bodyLines); 107 | this.addNewline(); 108 | } 109 | success({ title, bodyLines }) { 110 | this.addNewline(); 111 | this.writeOutputTitle({ 112 | color: 'green', 113 | title: chalk.green(title), 114 | }); 115 | this.writeOptionalOutputBody(bodyLines); 116 | this.addNewline(); 117 | } 118 | logSingleLine(message) { 119 | this.addNewline(); 120 | this.writeOutputTitle({ 121 | color: 'gray', 122 | title: message, 123 | }); 124 | this.addNewline(); 125 | } 126 | logCommand(message, taskStatus) { 127 | // normalize the message 128 | if (message.startsWith('nx run ')) { 129 | message = message.substring('nx run '.length); 130 | } 131 | else if (message.startsWith('run ')) { 132 | message = message.substring('run '.length); 133 | } 134 | this.addNewline(); 135 | let commandOutput = `${chalk.dim('> nx run')} ${message}`; 136 | if (taskStatus === 'local-cache') { 137 | commandOutput += ` ${chalk.grey('[local cache]')}`; 138 | } 139 | else if (taskStatus === 'remote-cache') { 140 | commandOutput += ` ${chalk.grey('[remote cache]')}`; 141 | } 142 | else if (taskStatus === 'local-cache-kept-existing') { 143 | commandOutput += ` ${chalk.grey('[existing outputs match the cache, left as is]')}`; 144 | } 145 | this.writeToStdOut(commandOutput); 146 | this.addNewline(); 147 | } 148 | log({ title, bodyLines, color }) { 149 | this.addNewline(); 150 | color = color || 'white'; 151 | this.writeOutputTitle({ 152 | color: 'cyan', 153 | title: chalk[color](title), 154 | }); 155 | this.writeOptionalOutputBody(bodyLines); 156 | this.addNewline(); 157 | } 158 | } 159 | 160 | module.exports = { output: new CLIOutput() } 161 | -------------------------------------------------------------------------------- /core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "core", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "echo 123" 7 | } 8 | } -------------------------------------------------------------------------------- /core/path.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | function removeWindowsDriveLetter(osSpecificPath) { 3 | return osSpecificPath.replace(/^[A-Z]:/, ''); 4 | } 5 | 6 | /** 7 | * Coverts an os specific path to a unix style path 8 | */ 9 | function normalizePath(osSpecificPath) { 10 | return removeWindowsDriveLetter(osSpecificPath).split('\\').join('/'); 11 | } 12 | 13 | /** 14 | * Normalized path fragments and joins them 15 | */ 16 | function joinPathFragments(...fragments) { 17 | return normalizePath(path.join(...fragments)); 18 | } 19 | 20 | module.exports = { normalizePath, joinPathFragments }; -------------------------------------------------------------------------------- /core/project-graph-builder.js: -------------------------------------------------------------------------------- 1 | const { DependencyType } = require("./type"); 2 | 3 | /** 4 | * Builder for adding nodes and dependencies to a {@link ProjectGraph} 5 | */ 6 | class ProjectGraphBuilder { 7 | graph; 8 | removedEdges; 9 | 10 | constructor(g) { 11 | this.removedEdges = {}; 12 | if (g) { 13 | this.graph = g; 14 | } else { 15 | this.graph = { 16 | nodes: {}, 17 | externalNodes: {}, 18 | dependencies: {}, 19 | }; 20 | } 21 | } 22 | 23 | /** 24 | * Adds a project node to the project graph 25 | */ 26 | addNode(node) { 27 | // Check if project with the same name already exists 28 | if (this.graph.nodes[node.name]) { 29 | // Throw if existing project is of a different type 30 | if (this.graph.nodes[node.name].type !== node.type) { 31 | throw new Error( 32 | `Multiple projects are named "${node.name}". One is of type "${node.type 33 | }" and the other is of type "${this.graph.nodes[node.name].type 34 | }". Please resolve the conflicting project names.` 35 | ); 36 | } 37 | } 38 | this.graph.nodes[node.name] = node; 39 | this.graph.dependencies[node.name] = []; 40 | } 41 | 42 | /** 43 | * Adds a external node to the project graph 44 | */ 45 | addExternalNode(node) { 46 | this.graph.externalNodes[node.name] = node; 47 | } 48 | 49 | /** 50 | * Adds a dependency from source project to target project 51 | */ 52 | addImplicitDependency( 53 | sourceProjectName, 54 | targetProjectName 55 | ) { 56 | if (sourceProjectName === targetProjectName) { 57 | return; 58 | } 59 | if (!this.graph.nodes[sourceProjectName]) { 60 | throw new Error(`Source project does not exist: ${sourceProjectName}`); 61 | } 62 | if ( 63 | !this.graph.nodes[targetProjectName] && 64 | !this.graph.externalNodes[targetProjectName] 65 | ) { 66 | throw new Error(`Target project does not exist: ${targetProjectName}`); 67 | } 68 | this.graph.dependencies[sourceProjectName].push({ 69 | source: sourceProjectName, 70 | target: targetProjectName, 71 | type: DependencyType.implicit, 72 | }); 73 | } 74 | 75 | /** 76 | * Removes a dependency from source project to target project 77 | */ 78 | removeDependency(sourceProjectName, targetProjectName) { 79 | if (sourceProjectName === targetProjectName) { 80 | return; 81 | } 82 | if (!this.graph.nodes[sourceProjectName]) { 83 | throw new Error(`Source project does not exist: ${sourceProjectName}`); 84 | } 85 | if ( 86 | !this.graph.nodes[targetProjectName] && 87 | !this.graph.externalNodes[targetProjectName] 88 | ) { 89 | throw new Error(`Target project does not exist: ${targetProjectName}`); 90 | } 91 | // this.graph.dependencies[sourceProjectName] = this.graph.dependencies[ 92 | // sourceProjectName 93 | // ].filter((d) => d.target !== targetProjectName); 94 | if (!this.removedEdges[sourceProjectName]) { 95 | this.removedEdges[sourceProjectName] = new Set(); 96 | } 97 | this.removedEdges[sourceProjectName].add(targetProjectName); 98 | } 99 | 100 | /** 101 | * Add an explicit dependency from a file in source project to target project 102 | */ 103 | addExplicitDependency( 104 | sourceProjectName, 105 | sourceProjectFile, 106 | targetProjectName 107 | ) { 108 | if (sourceProjectName === targetProjectName) { 109 | return; 110 | } 111 | const source = this.graph.nodes[sourceProjectName]; 112 | if (!source) { 113 | throw new Error(`Source project does not exist: ${sourceProjectName}`); 114 | } 115 | 116 | if ( 117 | !this.graph.nodes[targetProjectName] && 118 | !this.graph.externalNodes[targetProjectName] 119 | ) { 120 | throw new Error(`Target project does not exist: ${targetProjectName}`); 121 | } 122 | 123 | const fileData = source.data.files.find( 124 | (f) => f.file === sourceProjectFile 125 | ); 126 | if (!fileData) { 127 | throw new Error( 128 | `Source project ${sourceProjectName} does not have a file: ${sourceProjectFile}` 129 | ); 130 | } 131 | 132 | if (!fileData.deps) { 133 | fileData.deps = []; 134 | } 135 | 136 | if (!fileData.deps.find((t) => t === targetProjectName)) { 137 | fileData.deps.push(targetProjectName); 138 | } 139 | } 140 | 141 | /** 142 | * Set version of the project graph 143 | */ 144 | setVersion(version) { 145 | this.graph.version = version; 146 | } 147 | 148 | getUpdatedProjectGraph() { 149 | for (const sourceProject of Object.keys(this.graph.nodes)) { 150 | const alreadySetTargetProjects = 151 | this.calculateAlreadySetTargetDeps(sourceProject); 152 | this.graph.dependencies[sourceProject] = [ 153 | ...alreadySetTargetProjects.values(), 154 | ]; 155 | 156 | const fileDeps = this.calculateTargetDepsFromFiles(sourceProject); 157 | for (const targetProject of fileDeps) { 158 | if (!alreadySetTargetProjects.has(targetProject)) { 159 | if ( 160 | !this.removedEdges[sourceProject] || 161 | !this.removedEdges[sourceProject].has(targetProject) 162 | ) { 163 | this.graph.dependencies[sourceProject].push({ 164 | source: sourceProject, 165 | target: targetProject, 166 | type: DependencyType.static, 167 | }); 168 | } 169 | } 170 | } 171 | } 172 | return this.graph; 173 | } 174 | 175 | calculateTargetDepsFromFiles(sourceProject) { 176 | const fileDeps = new Set(); 177 | const files = this.graph.nodes[sourceProject].data.files; 178 | if (!files) return fileDeps; 179 | for (let f of files) { 180 | if (f.deps) { 181 | for (let p of f.deps) { 182 | fileDeps.add(p); 183 | } 184 | } 185 | } 186 | return fileDeps; 187 | } 188 | 189 | calculateAlreadySetTargetDeps(sourceProject) { 190 | const alreadySetTargetProjects = new Map(); 191 | const removed = this.removedEdges[sourceProject]; 192 | for (const d of this.graph.dependencies[sourceProject]) { 193 | if (!removed || !removed.has(d.target)) { 194 | alreadySetTargetProjects.set(d.target, d); 195 | } 196 | } 197 | return alreadySetTargetProjects; 198 | } 199 | } 200 | 201 | module.exports = { ProjectGraphBuilder } -------------------------------------------------------------------------------- /core/project-graph.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Backwards compatibility adapter for project graph 3 | * @param {string} sourceVersion 4 | * @param {string} targetVersion 5 | * @param projectGraph 6 | * @param {ProjectGraph} projectGraph 7 | * @returns {ProjectGraph} 8 | */ 9 | function projectGraphAdapter( 10 | sourceVersion, 11 | targetVersion, 12 | projectGraph 13 | ) { 14 | if (sourceVersion === targetVersion) { 15 | return projectGraph; 16 | } 17 | if (sourceVersion === '5.0' && targetVersion === '4.0') { 18 | return projectGraphCompat5to4(projectGraph); 19 | } 20 | throw new Error( 21 | `Invalid source or target versions. Source: ${sourceVersion}, Target: ${targetVersion}. 22 | Only backwards compatibility between "5.0" and "4.0" is supported. 23 | This error can be caused by "@nrwl/..." packages getting out of sync or outdated project graph cache. 24 | Check the versions running "nx report" and/or remove your "nxdeps.json" file (in node_modules/.cache/nx folder). 25 | ` 26 | ); 27 | } 28 | 29 | module.exports = { projectGraphAdapter } -------------------------------------------------------------------------------- /core/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "core", 3 | "targets": { 4 | "build": { 5 | "executor": "@nrwl/js:tsc", 6 | "outputs": [ 7 | "{options.outputPath}" 8 | ], 9 | "options": { 10 | "outputPath": "dist/apps/demoapp", 11 | "main": "apps/demoapp/src/index.ts", 12 | "tsConfig": "apps/demoapp/tsconfig.app.json", 13 | "assets": [ 14 | "apps/demoapp/*.md" 15 | ] 16 | } 17 | }, 18 | "serve": { 19 | "executor": "@nrwl/js:node", 20 | "options": { 21 | "buildTarget": "simple:test" 22 | } 23 | }, 24 | "make": { 25 | "executor": "@nrwl/workspace:run-commands", 26 | "options": { 27 | "commands": [ 28 | { 29 | "command": "node ./index.js" 30 | } 31 | ] 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /core/run-command.js: -------------------------------------------------------------------------------- 1 | const { StaticRunOneTerminalOutputLifeCycle } = require('./static-run-one-terminal-output-life-cycle'); 2 | const { CompositeLifeCycle } = require('./life-cycle'); 3 | async function getTerminalOutputLifeCycle( 4 | initiatingProject, 5 | terminalOutputStrategy, 6 | projectNames, 7 | tasks, 8 | nxArgs, 9 | overrides, 10 | runnerOptions 11 | ) { 12 | if (terminalOutputStrategy === 'run-one') { 13 | return { 14 | lifeCycle: new StaticRunOneTerminalOutputLifeCycle( 15 | initiatingProject, 16 | projectNames, 17 | tasks, 18 | nxArgs 19 | ), 20 | renderIsDone: Promise.resolve(), 21 | }; 22 | } 23 | } 24 | 25 | async function runCommand(projectsToRun, projectGraph, { nxJson }, nxArgs, overrides, terminalOutputStrategy, initiatingProject) { 26 | const { tasksRunner, runnerOptions } = getRunner(nxArgs, nxJson); 27 | console.log('runCommand'); 28 | const tasksMap = []; 29 | // 组合命令信息 30 | // createTasksForProjectToRun 31 | for (const project of projectsToRun) { 32 | tasksMap.push({ 33 | id: `${project.name}:${nxArgs.target}`, 34 | overrides, 35 | target: { 36 | project: project.name, 37 | // 先不演示有命令带参数的情况 38 | configuration: undefined, 39 | target: nxArgs.target 40 | }, 41 | projectRoot: project.data.root, 42 | 43 | }) 44 | } 45 | const projectNames = projectsToRun.map((t) => t.name); 46 | const { lifeCycle, renderIsDone } = await getTerminalOutputLifeCycle(initiatingProject, terminalOutputStrategy, projectNames, tasksMap, nxArgs, overrides, runnerOptions); 47 | const lifeCycles = [lifeCycle]; 48 | // 使用 default-tasks-runner 来执行代码 49 | const promiseOrObservable = tasksRunner( 50 | tasksMap, 51 | Object.assign( 52 | Object.assign({}, runnerOptions), { 53 | lifeCycle: new CompositeLifeCycle(lifeCycles) 54 | }), 55 | { 56 | initiatingProject, 57 | target: nxArgs.target, 58 | projectGraph, 59 | nxJson, 60 | } 61 | ); 62 | 63 | 64 | console.log(tasksMap); 65 | } 66 | 67 | function getRunner(nxArgs, nxJson) { 68 | let runner = nxArgs.runner; 69 | runner = runner || 'default'; 70 | if (nxJson.tasksRunnerOptions[runner]) { 71 | // 这里取的是 nx.json 里面默认的 tasksRunnerOptions 的 default runner 72 | let modulePath = nxJson.tasksRunnerOptions[runner].runner; 73 | let tasksRunner; 74 | if (modulePath) { 75 | tasksRunner = require(modulePath); 76 | // to support both babel and ts formats 77 | if (tasksRunner.default) { 78 | tasksRunner = tasksRunner.default; 79 | } 80 | } 81 | return { 82 | tasksRunner, 83 | runnerOptions: Object.assign(Object.assign({}, nxJson.tasksRunnerOptions[runner].options), nxArgs), 84 | }; 85 | } 86 | } 87 | 88 | module.exports = { runCommand } -------------------------------------------------------------------------------- /core/run-one.js: -------------------------------------------------------------------------------- 1 | function getProjects(projectGraph, project) { 2 | let projects = [projectGraph.nodes[project]]; 3 | let projectsMap = { 4 | [project]: projectGraph.nodes[project], 5 | }; 6 | 7 | return { projects, projectsMap }; 8 | } 9 | 10 | module.exports = { getProjects }; 11 | -------------------------------------------------------------------------------- /core/static-run-one-terminal-output-life-cycle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The following life cycle's outputs are static, meaning no previous content 3 | * is rewritten or modified as new outputs are added. It is therefore intended 4 | * for use in CI environments. 5 | * 6 | * For the common case of a user executing a command on their local machine, 7 | * the dynamic equivalent of this life cycle is usually preferable. 8 | */ 9 | class StaticRunOneTerminalOutputLifeCycle { 10 | constructor(initiatingProject, projectNames, tasks, args) { 11 | this.initiatingProject = initiatingProject; 12 | this.projectNames = projectNames; 13 | this.tasks = tasks; 14 | this.args = args; 15 | this.failedTasks = []; 16 | this.cachedTasks = []; 17 | } 18 | startCommand() { 19 | if (process.env.NX_INVOKED_BY_RUNNER) { 20 | return; 21 | } 22 | const numberOfDeps = this.tasks.length - 1; 23 | if (numberOfDeps > 0) { 24 | console.log(`Running target ${output.output.bold(this.args.target)} for project ${output.output.bold(this.initiatingProject)} and ${output.output.bold(numberOfDeps)} task(s) it depends on`); 25 | } 26 | } 27 | endCommand() { 28 | // Silent for a single task 29 | if (process.env.NX_INVOKED_BY_RUNNER) { 30 | return; 31 | } 32 | } 33 | endTasks(taskResults) { 34 | for (let t of taskResults) { 35 | if (t.status === 'failure') { 36 | this.failedTasks.push(t.task); 37 | } 38 | else if (t.status === 'local-cache') { 39 | this.cachedTasks.push(t.task); 40 | } 41 | else if (t.status === 'local-cache-kept-existing') { 42 | this.cachedTasks.push(t.task); 43 | } 44 | else if (t.status === 'remote-cache') { 45 | this.cachedTasks.push(t.task); 46 | } 47 | } 48 | } 49 | } 50 | 51 | module.exports = { StaticRunOneTerminalOutputLifeCycle } -------------------------------------------------------------------------------- /core/strip-source-code.js: -------------------------------------------------------------------------------- 1 | let SyntaxKind; 2 | function stripSourceCode(scanner, contents) { 3 | if (!SyntaxKind) { 4 | SyntaxKind = require('typescript').SyntaxKind; 5 | } 6 | 7 | if (contents.indexOf('loadChildren') > -1) { 8 | return contents; 9 | } 10 | 11 | scanner.setText(contents); 12 | let token = scanner.scan(); 13 | const statements = []; 14 | let start = null; 15 | while (token !== SyntaxKind.EndOfFileToken) { 16 | const potentialStart = scanner.getStartPos(); 17 | switch (token) { 18 | case SyntaxKind.MultiLineCommentTrivia: 19 | case SyntaxKind.SingleLineCommentTrivia: { 20 | const isMultiLineCommentTrivia = 21 | token === SyntaxKind.MultiLineCommentTrivia; 22 | const start = potentialStart + 2; 23 | token = scanner.scan(); 24 | const end = scanner.getStartPos() - (isMultiLineCommentTrivia ? 2 : 0); 25 | const comment = contents.substring(start, end).trim(); 26 | if (comment === 'nx-ignore-next-line') { 27 | // reading till the end of the line 28 | while ( 29 | token === SyntaxKind.WhitespaceTrivia || 30 | token === SyntaxKind.NewLineTrivia 31 | ) { 32 | token = scanner.scan(); 33 | } 34 | 35 | // ignore next line 36 | while ( 37 | token !== SyntaxKind.NewLineTrivia && 38 | token !== SyntaxKind.EndOfFileToken 39 | ) { 40 | token = scanner.scan(); 41 | } 42 | } 43 | break; 44 | } 45 | 46 | case SyntaxKind.RequireKeyword: 47 | case SyntaxKind.ImportKeyword: { 48 | token = scanner.scan(); 49 | while ( 50 | token === SyntaxKind.WhitespaceTrivia || 51 | token === SyntaxKind.NewLineTrivia 52 | ) { 53 | token = scanner.scan(); 54 | } 55 | start = potentialStart; 56 | break; 57 | } 58 | 59 | case SyntaxKind.ExportKeyword: { 60 | token = scanner.scan(); 61 | while ( 62 | token === SyntaxKind.WhitespaceTrivia || 63 | token === SyntaxKind.NewLineTrivia 64 | ) { 65 | token = scanner.scan(); 66 | } 67 | if ( 68 | token === SyntaxKind.OpenBraceToken || 69 | token === SyntaxKind.AsteriskToken 70 | ) { 71 | start = potentialStart; 72 | } 73 | break; 74 | } 75 | 76 | case SyntaxKind.StringLiteral: { 77 | if (start !== null) { 78 | token = scanner.scan(); 79 | if (token === SyntaxKind.CloseParenToken) { 80 | token = scanner.scan(); 81 | } 82 | const end = scanner.getStartPos(); 83 | statements.push(contents.substring(start, end)); 84 | start = null; 85 | } else { 86 | token = scanner.scan(); 87 | } 88 | break; 89 | } 90 | 91 | default: { 92 | token = scanner.scan(); 93 | } 94 | } 95 | } 96 | 97 | return statements.join('\n'); 98 | } 99 | 100 | module.exports = { stripSourceCode } -------------------------------------------------------------------------------- /core/target-project-locator.js: -------------------------------------------------------------------------------- 1 | const typescript = require("./typescript"); 2 | const path = require("path"); 3 | const fs = require("fs"); 4 | const app_root = require("./app-root"); 5 | const { parseJson } = require("./devkit"); 6 | 7 | function isRelativePath(path) { 8 | return (path === '.' || 9 | path === '..' || 10 | path.startsWith('./') || 11 | path.startsWith('../')); 12 | } 13 | 14 | 15 | 16 | function readFileIfExisting(path) { 17 | return fs.existsSync(path) ? fs.readFileSync(path, 'utf-8') : ''; 18 | } 19 | 20 | class TargetProjectLocator { 21 | constructor(nodes, externalNodes) { 22 | var _a, _b; 23 | this.nodes = nodes; 24 | this.externalNodes = externalNodes; 25 | this.projectRootMappings = createProjectRootMappings(this.nodes); 26 | this.npmProjects = this.externalNodes 27 | ? Object.values(this.externalNodes) 28 | : []; 29 | this.tsConfig = this.getRootTsConfig(); 30 | this.paths = (_b = (_a = this.tsConfig.config) === null || _a === void 0 ? void 0 : _a.compilerOptions) === null || _b === void 0 ? void 0 : _b.paths; 31 | this.typescriptResolutionCache = new Map(); 32 | this.npmResolutionCache = new Map(); 33 | } 34 | /** 35 | * Find a project based on its import 36 | * 37 | * @param importExpr 38 | * @param filePath 39 | * @param npmScope 40 | * Npm scope shouldn't be used finding a project, but, to improve backward 41 | * compatibility, we fallback to checking the scope. 42 | * This happens in cases where someone has the dist output in their tsconfigs 43 | * and typescript will find the dist before the src. 44 | */ 45 | findProjectWithImport(importExpr, filePath, npmScope) { 46 | const normalizedImportExpr = importExpr.split('#')[0]; 47 | if (isRelativePath(normalizedImportExpr)) { 48 | const resolvedModule = path.posix.join((0, path.dirname)(filePath), normalizedImportExpr); 49 | return this.findProjectOfResolvedModule(resolvedModule); 50 | } 51 | const paths = this.findPaths(normalizedImportExpr); 52 | if (paths) { 53 | for (let p of paths) { 54 | const maybeResolvedProject = this.findProjectOfResolvedModule(p); 55 | if (maybeResolvedProject) { 56 | return maybeResolvedProject; 57 | } 58 | } 59 | } 60 | // try to find npm package before using expensive typescript resolution 61 | const npmProject = this.findNpmPackage(normalizedImportExpr); 62 | if (npmProject) { 63 | return npmProject; 64 | } 65 | if (this.tsConfig.config) { 66 | // TODO(meeroslav): this block is probably obsolete 67 | // and existed only because of the incomplete `paths` matching 68 | // if import cannot be matched using tsconfig `paths` the compilation would fail anyway 69 | const resolvedProject = this.resolveImportWithTypescript(normalizedImportExpr, filePath); 70 | if (resolvedProject) { 71 | return resolvedProject; 72 | } 73 | } 74 | // nothing found, cache for later 75 | this.npmResolutionCache.set(normalizedImportExpr, undefined); 76 | return null; 77 | } 78 | findPaths(normalizedImportExpr) { 79 | if (!this.paths) { 80 | return undefined; 81 | } 82 | if (this.paths[normalizedImportExpr]) { 83 | return this.paths[normalizedImportExpr]; 84 | } 85 | const wildcardPath = Object.keys(this.paths).find((path) => path.endsWith('/*') && 86 | (normalizedImportExpr.startsWith(path.replace(/\*$/, '')) || 87 | normalizedImportExpr === path.replace(/\/\*$/, ''))); 88 | if (wildcardPath) { 89 | return this.paths[wildcardPath]; 90 | } 91 | return undefined; 92 | } 93 | resolveImportWithTypescript(normalizedImportExpr, filePath) { 94 | let resolvedModule; 95 | if (this.typescriptResolutionCache.has(normalizedImportExpr)) { 96 | resolvedModule = this.typescriptResolutionCache.get(normalizedImportExpr); 97 | } 98 | else { 99 | resolvedModule = (0, typescript.resolveModuleByImport)(normalizedImportExpr, filePath, this.tsConfig.absolutePath); 100 | this.typescriptResolutionCache.set(normalizedImportExpr, resolvedModule ? resolvedModule : null); 101 | } 102 | // TODO: vsavkin temporary workaround. Remove it once we reworking handling of npm packages. 103 | if (resolvedModule && resolvedModule.indexOf('node_modules/') === -1) { 104 | const resolvedProject = this.findProjectOfResolvedModule(resolvedModule); 105 | if (resolvedProject) { 106 | return resolvedProject; 107 | } 108 | } 109 | return; 110 | } 111 | findNpmPackage(npmImport) { 112 | if (this.npmResolutionCache.has(npmImport)) { 113 | return this.npmResolutionCache.get(npmImport); 114 | } 115 | else { 116 | const pkg = this.npmProjects.find((pkg) => npmImport === pkg.data.packageName || 117 | npmImport.startsWith(`${pkg.data.packageName}/`)); 118 | if (pkg) { 119 | this.npmResolutionCache.set(npmImport, pkg.name); 120 | return pkg.name; 121 | } 122 | } 123 | } 124 | findProjectOfResolvedModule(resolvedModule) { 125 | const normalizedResolvedModule = resolvedModule.startsWith('./') 126 | ? resolvedModule.substring(2) 127 | : resolvedModule; 128 | const importedProject = this.findMatchingProjectFiles(normalizedResolvedModule); 129 | return importedProject ? importedProject.name : void 0; 130 | } 131 | getAbsolutePath(path2) { 132 | return path.join(app_root.appRootPath, path2); 133 | } 134 | getRootTsConfig() { 135 | let path = 'tsconfig.base.json'; 136 | let absolutePath = this.getAbsolutePath(path); 137 | let content = readFileIfExisting(absolutePath); 138 | if (!content) { 139 | path = 'tsconfig.json'; 140 | absolutePath = this.getAbsolutePath(path); 141 | content = readFileIfExisting(absolutePath); 142 | } 143 | if (!content) { 144 | return { 145 | path: null, 146 | absolutePath: null, 147 | config: null, 148 | }; 149 | } 150 | return { path, absolutePath, config: parseJson(content) }; 151 | } 152 | findMatchingProjectFiles(file) { 153 | for (let currentPath = file; currentPath != (0, path.dirname)(currentPath); currentPath = (0, path.dirname)(currentPath)) { 154 | const p = this.projectRootMappings.get(currentPath); 155 | if (p) { 156 | return p; 157 | } 158 | } 159 | return null; 160 | } 161 | } 162 | function createProjectRootMappings(nodes) { 163 | const projectRootMappings = new Map(); 164 | for (const projectName of Object.keys(nodes)) { 165 | const root = nodes[projectName].data.root; 166 | projectRootMappings.set(root && root.endsWith('/') ? root.substring(0, root.length - 1) : root, nodes[projectName]); 167 | } 168 | return projectRootMappings; 169 | } 170 | 171 | module.exports = { TargetProjectLocator } -------------------------------------------------------------------------------- /core/type.js: -------------------------------------------------------------------------------- 1 | const DependencyType = { 2 | /** 3 | * Static dependencies are tied to the loading of the module 4 | */ 5 | static: 'static', 6 | /** 7 | * Dynamic dependencies are brought in by the module at run time 8 | */ 9 | dynamic: 'dynamic', 10 | /** 11 | * Implicit dependencies are inferred 12 | */ 13 | implicit: 'implicit', 14 | } 15 | 16 | module.exports = { DependencyType } -------------------------------------------------------------------------------- /core/typescript-import-locator.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const { stripSourceCode } = require("./strip-source-code"); 3 | const app_root = require("./app-root"); 4 | const { existsSync, readFileSync } = require("fs"); 5 | const { DependencyType } = require("./type"); 6 | 7 | function defaultFileRead(filePath) { 8 | return readFileSync(path.join(app_root.appRootPath, filePath), 'utf-8'); 9 | } 10 | 11 | let tsModule; 12 | class TypeScriptImportLocator { 13 | constructor() { 14 | tsModule = require('typescript'); 15 | this.scanner = tsModule.createScanner(tsModule.ScriptTarget.Latest, false); 16 | } 17 | fromFile(filePath, visitor) { 18 | const extension = path.extname(filePath); 19 | if (extension !== '.ts' && 20 | extension !== '.tsx' && 21 | extension !== '.js' && 22 | extension !== '.jsx') { 23 | return; 24 | } 25 | const content = defaultFileRead(filePath); 26 | const strippedContent = stripSourceCode(this.scanner, content); 27 | if (strippedContent !== '') { 28 | const tsFile = tsModule.createSourceFile(filePath, strippedContent, tsModule.ScriptTarget.Latest, true); 29 | this.fromNode(filePath, tsFile, visitor); 30 | } 31 | } 32 | fromNode(filePath, node, visitor) { 33 | if (tsModule.isImportDeclaration(node) || 34 | (tsModule.isExportDeclaration(node) && node.moduleSpecifier)) { 35 | if (!this.ignoreStatement(node)) { 36 | const imp = this.getStringLiteralValue(node.moduleSpecifier); 37 | visitor(imp, filePath, DependencyType.static); 38 | } 39 | return; // stop traversing downwards 40 | } 41 | if (tsModule.isCallExpression(node) && 42 | node.expression.kind === tsModule.SyntaxKind.ImportKeyword && 43 | node.arguments.length === 1 && 44 | tsModule.isStringLiteral(node.arguments[0])) { 45 | if (!this.ignoreStatement(node)) { 46 | const imp = this.getStringLiteralValue(node.arguments[0]); 47 | visitor(imp, filePath, DependencyType.dynamic); 48 | } 49 | return; 50 | } 51 | if (tsModule.isCallExpression(node) && 52 | node.expression.getText() === 'require' && 53 | node.arguments.length === 1 && 54 | tsModule.isStringLiteral(node.arguments[0])) { 55 | if (!this.ignoreStatement(node)) { 56 | const imp = this.getStringLiteralValue(node.arguments[0]); 57 | visitor(imp, filePath, DependencyType.static); 58 | } 59 | return; 60 | } 61 | if (node.kind === tsModule.SyntaxKind.PropertyAssignment) { 62 | const name = this.getPropertyAssignmentName(node.name); 63 | if (name === 'loadChildren') { 64 | const init = node.initializer; 65 | if (init.kind === tsModule.SyntaxKind.StringLiteral && 66 | !this.ignoreLoadChildrenDependency(node.getFullText())) { 67 | const childrenExpr = this.getStringLiteralValue(init); 68 | visitor(childrenExpr, filePath, DependencyType.dynamic); 69 | return; // stop traversing downwards 70 | } 71 | } 72 | } 73 | /** 74 | * Continue traversing down the AST from the current node 75 | */ 76 | tsModule.forEachChild(node, (child) => this.fromNode(filePath, child, visitor)); 77 | } 78 | ignoreStatement(node) { 79 | return stripSourceCode(this.scanner, node.getFullText()) === ''; 80 | } 81 | ignoreLoadChildrenDependency(contents) { 82 | this.scanner.setText(contents); 83 | let token = this.scanner.scan(); 84 | while (token !== tsModule.SyntaxKind.EndOfFileToken) { 85 | if (token === tsModule.SyntaxKind.SingleLineCommentTrivia || 86 | token === tsModule.SyntaxKind.MultiLineCommentTrivia) { 87 | const start = this.scanner.getStartPos() + 2; 88 | token = this.scanner.scan(); 89 | const isMultiLineCommentTrivia = token === tsModule.SyntaxKind.MultiLineCommentTrivia; 90 | const end = this.scanner.getStartPos() - (isMultiLineCommentTrivia ? 2 : 0); 91 | const comment = contents.substring(start, end).trim(); 92 | if (comment === 'nx-ignore-next-line') { 93 | return true; 94 | } 95 | } 96 | else { 97 | token = this.scanner.scan(); 98 | } 99 | } 100 | return false; 101 | } 102 | getPropertyAssignmentName(nameNode) { 103 | switch (nameNode.kind) { 104 | case tsModule.SyntaxKind.Identifier: 105 | return nameNode.getText(); 106 | case tsModule.SyntaxKind.StringLiteral: 107 | return nameNode.text; 108 | default: 109 | return null; 110 | } 111 | } 112 | getStringLiteralValue(node) { 113 | return node.getText().substr(1, node.getText().length - 2); 114 | } 115 | } 116 | 117 | module.exports = { TypeScriptImportLocator } 118 | -------------------------------------------------------------------------------- /core/typescript.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const app_root = require("./app-root"); 3 | const normalizedAppRoot = app_root.appRootPath.replace(/\\/g, '/'); 4 | let tsModule; 5 | function readTsConfig(tsConfigPath) { 6 | if (!tsModule) { 7 | tsModule = require('typescript'); 8 | } 9 | const readResult = tsModule.readConfigFile(tsConfigPath, tsModule.sys.readFile); 10 | return tsModule.parseJsonConfigFileContent(readResult.config, tsModule.sys, (0, path.dirname)(tsConfigPath)); 11 | } 12 | exports.readTsConfig = readTsConfig; 13 | function readTsConfigOptions(tsConfigPath) { 14 | if (!tsModule) { 15 | tsModule = require('typescript'); 16 | } 17 | const readResult = tsModule.readConfigFile(tsConfigPath, tsModule.sys.readFile); 18 | // we don't need to scan the files, we only care about options 19 | const host = { 20 | readDirectory: () => [], 21 | fileExists: tsModule.sys.fileExists, 22 | }; 23 | return tsModule.parseJsonConfigFileContent(readResult.config, host, (0, path.dirname)(tsConfigPath)).options; 24 | } 25 | let compilerHost; 26 | /** 27 | * Find a module based on it's import 28 | * 29 | * @param importExpr Import used to resolve to a module 30 | * @param filePath 31 | * @param tsConfigPath 32 | */ 33 | function resolveModuleByImport(importExpr, filePath, tsConfigPath) { 34 | compilerHost = compilerHost || getCompilerHost(tsConfigPath); 35 | const { options, host, moduleResolutionCache } = compilerHost; 36 | const { resolvedModule } = tsModule.resolveModuleName(importExpr, filePath, options, host, moduleResolutionCache); 37 | if (!resolvedModule) { 38 | return; 39 | } 40 | else { 41 | return resolvedModule.resolvedFileName.replace(`${normalizedAppRoot}/`, ''); 42 | } 43 | } 44 | function getCompilerHost(tsConfigPath) { 45 | const options = readTsConfigOptions(tsConfigPath); 46 | const host = tsModule.createCompilerHost(options, true); 47 | const moduleResolutionCache = tsModule.createModuleResolutionCache(app_root.appRootPath, host.getCanonicalFileName); 48 | return { options, host, moduleResolutionCache }; 49 | } 50 | 51 | module.exports = { resolveModuleByImport } -------------------------------------------------------------------------------- /core/workspace-projects.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | const { statSync, existsSync, readFileSync } = require("fs-extra"); 4 | const { sync } = require("fast-glob"); 5 | 6 | 7 | function findPluginPackageJson(path, plugin) { 8 | while (true) { 9 | if (!path.startsWith(appRootPath)) { 10 | throw new Error("Couldn't find a package.json for Nx plugin:" + plugin); 11 | } 12 | if (existsSync(join(path, 'package.json'))) { 13 | return join(path, 'package.json'); 14 | } 15 | path = path.dirname(path); 16 | } 17 | } 18 | 19 | let nxPluginCache = null; 20 | function loadNxPlugins(plugins) { 21 | return plugins && plugins.length 22 | ? nxPluginCache || 23 | (nxPluginCache = plugins.map((path) => { 24 | const pluginPath = require.resolve(path, { 25 | paths: [appRootPath], 26 | }); 27 | 28 | const { name } = readJsonFile( 29 | findPluginPackageJson(pluginPath, path) 30 | ); 31 | const plugin = require(pluginPath); 32 | plugin.name = name; 33 | 34 | return plugin; 35 | })) 36 | : []; 37 | } 38 | 39 | function mergePluginTargetsWithNxTargets( 40 | projectRoot, 41 | targets, 42 | plugins 43 | ) { 44 | let newTargets = {}; 45 | for (const plugin of plugins) { 46 | if (!plugin.projectFilePatterns && plugin.projectFilePatterns.length || !plugin.registerProjectTargets) { 47 | continue; 48 | } 49 | 50 | const projectFiles = sync(`+(${plugin.projectFilePatterns.join('|')})`, { 51 | cwd: path.join(appRootPath, projectRoot), 52 | }); 53 | for (const projectFile of projectFiles) { 54 | newTargets = { 55 | ...newTargets, 56 | ...plugin.registerProjectTargets(path.join(projectRoot, projectFile)), 57 | }; 58 | } 59 | } 60 | return { ...newTargets, ...targets }; 61 | } 62 | 63 | function readJsonFile(path) { 64 | const content = fs.readFileSync(path, 'utf-8'); 65 | return JSON.parse(content); 66 | } 67 | 68 | function buildTargetFromScript( 69 | script, 70 | nx 71 | ) { 72 | const nxTargetConfiguration = nx && nx.targets && nx.targets[script] || {}; 73 | 74 | return { 75 | ...nxTargetConfiguration, 76 | executor: '@nrwl/workspace:run-script', 77 | options: { 78 | ...(nxTargetConfiguration.options || {}), 79 | script, 80 | }, 81 | }; 82 | } 83 | 84 | function mergeNpmScriptsWithTargets( 85 | projectRoot, 86 | targets 87 | ) { 88 | try { 89 | const { scripts, nx } = readJsonFile( 90 | `${projectRoot}/package.json` 91 | ); 92 | const res = {}; 93 | // handle no scripts 94 | Object.keys(scripts || {}).forEach((script) => { 95 | res[script] = buildTargetFromScript(script, nx); 96 | }); 97 | return { ...res, ...(targets || {}) }; 98 | } catch (e) { 99 | return undefined; 100 | } 101 | } 102 | 103 | function buildWorkspaceProjectNodes( 104 | ctx, 105 | builder, 106 | appRootPath, 107 | ) { 108 | const toAdd = []; 109 | Object.keys(ctx.workspace.projects).forEach((key) => { 110 | const p = ctx.workspace.projects[key]; 111 | const projectRoot = path.join(appRootPath, p.root); 112 | if (existsSync(path.join(projectRoot, 'package.json'))) { 113 | p.targets = mergeNpmScriptsWithTargets(projectRoot, p.targets); 114 | } 115 | p.targets = mergePluginTargetsWithNxTargets( 116 | p.root, 117 | p.targets, 118 | loadNxPlugins(ctx.workspace.plugins) 119 | ); 120 | const projectType = 121 | p.projectType === 'application' 122 | ? key.endsWith('-e2e') 123 | ? 'e2e' 124 | : 'app' 125 | : 'lib'; 126 | const tags = 127 | ctx.workspace.projects && ctx.workspace.projects[key] 128 | ? ctx.workspace.projects[key].tags || [] 129 | : []; 130 | 131 | toAdd.push({ 132 | name: key, 133 | type: projectType, 134 | data: { 135 | ...p, 136 | tags, 137 | files: ctx.fileMap[key], 138 | }, 139 | }); 140 | }); 141 | 142 | // Sort by root directory length (do we need this?) 143 | toAdd.sort((a, b) => { 144 | if (!a.data.root) return -1; 145 | if (!b.data.root) return -1; 146 | return a.data.root.length > b.data.root.length ? -1 : 1; 147 | }); 148 | 149 | toAdd.forEach((n) => { 150 | builder.addNode({ 151 | name: n.name, 152 | type: n.type, 153 | data: n.data, 154 | }); 155 | }); 156 | } 157 | 158 | module.exports = { buildWorkspaceProjectNodes } -------------------------------------------------------------------------------- /core/workspace.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | function readJsonFile(path) { 4 | const content = fs.readFileSync(path, 'utf-8'); 5 | return JSON.parse(content); 6 | } 7 | function toNewFormatOrNull(w) { 8 | let formatted = false; 9 | Object.values(w.projects || {}).forEach((projectConfig) => { 10 | if (projectConfig.architect) { 11 | renamePropertyWithStableKeys(projectConfig, 'architect', 'targets'); 12 | formatted = true; 13 | } 14 | if (projectConfig.schematics) { 15 | renamePropertyWithStableKeys(projectConfig, 'schematics', 'generators'); 16 | formatted = true; 17 | } 18 | Object.values(projectConfig.targets || {}).forEach((target) => { 19 | if (target.builder !== undefined) { 20 | renamePropertyWithStableKeys(target, 'builder', 'executor'); 21 | formatted = true; 22 | } 23 | }); 24 | }); 25 | if (w.schematics) { 26 | renamePropertyWithStableKeys(w, 'schematics', 'generators'); 27 | formatted = true; 28 | } 29 | if (w.version !== 2) { 30 | w.version = 2; 31 | formatted = true; 32 | } 33 | return formatted ? w : null; 34 | } 35 | 36 | // we have to do it this way to preserve the order of properties 37 | // not to screw up the formatting 38 | function renamePropertyWithStableKeys( 39 | obj, 40 | from, 41 | to 42 | ) { 43 | const copy = { ...obj }; 44 | Object.keys(obj).forEach((k) => { 45 | delete obj[k]; 46 | }); 47 | Object.keys(copy).forEach((k) => { 48 | if (k === from) { 49 | obj[to] = copy[k]; 50 | } else { 51 | obj[k] = copy[k]; 52 | } 53 | }); 54 | } 55 | 56 | function inlineProjectConfigurations(w, root) { 57 | Object.entries(w.projects || {}).forEach( 58 | ([project, config]) => { 59 | if (typeof config === 'string') { 60 | const configFilePath = path.join(root, config, 'project.json'); 61 | const fileConfig = readJsonFile(configFilePath); 62 | w.projects[project] = fileConfig; 63 | } 64 | } 65 | ); 66 | return w; 67 | } 68 | 69 | function toNewFormat(w) { 70 | const f = toNewFormatOrNull(w); 71 | // 相当于 return f ?? w; 72 | return f !== null && f !== void 0 ? f : w; 73 | } 74 | 75 | function resolveNewFormatWithInlineProjects( 76 | w, 77 | root, 78 | ) { 79 | return toNewFormat(inlineProjectConfigurations(w, root)); 80 | } 81 | 82 | module.exports = { resolveNewFormatWithInlineProjects } -------------------------------------------------------------------------------- /demo/args.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 |