UNPKG

11.7 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright Google LLC All Rights Reserved.
5 *
6 * Use of this source code is governed by an MIT-style license that can be
7 * found in the LICENSE file at https://angular.io/license
8 */
9var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10 if (k2 === undefined) k2 = k;
11 var desc = Object.getOwnPropertyDescriptor(m, k);
12 if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13 desc = { enumerable: true, get: function() { return m[k]; } };
14 }
15 Object.defineProperty(o, k2, desc);
16}) : (function(o, m, k, k2) {
17 if (k2 === undefined) k2 = k;
18 o[k2] = m[k];
19}));
20var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21 Object.defineProperty(o, "default", { enumerable: true, value: v });
22}) : function(o, v) {
23 o["default"] = v;
24});
25var __importStar = (this && this.__importStar) || function (mod) {
26 if (mod && mod.__esModule) return mod;
27 var result = {};
28 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
29 __setModuleDefault(result, mod);
30 return result;
31};
32Object.defineProperty(exports, "__esModule", { value: true });
33exports.NgccProcessor = void 0;
34const child_process_1 = require("child_process");
35const crypto_1 = require("crypto");
36const fs_1 = require("fs");
37const path = __importStar(require("path"));
38const benchmark_1 = require("./benchmark");
39// We cannot create a plugin for this, because NGTSC requires addition type
40// information which ngcc creates when processing a package which was compiled with NGC.
41// Example of such errors:
42// ERROR in node_modules/@angular/platform-browser/platform-browser.d.ts(42,22):
43// error TS-996002: Appears in the NgModule.imports of AppModule,
44// but could not be resolved to an NgModule class
45// We now transform a package and it's typings when NGTSC is resolving a module.
46class NgccProcessor {
47 constructor(compilerNgcc, propertiesToConsider, compilationWarnings, compilationErrors, basePath, tsConfigPath, inputFileSystem, resolver) {
48 this.compilerNgcc = compilerNgcc;
49 this.propertiesToConsider = propertiesToConsider;
50 this.compilationWarnings = compilationWarnings;
51 this.compilationErrors = compilationErrors;
52 this.basePath = basePath;
53 this.tsConfigPath = tsConfigPath;
54 this.inputFileSystem = inputFileSystem;
55 this.resolver = resolver;
56 this._processedModules = new Set();
57 this._logger = new NgccLogger(this.compilationWarnings, this.compilationErrors, compilerNgcc.LogLevel.info);
58 this._nodeModulesDirectory = this.findNodeModulesDirectory(this.basePath);
59 }
60 /** Process the entire node modules tree. */
61 process() {
62 // Under Bazel when running in sandbox mode parts of the filesystem is read-only.
63 if (process.env.BAZEL_TARGET) {
64 return;
65 }
66 // Skip if node_modules are read-only
67 const corePackage = this.tryResolvePackage('@angular/core', this._nodeModulesDirectory);
68 if (corePackage && isReadOnlyFile(corePackage)) {
69 return;
70 }
71 // Perform a ngcc run check to determine if an initial execution is required.
72 // If a run hash file exists that matches the current package manager lock file and the
73 // project's tsconfig, then an initial ngcc run has already been performed.
74 let skipProcessing = false;
75 let runHashFilePath;
76 const runHashBasePath = path.join(this._nodeModulesDirectory, '.cli-ngcc');
77 const projectBasePath = path.join(this._nodeModulesDirectory, '..');
78 try {
79 let ngccConfigData;
80 try {
81 ngccConfigData = (0, fs_1.readFileSync)(path.join(projectBasePath, 'ngcc.config.js'));
82 }
83 catch {
84 ngccConfigData = '';
85 }
86 const relativeTsconfigPath = path.relative(projectBasePath, this.tsConfigPath);
87 const tsconfigData = (0, fs_1.readFileSync)(this.tsConfigPath);
88 const { lockFileData, lockFilePath } = this.findPackageManagerLockFile(projectBasePath);
89 // Generate a hash that represents the state of the package lock file and used tsconfig
90 const runHash = (0, crypto_1.createHash)('sha256')
91 .update(lockFileData)
92 .update(lockFilePath)
93 .update(ngccConfigData)
94 .update(tsconfigData)
95 .update(relativeTsconfigPath)
96 .digest('hex');
97 // The hash is used directly in the file name to mitigate potential read/write race
98 // conditions as well as to only require a file existence check
99 runHashFilePath = path.join(runHashBasePath, runHash + '.lock');
100 // If the run hash lock file exists, then ngcc was already run against this project state
101 if ((0, fs_1.existsSync)(runHashFilePath)) {
102 skipProcessing = true;
103 }
104 }
105 catch {
106 // Any error means an ngcc execution is needed
107 }
108 if (skipProcessing) {
109 return;
110 }
111 const timeLabel = 'NgccProcessor.process';
112 (0, benchmark_1.time)(timeLabel);
113 // We spawn instead of using the API because:
114 // - NGCC Async uses clustering which is problematic when used via the API which means
115 // that we cannot setup multiple cluster masters with different options.
116 // - We will not be able to have concurrent builds otherwise Ex: App-Shell,
117 // as NGCC will create a lock file for both builds and it will cause builds to fails.
118 const originalProcessTitle = process.title;
119 try {
120 const { status, error } = (0, child_process_1.spawnSync)(process.execPath, [
121 this.compilerNgcc.ngccMainFilePath,
122 '--source' /** basePath */,
123 this._nodeModulesDirectory,
124 '--properties' /** propertiesToConsider */,
125 ...this.propertiesToConsider,
126 '--first-only' /** compileAllFormats */,
127 '--create-ivy-entry-points' /** createNewEntryPointFormats */,
128 '--async',
129 '--tsconfig' /** tsConfigPath */,
130 this.tsConfigPath,
131 '--use-program-dependencies',
132 ], {
133 stdio: ['inherit', process.stderr, process.stderr],
134 });
135 if (status !== 0) {
136 const errorMessage = (error === null || error === void 0 ? void 0 : error.message) || '';
137 throw new Error(errorMessage + `NGCC failed${errorMessage ? ', see above' : ''}.`);
138 }
139 }
140 finally {
141 process.title = originalProcessTitle;
142 }
143 (0, benchmark_1.timeEnd)(timeLabel);
144 // ngcc was successful so if a run hash was generated, write it for next time
145 if (runHashFilePath) {
146 try {
147 if (!(0, fs_1.existsSync)(runHashBasePath)) {
148 (0, fs_1.mkdirSync)(runHashBasePath, { recursive: true });
149 }
150 (0, fs_1.writeFileSync)(runHashFilePath, '');
151 }
152 catch {
153 // Errors are non-fatal
154 }
155 }
156 }
157 /** Process a module and it's depedencies. */
158 processModule(moduleName, resolvedModule) {
159 var _a, _b;
160 const resolvedFileName = resolvedModule.resolvedFileName;
161 if (!resolvedFileName ||
162 moduleName.startsWith('.') ||
163 this._processedModules.has(resolvedFileName)) {
164 // Skip when module is unknown, relative or NGCC compiler is not found or already processed.
165 return;
166 }
167 const packageJsonPath = this.tryResolvePackage(moduleName, resolvedFileName);
168 // If the package.json is read only we should skip calling NGCC.
169 // With Bazel when running under sandbox the filesystem is read-only.
170 if (!packageJsonPath || isReadOnlyFile(packageJsonPath)) {
171 // add it to processed so the second time round we skip this.
172 this._processedModules.add(resolvedFileName);
173 return;
174 }
175 const timeLabel = `NgccProcessor.processModule.ngcc.process+${moduleName}`;
176 (0, benchmark_1.time)(timeLabel);
177 this.compilerNgcc.process({
178 basePath: this._nodeModulesDirectory,
179 targetEntryPointPath: path.dirname(packageJsonPath),
180 propertiesToConsider: this.propertiesToConsider,
181 compileAllFormats: false,
182 createNewEntryPointFormats: true,
183 logger: this._logger,
184 tsConfigPath: this.tsConfigPath,
185 });
186 (0, benchmark_1.timeEnd)(timeLabel);
187 // Purge this file from cache, since NGCC add new mainFields. Ex: module_ivy_ngcc
188 // which are unknown in the cached file.
189 (_b = (_a = this.inputFileSystem).purge) === null || _b === void 0 ? void 0 : _b.call(_a, packageJsonPath);
190 this._processedModules.add(resolvedFileName);
191 }
192 invalidate(fileName) {
193 this._processedModules.delete(fileName);
194 }
195 /**
196 * Try resolve a package.json file from the resolved .d.ts file.
197 */
198 tryResolvePackage(moduleName, resolvedFileName) {
199 try {
200 const resolvedPath = this.resolver.resolveSync({}, resolvedFileName, `${moduleName}/package.json`);
201 return resolvedPath || undefined;
202 }
203 catch {
204 // Ex: @angular/compiler/src/i18n/i18n_ast/package.json
205 // or local libraries which don't reside in node_modules
206 const packageJsonPath = path.resolve(resolvedFileName, '../package.json');
207 return (0, fs_1.existsSync)(packageJsonPath) ? packageJsonPath : undefined;
208 }
209 }
210 findNodeModulesDirectory(startPoint) {
211 let current = startPoint;
212 while (path.dirname(current) !== current) {
213 const nodePath = path.join(current, 'node_modules');
214 if ((0, fs_1.existsSync)(nodePath)) {
215 return nodePath;
216 }
217 current = path.dirname(current);
218 }
219 throw new Error(`Cannot locate the 'node_modules' directory.`);
220 }
221 findPackageManagerLockFile(projectBasePath) {
222 for (const lockFile of ['yarn.lock', 'pnpm-lock.yaml', 'package-lock.json']) {
223 const lockFilePath = path.join(projectBasePath, lockFile);
224 try {
225 return {
226 lockFilePath,
227 lockFileData: (0, fs_1.readFileSync)(lockFilePath),
228 };
229 }
230 catch { }
231 }
232 throw new Error('Cannot locate a package manager lock file.');
233 }
234}
235exports.NgccProcessor = NgccProcessor;
236class NgccLogger {
237 constructor(compilationWarnings, compilationErrors, level) {
238 this.compilationWarnings = compilationWarnings;
239 this.compilationErrors = compilationErrors;
240 this.level = level;
241 }
242 // eslint-disable-next-line @typescript-eslint/no-empty-function
243 debug() { }
244 info(...args) {
245 // Log to stderr because it's a progress-like info message.
246 process.stderr.write(`\n${args.join(' ')}\n`);
247 }
248 warn(...args) {
249 this.compilationWarnings.push(args.join(' '));
250 }
251 error(...args) {
252 this.compilationErrors.push(new Error(args.join(' ')));
253 }
254}
255function isReadOnlyFile(fileName) {
256 try {
257 (0, fs_1.accessSync)(fileName, fs_1.constants.W_OK);
258 return false;
259 }
260 catch {
261 return true;
262 }
263}