@monorepo-utils/workspaces-to-typescript-project-references
Version:
Convert Workspaces to TypeScript's Project References
293 lines (286 loc) • 13.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.toRootProjectReferences = exports.toProjectReferences = exports.DEFAULT_TSCONFIGPATH = void 0;
const fs_1 = __importDefault(require("fs"));
const upath_1 = __importDefault(require("upath"));
const comment_json_1 = __importDefault(require("comment-json"));
const workspaces_1 = require("./manager/workspaces");
const assert_1 = __importDefault(require("assert"));
exports.DEFAULT_TSCONFIGPATH = "tsconfig.json";
const sortReferences = (references) => {
return references.slice().sort((refA, refB) => (refA.path > refB.path ? 1 : -1));
};
const getReferenceInPackage = (tsconfigFilePath) => {
try {
const dirPath = upath_1.default.dirname(tsconfigFilePath);
const fileName = upath_1.default.basename(tsconfigFilePath);
const files = fs_1.default.readdirSync(dirPath);
const tsconfigFilePaths = files
.filter(file => file !== fileName && file.startsWith('tsconfig.') && file.endsWith('.json'))
.map(file => ({ path: `./${file}` }));
return tsconfigFilePaths;
}
catch (err) {
return [];
}
};
// when tsconfig.json → ../path/to/dir
// when tsconfig.x.json → ../path/to/dir/tsconfig.x.json
const getReferencePathToTsConfig = (options) => {
var _a;
const tsConfigPath = (_a = options.tsConfigPath) !== null && _a !== void 0 ? _a : exports.DEFAULT_TSCONFIGPATH;
const tsConfigDirName = upath_1.default.dirname(tsConfigPath);
const pathComponents = [options.packageLocation, tsConfigDirName];
// If the file name is not tsconfig.json (the default),
// then append it to the generated path
const tsConfigFileName = upath_1.default.basename(tsConfigPath);
if (tsConfigFileName !== exports.DEFAULT_TSCONFIGPATH) {
pathComponents.push(tsConfigFileName);
}
return upath_1.default.join(...pathComponents);
};
const validateDuplicatedDependencies = (packageReferences) => {
const packageNameSet = new Set();
const errors = [];
packageReferences.forEach((ref) => {
if (packageNameSet.has(ref.name)) {
errors.push(new Error(`This package is deduplicated in dependencies and devDependencies: ${ref.name}
Please remove duplicate dependencies from dependencies or devDependencies.`));
return;
}
packageNameSet.add(ref.name);
});
return {
ok: errors.length === 0,
errors
};
};
const toProjectReferences = (options) => {
const plugins = Array.isArray(options.plugins) && options.plugins.length > 0 ? options.plugins : [workspaces_1.plugin];
const pluginImplementations = plugins.map((plugin) => plugin(options));
// use first plugin
const supportPlugin = pluginImplementations.find((plugin) => {
return plugin.supports();
});
if (!supportPlugin) {
throw new Error("Not found supported plugin");
}
const relativeName = (pkgPath) => {
return upath_1.default.relative(options.rootDir, pkgPath);
};
const allPackages = supportPlugin.getAllPackages();
const errors = [];
const canNotRecoverErrors = [];
allPackages.forEach((packageInfo) => {
var _a, _b, _c;
const tsconfigFilePath = (_b = (_a = options.tsConfigPathFinder) === null || _a === void 0 ? void 0 : _a.call(options, packageInfo.location)) !== null && _b !== void 0 ? _b : upath_1.default.join(packageInfo.location, exports.DEFAULT_TSCONFIGPATH);
if (!fs_1.default.existsSync(tsconfigFilePath)) {
// Skip has not tsconfig.json
return;
}
const tsconfigJSON = comment_json_1.default.parse(fs_1.default.readFileSync(tsconfigFilePath, "utf-8"));
const references = supportPlugin.getDependencies(packageInfo.packageJSON);
const resolvedReferences = references
.map((reference) => {
const absolutePathOrNull = supportPlugin.resolve(reference);
if (!absolutePathOrNull) {
return;
}
if (!upath_1.default.isAbsolute(absolutePathOrNull)) {
throw new Error(`Plugin#resolve should return absolute path: ${absolutePathOrNull}, plugin: ${supportPlugin}`);
}
return {
name: reference.name,
version: reference.version,
absolutePath: absolutePathOrNull
};
})
.filter((r) => Boolean(r));
const referenceValidateResult = validateDuplicatedDependencies(resolvedReferences);
// if found an error, do early return: https://github.com/azu/monorepo-utils/issues/56
if (!referenceValidateResult.ok) {
canNotRecoverErrors.push(...referenceValidateResult.errors);
return;
}
const newProjectReferences = resolvedReferences
.map((reference) => {
var _a;
const absolutePath = reference.absolutePath;
// Should ignore no-ts package
// https://github.com/azu/monorepo-utils/issues/53
const referencePackageTsConfigFilePath = upath_1.default.resolve(absolutePath, (_a = options.tsConfigPath) !== null && _a !== void 0 ? _a : exports.DEFAULT_TSCONFIGPATH);
if (!fs_1.default.existsSync(referencePackageTsConfigFilePath)) {
return;
}
// { path } value
const referenceTsConfigPath = getReferencePathToTsConfig({
packageLocation: absolutePath,
tsConfigPath: options.tsConfigPath
});
if (packageInfo.location === referenceTsConfigPath) {
const selfName = relativeName(packageInfo.location);
errors.push(new Error(`[${selfName}] Self dependencies is something wrong: ${selfName} refer to ${relativeName(referenceTsConfigPath)}`));
}
const basePackageLocation = upath_1.default.dirname(upath_1.default.resolve(packageInfo.location, tsconfigFilePath));
return {
path: upath_1.default.relative(basePackageLocation, referenceTsConfigPath)
};
})
.filter((r) => Boolean(r));
if (options.includesLocal) {
const referenceInLocal = getReferenceInPackage(tsconfigFilePath);
newProjectReferences.push(...referenceInLocal);
}
const currentProjectReferences = (_c = tsconfigJSON["references"]) !== null && _c !== void 0 ? _c : [];
if (options.checkOnly) {
// check
try {
// create pure object for compare
const cleanCurrentProjectReferences = JSON.parse(JSON.stringify(comment_json_1.default.parse(comment_json_1.default.stringify(currentProjectReferences), undefined, true)));
// TODO: move sorting to updating logic when next major release
// https://github.com/azu/monorepo-utils/issues/44
assert_1.default.deepStrictEqual(sortReferences(cleanCurrentProjectReferences), sortReferences(newProjectReferences));
}
catch (error) {
const selfName = relativeName(packageInfo.location);
errors.push(new Error(`[${selfName}] ${error.message}`));
}
return;
}
else {
// update
if (errors.length === 0) {
if (currentProjectReferences.length === 0 && newProjectReferences.length === 0) {
// As to not add `references: []` needlessly
return;
}
tsconfigJSON["references"] = newProjectReferences;
const oldContents = comment_json_1.default.stringify(comment_json_1.default.parse(fs_1.default.readFileSync(tsconfigFilePath, { encoding: "utf-8" })), null, 2);
const newContents = comment_json_1.default.stringify(tsconfigJSON, null, 2);
if (newContents !== oldContents) {
fs_1.default.writeFileSync(tsconfigFilePath, `${newContents}\n`, "utf-8");
}
}
}
});
if (canNotRecoverErrors.length > 0) {
return {
ok: false,
aggregateError: {
message: `workspaces-to-typescript-project-references found ${canNotRecoverErrors.length} errors.
- ${canNotRecoverErrors.map((error) => error.message.split("\n")[0]).join("\n- ")}
Please resolve these error before updates tsconfig.json
`,
errors: canNotRecoverErrors
}
};
}
if (errors.length > 0) {
return {
ok: false,
aggregateError: {
message: `workspaces-to-typescript-project-references found ${errors.length} errors.
Please update your tsconfig.json via following command.
$ workspaces-to-typescript-project-references
`,
errors
}
};
}
return {
ok: true
};
};
exports.toProjectReferences = toProjectReferences;
const toRootProjectReferences = (options) => {
var _a, _b, _c, _d;
const plugins = Array.isArray(options.plugins) && options.plugins.length > 0 ? options.plugins : [workspaces_1.plugin];
const pluginImplementations = plugins.map((plugin) => plugin(options));
// use first plugin
const supportPlugin = pluginImplementations.find((plugin) => {
return plugin.supports();
});
if (!supportPlugin) {
throw new Error("Not found supported plugin");
}
const allPackages = supportPlugin.getAllPackages();
const errors = [];
const rootTsconfigFilePath = (_b = (_a = options.tsConfigPathFinder) === null || _a === void 0 ? void 0 : _a.call(options, options.rootDir)) !== null && _b !== void 0 ? _b : upath_1.default.join(options.rootDir, exports.DEFAULT_TSCONFIGPATH);
if (!fs_1.default.existsSync(rootTsconfigFilePath)) {
const tsConfigPath = (_c = options.tsConfigPath) !== null && _c !== void 0 ? _c : exports.DEFAULT_TSCONFIGPATH;
return {
ok: false,
aggregateError: {
message: `Not found ${tsConfigPath} in ${rootTsconfigFilePath}`,
errors
}
};
}
const tsconfigJSON = comment_json_1.default.parse(fs_1.default.readFileSync(rootTsconfigFilePath, "utf-8"));
const projectReferences = allPackages
.filter((pkg) => {
var _a, _b;
const tsconfigFilePath = (_b = (_a = options.tsConfigPathFinder) === null || _a === void 0 ? void 0 : _a.call(options, pkg.location)) !== null && _b !== void 0 ? _b : upath_1.default.join(pkg.location, exports.DEFAULT_TSCONFIGPATH);
// Skip if the package has not tsconfig.json
return fs_1.default.existsSync(tsconfigFilePath);
})
.map((pkg) => {
var _a;
const tsConfigPath = (_a = options.tsConfigPath) !== null && _a !== void 0 ? _a : exports.DEFAULT_TSCONFIGPATH;
const pathComponents = [upath_1.default.relative(options.rootDir, pkg.location)];
// If the file name is not tsconfig.json (the default),
// then append it to the generated path
const tsConfigFileName = upath_1.default.basename(tsConfigPath);
if (tsConfigFileName !== exports.DEFAULT_TSCONFIGPATH) {
pathComponents.push(tsConfigFileName);
}
return {
path: upath_1.default.join(...pathComponents)
};
});
if (options.includesLocal) {
const referenceInLocal = getReferenceInPackage(rootTsconfigFilePath);
projectReferences.push(...referenceInLocal);
}
if (options.checkOnly) {
// check
try {
const currentProjectReferences = (_d = tsconfigJSON["references"]) !== null && _d !== void 0 ? _d : [];
const cleanCurrentProjectReferences = JSON.parse(JSON.stringify(comment_json_1.default.parse(comment_json_1.default.stringify(currentProjectReferences), undefined, true)));
// TODO: move sorting to updating logic when next major release
// https://github.com/azu/monorepo-utils/issues/44
assert_1.default.deepStrictEqual(sortReferences(cleanCurrentProjectReferences), sortReferences(projectReferences));
}
catch (error) {
errors.push(new Error(`[root] ${error.message}`));
}
}
else {
const updatedTsConfigJSON = Object.assign(Object.assign({}, tsconfigJSON), { references: projectReferences });
const oldContents = comment_json_1.default.stringify(projectReferences, null, 2);
const newContents = comment_json_1.default.stringify(updatedTsConfigJSON, null, 2);
if (newContents !== oldContents) {
fs_1.default.writeFileSync(rootTsconfigFilePath, `${newContents}\n`, "utf-8");
}
}
if (errors.length > 0) {
return {
ok: false,
aggregateError: {
message: `workspaces-to-typescript-project-references found ${errors.length} errors in root tsconfig.
Please update your <root>/tsconfig.json via following command.
$ workspaces-to-typescript-project-references --includesRoot
`,
errors
}
};
}
return {
ok: true
};
};
exports.toRootProjectReferences = toRootProjectReferences;
//# sourceMappingURL=index.js.map