Skip to content

Commit

Permalink
ci: check typescript references
Browse files Browse the repository at this point in the history
Modified the `scripts/compile-references.js` script to do dry-runs, this
will allow the CI to check if typescript references are out of sync.

Add 2 new npm scripts:

- `test:references`: fails if references are out of sync.
- `update:references`: updates the references, if required.

Signed-off-by: Paul Maréchal <paul.marechal@ericsson.com>
  • Loading branch information
paul-marechal committed Feb 6, 2020
1 parent 4247834 commit 460155e
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 29 deletions.
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ cache:
- packages/userstorage/node_modules
- packages/variable-resolver/node_modules
- packages/workspace/node_modules
# end_cache_directories
# end_cache_directories
- packages/java-debug/download
- packages/debug-nodejs/download
before_cache:
Expand Down Expand Up @@ -105,7 +105,8 @@ install:
- THEIA_SKIP_NPM_PREPARE=1 yarn install --skip-integrity-check
- npx electron-replace-ffmpeg # re-download library (in case it was cached)
- npx electron-codecs-test # test library
- yarn prepare
- yarn test:references # check typescript references
- yarn prepare # actually build
- scripts/check_git_status.sh
script:
- travis_retry yarn test:theia
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
## v0.16.0

- [core] added a new React-based dialog type `ReactDialog` [#6855](https://github.com/eclipse-theia/theia/pull/6855)
- [repo] added 2 new npm scripts:
- `test:references`: fails if typescript references are out of sync.
- `update:references`: updates typescript references, if required.
- [repo] the `test` script now checks if typescript references are in sync.

Breaking changes:

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@
"lint:clean": "rimraf .eslintcache",
"lint:oneshot": "node --max-old-space-size=4096 node_modules/eslint/bin/eslint.js --cache=true \"{dev-packages,packages,examples}/**/*.{ts,tsx}\"",
"docs": "rimraf gh-pages/docs/next && typedoc --tsconfig configs/typedoc-tsconfig.json --options configs/typedoc.json",
"test": "yarn test:theia && yarn test:electron && yarn test:browser",
"test": "yarn test:references && yarn test:theia && yarn test:electron && yarn test:browser",
"test:references": "node scripts/compile-references --dry-run",
"test:theia": "run test \"@theia/!(example-)*\" --stream --concurrency=1",
"test:browser": "yarn rebuild:browser && run test \"@theia/example-browser\"",
"test:electron": "yarn rebuild:electron && run test \"@theia/example-electron\"",
"update:references": "node scripts/compile-references.js",
"rebuild:clean": "rimraf .browser_modules",
"rebuild:browser": "theia rebuild:browser",
"rebuild:electron": "theia rebuild:electron",
Expand Down
123 changes: 97 additions & 26 deletions scripts/compile-references.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
* only when required, but it cannot infer workspaces by itself, it has to be
* explicitly defined [1].
*
* This script exits with a code different from zero if something needed to be
* updated.
*
* You can do a dry run using the cli flag `--dry-run`.
*
* [1]: https://www.typescriptlang.org/docs/handbook/project-references.html
*/

Expand All @@ -32,53 +37,72 @@ const cp = require('child_process');
const path = require('path').posix;
const fs = require('fs');

const CWD = path.join(__dirname, '..');
const ROOT = path.join(__dirname, '..');

const DRY_RUN = popFlag(process.argv, '--dry-run');

const FORCE_REWRITE = Boolean(process.env['THEIA_REPO_FORCE_REWRITE']);
const FORCE_REWRITE = popFlag(process.argv, '--force-rewrite');

/** @type {{ [packageName: string]: YarnWorkspace }} */
const YARN_WORKSPACES = JSON.parse(cp.execSync('yarn --silent workspaces info').toString());

// Add the package name inside each package object.
for (const [packageName, yarnWorkspace] of Object.entries(YARN_WORKSPACES)) {
yarnWorkspace.name = packageName;
}

/** @type {YarnWorkspace} */
const THEIA_MONOREPO = {
name: '@theia/monorepo',
workspaceDependencies: Object.keys(YARN_WORKSPACES),
location: '.',
location: ROOT,
};

// Configure all `compile.tsconfig.json` files of this monorepo
for (const packageName of Object.keys(YARN_WORKSPACES)) {
const workspacePackage = YARN_WORKSPACES[packageName];
const tsconfigCompilePath = path.join(CWD, workspacePackage.location, 'compile.tsconfig.json');
const references = getTypescriptReferences(workspacePackage);
configureTypeScriptCompilation(tsconfigCompilePath, references);
}

{
const tsconfigCompilePath = path.join(CWD, 'configs', 'root-compilation.tsconfig.json');
const references = getTypescriptReferences({
workspaceDependencies: Object.keys(YARN_WORKSPACES),
location: path.join(CWD, 'configs'),
});
configureTypeScriptCompilation(tsconfigCompilePath, references);
let rewriteRequired = false;

// Configure all `compile.tsconfig.json` files of this monorepo
for (const packageName of Object.keys(YARN_WORKSPACES)) {
const workspacePackage = YARN_WORKSPACES[packageName];
const tsconfigCompilePath = path.join(ROOT, workspacePackage.location, 'compile.tsconfig.json');
const references = getTypescriptReferences(workspacePackage);
rewriteRequired |= configureTypeScriptCompilation(workspacePackage, tsconfigCompilePath, references);
}

// Configure our root compilation configuration, living inside `configs/root-compilation.tsconfig.json`.
const configsFolder = path.join(ROOT, 'configs');
const tsconfigCompilePath = path.join(configsFolder, 'root-compilation.tsconfig.json');
const references = getTypescriptReferences(THEIA_MONOREPO, configsFolder);
rewriteRequired |= configureTypeScriptCompilation(THEIA_MONOREPO, tsconfigCompilePath, references);

// Configure the root `tsconfig.json` for code navigation using `tsserver`.
const tsconfigNavPath = path.join(CWD, 'tsconfig.json');
configureTypeScriptNavigation(tsconfigNavPath);
const tsconfigNavPath = path.join(ROOT, 'tsconfig.json');
rewriteRequired |= configureTypeScriptNavigation(THEIA_MONOREPO, tsconfigNavPath);

// CI will be able to tell if references got changed by looking at the exit code.
if (rewriteRequired) {
if (DRY_RUN) {
// Running a dry run usually only happens when a developer or CI runs the tests, so we only print the help then.
console.error('TypeScript references seem to be out of sync, run "yarn update:references" to fix.');
}
process.exitCode = 1;
}
}

/**
* @param {YarnWorkspace} requestedPackage
* @param {string} [overrideLocation] affects how relative paths are computed.
* @returns {string[]} project references for `requestedPackage`.
*/
function getTypescriptReferences(requestedPackage) {
function getTypescriptReferences(requestedPackage, overrideLocation) {
const references = [];
for (const dependency of requestedPackage.workspaceDependencies || []) {
const depWorkspace = YARN_WORKSPACES[dependency];
const depConfig = path.join(depWorkspace.location, 'compile.tsconfig.json');
if (!fs.existsSync(depConfig)) {
continue;
}
const relativePath = path.relative(requestedPackage.location, depWorkspace.location);
const relativePath = path.relative(overrideLocation || requestedPackage.location, depWorkspace.location);
references.push(relativePath);
}
return references;
Expand All @@ -88,10 +112,12 @@ function getTypescriptReferences(requestedPackage) {
* Wires a given compilation tsconfig file according to the provided references.
* This allows TypeScript to operate in build mode.
*
* @param {YarnWorkspace} targetPackage for debug purpose.
* @param {string} tsconfigPath path to the tsconfig file to edit.
* @param {string[]} references list of paths to the related project roots.
* @returns {boolean} rewrite was needed.
*/
function configureTypeScriptCompilation(tsconfigPath, references) {
function configureTypeScriptCompilation(targetPackage, tsconfigPath, references) {
if (!fs.existsSync(tsconfigPath)) {
return;
}
Expand All @@ -112,15 +138,38 @@ function configureTypeScriptCompilation(tsconfigPath, references) {
};
needRewrite = true;
}
const currentReferences = new Set((tsconfigJson['references'] || []).map(reference => reference.path));
const currentReferences = new Set(
(tsconfigJson['references'] || [])
// We will work on a set of paths, easier to handle than objects.
.map(reference => reference.path)
// Remove any invalid reference (maybe outdated).
.filter(referenceRelativePath => {
const referencePath = path.join(path.dirname(tsconfigPath), referenceRelativePath);
try {
const referenceStat = fs.statSync(referencePath);
const isValid = referenceStat.isDirectory() && fs.statSync(path.join(referencePath, 'tsconfig.json')).isFile()
|| referenceStat.isFile(); // still could be something else than a tsconfig, but good enough.

if (!isValid) {
needRewrite = true;
}
return isValid; // keep or not

} catch {
console.error(`${targetPackage.name} invalid typescript reference: ${referencePath}`);
needRewrite = true;
return false; // remove
}
})
);
for (const reference of references) {
const tsconfigReference = path.join(reference, 'compile.tsconfig.json');
if (!currentReferences.has(tsconfigReference)) {
currentReferences.add(tsconfigReference);
needRewrite = true;
}
}
if (FORCE_REWRITE || needRewrite) {
if (!DRY_RUN && (FORCE_REWRITE || needRewrite)) {
tsconfigJson.references = [];
for (const reference of currentReferences) {
tsconfigJson.references.push({
Expand All @@ -130,16 +179,19 @@ function configureTypeScriptCompilation(tsconfigPath, references) {
const content = JSON.stringify(tsconfigJson, undefined, 2);
fs.writeFileSync(tsconfigPath, content + '\n');
}
return needRewrite;
}

/**
* Wire the root `tsconfig.json` to map scoped import to real location in the monorepo.
* This setup is a shim for the TypeScript language server to provide cross-package navigation.
* Compilation is done via `compile.tsconfig.json` files.
*
* @param {YarnWorkspace} targetPackage for debug purpose.
* @param {string} tsconfigPath
* @returns {boolean} rewrite was needed.
*/
function configureTypeScriptNavigation(tsconfigPath) {
function configureTypeScriptNavigation(targetPackage, tsconfigPath) {
let needRewrite = false;
const tsconfigJson = readJsonFile(tsconfigPath);
if (typeof tsconfigJson.compilerOptions === 'undefined') {
Expand Down Expand Up @@ -185,9 +237,27 @@ function configureTypeScriptNavigation(tsconfigPath) {
needRewrite = true;
}
}
if (FORCE_REWRITE || needRewrite) {
if (!DRY_RUN && (FORCE_REWRITE || needRewrite)) {
const content = JSON.stringify(tsconfigJson, undefined, 2);
fs.writeFileSync(tsconfigPath, content + '\n');
console.warn(`Updated references for ${targetPackage.name}.`);
}
return needRewrite;
}

/**
*
* @param {string[]} argv
* @param {string} flag
* @returns {boolean}
*/
function popFlag(argv, flag) {
const flagIndex = argv.indexOf(flag)
if (flagIndex !== -1) {
argv.splice(flagIndex, 1);
return true;
} else {
return false;
}
}

Expand All @@ -206,6 +276,7 @@ function readJsonFile(filePath) {

/**
* @typedef YarnWorkspace
* @property {string} name
* @property {string} location
* @property {string[]} workspaceDependencies
*/

0 comments on commit 460155e

Please sign in to comment.