projects.mjs (5257B)
1 /** 2 * @license 3 * Copyright 2023 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 import {spawn} from 'child_process'; 8 import {randomUUID} from 'crypto'; 9 import {readFile, writeFile} from 'fs/promises'; 10 import {join} from 'path'; 11 import {cwd} from 'process'; 12 13 class AngularProject { 14 static ports = new Set(); 15 static randomPort() { 16 /** 17 * Some ports are restricted by Chromium and will fail to connect 18 * to prevent we start after the 19 * 20 * https://source.chromium.org/chromium/chromium/src/+/main:net/base/port_util.cc;l=107?q=kRestrictedPorts&ss=chromium 21 */ 22 const min = 10101; 23 const max = 20202; 24 return Math.floor(Math.random() * (max - min + 1) + min); 25 } 26 static port() { 27 const port = AngularProject.randomPort(); 28 if (AngularProject.ports.has(port)) { 29 return AngularProject.port(); 30 } 31 return port; 32 } 33 34 static #scripts = testRunner => { 35 return { 36 // Builds the ng-schematics before running them 37 'build:schematics': 'npm run --prefix ../../ build', 38 // Deletes all files created by Puppeteer Ng-Schematics to avoid errors 39 'delete:file': 40 'rm -f .puppeteerrc.cjs && rm -f tsconfig.e2e.json && rm -R -f e2e/', 41 // Runs the Puppeteer Ng-Schematics against the sandbox 42 schematics: 'schematics ../../:ng-add --dry-run=false', 43 'schematics:e2e': 'schematics ../../:e2e --dry-run=false', 44 'schematics:config': 'schematics ../../:config --dry-run=false', 45 'schematics:add': `schematics ../../:ng-add --dry-run=false --test-runner="${testRunner}"`, 46 'schematics:smoke': 'ng e2e', 47 }; 48 }; 49 /** Folder name */ 50 #name; 51 /** E2E test runner to use */ 52 #runner; 53 54 type = ''; 55 56 constructor(runner, name) { 57 this.#runner = runner ?? 'node'; 58 this.#name = name ?? randomUUID(); 59 } 60 61 get runner() { 62 return this.#runner; 63 } 64 65 get name() { 66 return this.#name; 67 } 68 69 async executeCommand(command, options) { 70 const [executable, ...args] = command.split(' '); 71 await new Promise((resolve, reject) => { 72 const createProcess = spawn(executable, args, { 73 shell: true, 74 ...options, 75 }); 76 77 const onData = data => { 78 data = data 79 .toString() 80 // Replace new lines with a prefix including the test runner 81 .replace( 82 /(?:\r\n?|\n)(?=.*[\r\n])/g, 83 `\n${this.#runner}:${this.type} - `, 84 ); 85 console.log(`${this.#runner}:${this.type} - ${data}`); 86 }; 87 88 createProcess.stdout.on('data', onData); 89 createProcess.stderr.on('data', onData); 90 91 createProcess.on('error', message => { 92 console.error(`Running ${command} exited with error:`, message); 93 reject(message); 94 }); 95 96 createProcess.on('exit', code => { 97 if (code === 0) { 98 resolve(true); 99 } else { 100 reject(); 101 } 102 }); 103 }); 104 } 105 106 async create() { 107 await this.createProject(); 108 await this.updatePackageJson(); 109 } 110 111 async updatePackageJson() { 112 const packageJsonFile = join(cwd(), `/sandbox/${this.#name}/package.json`); 113 const packageJson = JSON.parse(await readFile(packageJsonFile)); 114 packageJson['scripts'] = { 115 ...packageJson['scripts'], 116 ...AngularProject.#scripts(this.#runner), 117 }; 118 await writeFile(packageJsonFile, JSON.stringify(packageJson, null, 2)); 119 } 120 121 get commandOptions() { 122 return { 123 ...process.env, 124 cwd: join(cwd(), `/sandbox/${this.#name}/`), 125 }; 126 } 127 128 async runNpmScripts(command, options) { 129 await this.executeCommand(`npm run ${command}`, { 130 ...this.commandOptions, 131 options, 132 }); 133 } 134 135 async runSchematics() { 136 await this.runNpmScripts('schematics'); 137 } 138 139 async runSchematicsE2E() { 140 await this.runNpmScripts('schematics:e2e'); 141 } 142 143 async runSchematicsConfig() { 144 await this.runNpmScripts('schematics:config'); 145 } 146 147 async runNgAdd() { 148 await this.runNpmScripts( 149 `schematics:add -- --port=${AngularProject.port()}`, 150 ); 151 } 152 153 async runSmoke() { 154 await this.runNpmScripts( 155 `schematics:smoke -- --port=${AngularProject.port()}`, 156 ); 157 } 158 } 159 160 export class AngularProjectSingle extends AngularProject { 161 type = 'single'; 162 163 async createProject() { 164 await this.executeCommand( 165 `ng new ${this.name} --directory=sandbox/${this.name} --defaults --skip-git`, 166 { 167 env: { 168 PUPPETEER_SKIP_DOWNLOAD: 'true', 169 ...process.env, 170 }, 171 }, 172 ); 173 } 174 } 175 176 export class AngularProjectMulti extends AngularProject { 177 type = 'multi'; 178 179 async createProject() { 180 await this.executeCommand( 181 `ng new ${this.name} --create-application=false --directory=sandbox/${this.name} --defaults --skip-git`, 182 { 183 env: { 184 PUPPETEER_SKIP_DOWNLOAD: 'true', 185 ...process.env, 186 }, 187 }, 188 ); 189 190 await this.executeCommand( 191 `ng generate application core --style=css --routing=true`, 192 { 193 PUPPETEER_SKIP_DOWNLOAD: 'true', 194 ...this.commandOptions, 195 }, 196 ); 197 await this.executeCommand( 198 `ng generate application admin --style=css --routing=false`, 199 { 200 PUPPETEER_SKIP_DOWNLOAD: 'true', 201 ...this.commandOptions, 202 }, 203 ); 204 } 205 }