307 lines
8.9 KiB
JavaScript
307 lines
8.9 KiB
JavaScript
/*
|
|
* 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;
|
|
}
|