UNPKG

jsii

Version:

[![Join the chat at https://cdk.Dev](https://img.shields.io/static/v1?label=Slack&message=cdk.dev&color=brightgreen&logo=slack)](https://cdk.dev) [![All Contributors](https://img.shields.io/github/all-contributors/aws/jsii/main?label=%E2%9C%A8%20All%20Con

190 lines • 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.emitDownleveledDeclarations = exports.TYPES_COMPAT = void 0; const node_fs_1 = require("node:fs"); const node_os_1 = require("node:os"); const node_path_1 = require("node:path"); const downlevel_dts_1 = require("downlevel-dts"); const log4js = require("log4js"); const semver_1 = require("semver"); const ts = require("typescript"); exports.TYPES_COMPAT = '.types-compat'; const LOG = log4js.getLogger('jsii/compiler'); const TS_VERSION = new semver_1.SemVer(`${ts.versionMajorMinor}.0`); /** * Declares what versions of the TypeScript language will be supported by the * declarations files (and `typesVersions` entries) produced by this compiler * release. * * This should contain only `major.minor` specifiers, similar to the value of * the `ts.versionMajorMinor` property, and must be sorted in ascending version * order, as this dictates the order of entries in the `typesVersions` redirects * which has a direct impact on resolution (first match wins), and we don't want * to have to perform a sort pass on this list. */ const DOWNLEVEL_BREAKPOINTS = ['3.9'].map((ver) => new semver_1.SemVer(`${ver}.0`)); /** * Produces down-leveled declaration files to ensure compatibility with previous * compiler releases (macthing TypeScript's `major.minor` versioning scheme). * This is necessary in order to ensure a package change compiler release lines * does not force all it's consumers to do the same (and vice-versa). * * @returns the `typesVersions` object that should be recorded in `package.json` */ function emitDownleveledDeclarations({ packageJson, projectRoot, tsc }) { const compatRoot = (0, node_path_1.join)(projectRoot, ...(tsc?.outDir != null ? [tsc?.outDir] : []), exports.TYPES_COMPAT); (0, node_fs_1.rmSync)(compatRoot, { force: true, recursive: true }); const rewrites = new Set(); for (const breakpoint of DOWNLEVEL_BREAKPOINTS) { if (TS_VERSION.compare(breakpoint) <= 0) { // This TypeScript release is older or same as the breakpoint, so no need // for down-leveling here. continue; } const rewriteSet = new Map(); let needed = false; // We'll emit down-leveled declarations in a temporary directory... const workdir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), `downlevel-dts-${breakpoint}-${(0, node_path_1.basename)(projectRoot)}-`)); try { (0, downlevel_dts_1.main)(projectRoot, workdir, breakpoint.version); const projectOutDir = tsc?.outDir != null ? (0, node_path_1.join)(projectRoot, tsc.outDir) : projectRoot; const workOutDir = tsc?.outDir != null ? (0, node_path_1.join)(workdir, tsc.outDir) : workdir; for (const dts of walkDirectory(workOutDir)) { const original = (0, node_fs_1.readFileSync)((0, node_path_1.join)(projectOutDir, dts), 'utf-8'); const downleveledPath = (0, node_path_1.join)(workOutDir, dts); const downleveled = (0, node_fs_1.readFileSync)(downleveledPath, 'utf-8'); needed || (needed = !semanticallyEqualDeclarations(original, downleveled)); rewriteSet.set(dts, downleveledPath); } // If none of the declarations files changed during the down-level, then // we don't need to actually write it out & cause a redirect. This would // be wasteful. Most codebases won't incur any rewrite at all, since the // declarations files only reference "visible" members, and `jsii` // actually does not allow most of the unsupported syntaxes to be used // anyway. if (needed) { rewrites.add(`${breakpoint.major}.${breakpoint.minor}`); const versionSuffix = `ts${breakpoint.major}.${breakpoint.minor}`; const compatDir = (0, node_path_1.join)(compatRoot, versionSuffix); if (!(0, node_fs_1.existsSync)(compatDir)) { (0, node_fs_1.mkdirSync)(compatDir, { recursive: true }); try { // Write an empty .npmignore file so that npm pack doesn't use the .gitignore file... (0, node_fs_1.writeFileSync)((0, node_path_1.join)(compatRoot, '.npmignore'), '\n', 'utf-8'); // Make sure all of this is gitignored, out of courtesy... (0, node_fs_1.writeFileSync)((0, node_path_1.join)(compatRoot, '.gitignore'), '*\n', 'utf-8'); } catch { // Ignore any error here... This is inconsequential. } } for (const [dts, downleveledPath] of rewriteSet) { const rewritten = (0, node_path_1.join)(compatDir, dts); // Make sure the parent directory exists (dts might be nested) (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(rewritten), { recursive: true }); // Write the re-written declarations file there... (0, node_fs_1.copyFileSync)(downleveledPath, rewritten); } } } finally { // Clean up after outselves... (0, node_fs_1.rmSync)(workdir, { force: true, recursive: true }); } } let typesVersions; for (const version of rewrites) { // Register the type redirect in the typesVersions configuration typesVersions ?? (typesVersions = {}); const from = [...(tsc?.outDir != null ? [tsc?.outDir] : []), '*'].join('/'); const to = [...(tsc?.outDir != null ? [tsc?.outDir] : []), exports.TYPES_COMPAT, `ts${version}`, '*'].join('/'); // We put 2 candidate redirects (first match wins), so that it works for nested imports, too (see: https://github.com/microsoft/TypeScript/issues/43133) typesVersions[`<=${version}`] = { [from]: [to, `${to}/index.d.ts`] }; } // Compare JSON stringifications, as the order of keys is important here... if (JSON.stringify(packageJson.typesVersions) === JSON.stringify(typesVersions)) { // The existing configuration matches the new one. We're done here. return; } LOG.info('The required `typesVersions` configuration has changed. Updating "package.json" accordingly...'); // Prepare the new contents of `PackageJson`. const newPackageJson = Object.entries(packageJson).reduce((obj, [key, value]) => { // NB: "as any" below are required becuase we must ignore `readonly` attributes from the source. if (key === 'typesVersions') { if (typesVersions != null) { obj[key] = typesVersions; } } else { obj[key] = value; // If there isn't currently a `typesVersions` entry, but there is a `types` entry, // we'll insert `typesVersions` right after `types`. if (key === 'types' && typesVersions != null && !('typesVersions' in packageJson)) { obj.typesVersions = typesVersions; } } return obj; }, {}); // If there was neither `types` nor `typesVersions` in the original `package.json`, we'll // add `typesVersions` at the end of it. if (!('typesVersions' in newPackageJson)) { newPackageJson.typesVersions = typesVersions; } const packageJsonFile = (0, node_path_1.join)(projectRoot, 'package.json'); // We try "hard" to preserve the existing indent in the `package.json` file when updating it. const [, indent] = (0, node_fs_1.readFileSync)(packageJsonFile, 'utf-8').match(/^(\s*)"/m) ?? [null, 2]; (0, node_fs_1.writeFileSync)(packageJsonFile, `${JSON.stringify(newPackageJson, undefined, indent)}\n`, 'utf-8'); } exports.emitDownleveledDeclarations = emitDownleveledDeclarations; /** * Compares the contents of two declaration files semantically. * * @param left the first string. * @param right the second string. * * @returns `true` if `left` and `right` contain the same declarations. */ function semanticallyEqualDeclarations(left, right) { // We normalize declarations largely by parsing & re-printing them. const normalizeDeclarations = (code) => { const sourceFile = ts.createSourceFile('index.d.ts', code, ts.ScriptTarget.Latest, false, ts.ScriptKind.TS); const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, noEmitHelpers: true, omitTrailingSemicolon: false, removeComments: true, }); let normalized = printer.printFile(sourceFile); // TypeScript may emit duplicated reference declarations... which are absent from Downlevel-DTS' output... // https://github.com/microsoft/TypeScript/issues/48143 const REFERENCES_TYPES_NODE = '/// <reference types="node" />'; while (normalized.startsWith(`${REFERENCES_TYPES_NODE}\n${REFERENCES_TYPES_NODE}`)) { normalized = normalized.slice(REFERENCES_TYPES_NODE.length + 1); } return normalized; }; left = normalizeDeclarations(left); right = normalizeDeclarations(right); return left === right; } /** * Recursively traverse the provided directory and yield the relative (to the * specified `root`) paths of all the `.d.ts` files found there. * * @param dir the directory to be walked. * @param root the root to which paths should be relative. */ function* walkDirectory(dir, root = dir) { for (const file of (0, node_fs_1.readdirSync)(dir)) { const filePath = (0, node_path_1.join)(dir, file); if ((0, node_fs_1.statSync)(filePath).isDirectory()) { // This is a directory, recurse down... yield* walkDirectory(filePath, root); } else if (file.toLowerCase().endsWith('.d.ts')) { // This is a declaration file, yield it... yield (0, node_path_1.relative)(root, filePath); } } } //# sourceMappingURL=downlevel-dts.js.map