Skip to content

Commit ee3cf82

Browse files
committed
chore: add script to build typescript project references
1 parent ec37f2f commit ee3cf82

File tree

4 files changed

+154
-5
lines changed

4 files changed

+154
-5
lines changed

bin/update-project-refs.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env node
2+
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
3+
// Node module: loopback-next
4+
// This file is licensed under the MIT License.
5+
// License text available at https://opensource.org/licenses/MIT
6+
7+
/**
8+
* This is an internal script to update TypeScript project references based on
9+
* lerna's local package dependencies.
10+
*
11+
* See https://www.typescriptlang.org/docs/handbook/project-references.html
12+
*/
13+
'use strict';
14+
15+
const path = require('path');
16+
const fs = require('fs');
17+
const debug = require('debug')('loopback:build');
18+
const buildUtils = require('../packages/build/bin/utils');
19+
20+
const Project = require('@lerna/project');
21+
const PackageGraph = require('@lerna/package-graph');
22+
23+
const TSCONFIG = 'tsconfig.build.json';
24+
25+
async function updateReferences(options) {
26+
options = options || {};
27+
const dryRun = options.dryRun;
28+
const project = new Project(process.cwd());
29+
const packages = await project.getPackages();
30+
31+
const rootRefs = [];
32+
const graph = new PackageGraph(packages);
33+
34+
for (const p of graph.values()) {
35+
debug('Package %s', p.pkg.name);
36+
const pkgLocation = p.pkg.location;
37+
const tsconfigFile = path.join(pkgLocation, TSCONFIG);
38+
// Skip non-typescript packages
39+
if (!fs.existsSync(tsconfigFile)) {
40+
debug('Skipping non-TS package: %s', p.pkg.name);
41+
continue;
42+
}
43+
rootRefs.push({
44+
path: path.join(path.relative(project.rootPath, pkgLocation), TSCONFIG),
45+
});
46+
const tsconfig = require(tsconfigFile);
47+
const refs = [];
48+
for (const d of p.localDependencies.keys()) {
49+
const depPkg = graph.get(d);
50+
// Skip non-typescript packages
51+
if (!fs.existsSync(path.join(depPkg.pkg.location, TSCONFIG))) {
52+
debug('Skipping non-TS dependency: %s', depPkg.pkg.name);
53+
continue;
54+
}
55+
const relativePath = path.relative(pkgLocation, depPkg.pkg.location);
56+
refs.push({path: path.join(relativePath, TSCONFIG)});
57+
}
58+
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
59+
// composite must be true for project refs
60+
tsconfig.compilerOptions.composite = true;
61+
// outDir & target have to be set in tsconfig instead of CLI for tsc -b
62+
tsconfig.compilerOptions.target =
63+
tsconfig.compilerOptions.target || buildUtils.getCompilationTarget();
64+
tsconfig.compilerOptions.outDir =
65+
tsconfig.compilerOptions.outDir ||
66+
buildUtils.getDistribution(tsconfig.compilerOptions.target);
67+
tsconfig.references = refs;
68+
69+
// Convert to JSON
70+
const tsconfigJson = JSON.stringify(tsconfig, null, 2);
71+
72+
if (!dryRun) {
73+
// Using `-f` to overwrite tsconfig.build.json
74+
fs.writeFileSync(tsconfigFile, tsconfigJson + '\n', {encoding: 'utf-8'});
75+
debug('%s has been updated.', tsconfigFile);
76+
} else {
77+
// Otherwise write to console
78+
debug(tsconfigJson);
79+
}
80+
}
81+
82+
const rootTsconfigFile = path.join(project.rootPath, 'tsconfig.json');
83+
const rootTsconfig = require(rootTsconfigFile);
84+
rootTsconfig.compilerOptions = rootTsconfig.compilerOptions || {};
85+
rootTsconfig.compilerOptions.composite = true;
86+
rootTsconfig.references = rootRefs;
87+
// Convert to JSON
88+
const rootTsconfigJson = JSON.stringify(rootTsconfig, null, 2);
89+
if (!dryRun) {
90+
// Using `-f` to overwrite tsconfig.json
91+
fs.writeFileSync(rootTsconfigFile, rootTsconfigJson + '\n', {
92+
encoding: 'utf-8',
93+
});
94+
debug('%s has been updated.', rootTsconfigFile);
95+
} else {
96+
debug(rootTsconfigJson);
97+
console.log(
98+
'This is a dry-run. Please use -f option to update tsconfig files.',
99+
);
100+
}
101+
}
102+
103+
if (require.main === module) {
104+
const dryRun = process.argv[2] !== '-f';
105+
updateReferences({dryRun});
106+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@types/mocha": "^5.0.0",
1717
"coveralls": "^3.0.0",
1818
"cz-conventional-changelog": "^2.1.0",
19+
"debug": "^3.1.0",
1920
"husky": "^0.14.3",
2021
"lerna": "^3.1.4"
2122
},

packages/build/bin/compile-package.js

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ function run(argv, options) {
2424
const glob = require('glob');
2525
const fse = require('fs-extra');
2626

27+
if (options === true) {
28+
options = {dryRun: true};
29+
} else {
30+
options = options || {};
31+
}
32+
2733
const packageDir = utils.getPackageDir();
2834

2935
const compilerOpts = argv.slice(2);
@@ -36,6 +42,11 @@ function run(argv, options) {
3642
'--ignore-resources',
3743
);
3844

45+
// Honor --dry from tsc
46+
if (utils.isOptionSet(compilerOpts, '--dry')) {
47+
options.dryRun = true;
48+
}
49+
3950
var target;
4051

4152
// --ignore-resources is not a TS Compiler option so we remove it from the
@@ -126,7 +137,7 @@ function run(argv, options) {
126137
// Since outDir is set, ts files are compiled into that directory.
127138
// If ignore-resources flag is not passed, copy resources (non-ts files)
128139
// to the same outDir as well.
129-
if (rootDir && tsConfigFile && !isIgnoreResourcesSet) {
140+
if (rootDir && tsConfigFile && !isIgnoreResourcesSet && !options.dryRun) {
130141
const tsConfig = require(tsConfigFile);
131142
const dirs = tsConfig.include
132143
? tsConfig.include.join('|')
@@ -146,11 +157,21 @@ function run(argv, options) {
146157

147158
args.push(...compilerOpts);
148159

149-
if (options === true) {
150-
options = {dryRun: true};
151-
} else {
152-
options = options || {};
160+
// Move --build or -b as the 1st argument to avoid:
161+
// error TS6369: Option '--build' must be the first command line argument.
162+
const buildOptions = utils.removeOptions(args, '-b', '--build');
163+
if (buildOptions.length) {
164+
let projectOptions = utils.removeOptions(args, '-p', '--project');
165+
projectOptions = projectOptions.filter(p => !p.startsWith('-'));
166+
// Remove conflict options with '--build'
167+
utils.removeOptions(args, '--outDir', '--target');
168+
if (buildOptions.length === 1) {
169+
args.unshift(...buildOptions, ...projectOptions);
170+
} else {
171+
args.unshift(...buildOptions);
172+
}
153173
}
174+
154175
return utils.runCLI('typescript/lib/tsc', args, {cwd, ...options});
155176
}
156177

packages/build/bin/utils.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,26 @@ function isOptionSet(opts, ...optionNames) {
194194
);
195195
}
196196

197+
/**
198+
* Remove options and their values from args
199+
*/
200+
function removeOptions(args, ...options) {
201+
const removed = [];
202+
for (const e of options) {
203+
const index = args.indexOf(e);
204+
if (index !== -1) {
205+
const next = args[index + 1];
206+
if (typeof next === 'string' && !next.startsWith('-')) {
207+
// The next element is the value of the option, remove it too
208+
removed.push(...args.splice(index, 2));
209+
} else {
210+
removed.push(...args.splice(index, 1));
211+
}
212+
}
213+
}
214+
return removed;
215+
}
216+
197217
exports.getCompilationTarget = getCompilationTarget;
198218
exports.getDistribution = getDistribution;
199219
exports.getRootDir = getRootDir;
@@ -203,3 +223,4 @@ exports.resolveCLI = resolveCLI;
203223
exports.runCLI = runCLI;
204224
exports.runShell = runShell;
205225
exports.isOptionSet = isOptionSet;
226+
exports.removeOptions = removeOptions;

0 commit comments

Comments
 (0)