Files
trezor-suite/scripts/updateProjectReferences.ts
2026-01-22 12:37:20 +01:00

168 lines
6.6 KiB
TypeScript

import chalk from 'chalk';
import { minimatch } from 'minimatch';
import fs from 'node:fs';
import path from 'node:path';
import prettier from 'prettier';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { getPrettierConfig } from './utils/getPrettierConfig';
import { getWorkspacesList } from './utils/getWorkspacesList';
/**
* Example usage:
* yarn update-project-references --read-only 1 2 3 --test true
* yarn update-project-references --ignore *\/firmware
*/
(async () => {
const { argv } = yargs(hideBin(process.argv))
.array('read-only')
.array('ignore')
.boolean('test') as any;
const readOnlyGlobs: string[] = argv.readOnly || [];
const ignoreGlobs: string[] = argv.ignore || [];
const isTesting = argv.test || false;
const prettierConfig = await getPrettierConfig();
const serializeConfig = (config: any, stringifySpaces?: number) => {
try {
return prettier.format(
JSON.stringify(config, null, stringifySpaces).replace(/\\\\/g, '/'),
prettierConfig,
);
} catch (error) {
console.error(error);
process.exit(1);
}
};
const parseTSConfigFile = (configPath: string) => {
try {
return fs.existsSync(configPath)
? JSON.parse(fs.readFileSync(configPath).toString())
: null;
} catch {
console.error(chalk.bold.red('Error while parsing file: '), configPath);
process.exit(1);
}
};
const isDiffInConfig = async (actualConfig: any[] = [], expectedConfig: any[] = []) =>
(await serializeConfig(actualConfig)) !== (await serializeConfig(expectedConfig));
const workspaces = getWorkspacesList();
// NOTE: Workspace keys must be sorted due to file systems being a part of the equation...
Object.keys(workspaces)
.sort()
.forEach(async workspaceName => {
const workspace = workspaces[workspaceName];
if (workspace.location === '.') {
// Skip root workspace
return;
}
if (ignoreGlobs.some((path: string) => minimatch(workspace.location, path))) {
return;
}
const workspacePath = path.resolve(process.cwd(), workspace.location);
const workspaceConfigPath = path.resolve(workspacePath, 'tsconfig.json');
const workspaceLibConfigPath = path.resolve(workspacePath, 'tsconfig.lib.json');
const workspaceLibESMConfigPath = path.resolve(workspacePath, 'tsconfig.libESM.json');
const defaultWorkspaceConfig = {
extends: path.relative(workspacePath, path.resolve(process.cwd(), 'tsconfig.json')),
compilerOptions: { outDir: './libDev' },
include: ['.'],
};
// parse tsconfig.json, which should exist, so if it doesn't, assign default config to have it created
const workspaceConfig =
parseTSConfigFile(workspaceConfigPath) ?? defaultWorkspaceConfig;
// parse tsconfig.lib.json, which may not exist, and shall not be created
const workspaceLibConfig = parseTSConfigFile(workspaceLibConfigPath);
// parse tsconfig.libESM.json, which may not exist, and shall not be created
const workspaceLibESMConfig = parseTSConfigFile(workspaceLibESMConfigPath);
// actual references of the workspace from parsed package.json (assigned later)
const nextWorkspaceReferences: Array<{ path: string }> = [];
Object.values(workspace.workspaceDependencies).forEach(dependencyLocation => {
const dependencyPath = path.resolve(process.cwd(), dependencyLocation);
const relativeDependencyPath = path.relative(workspacePath, dependencyPath);
if (relativeDependencyPath) {
nextWorkspaceReferences.push({ path: relativeDependencyPath });
} else {
console.warn(
chalk.yellow(
`${dependencyLocation} might be referencing itself in package.json#dependencies.`,
),
);
}
});
const expectedReferences = nextWorkspaceReferences;
const expectedLibReferences = nextWorkspaceReferences.filter(
// Don't include reference to schema-utils due to issues with the @sinclair/typebox library
// When using a reference it results in incorrect imports
({ path }) => path !== '../schema-utils',
);
if (isTesting) {
const isConfigDiff = await isDiffInConfig(
workspaceConfig.references,
expectedReferences,
);
const isConfigLibDiff =
workspaceLibConfig !== null &&
(await isDiffInConfig(workspaceLibConfig.references, expectedLibReferences));
const isConfigLibESMDiff =
workspaceLibESMConfig !== null &&
(await isDiffInConfig(workspaceLibESMConfig.references, expectedLibReferences));
if (isConfigDiff || isConfigLibDiff || isConfigLibESMDiff) {
console.error(
chalk.red(
`TypeScript project references in ${workspace.location} are inconsistent with package.json#dependencies.`,
),
chalk.red.bold(`Run "yarn update-project-references" to fix them.`),
);
process.exit(1);
}
return;
}
if (readOnlyGlobs.some((path: string) => minimatch(workspace.location, path))) return;
workspaceConfig.references = expectedReferences;
fs.writeFileSync(workspaceConfigPath, await serializeConfig(workspaceConfig));
if (workspaceLibConfig !== null) {
workspaceLibConfig.references = expectedLibReferences;
fs.writeFileSync(
workspaceLibConfigPath,
await serializeConfig(workspaceLibConfig, 2),
);
}
if (workspaceLibESMConfig !== null) {
workspaceLibESMConfig.references = expectedLibReferences;
fs.writeFileSync(
workspaceLibESMConfigPath,
await serializeConfig(workspaceLibESMConfig, 2),
);
}
});
})();