Skip to content

Commit 1e6dd21

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

File tree

4 files changed

+159
-5
lines changed

4 files changed

+159
-5
lines changed

bin/update-project-refs.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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 util = require('util');
18+
const debug = require('debug')('loopback:build');
19+
const buildUtils = require('../packages/build/bin/utils');
20+
21+
const Project = require('@lerna/project');
22+
const PackageGraph = require('@lerna/package-graph');
23+
24+
const TSCONFIG = 'tsconfig.build.json';
25+
26+
async function updateReferences(options) {
27+
options = options || {};
28+
const dryRun = options.dryRun;
29+
const project = new Project(process.cwd());
30+
const packages = await project.getPackages();
31+
32+
const rootRefs = [];
33+
const graph = new PackageGraph(packages);
34+
35+
for (const p of graph.values()) {
36+
debug('Package %s', p.pkg.name);
37+
const pkgLocation = p.pkg.location;
38+
const tsconfigFile = path.join(pkgLocation, TSCONFIG);
39+
// Skip non-typescript packages
40+
if (!fs.existsSync(tsconfigFile)) {
41+
debug('Skipping non-TS package: %s', p.pkg.name);
42+
continue;
43+
}
44+
rootRefs.push({
45+
path: path.join(path.relative(project.rootPath, pkgLocation), TSCONFIG),
46+
});
47+
const tsconfig = require(tsconfigFile);
48+
const refs = [];
49+
for (const d of p.localDependencies.keys()) {
50+
const depPkg = graph.get(d);
51+
// Skip non-typescript packages
52+
if (!fs.existsSync(path.join(depPkg.pkg.location, TSCONFIG))) {
53+
debug('Skipping non-TS dependency: %s', depPkg.pkg.name);
54+
continue;
55+
}
56+
const relativePath = path.relative(pkgLocation, depPkg.pkg.location);
57+
refs.push({path: path.join(relativePath, TSCONFIG)});
58+
}
59+
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
60+
// composite must be true for project refs
61+
tsconfig.compilerOptions.composite = true;
62+
// outDir & target have to be set in tsconfig instead of CLI for tsc -b
63+
tsconfig.compilerOptions.target =
64+
tsconfig.compilerOptions.target || buildUtils.getCompilationTarget();
65+
tsconfig.compilerOptions.outDir =
66+
tsconfig.compilerOptions.outDir ||
67+
buildUtils.getDistribution(tsconfig.compilerOptions.target);
68+
tsconfig.references = refs;
69+
70+
// Convert to JSON
71+
const tsconfigJson = JSON.stringify(tsconfig, null, 2);
72+
73+
if (!dryRun) {
74+
// Using `-f` to overwrite tsconfig.build.json
75+
fs.writeFileSync(tsconfigFile, tsconfigJson + '\n', {encoding: 'utf-8'});
76+
debug('%s has been updated.', tsconfigFile);
77+
} else {
78+
// Otherwise write to console
79+
debug(tsconfigJson);
80+
console.log('%s', p.pkg.name);
81+
refs.forEach(r => console.log(' %s', r.path));
82+
}
83+
}
84+
85+
const rootTsconfigFile = path.join(project.rootPath, 'tsconfig.json');
86+
const rootTsconfig = require(rootTsconfigFile);
87+
rootTsconfig.compilerOptions = rootTsconfig.compilerOptions || {};
88+
rootTsconfig.compilerOptions.composite = true;
89+
rootTsconfig.references = rootRefs;
90+
// Convert to JSON
91+
const rootTsconfigJson = JSON.stringify(rootTsconfig, null, 2);
92+
if (!dryRun) {
93+
// Using `-f` to overwrite tsconfig.json
94+
fs.writeFileSync(rootTsconfigFile, rootTsconfigJson + '\n', {
95+
encoding: 'utf-8',
96+
});
97+
debug('%s has been updated.', rootTsconfigFile);
98+
} else {
99+
debug(rootTsconfigJson);
100+
console.log('\n%s', path.relative(project.rootPath, rootTsconfigFile));
101+
rootRefs.forEach(r => console.log(' %s', r.path));
102+
console.log(
103+
'\nThis is a dry-run. Please use -f option to update tsconfig files.',
104+
);
105+
}
106+
}
107+
108+
if (require.main === module) {
109+
const dryRun = process.argv[2] !== '-f';
110+
updateReferences({dryRun});
111+
}

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)