/* * Copyright The OpenTelemetry Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * This script generates per-package tsconfig.*.json from the definition in * package/package.json. * * Specifically, * 1. If the package.json has fields `main`, `module` and `esnext`, targets * like ESM and ESNEXT tsconfig.json are generated. Otherwise only one * default CJS target is generated. * 2. References in tsconfig.json are generated from the package.json fields * `dependencies`, `devDependencies` and `peerDependencies`. */ const fs = require('fs'); const path = require('path'); const { getDefaultTsConfig, getEsmTsConfig, getEsnextTsConfig, toPosix } = require('./update-ts-configs-constants'); const packageJsonDependencyFields = ['dependencies', 'peerDependencies', 'devDependencies']; const tsConfigMergeKeys = [ 'compilerOptions', 'include', 'files', ]; // Make `extends` the first field. const tsConfigPriorityKeys = ['extends']; const ignoredLernaProjects = [ 'experimental/examples/*', 'experimental/backwards-compatability/*', 'integration-tests/*', 'selenium-tests', 'examples/otlp-exporter-node', 'examples/opentelemetry-web', 'examples/https', ]; let dryRun = false; const argv = process.argv.slice(2); while (argv.length) { switch (argv[0]) { case '--dry': { dryRun = true; } default: {} } argv.shift(); } main(); function main() { const pkgRoot = process.cwd(); const projectRoot = findProjectRoot(pkgRoot); const lernaPackages = resolveLernaPackages(projectRoot); generateTsConfig(projectRoot, lernaPackages, pkgRoot, true); for (const packageMeta of lernaPackages.values()) { generateTsConfig(projectRoot, lernaPackages, path.join(projectRoot, packageMeta.dir), false, packageMeta); } } function generateTsConfig(projectRoot, lernaProjects, pkgRoot, isLernaRoot, packageMeta) { // Root tsconfig.json if (isLernaRoot) { writeRootTsConfigJson(pkgRoot, projectRoot, lernaProjects); return; } const otelDependencies = getOtelDependencies(packageMeta.pkgJson); const dependenciesDir = resolveDependencyDirs(lernaProjects, otelDependencies); const references = dependenciesDir.map(it => path.relative(pkgRoot, path.join(projectRoot, it))).sort(); if (packageMeta.hasMultiTarget) { writeMultiTargetTsConfigs(pkgRoot, projectRoot, references); return; } writeSingleTargetTsConfig(pkgRoot, projectRoot, references); } function writeRootTsConfigJson(pkgRoot, projectRoot, lernaProjects) { const tsconfigPath = path.join(pkgRoot, 'tsconfig.json'); const tsconfig = readJSON(tsconfigPath); const references = Array.from(lernaProjects.values()) .filter(it => it.isTsProject) .map(it => toPosix(path.relative(pkgRoot, path.join(projectRoot, it.dir)))).sort(); tsconfig.references = references.map(path => { return { path: toPosix(path) } }); tsconfig.typedocOptions.entryPoints = Array.from(lernaProjects.values()) .filter(it => !it.private && it.isTsProject) .map(it => toPosix(path.relative(pkgRoot, path.join(projectRoot, it.dir)))).sort(); writeJSON(tsconfigPath, tsconfig, dryRun); for (const tsconfigName of ['tsconfig.esm.json', 'tsconfig.esnext.json']) { const tsconfigPath = path.join(pkgRoot, tsconfigName); const tsconfig = readJSON(tsconfigPath); const references = Array.from(lernaProjects.values()) .filter(it => it.isTsProject && it.hasMultiTarget) .map(it => toPosix(path.relative(pkgRoot, path.join(projectRoot, it.dir)))).sort(); tsconfig.references = references.map(pkgPath => { return { path: toPosix(path.join(pkgPath, tsconfigName)), } }); writeJSON(tsconfigPath, tsconfig, dryRun); } } function writeMultiTargetTsConfigs(pkgRoot, projectRoot, references) { const pairs = [ ['tsconfig.json', getDefaultTsConfig], ['tsconfig.esm.json', getEsmTsConfig], ['tsconfig.esnext.json', getEsnextTsConfig] ]; for (const [tsconfigName, getTsConfig] of pairs) { const tsconfigPath = path.join(pkgRoot, tsconfigName); let tsconfig = getTsConfig(pkgRoot, projectRoot); tsconfig.references = references.map(path => { return { path: toPosix(path) }; }); tsconfig = readAndMaybeMergeTsConfig(tsconfigPath, tsconfig); writeJSON(tsconfigPath, tsconfig, dryRun); } } function writeSingleTargetTsConfig(pkgRoot, projectRoot, references) { const tsconfigPath = path.join(pkgRoot, 'tsconfig.json'); let tsconfig = getDefaultTsConfig(pkgRoot, projectRoot); tsconfig.references = references.map(path => { return { path: toPosix(path) } }); tsconfig = readAndMaybeMergeTsConfig(tsconfigPath, tsconfig); writeJSON(tsconfigPath, tsconfig, dryRun); } function findProjectRoot(pkgRoot) { let dir; let parent = pkgRoot; do { dir = parent; try { const stat = fs.statSync(path.join(dir, 'lerna.json')); if (stat.isFile()) { return dir; } } catch (e) { /* ignore */ } parent = path.dirname(dir); } while (dir !== parent) } function getOtelDependencies(packageJson) { const deps = new Set(); for (const type of packageJsonDependencyFields) { if (packageJson[type] == null) { continue; } Object.keys(packageJson[type]).filter(it => it.startsWith('@opentelemetry')) .forEach(it => deps.add(it)) } return Array.from(deps.values()); } function resolveLernaPackages(projectRoot) { const map = new Map(); const lernaJson = readJSON(`${projectRoot}/lerna.json`); for (const pkgDefinition of lernaJson.packages) { if (ignoredLernaProjects.includes(pkgDefinition)) { continue; } if (pkgDefinition.endsWith('*')) { const relDir = path.dirname(pkgDefinition) const pkgs = fs.readdirSync(path.join(projectRoot, relDir)).filter(it => !it.startsWith('.')); for (const pkg of pkgs) { const pkgDir = path.join(relDir, pkg); const meta = resolvePackageMeta(path.join(projectRoot, pkgDir)); if (meta == null) { continue; } map.set(meta.name, { ...meta, dir: pkgDir, }); } } else { const meta = resolvePackageMeta(path.join(projectRoot, pkgDefinition)); if (meta == null) { continue; } map.set(meta.name, { ...meta, dir: pkgDefinition, }); } } return map; } function resolveDependencyDirs(lernaProjectMap, deps) { const results = []; for (const dep of deps) { const meta = lernaProjectMap.get(dep); if (meta == null) { continue; } results.push(meta.dir); } return results; } function resolvePackageMeta(pkgDir) { try { const pkgJson = readJSON(path.join(pkgDir, 'package.json')); let isTsProject = false; try { isTsProject = fs.statSync(path.join(pkgDir, 'tsconfig.json')).isFile() } catch {/** ignore */} return { name: pkgJson.name, private: pkgJson.private, isTsProject, hasMultiTarget: hasEsTargets(pkgJson), pkgJson, }; } catch (e) { return null } } function readAndMaybeMergeTsConfig(tsconfigPath, updates) { const tsconfig = readJSON(tsconfigPath); updates = mergeTsConfig(tsconfig, updates); return updates; } function mergeTsConfig(existing, updates) { for (const key of tsConfigMergeKeys) { const value = existing[key]; if (value === undefined) { continue; } if (updates[key] === undefined) { updates[key] = value; continue; } if (Array.isArray(value)) { updates[key] = Array.from(new Set([...value, ...updates[key]])); } else { updates[key] = sortObjectKeys({ ...updates[key], ...value }); } } // Make `extends` the first field. updates = sortObjectKeys(updates, tsConfigPriorityKeys); return updates; } function hasEsTargets(pjson) { return typeof pjson.module === 'string'; } function readJSON(filepath) { const fileContent = fs.readFileSync(filepath, 'utf8'); try { return JSON.parse(fileContent); } catch (e) { throw new Error(`Invalid JSON ${filepath}: ${e.message}`); } } function writeJSON(filepath, content, dry) { const text = JSON.stringify(content, null, 2); if (dry) { console.log(text); } else { fs.writeFileSync(filepath, text + '\n', 'utf8'); } } function sortObjectKeys(obj, priorityKeys = []) { let keys = Object.keys(obj).sort(); keys = Array.from(new Set([...priorityKeys, ...keys])); const ret = {}; keys.forEach(key => { ret[key] = obj[key]; }); return ret; }