1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.Compiler = exports.JSII_DIAGNOSTICS_CODE = exports.DIAGNOSTICS = void 0;
|
4 | const fs = require("node:fs");
|
5 | const path = require("node:path");
|
6 | const chalk = require("chalk");
|
7 | const log4js = require("log4js");
|
8 | const ts = require("typescript");
|
9 | const assembler_1 = require("./assembler");
|
10 | const find_utils_1 = require("./common/find-utils");
|
11 | const downlevel_dts_1 = require("./downlevel-dts");
|
12 | const jsii_diagnostic_1 = require("./jsii-diagnostic");
|
13 | const deprecation_warnings_1 = require("./transforms/deprecation-warnings");
|
14 | const tsconfig_1 = require("./tsconfig");
|
15 | const compiler_options_1 = require("./tsconfig/compiler-options");
|
16 | const tsconfig_validator_1 = require("./tsconfig/tsconfig-validator");
|
17 | const validator_1 = require("./tsconfig/validator");
|
18 | const utils = require("./utils");
|
19 | const LOG = log4js.getLogger('jsii/compiler');
|
20 | exports.DIAGNOSTICS = 'diagnostics';
|
21 | exports.JSII_DIAGNOSTICS_CODE = 9999;
|
22 | class Compiler {
|
23 | constructor(options) {
|
24 | this.options = options;
|
25 | this.rootFiles = [];
|
26 | if (options.generateTypeScriptConfig != null && options.typeScriptConfig != null) {
|
27 | throw new Error('Cannot use `generateTypeScriptConfig` and `typeScriptConfig` together. Provide only one of them.');
|
28 | }
|
29 | this.projectRoot = this.options.projectInfo.projectRoot;
|
30 | const configFileName = options.typeScriptConfig ?? options.generateTypeScriptConfig ?? 'tsconfig.json';
|
31 | this.configPath = path.join(this.projectRoot, configFileName);
|
32 | this.userProvidedTypeScriptConfig = Boolean(options.typeScriptConfig);
|
33 | this.system = {
|
34 | ...ts.sys,
|
35 | getCurrentDirectory: () => this.projectRoot,
|
36 | createDirectory: (pth) => ts.sys.createDirectory(path.resolve(this.projectRoot, pth)),
|
37 | deleteFile: ts.sys.deleteFile && ((pth) => ts.sys.deleteFile(path.join(this.projectRoot, pth))),
|
38 | fileExists: (pth) => ts.sys.fileExists(path.resolve(this.projectRoot, pth)),
|
39 | getFileSize: ts.sys.getFileSize && ((pth) => ts.sys.getFileSize(path.resolve(this.projectRoot, pth))),
|
40 | readFile: (pth, encoding) => ts.sys.readFile(path.resolve(this.projectRoot, pth), encoding),
|
41 | watchFile: ts.sys.watchFile &&
|
42 | ((pth, callback, pollingInterval, watchOptions) => ts.sys.watchFile(path.resolve(this.projectRoot, pth), callback, pollingInterval, watchOptions)),
|
43 | writeFile: (pth, data, writeByteOrderMark) => ts.sys.writeFile(path.resolve(this.projectRoot, pth), data, writeByteOrderMark),
|
44 | };
|
45 | this.tsconfig = this.configureTypeScript();
|
46 | this.compilerHost = ts.createIncrementalCompilerHost(this.tsconfig.compilerOptions, this.system);
|
47 | }
|
48 | |
49 |
|
50 |
|
51 |
|
52 |
|
53 | emit(...files) {
|
54 | this.prepareForBuild(...files);
|
55 | return this.buildOnce();
|
56 | }
|
57 | async watch(opts) {
|
58 | this.prepareForBuild();
|
59 | const host = ts.createWatchCompilerHost(this.configPath, {
|
60 | ...this.tsconfig.compilerOptions,
|
61 | noEmitOnError: false,
|
62 | }, this.system, ts.createEmitAndSemanticDiagnosticsBuilderProgram, opts?.reportDiagnostics, opts?.reportWatchStatus, this.tsconfig.watchOptions);
|
63 | if (!host.getDefaultLibLocation) {
|
64 | throw new Error('No default library location was found on the TypeScript compiler host!');
|
65 | }
|
66 | const orig = host.afterProgramCreate;
|
67 |
|
68 |
|
69 |
|
70 |
|
71 | host.afterProgramCreate = (builderProgram) => {
|
72 | const emitResult = this.consumeProgram(builderProgram.getProgram(), host.getDefaultLibLocation());
|
73 | for (const diag of emitResult.diagnostics.filter((d) => d.code === exports.JSII_DIAGNOSTICS_CODE)) {
|
74 | utils.logDiagnostic(diag, this.projectRoot);
|
75 | }
|
76 | if (orig) {
|
77 | orig.call(host, builderProgram);
|
78 | }
|
79 | if (opts?.compilationComplete) {
|
80 | opts.compilationComplete(emitResult);
|
81 | }
|
82 | };
|
83 | const watch = ts.createWatchProgram(host);
|
84 | if (opts?.nonBlocking) {
|
85 |
|
86 | return watch;
|
87 | }
|
88 |
|
89 | return new Promise(() => null);
|
90 | }
|
91 | |
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | configureTypeScript() {
|
98 | if (this.userProvidedTypeScriptConfig) {
|
99 | const config = this.readTypeScriptConfig();
|
100 |
|
101 | const rules = this.options.validateTypeScriptConfig ?? tsconfig_1.TypeScriptConfigValidationRuleSet.NONE;
|
102 | if (rules === tsconfig_1.TypeScriptConfigValidationRuleSet.NONE) {
|
103 | utils.logDiagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_4009_DISABLED_TSCONFIG_VALIDATION.create(undefined, this.configPath), this.projectRoot);
|
104 | }
|
105 |
|
106 | if (rules !== tsconfig_1.TypeScriptConfigValidationRuleSet.NONE) {
|
107 | const configName = path.relative(this.projectRoot, this.configPath);
|
108 | try {
|
109 | const validator = new tsconfig_validator_1.TypeScriptConfigValidator(rules);
|
110 | validator.validate({
|
111 | ...config,
|
112 |
|
113 | compilerOptions: (0, compiler_options_1.convertForJson)(config.compilerOptions),
|
114 | });
|
115 | }
|
116 | catch (error) {
|
117 | if (error instanceof validator_1.ValidationError) {
|
118 | utils.logDiagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_4000_FAILED_TSCONFIG_VALIDATION.create(undefined, configName, rules, error.violations), this.projectRoot);
|
119 | }
|
120 | throw new Error(`Failed validation of tsconfig "compilerOptions" in "${configName}" against rule set "${rules}"!`);
|
121 | }
|
122 | }
|
123 | return config;
|
124 | }
|
125 |
|
126 | return this.buildTypeScriptConfig();
|
127 | }
|
128 | |
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | prepareForBuild(...files) {
|
138 | if (!this.userProvidedTypeScriptConfig) {
|
139 | this.writeTypeScriptConfig();
|
140 | }
|
141 | this.rootFiles = this.determineSources(files);
|
142 | }
|
143 | |
144 |
|
145 |
|
146 | buildOnce() {
|
147 | if (!this.compilerHost.getDefaultLibLocation) {
|
148 | throw new Error('No default library location was found on the TypeScript compiler host!');
|
149 | }
|
150 | const tsconf = this.tsconfig;
|
151 | const prog = ts.createIncrementalProgram({
|
152 | rootNames: this.rootFiles.concat(_pathOfLibraries(this.compilerHost)),
|
153 | options: tsconf.compilerOptions,
|
154 |
|
155 | projectReferences: tsconf.references?.map((ref) => ({
|
156 | path: path.resolve(path.dirname(this.configPath), ref.path),
|
157 | })),
|
158 | host: this.compilerHost,
|
159 | });
|
160 | return this.consumeProgram(prog.getProgram(), this.compilerHost.getDefaultLibLocation());
|
161 | }
|
162 | consumeProgram(program, stdlib) {
|
163 | const diagnostics = [...ts.getPreEmitDiagnostics(program)];
|
164 | let hasErrors = false;
|
165 | if (!hasErrors && this.diagsHaveAbortableErrors(diagnostics)) {
|
166 | hasErrors = true;
|
167 | LOG.error('Compilation errors prevented the JSII assembly from being created');
|
168 | }
|
169 |
|
170 |
|
171 | const assembler = new assembler_1.Assembler(this.options.projectInfo, this.system, program, stdlib, {
|
172 | stripDeprecated: this.options.stripDeprecated,
|
173 | stripDeprecatedAllowListFile: this.options.stripDeprecatedAllowListFile,
|
174 | addDeprecationWarnings: this.options.addDeprecationWarnings,
|
175 | compressAssembly: this.options.compressAssembly,
|
176 | });
|
177 | try {
|
178 | const assmEmit = assembler.emit();
|
179 | if (!hasErrors && (assmEmit.emitSkipped || this.diagsHaveAbortableErrors(assmEmit.diagnostics))) {
|
180 | hasErrors = true;
|
181 | LOG.error('Type model errors prevented the JSII assembly from being created');
|
182 | }
|
183 | diagnostics.push(...assmEmit.diagnostics);
|
184 | }
|
185 | catch (e) {
|
186 | diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_9997_UNKNOWN_ERROR.createDetached(e));
|
187 | hasErrors = true;
|
188 | }
|
189 |
|
190 |
|
191 | const emit = program.emit(undefined,
|
192 | undefined,
|
193 | undefined,
|
194 | undefined,
|
195 | assembler.customTransformers);
|
196 | diagnostics.push(...emit.diagnostics);
|
197 | if (!hasErrors && (emit.emitSkipped || this.diagsHaveAbortableErrors(emit.diagnostics))) {
|
198 | hasErrors = true;
|
199 | LOG.error('Compilation errors prevented the JSII assembly from being created');
|
200 | }
|
201 | if (!hasErrors) {
|
202 | (0, downlevel_dts_1.emitDownleveledDeclarations)(this.options.projectInfo);
|
203 | }
|
204 |
|
205 |
|
206 |
|
207 | if (this.options.addDeprecationWarnings && this.options.projectInfo.exports !== undefined) {
|
208 | const expected = `./${deprecation_warnings_1.WARNINGSCODE_FILE_NAME}`;
|
209 | const warningsExport = Object.entries(this.options.projectInfo.exports).filter(([k, v]) => k === expected && v === expected);
|
210 | if (warningsExport.length === 0) {
|
211 | hasErrors = true;
|
212 | diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_0007_MISSING_WARNINGS_EXPORT.createDetached());
|
213 | }
|
214 | }
|
215 | return {
|
216 | emitSkipped: hasErrors,
|
217 | diagnostics: ts.sortAndDeduplicateDiagnostics(diagnostics),
|
218 | emittedFiles: emit.emittedFiles,
|
219 | };
|
220 | }
|
221 | |
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 | buildTypeScriptConfig() {
|
228 | let references;
|
229 | const isComposite = this.options.projectReferences !== undefined
|
230 | ? this.options.projectReferences
|
231 | : this.options.projectInfo.projectReferences !== undefined
|
232 | ? this.options.projectInfo.projectReferences
|
233 | : false;
|
234 | if (isComposite) {
|
235 | references = this.findProjectReferences();
|
236 | }
|
237 | const pi = this.options.projectInfo;
|
238 | return {
|
239 | compilerOptions: {
|
240 | ...pi.tsc,
|
241 | ...compiler_options_1.BASE_COMPILER_OPTIONS,
|
242 |
|
243 | composite: isComposite,
|
244 |
|
245 | tsBuildInfoFile: path.join(pi.tsc?.outDir ?? '.', 'tsconfig.tsbuildinfo'),
|
246 | },
|
247 | include: [pi.tsc?.rootDir != null ? path.join(pi.tsc.rootDir, '**', '*.ts') : path.join('**', '*.ts')],
|
248 | exclude: [
|
249 | 'node_modules',
|
250 | pi.tsc?.outDir != null ? path.resolve(pi.tsc.outDir, downlevel_dts_1.TYPES_COMPAT) : downlevel_dts_1.TYPES_COMPAT,
|
251 | ...(pi.excludeTypescript ?? []),
|
252 | ...(pi.tsc?.outDir != null &&
|
253 | (pi.tsc?.rootDir == null || path.resolve(pi.tsc.outDir).startsWith(path.resolve(pi.tsc.rootDir) + path.sep))
|
254 | ? [path.join(pi.tsc.outDir, '**', '*.ts')]
|
255 | : []),
|
256 | ],
|
257 |
|
258 |
|
259 |
|
260 |
|
261 | references: references?.map((p) => ({ path: p })),
|
262 | };
|
263 | }
|
264 | |
265 |
|
266 |
|
267 | readTypeScriptConfig() {
|
268 | const projectRoot = this.options.projectInfo.projectRoot;
|
269 | const { config, error } = ts.readConfigFile(this.configPath, ts.sys.readFile);
|
270 | if (error) {
|
271 | utils.logDiagnostic(error, projectRoot);
|
272 | throw new Error(`Failed to load tsconfig at ${this.configPath}`);
|
273 | }
|
274 | const extended = ts.parseJsonConfigFileContent(config, ts.sys, projectRoot);
|
275 |
|
276 | delete extended.options.configFilePath;
|
277 | return {
|
278 | compilerOptions: extended.options,
|
279 | watchOptions: extended.watchOptions,
|
280 | include: extended.fileNames,
|
281 | };
|
282 | }
|
283 | |
284 |
|
285 |
|
286 |
|
287 |
|
288 | writeTypeScriptConfig() {
|
289 | const commentKey = '_generated_by_jsii_';
|
290 | const commentValue = 'Generated by jsii - safe to delete, and ideally should be in .gitignore';
|
291 | this.tsconfig[commentKey] = commentValue;
|
292 | if (fs.existsSync(this.configPath)) {
|
293 | const currentConfig = JSON.parse(fs.readFileSync(this.configPath, 'utf-8'));
|
294 | if (!(commentKey in currentConfig)) {
|
295 | throw new Error(`A '${this.configPath}' file that was not generated by jsii is in ${this.options.projectInfo.projectRoot}. Aborting instead of overwriting.`);
|
296 | }
|
297 | }
|
298 | const outputConfig = {
|
299 | ...this.tsconfig,
|
300 | compilerOptions: (0, compiler_options_1.convertForJson)(this.tsconfig?.compilerOptions),
|
301 | };
|
302 | LOG.debug(`Creating or updating ${chalk.blue(this.configPath)}`);
|
303 | fs.writeFileSync(this.configPath, JSON.stringify(outputConfig, null, 2), 'utf8');
|
304 | }
|
305 | |
306 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 | findProjectReferences() {
|
316 | const pkg = this.options.projectInfo.packageJson;
|
317 | const ret = new Array();
|
318 | const dependencyNames = new Set();
|
319 | for (const dependencyMap of [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies]) {
|
320 | if (dependencyMap === undefined) {
|
321 | continue;
|
322 | }
|
323 | for (const name of Object.keys(dependencyMap)) {
|
324 | dependencyNames.add(name);
|
325 | }
|
326 | }
|
327 | for (const tsconfigFile of Array.from(dependencyNames).map((depName) => this.findMonorepoPeerTsconfig(depName))) {
|
328 | if (!tsconfigFile) {
|
329 | continue;
|
330 | }
|
331 | const { config: tsconfig } = ts.readConfigFile(tsconfigFile, this.system.readFile);
|
332 |
|
333 |
|
334 | if (tsconfig.compilerOptions?.composite) {
|
335 | ret.push(path.relative(this.options.projectInfo.projectRoot, path.dirname(tsconfigFile)));
|
336 | }
|
337 | else {
|
338 |
|
339 |
|
340 |
|
341 | if (tsconfigFile.includes('node_modules')) {
|
342 | LOG.warn('%s: not a composite TypeScript package, but it probably should be', path.dirname(tsconfigFile));
|
343 | }
|
344 | }
|
345 | }
|
346 | return ret;
|
347 | }
|
348 | |
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 | determineSources(files) {
|
356 |
|
357 | if (files.length > 0) {
|
358 | return [...files];
|
359 | }
|
360 |
|
361 | if (this.userProvidedTypeScriptConfig) {
|
362 | return [...(this.tsconfig.include ?? [])];
|
363 | }
|
364 |
|
365 | const parseConfigHost = parseConfigHostFromCompilerHost(this.compilerHost);
|
366 | const parsed = ts.parseJsonConfigFileContent(this.tsconfig, parseConfigHost, this.options.projectInfo.projectRoot);
|
367 | return [...parsed.fileNames];
|
368 | }
|
369 | |
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 | findMonorepoPeerTsconfig(depName) {
|
383 |
|
384 | const { builtinModules } = require('node:module');
|
385 | if ((builtinModules ?? []).includes(depName)) {
|
386 |
|
387 | return undefined;
|
388 | }
|
389 | try {
|
390 | const depDir = (0, find_utils_1.findDependencyDirectory)(depName, this.options.projectInfo.projectRoot);
|
391 | const dep = path.join(depDir, 'tsconfig.json');
|
392 | if (!fs.existsSync(dep)) {
|
393 | return undefined;
|
394 | }
|
395 |
|
396 | const dependencyRealPath = fs.realpathSync(dep);
|
397 | if (dependencyRealPath.split(path.sep).includes('node_modules')) {
|
398 | return undefined;
|
399 | }
|
400 | return dependencyRealPath;
|
401 | }
|
402 | catch (e) {
|
403 |
|
404 | if (['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(e.code)) {
|
405 | return undefined;
|
406 | }
|
407 | throw e;
|
408 | }
|
409 | }
|
410 | diagsHaveAbortableErrors(diags) {
|
411 | return diags.some((d) => d.category === ts.DiagnosticCategory.Error ||
|
412 | (this.options.failOnWarnings && d.category === ts.DiagnosticCategory.Warning));
|
413 | }
|
414 | }
|
415 | exports.Compiler = Compiler;
|
416 | function _pathOfLibraries(host) {
|
417 | if (!compiler_options_1.BASE_COMPILER_OPTIONS.lib || compiler_options_1.BASE_COMPILER_OPTIONS.lib.length === 0) {
|
418 | return [];
|
419 | }
|
420 | const lib = host.getDefaultLibLocation?.();
|
421 | if (!lib) {
|
422 | throw new Error(`Compiler host doesn't have a default library directory available for ${compiler_options_1.BASE_COMPILER_OPTIONS.lib.join(', ')}`);
|
423 | }
|
424 | return compiler_options_1.BASE_COMPILER_OPTIONS.lib.map((name) => path.join(lib, name));
|
425 | }
|
426 | function parseConfigHostFromCompilerHost(host) {
|
427 |
|
428 |
|
429 | return {
|
430 | fileExists: (f) => host.fileExists(f),
|
431 | readDirectory(root, extensions, excludes, includes, depth) {
|
432 | if (host.readDirectory === undefined) {
|
433 | throw new Error("'CompilerHost.readDirectory' must be implemented to correctly process 'projectReferences'");
|
434 | }
|
435 | return host.readDirectory(root, extensions, excludes, includes, depth);
|
436 | },
|
437 | readFile: (f) => host.readFile(f),
|
438 | useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(),
|
439 | trace: host.trace ? (s) => host.trace(s) : undefined,
|
440 | };
|
441 | }
|
442 |
|
\ | No newline at end of file |