@knighted/duel 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/duel.cjs +215 -0
- package/dist/cjs/duel.d.cts +2 -0
- package/dist/cjs/init.cjs +126 -0
- package/dist/cjs/init.d.ts +8 -0
- package/dist/cjs/util.cjs +18 -0
- package/dist/cjs/util.d.cts +3 -0
- package/dist/esm/duel.d.ts +2 -0
- package/dist/esm/duel.js +212 -0
- package/dist/esm/init.d.ts +8 -0
- package/dist/esm/init.js +120 -0
- package/dist/esm/util.d.ts +3 -0
- package/dist/esm/util.js +13 -0
- package/package.json +13 -13
- package/dist/duel.cjs +0 -214
- package/dist/duel.js +0 -208
- package/dist/init.cjs +0 -129
- package/dist/init.js +0 -122
- package/dist/util.cjs +0 -20
- package/dist/util.js +0 -12
package/dist/esm/init.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { cwd } from 'node:process';
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { resolve, join, dirname } from 'node:path';
|
|
4
|
+
import { stat, readFile } from 'node:fs/promises';
|
|
5
|
+
import { readPackageUp } from 'read-pkg-up';
|
|
6
|
+
import JSONC from 'jsonc-parser';
|
|
7
|
+
import { logError, log } from './util.js';
|
|
8
|
+
const init = async (args) => {
|
|
9
|
+
let parsed = null;
|
|
10
|
+
try {
|
|
11
|
+
const { values } = parseArgs({
|
|
12
|
+
args,
|
|
13
|
+
options: {
|
|
14
|
+
project: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
short: 'p',
|
|
17
|
+
default: 'tsconfig.json',
|
|
18
|
+
},
|
|
19
|
+
'target-extension': {
|
|
20
|
+
type: 'string',
|
|
21
|
+
short: 'x',
|
|
22
|
+
default: '',
|
|
23
|
+
},
|
|
24
|
+
'pkg-dir': {
|
|
25
|
+
type: 'string',
|
|
26
|
+
short: 'k',
|
|
27
|
+
default: cwd(),
|
|
28
|
+
},
|
|
29
|
+
parallel: {
|
|
30
|
+
type: 'boolean',
|
|
31
|
+
short: 'l',
|
|
32
|
+
default: false,
|
|
33
|
+
},
|
|
34
|
+
dirs: {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
short: 'd',
|
|
37
|
+
default: false,
|
|
38
|
+
},
|
|
39
|
+
help: {
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
short: 'h',
|
|
42
|
+
default: false,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
parsed = values;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
logError(err.message);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (parsed.help) {
|
|
53
|
+
log('Usage: duel [options]\n');
|
|
54
|
+
log('Options:');
|
|
55
|
+
log("--project, -p [path] \t Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.");
|
|
56
|
+
log('--pkg-dir, -k [path] \t The directory to start looking for a package.json file. Defaults to cwd.');
|
|
57
|
+
log('--dirs, -d \t\t Output both builds to directories inside of outDir. [esm, cjs].');
|
|
58
|
+
log('--parallel, -l \t\t Run the builds in parallel.');
|
|
59
|
+
log('--help, -h \t\t Print this message.');
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const { project, 'target-extension': targetExt, 'pkg-dir': pkgDir, parallel, dirs, } = parsed;
|
|
63
|
+
let configPath = resolve(project);
|
|
64
|
+
let stats = null;
|
|
65
|
+
let pkg = null;
|
|
66
|
+
if (targetExt) {
|
|
67
|
+
logError('--target-extension is deprecated. Define "type" in your package.json instead and the dual build will be inferred from that.');
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
pkg = await readPackageUp({ cwd: pkgDir });
|
|
71
|
+
if (!pkg) {
|
|
72
|
+
logError('No package.json file found.');
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
stats = await stat(configPath);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
logError(`Provided --project '${project}' resolves to ${configPath} which is not a file or directory.`);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (stats.isDirectory()) {
|
|
83
|
+
configPath = join(configPath, 'tsconfig.json');
|
|
84
|
+
try {
|
|
85
|
+
stats = await stat(configPath);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
logError(`Provided --project '${project}' resolves to a directory ${dirname(configPath)} with no tsconfig.json.`);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (stats.isFile()) {
|
|
93
|
+
let tsconfig = null;
|
|
94
|
+
const errors = [];
|
|
95
|
+
const jsonText = (await readFile(configPath)).toString();
|
|
96
|
+
tsconfig = JSONC.parse(jsonText, errors, {
|
|
97
|
+
disallowComments: false,
|
|
98
|
+
allowTrailingComma: true,
|
|
99
|
+
});
|
|
100
|
+
if (errors.length) {
|
|
101
|
+
logError(`The config file found at ${configPath} is not parsable as JSONC.`);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
if (!tsconfig.compilerOptions?.outDir) {
|
|
105
|
+
log('No outDir defined in tsconfig.json. Build output will be in "dist".');
|
|
106
|
+
}
|
|
107
|
+
const projectDir = dirname(configPath);
|
|
108
|
+
return {
|
|
109
|
+
pkg,
|
|
110
|
+
dirs,
|
|
111
|
+
parallel,
|
|
112
|
+
tsconfig,
|
|
113
|
+
projectDir,
|
|
114
|
+
configPath,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
};
|
|
120
|
+
export { init };
|
package/dist/esm/util.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
2
|
+
import { realpath } from 'node:fs/promises';
|
|
3
|
+
const log = (color = '\x1b[30m', msg = '') => {
|
|
4
|
+
// eslint-disable-next-line no-console
|
|
5
|
+
console.log(`${color}%s\x1b[0m`, msg);
|
|
6
|
+
};
|
|
7
|
+
const logError = log.bind(null, '\x1b[31m');
|
|
8
|
+
const getRealPathAsFileUrl = async (path) => {
|
|
9
|
+
const realPath = await realpath(path);
|
|
10
|
+
const asFileUrl = pathToFileURL(realPath).href;
|
|
11
|
+
return asFileUrl;
|
|
12
|
+
};
|
|
13
|
+
export { log, logError, getRealPathAsFileUrl };
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knighted/duel",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "TypeScript dual packages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist",
|
|
7
|
-
"bin": "dist/duel.js",
|
|
7
|
+
"bin": "dist/esm/duel.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"import": "./dist/duel.js",
|
|
11
|
-
"require": "./dist/duel.cjs",
|
|
12
|
-
"default": "./dist/duel.js"
|
|
10
|
+
"import": "./dist/esm/duel.js",
|
|
11
|
+
"require": "./dist/cjs/duel.cjs",
|
|
12
|
+
"default": "./dist/esm/duel.js"
|
|
13
13
|
},
|
|
14
14
|
"./package.json": "./package.json"
|
|
15
15
|
},
|
|
@@ -21,16 +21,17 @@
|
|
|
21
21
|
"prettier": "prettier -w src/*.js test/*.js",
|
|
22
22
|
"lint": "eslint src/*.js test/*.js",
|
|
23
23
|
"test": "c8 --reporter=text --reporter=text-summary --reporter=lcov node --test --test-reporter=spec test/*.js",
|
|
24
|
-
"build": "
|
|
24
|
+
"build": "node src/duel.js --dirs",
|
|
25
25
|
"prepack": "npm run build"
|
|
26
26
|
},
|
|
27
27
|
"keywords": [
|
|
28
28
|
"node",
|
|
29
29
|
"typescript",
|
|
30
|
+
"dual package",
|
|
31
|
+
"es module",
|
|
32
|
+
"commonjs",
|
|
30
33
|
"esm",
|
|
31
34
|
"cjs",
|
|
32
|
-
"commonjs",
|
|
33
|
-
"dual package",
|
|
34
35
|
"build",
|
|
35
36
|
"tsc",
|
|
36
37
|
"cts",
|
|
@@ -54,20 +55,19 @@
|
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"@types/node": "^20.4.6",
|
|
57
|
-
"babel-dual-package": "^1.1.2",
|
|
58
58
|
"c8": "^8.0.1",
|
|
59
59
|
"eslint": "^8.45.0",
|
|
60
60
|
"eslint-plugin-n": "^16.0.1",
|
|
61
|
-
"prettier": "^3.
|
|
61
|
+
"prettier": "^3.2.4",
|
|
62
62
|
"typescript": "^5.4.0-dev.20231206",
|
|
63
|
-
"vite": "^
|
|
63
|
+
"vite": "^5.0.12"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@knighted/specifier": "^1.0.1",
|
|
67
67
|
"find-up": "^6.3.0",
|
|
68
68
|
"glob": "^10.3.3",
|
|
69
|
-
"
|
|
70
|
-
"
|
|
69
|
+
"jsonc-parser": "^3.2.0",
|
|
70
|
+
"read-pkg-up": "^10.0.0"
|
|
71
71
|
},
|
|
72
72
|
"prettier": {
|
|
73
73
|
"arrowParens": "avoid",
|
package/dist/duel.cjs
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
Object.defineProperty(exports, "__esModule", {
|
|
5
|
-
value: true
|
|
6
|
-
});
|
|
7
|
-
exports.duel = void 0;
|
|
8
|
-
var _nodeProcess = require("node:process");
|
|
9
|
-
var _nodePath = require("node:path");
|
|
10
|
-
var _nodeChild_process = require("node:child_process");
|
|
11
|
-
var _promises = require("node:fs/promises");
|
|
12
|
-
var _nodeCrypto = require("node:crypto");
|
|
13
|
-
var _nodePerf_hooks = require("node:perf_hooks");
|
|
14
|
-
var _glob = require("glob");
|
|
15
|
-
var _findUp = require("find-up");
|
|
16
|
-
var _specifier = require("@knighted/specifier");
|
|
17
|
-
var _init = require("./init.cjs");
|
|
18
|
-
var _util = require("./util.cjs");
|
|
19
|
-
const tsc = await (0, _findUp.findUp)(async dir => {
|
|
20
|
-
const tscBin = (0, _nodePath.join)(dir, 'node_modules', '.bin', 'tsc');
|
|
21
|
-
if (await (0, _findUp.pathExists)(tscBin)) {
|
|
22
|
-
return tscBin;
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
const runBuild = (project, outDir) => {
|
|
26
|
-
return new Promise((resolve, reject) => {
|
|
27
|
-
const args = outDir ? ['-p', project, '--outDir', outDir] : ['-p', project];
|
|
28
|
-
const build = (0, _nodeChild_process.spawn)(tsc, args, {
|
|
29
|
-
stdio: 'inherit'
|
|
30
|
-
});
|
|
31
|
-
build.on('error', err => {
|
|
32
|
-
reject(new Error(`Failed to compile: ${err.message}`));
|
|
33
|
-
});
|
|
34
|
-
build.on('close', code => {
|
|
35
|
-
if (code === null) {
|
|
36
|
-
return reject(new Error('Failed to compile.'));
|
|
37
|
-
}
|
|
38
|
-
if (code > 0) {
|
|
39
|
-
return reject(new Error('Compilation errors found.'));
|
|
40
|
-
}
|
|
41
|
-
resolve(code);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
|
-
const duel = async args => {
|
|
46
|
-
const ctx = await (0, _init.init)(args);
|
|
47
|
-
if (ctx) {
|
|
48
|
-
const {
|
|
49
|
-
projectDir,
|
|
50
|
-
tsconfig,
|
|
51
|
-
configPath,
|
|
52
|
-
parallel,
|
|
53
|
-
dirs,
|
|
54
|
-
pkg
|
|
55
|
-
} = ctx;
|
|
56
|
-
const pkgDir = (0, _nodePath.dirname)(pkg.path);
|
|
57
|
-
const outDir = tsconfig.compilerOptions?.outDir ?? 'dist';
|
|
58
|
-
const absoluteOutDir = (0, _nodePath.resolve)(projectDir, outDir);
|
|
59
|
-
const originalType = pkg.packageJson.type ?? 'commonjs';
|
|
60
|
-
const isCjsBuild = originalType !== 'commonjs';
|
|
61
|
-
const targetExt = isCjsBuild ? '.cjs' : '.mjs';
|
|
62
|
-
const hex = (0, _nodeCrypto.randomBytes)(4).toString('hex');
|
|
63
|
-
const getOverrideTsConfig = () => {
|
|
64
|
-
return {
|
|
65
|
-
...tsconfig,
|
|
66
|
-
compilerOptions: {
|
|
67
|
-
...tsconfig.compilerOptions,
|
|
68
|
-
module: 'NodeNext',
|
|
69
|
-
moduleResolution: 'NodeNext'
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
};
|
|
73
|
-
const runPrimaryBuild = () => {
|
|
74
|
-
return runBuild(configPath, dirs ? isCjsBuild ? (0, _nodePath.join)(absoluteOutDir, 'esm') : (0, _nodePath.join)(absoluteOutDir, 'cjs') : absoluteOutDir);
|
|
75
|
-
};
|
|
76
|
-
const updateSpecifiersAndFileExtensions = async filenames => {
|
|
77
|
-
for (const filename of filenames) {
|
|
78
|
-
const dts = /(\.d\.ts)$/;
|
|
79
|
-
const outFilename = dts.test(filename) ? filename.replace(dts, isCjsBuild ? '.d.cts' : '.d.mts') : filename.replace(/\.js$/, targetExt);
|
|
80
|
-
const {
|
|
81
|
-
code,
|
|
82
|
-
error
|
|
83
|
-
} = await _specifier.specifier.update(filename, ({
|
|
84
|
-
value
|
|
85
|
-
}) => {
|
|
86
|
-
const collapsed = value.replace(/['"`+)\s]|new String\(/g, '');
|
|
87
|
-
const relative = /^(?:\.|\.\.)\//;
|
|
88
|
-
if (relative.test(collapsed)) {
|
|
89
|
-
return value.replace(/(.+)\.js([)'"`]*)?$/, `$1${targetExt}$2`);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
if (code && !error) {
|
|
93
|
-
await (0, _promises.writeFile)(outFilename, code);
|
|
94
|
-
await (0, _promises.rm)(filename, {
|
|
95
|
-
force: true
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
const logSuccess = start => {
|
|
101
|
-
(0, _util.log)(`Successfully created a dual ${isCjsBuild ? 'CJS' : 'ESM'} build in ${Math.round(_nodePerf_hooks.performance.now() - start)}ms.`);
|
|
102
|
-
};
|
|
103
|
-
if (parallel) {
|
|
104
|
-
const paraName = `_${hex}_`;
|
|
105
|
-
const paraParent = (0, _nodePath.join)(projectDir, '..');
|
|
106
|
-
const paraTempDir = (0, _nodePath.join)(paraParent, paraName);
|
|
107
|
-
let isDirWritable = true;
|
|
108
|
-
try {
|
|
109
|
-
const stats = await (0, _promises.stat)(paraParent);
|
|
110
|
-
if (stats.isDirectory()) {
|
|
111
|
-
await (0, _promises.access)(paraParent, _promises.constants.W_OK);
|
|
112
|
-
} else {
|
|
113
|
-
isDirWritable = false;
|
|
114
|
-
}
|
|
115
|
-
} catch {
|
|
116
|
-
isDirWritable = false;
|
|
117
|
-
}
|
|
118
|
-
if (!isDirWritable) {
|
|
119
|
-
(0, _util.logError)('No writable directory to prepare parallel builds. Exiting.');
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
(0, _util.log)('Preparing parallel build...');
|
|
123
|
-
const prepStart = _nodePerf_hooks.performance.now();
|
|
124
|
-
await (0, _promises.cp)(projectDir, paraTempDir, {
|
|
125
|
-
recursive: true,
|
|
126
|
-
filter: src => !/logs|pids|lib-cov|coverage|bower_components|build|dist|jspm_packages|web_modules|out|\.next|\.tsbuildinfo|\.npm|\.node_repl_history|\.tgz|\.yarn|\.pnp|\.nyc_output|\.grunt|\.DS_Store/i.test(src)
|
|
127
|
-
});
|
|
128
|
-
const dualConfigPath = (0, _nodePath.join)(paraTempDir, 'tsconfig.json');
|
|
129
|
-
const absoluteDualOutDir = (0, _nodePath.join)(paraTempDir, isCjsBuild ? (0, _nodePath.join)(outDir, 'cjs') : (0, _nodePath.join)(outDir, 'esm'));
|
|
130
|
-
const tsconfigDual = getOverrideTsConfig();
|
|
131
|
-
await (0, _promises.writeFile)(dualConfigPath, JSON.stringify(tsconfigDual));
|
|
132
|
-
await (0, _promises.writeFile)((0, _nodePath.join)(paraTempDir, 'package.json'), JSON.stringify({
|
|
133
|
-
type: isCjsBuild ? 'commonjs' : 'module'
|
|
134
|
-
}));
|
|
135
|
-
(0, _util.log)(`Prepared in ${Math.round(_nodePerf_hooks.performance.now() - prepStart)}ms.`);
|
|
136
|
-
(0, _util.log)('Starting parallel dual builds...');
|
|
137
|
-
let success = false;
|
|
138
|
-
const startTime = _nodePerf_hooks.performance.now();
|
|
139
|
-
try {
|
|
140
|
-
await Promise.all([runPrimaryBuild(), runBuild(dualConfigPath, absoluteDualOutDir)]);
|
|
141
|
-
success = true;
|
|
142
|
-
} catch ({
|
|
143
|
-
message
|
|
144
|
-
}) {
|
|
145
|
-
(0, _util.logError)(message);
|
|
146
|
-
}
|
|
147
|
-
if (success) {
|
|
148
|
-
const filenames = await (0, _glob.glob)(`${absoluteDualOutDir}/**/*{.js,.d.ts}`, {
|
|
149
|
-
ignore: 'node_modules/**'
|
|
150
|
-
});
|
|
151
|
-
await updateSpecifiersAndFileExtensions(filenames);
|
|
152
|
-
await (0, _promises.cp)(absoluteDualOutDir, (0, _nodePath.join)(absoluteOutDir, isCjsBuild ? 'cjs' : 'esm'), {
|
|
153
|
-
recursive: true
|
|
154
|
-
});
|
|
155
|
-
await (0, _promises.rm)(paraTempDir, {
|
|
156
|
-
force: true,
|
|
157
|
-
recursive: true
|
|
158
|
-
});
|
|
159
|
-
logSuccess(startTime);
|
|
160
|
-
}
|
|
161
|
-
} else {
|
|
162
|
-
(0, _util.log)('Starting primary build...');
|
|
163
|
-
let success = false;
|
|
164
|
-
const startTime = _nodePerf_hooks.performance.now();
|
|
165
|
-
try {
|
|
166
|
-
await runPrimaryBuild();
|
|
167
|
-
success = true;
|
|
168
|
-
} catch ({
|
|
169
|
-
message
|
|
170
|
-
}) {
|
|
171
|
-
(0, _util.logError)(message);
|
|
172
|
-
}
|
|
173
|
-
if (success) {
|
|
174
|
-
const dualConfigPath = (0, _nodePath.join)(projectDir, `tsconfig.${hex}.json`);
|
|
175
|
-
const absoluteDualOutDir = (0, _nodePath.join)(projectDir, isCjsBuild ? (0, _nodePath.join)(outDir, 'cjs') : (0, _nodePath.join)(outDir, 'esm'));
|
|
176
|
-
const tsconfigDual = getOverrideTsConfig();
|
|
177
|
-
const pkgRename = 'package.json.bak';
|
|
178
|
-
await (0, _promises.rename)(pkg.path, (0, _nodePath.join)(pkgDir, pkgRename));
|
|
179
|
-
await (0, _promises.writeFile)(pkg.path, JSON.stringify({
|
|
180
|
-
type: isCjsBuild ? 'commonjs' : 'module'
|
|
181
|
-
}));
|
|
182
|
-
await (0, _promises.writeFile)(dualConfigPath, JSON.stringify(tsconfigDual));
|
|
183
|
-
(0, _util.log)('Starting dual build...');
|
|
184
|
-
try {
|
|
185
|
-
await runBuild(dualConfigPath, absoluteDualOutDir);
|
|
186
|
-
} catch ({
|
|
187
|
-
message
|
|
188
|
-
}) {
|
|
189
|
-
success = false;
|
|
190
|
-
(0, _util.logError)(message);
|
|
191
|
-
}
|
|
192
|
-
await (0, _promises.rm)(dualConfigPath, {
|
|
193
|
-
force: true
|
|
194
|
-
});
|
|
195
|
-
await (0, _promises.rm)(pkg.path, {
|
|
196
|
-
force: true
|
|
197
|
-
});
|
|
198
|
-
await (0, _promises.rename)((0, _nodePath.join)(pkgDir, pkgRename), pkg.path);
|
|
199
|
-
if (success) {
|
|
200
|
-
const filenames = await (0, _glob.glob)(`${absoluteDualOutDir}/**/*{.js,.d.ts}`, {
|
|
201
|
-
ignore: 'node_modules/**'
|
|
202
|
-
});
|
|
203
|
-
await updateSpecifiersAndFileExtensions(filenames);
|
|
204
|
-
logSuccess(startTime);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
exports.duel = duel;
|
|
211
|
-
const realFileUrlArgv1 = await (0, _util.getRealPathAsFileUrl)(_nodeProcess.argv[1]);
|
|
212
|
-
if (import.meta.url === realFileUrlArgv1) {
|
|
213
|
-
await duel();
|
|
214
|
-
}
|
package/dist/duel.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { argv } from 'node:process';
|
|
3
|
-
import { join, dirname, resolve } from 'node:path';
|
|
4
|
-
import { spawn } from 'node:child_process';
|
|
5
|
-
import { writeFile, rm, cp, rename, stat, access, constants } from 'node:fs/promises';
|
|
6
|
-
import { randomBytes } from 'node:crypto';
|
|
7
|
-
import { performance } from 'node:perf_hooks';
|
|
8
|
-
import { glob } from 'glob';
|
|
9
|
-
import { findUp, pathExists } from 'find-up';
|
|
10
|
-
import { specifier } from '@knighted/specifier';
|
|
11
|
-
import { init } from './init.js';
|
|
12
|
-
import { getRealPathAsFileUrl, logError, log } from './util.js';
|
|
13
|
-
const tsc = await findUp(async dir => {
|
|
14
|
-
const tscBin = join(dir, 'node_modules', '.bin', 'tsc');
|
|
15
|
-
if (await pathExists(tscBin)) {
|
|
16
|
-
return tscBin;
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
const runBuild = (project, outDir) => {
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
const args = outDir ? ['-p', project, '--outDir', outDir] : ['-p', project];
|
|
22
|
-
const build = spawn(tsc, args, {
|
|
23
|
-
stdio: 'inherit'
|
|
24
|
-
});
|
|
25
|
-
build.on('error', err => {
|
|
26
|
-
reject(new Error(`Failed to compile: ${err.message}`));
|
|
27
|
-
});
|
|
28
|
-
build.on('close', code => {
|
|
29
|
-
if (code === null) {
|
|
30
|
-
return reject(new Error('Failed to compile.'));
|
|
31
|
-
}
|
|
32
|
-
if (code > 0) {
|
|
33
|
-
return reject(new Error('Compilation errors found.'));
|
|
34
|
-
}
|
|
35
|
-
resolve(code);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
};
|
|
39
|
-
const duel = async args => {
|
|
40
|
-
const ctx = await init(args);
|
|
41
|
-
if (ctx) {
|
|
42
|
-
const {
|
|
43
|
-
projectDir,
|
|
44
|
-
tsconfig,
|
|
45
|
-
configPath,
|
|
46
|
-
parallel,
|
|
47
|
-
dirs,
|
|
48
|
-
pkg
|
|
49
|
-
} = ctx;
|
|
50
|
-
const pkgDir = dirname(pkg.path);
|
|
51
|
-
const outDir = tsconfig.compilerOptions?.outDir ?? 'dist';
|
|
52
|
-
const absoluteOutDir = resolve(projectDir, outDir);
|
|
53
|
-
const originalType = pkg.packageJson.type ?? 'commonjs';
|
|
54
|
-
const isCjsBuild = originalType !== 'commonjs';
|
|
55
|
-
const targetExt = isCjsBuild ? '.cjs' : '.mjs';
|
|
56
|
-
const hex = randomBytes(4).toString('hex');
|
|
57
|
-
const getOverrideTsConfig = () => {
|
|
58
|
-
return {
|
|
59
|
-
...tsconfig,
|
|
60
|
-
compilerOptions: {
|
|
61
|
-
...tsconfig.compilerOptions,
|
|
62
|
-
module: 'NodeNext',
|
|
63
|
-
moduleResolution: 'NodeNext'
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
};
|
|
67
|
-
const runPrimaryBuild = () => {
|
|
68
|
-
return runBuild(configPath, dirs ? isCjsBuild ? join(absoluteOutDir, 'esm') : join(absoluteOutDir, 'cjs') : absoluteOutDir);
|
|
69
|
-
};
|
|
70
|
-
const updateSpecifiersAndFileExtensions = async filenames => {
|
|
71
|
-
for (const filename of filenames) {
|
|
72
|
-
const dts = /(\.d\.ts)$/;
|
|
73
|
-
const outFilename = dts.test(filename) ? filename.replace(dts, isCjsBuild ? '.d.cts' : '.d.mts') : filename.replace(/\.js$/, targetExt);
|
|
74
|
-
const {
|
|
75
|
-
code,
|
|
76
|
-
error
|
|
77
|
-
} = await specifier.update(filename, ({
|
|
78
|
-
value
|
|
79
|
-
}) => {
|
|
80
|
-
const collapsed = value.replace(/['"`+)\s]|new String\(/g, '');
|
|
81
|
-
const relative = /^(?:\.|\.\.)\//;
|
|
82
|
-
if (relative.test(collapsed)) {
|
|
83
|
-
return value.replace(/(.+)\.js([)'"`]*)?$/, `$1${targetExt}$2`);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
if (code && !error) {
|
|
87
|
-
await writeFile(outFilename, code);
|
|
88
|
-
await rm(filename, {
|
|
89
|
-
force: true
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
const logSuccess = start => {
|
|
95
|
-
log(`Successfully created a dual ${isCjsBuild ? 'CJS' : 'ESM'} build in ${Math.round(performance.now() - start)}ms.`);
|
|
96
|
-
};
|
|
97
|
-
if (parallel) {
|
|
98
|
-
const paraName = `_${hex}_`;
|
|
99
|
-
const paraParent = join(projectDir, '..');
|
|
100
|
-
const paraTempDir = join(paraParent, paraName);
|
|
101
|
-
let isDirWritable = true;
|
|
102
|
-
try {
|
|
103
|
-
const stats = await stat(paraParent);
|
|
104
|
-
if (stats.isDirectory()) {
|
|
105
|
-
await access(paraParent, constants.W_OK);
|
|
106
|
-
} else {
|
|
107
|
-
isDirWritable = false;
|
|
108
|
-
}
|
|
109
|
-
} catch {
|
|
110
|
-
isDirWritable = false;
|
|
111
|
-
}
|
|
112
|
-
if (!isDirWritable) {
|
|
113
|
-
logError('No writable directory to prepare parallel builds. Exiting.');
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
log('Preparing parallel build...');
|
|
117
|
-
const prepStart = performance.now();
|
|
118
|
-
await cp(projectDir, paraTempDir, {
|
|
119
|
-
recursive: true,
|
|
120
|
-
filter: src => !/logs|pids|lib-cov|coverage|bower_components|build|dist|jspm_packages|web_modules|out|\.next|\.tsbuildinfo|\.npm|\.node_repl_history|\.tgz|\.yarn|\.pnp|\.nyc_output|\.grunt|\.DS_Store/i.test(src)
|
|
121
|
-
});
|
|
122
|
-
const dualConfigPath = join(paraTempDir, 'tsconfig.json');
|
|
123
|
-
const absoluteDualOutDir = join(paraTempDir, isCjsBuild ? join(outDir, 'cjs') : join(outDir, 'esm'));
|
|
124
|
-
const tsconfigDual = getOverrideTsConfig();
|
|
125
|
-
await writeFile(dualConfigPath, JSON.stringify(tsconfigDual));
|
|
126
|
-
await writeFile(join(paraTempDir, 'package.json'), JSON.stringify({
|
|
127
|
-
type: isCjsBuild ? 'commonjs' : 'module'
|
|
128
|
-
}));
|
|
129
|
-
log(`Prepared in ${Math.round(performance.now() - prepStart)}ms.`);
|
|
130
|
-
log('Starting parallel dual builds...');
|
|
131
|
-
let success = false;
|
|
132
|
-
const startTime = performance.now();
|
|
133
|
-
try {
|
|
134
|
-
await Promise.all([runPrimaryBuild(), runBuild(dualConfigPath, absoluteDualOutDir)]);
|
|
135
|
-
success = true;
|
|
136
|
-
} catch ({
|
|
137
|
-
message
|
|
138
|
-
}) {
|
|
139
|
-
logError(message);
|
|
140
|
-
}
|
|
141
|
-
if (success) {
|
|
142
|
-
const filenames = await glob(`${absoluteDualOutDir}/**/*{.js,.d.ts}`, {
|
|
143
|
-
ignore: 'node_modules/**'
|
|
144
|
-
});
|
|
145
|
-
await updateSpecifiersAndFileExtensions(filenames);
|
|
146
|
-
await cp(absoluteDualOutDir, join(absoluteOutDir, isCjsBuild ? 'cjs' : 'esm'), {
|
|
147
|
-
recursive: true
|
|
148
|
-
});
|
|
149
|
-
await rm(paraTempDir, {
|
|
150
|
-
force: true,
|
|
151
|
-
recursive: true
|
|
152
|
-
});
|
|
153
|
-
logSuccess(startTime);
|
|
154
|
-
}
|
|
155
|
-
} else {
|
|
156
|
-
log('Starting primary build...');
|
|
157
|
-
let success = false;
|
|
158
|
-
const startTime = performance.now();
|
|
159
|
-
try {
|
|
160
|
-
await runPrimaryBuild();
|
|
161
|
-
success = true;
|
|
162
|
-
} catch ({
|
|
163
|
-
message
|
|
164
|
-
}) {
|
|
165
|
-
logError(message);
|
|
166
|
-
}
|
|
167
|
-
if (success) {
|
|
168
|
-
const dualConfigPath = join(projectDir, `tsconfig.${hex}.json`);
|
|
169
|
-
const absoluteDualOutDir = join(projectDir, isCjsBuild ? join(outDir, 'cjs') : join(outDir, 'esm'));
|
|
170
|
-
const tsconfigDual = getOverrideTsConfig();
|
|
171
|
-
const pkgRename = 'package.json.bak';
|
|
172
|
-
await rename(pkg.path, join(pkgDir, pkgRename));
|
|
173
|
-
await writeFile(pkg.path, JSON.stringify({
|
|
174
|
-
type: isCjsBuild ? 'commonjs' : 'module'
|
|
175
|
-
}));
|
|
176
|
-
await writeFile(dualConfigPath, JSON.stringify(tsconfigDual));
|
|
177
|
-
log('Starting dual build...');
|
|
178
|
-
try {
|
|
179
|
-
await runBuild(dualConfigPath, absoluteDualOutDir);
|
|
180
|
-
} catch ({
|
|
181
|
-
message
|
|
182
|
-
}) {
|
|
183
|
-
success = false;
|
|
184
|
-
logError(message);
|
|
185
|
-
}
|
|
186
|
-
await rm(dualConfigPath, {
|
|
187
|
-
force: true
|
|
188
|
-
});
|
|
189
|
-
await rm(pkg.path, {
|
|
190
|
-
force: true
|
|
191
|
-
});
|
|
192
|
-
await rename(join(pkgDir, pkgRename), pkg.path);
|
|
193
|
-
if (success) {
|
|
194
|
-
const filenames = await glob(`${absoluteDualOutDir}/**/*{.js,.d.ts}`, {
|
|
195
|
-
ignore: 'node_modules/**'
|
|
196
|
-
});
|
|
197
|
-
await updateSpecifiersAndFileExtensions(filenames);
|
|
198
|
-
logSuccess(startTime);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
const realFileUrlArgv1 = await getRealPathAsFileUrl(argv[1]);
|
|
205
|
-
if (import.meta.url === realFileUrlArgv1) {
|
|
206
|
-
await duel();
|
|
207
|
-
}
|
|
208
|
-
export { duel };
|