@knighted/duel 1.0.7 → 2.0.0-rc.0
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/README.md +3 -17
- package/dist/cjs/duel.cjs +76 -131
- package/dist/cjs/init.cjs +1 -8
- package/dist/cjs/{init.d.ts → init.d.cts} +0 -1
- package/dist/cjs/util.cjs +12 -1
- package/dist/cjs/util.d.cts +1 -0
- package/dist/esm/duel.js +78 -133
- package/dist/esm/init.d.ts +0 -1
- package/dist/esm/init.js +1 -8
- package/dist/esm/util.d.ts +1 -0
- package/dist/esm/util.js +11 -1
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -10,11 +10,12 @@ Tool for building a Node.js [dual package](https://nodejs.org/api/packages.html#
|
|
|
10
10
|
|
|
11
11
|
- Bidirectional ESM ↔️ CJS dual builds inferred from the package.json `type`.
|
|
12
12
|
- Correctly preserves module systems for `.mts` and `.cts` file extensions.
|
|
13
|
+
- Resolves the [differences between ES modules and CommonJS](https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs).
|
|
13
14
|
- Use only one package.json and tsconfig.json.
|
|
14
15
|
|
|
15
16
|
## Requirements
|
|
16
17
|
|
|
17
|
-
- Node >=
|
|
18
|
+
- Node >= 20.11.0
|
|
18
19
|
|
|
19
20
|
## Example
|
|
20
21
|
|
|
@@ -67,18 +68,6 @@ If you prefer to have both builds in directories inside of your defined `outDir`
|
|
|
67
68
|
|
|
68
69
|
Assuming an `outDir` of `dist`, running the above will create `dist/esm` and `dist/cjs` directories.
|
|
69
70
|
|
|
70
|
-
### Parallel builds
|
|
71
|
-
|
|
72
|
-
This is experimental, as your mileage may vary based on the size of your `node_modules` directory.
|
|
73
|
-
|
|
74
|
-
```json
|
|
75
|
-
"scripts": {
|
|
76
|
-
"build": "duel --parallel"
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
You _might_ reduce your build times, but only if your project has minimal dependencies. This requires first copying your project to a parent directory of `--project` if it exists as a writable folder. Common [gitignored directories for Node.js projects](https://github.com/github/gitignore/blob/main/Node.gitignore) are not copied, with the exception of `node_modules`. See the [notes](#notes) as to why this can't be improved much further. In most cases, you're better off with serial builds.
|
|
81
|
-
|
|
82
71
|
## Options
|
|
83
72
|
|
|
84
73
|
The available options are limited, because you should define most of them inside your project's `tsconfig.json` file.
|
|
@@ -86,7 +75,6 @@ The available options are limited, because you should define most of them inside
|
|
|
86
75
|
- `--project, -p` The path to the project's configuration file. Defaults to `tsconfig.json`.
|
|
87
76
|
- `--pkg-dir, -k` The directory to start looking for a package.json file. Defaults to the cwd.
|
|
88
77
|
- `--dirs, -d` Outputs both builds to directories inside of `outDir`. Defaults to `false`.
|
|
89
|
-
- `--parallel, -l` Run the builds in parallel. Defaults to `false`.
|
|
90
78
|
|
|
91
79
|
You can run `duel --help` to get the same info. Below is the output of that:
|
|
92
80
|
|
|
@@ -97,7 +85,6 @@ Options:
|
|
|
97
85
|
--project, -p [path] Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.
|
|
98
86
|
--pkg-dir, -k [path] The directory to start looking for a package.json file. Defaults to cwd.
|
|
99
87
|
--dirs, -d Output both builds to directories inside of outDir. [esm, cjs].
|
|
100
|
-
--parallel, -l Run the builds in parallel.
|
|
101
88
|
--help, -h Print this message.
|
|
102
89
|
```
|
|
103
90
|
|
|
@@ -115,5 +102,4 @@ These are definitely edge cases, and would only really come up if your project m
|
|
|
115
102
|
|
|
116
103
|
## Notes
|
|
117
104
|
|
|
118
|
-
As far as I can tell, `duel` is one (if not the only) way to get a correct dual package build using `tsc` with only **one package.json and tsconfig.json file**,
|
|
119
|
-
directory before attempting to run the builds in parallel.
|
|
105
|
+
As far as I can tell, `duel` is one (if not the only) way to get a correct dual package build using `tsc` with only **one package.json and tsconfig.json file**, _while also preserving module system by file extension_. Basically, how you expect things to work. The Microsoft backed TypeScript team [keep](https://github.com/microsoft/TypeScript/pull/54546) [talking](https://github.com/microsoft/TypeScript/issues/54593) about dual build support, but they continue to [refuse to rewrite specifiers](https://github.com/microsoft/TypeScript/issues/16577).
|
package/dist/cjs/duel.cjs
CHANGED
|
@@ -11,29 +11,9 @@ const node_perf_hooks_1 = require("node:perf_hooks");
|
|
|
11
11
|
const glob_1 = require("glob");
|
|
12
12
|
const find_up_1 = require("find-up");
|
|
13
13
|
const specifier_1 = require("@knighted/specifier");
|
|
14
|
+
const module_1 = require("@knighted/module");
|
|
14
15
|
const init_js_1 = require("./init.cjs");
|
|
15
16
|
const util_js_1 = require("./util.cjs");
|
|
16
|
-
const tsc = await (0, find_up_1.findUp)(async (dir) => {
|
|
17
|
-
const tscBin = (0, node_path_1.join)(dir, 'node_modules', '.bin', 'tsc');
|
|
18
|
-
if (await (0, find_up_1.pathExists)(tscBin)) {
|
|
19
|
-
return tscBin;
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
const runBuild = (project, outDir) => {
|
|
23
|
-
return new Promise((resolve, reject) => {
|
|
24
|
-
const args = outDir ? ['-p', project, '--outDir', outDir] : ['-p', project];
|
|
25
|
-
const build = (0, node_child_process_1.spawn)(tsc, args, { stdio: 'inherit' });
|
|
26
|
-
build.on('error', err => {
|
|
27
|
-
reject(new Error(`Failed to compile: ${err.message}`));
|
|
28
|
-
});
|
|
29
|
-
build.on('exit', code => {
|
|
30
|
-
if (code > 0) {
|
|
31
|
-
return reject(new Error(code));
|
|
32
|
-
}
|
|
33
|
-
resolve(code);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
};
|
|
37
17
|
const handleErrorAndExit = message => {
|
|
38
18
|
const exitCode = Number(message);
|
|
39
19
|
if (isNaN(exitCode)) {
|
|
@@ -48,7 +28,28 @@ const handleErrorAndExit = message => {
|
|
|
48
28
|
const duel = async (args) => {
|
|
49
29
|
const ctx = await (0, init_js_1.init)(args);
|
|
50
30
|
if (ctx) {
|
|
51
|
-
const { projectDir, tsconfig, configPath,
|
|
31
|
+
const { projectDir, tsconfig, configPath, dirs, pkg } = ctx;
|
|
32
|
+
const tsc = await (0, find_up_1.findUp)(async (dir) => {
|
|
33
|
+
const tscBin = (0, node_path_1.join)(dir, 'node_modules', '.bin', 'tsc');
|
|
34
|
+
if (await (0, find_up_1.pathExists)(tscBin)) {
|
|
35
|
+
return tscBin;
|
|
36
|
+
}
|
|
37
|
+
}, { cwd: projectDir });
|
|
38
|
+
const runBuild = (project, outDir) => {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const args = outDir ? ['-p', project, '--outDir', outDir] : ['-p', project];
|
|
41
|
+
const build = (0, node_child_process_1.spawn)(tsc, args, { stdio: 'inherit' });
|
|
42
|
+
build.on('error', err => {
|
|
43
|
+
reject(new Error(`Failed to compile: ${err.message}`));
|
|
44
|
+
});
|
|
45
|
+
build.on('exit', code => {
|
|
46
|
+
if (code > 0) {
|
|
47
|
+
return reject(new Error(code));
|
|
48
|
+
}
|
|
49
|
+
resolve(code);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
};
|
|
52
53
|
const pkgDir = (0, node_path_1.dirname)(pkg.path);
|
|
53
54
|
const outDir = tsconfig.compilerOptions?.outDir ?? 'dist';
|
|
54
55
|
const absoluteOutDir = (0, node_path_1.resolve)(projectDir, outDir);
|
|
@@ -97,133 +98,77 @@ const duel = async (args) => {
|
|
|
97
98
|
const logSuccess = start => {
|
|
98
99
|
(0, util_js_1.log)(`Successfully created a dual ${isCjsBuild ? 'CJS' : 'ESM'} build in ${Math.round(node_perf_hooks_1.performance.now() - start)}ms.`);
|
|
99
100
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
isDirWritable = false;
|
|
116
|
-
}
|
|
117
|
-
if (!isDirWritable) {
|
|
118
|
-
(0, util_js_1.logError)('No writable directory to prepare parallel builds. Exiting.');
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
(0, util_js_1.log)('Preparing parallel build...');
|
|
122
|
-
const prepStart = node_perf_hooks_1.performance.now();
|
|
123
|
-
await (0, promises_1.cp)(projectDir, paraTempDir, {
|
|
124
|
-
recursive: true,
|
|
125
|
-
/**
|
|
126
|
-
* Ignore common .gitignored directories in Node.js projects.
|
|
127
|
-
* Except node_modules.
|
|
128
|
-
*
|
|
129
|
-
* @see https://github.com/github/gitignore/blob/main/Node.gitignore
|
|
130
|
-
*/
|
|
131
|
-
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),
|
|
132
|
-
});
|
|
133
|
-
const dualConfigPath = (0, node_path_1.join)(paraTempDir, 'tsconfig.json');
|
|
134
|
-
const absoluteDualOutDir = (0, node_path_1.join)(paraTempDir, isCjsBuild ? (0, node_path_1.join)(outDir, 'cjs') : (0, node_path_1.join)(outDir, 'esm'));
|
|
101
|
+
(0, util_js_1.log)('Starting primary build...');
|
|
102
|
+
let success = false;
|
|
103
|
+
const startTime = node_perf_hooks_1.performance.now();
|
|
104
|
+
try {
|
|
105
|
+
await runPrimaryBuild();
|
|
106
|
+
success = true;
|
|
107
|
+
}
|
|
108
|
+
catch ({ message }) {
|
|
109
|
+
handleErrorAndExit(message);
|
|
110
|
+
}
|
|
111
|
+
if (success) {
|
|
112
|
+
const compileFiles = (0, util_js_1.getCompileFiles)(tsc, projectDir);
|
|
113
|
+
const subDir = (0, node_path_1.join)(projectDir, `_${hex}_`);
|
|
114
|
+
const dualConfigPath = (0, node_path_1.join)(subDir, `tsconfig.${hex}.json`);
|
|
115
|
+
const absoluteDualOutDir = (0, node_path_1.join)(projectDir, isCjsBuild ? (0, node_path_1.join)(outDir, 'cjs') : (0, node_path_1.join)(outDir, 'esm'));
|
|
135
116
|
const tsconfigDual = getOverrideTsConfig();
|
|
136
|
-
|
|
137
|
-
|
|
117
|
+
const pkgRename = 'package.json.bak';
|
|
118
|
+
let errorMsg = '';
|
|
119
|
+
// Copy project directory as a subdirectory
|
|
120
|
+
await (0, promises_1.mkdir)(subDir);
|
|
121
|
+
await Promise.all(compileFiles.map(file => (0, promises_1.cp)(file, (0, node_path_1.resolve)(subDir, (0, node_path_1.relative)(projectDir, file)))));
|
|
122
|
+
/**
|
|
123
|
+
* Create a new package.json with updated `type` field.
|
|
124
|
+
* Create a new tsconfig.json.
|
|
125
|
+
*/
|
|
126
|
+
await (0, promises_1.rename)(pkg.path, (0, node_path_1.join)(pkgDir, pkgRename));
|
|
127
|
+
await (0, promises_1.writeFile)(pkg.path, JSON.stringify({
|
|
138
128
|
type: isCjsBuild ? 'commonjs' : 'module',
|
|
139
129
|
}));
|
|
140
|
-
(0,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
130
|
+
await (0, promises_1.writeFile)(dualConfigPath, JSON.stringify(tsconfigDual));
|
|
131
|
+
/**
|
|
132
|
+
* Transform ambiguous modules for the target dual build.
|
|
133
|
+
* @see https://github.com/microsoft/TypeScript/issues/58658
|
|
134
|
+
*/
|
|
135
|
+
const toTransform = await (0, glob_1.glob)(`${subDir}/**/*{.js,.jsx,.ts,.tsx}`, {
|
|
136
|
+
ignore: 'node_modules/**',
|
|
137
|
+
});
|
|
138
|
+
for (const file of toTransform) {
|
|
139
|
+
await (0, module_1.transform)(file, { out: file, type: isCjsBuild ? 'commonjs' : 'module' });
|
|
140
|
+
}
|
|
141
|
+
// Build dual
|
|
142
|
+
(0, util_js_1.log)('Starting dual build...');
|
|
144
143
|
try {
|
|
145
|
-
await
|
|
146
|
-
runPrimaryBuild(),
|
|
147
|
-
runBuild(dualConfigPath, absoluteDualOutDir),
|
|
148
|
-
]);
|
|
149
|
-
success = true;
|
|
144
|
+
await runBuild(dualConfigPath, absoluteDualOutDir);
|
|
150
145
|
}
|
|
151
146
|
catch ({ message }) {
|
|
152
|
-
|
|
147
|
+
success = false;
|
|
148
|
+
errorMsg = message;
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
// Cleanup and restore
|
|
152
|
+
await (0, promises_1.rm)(dualConfigPath, { force: true });
|
|
153
|
+
await (0, promises_1.rm)(pkg.path, { force: true });
|
|
154
|
+
await (0, promises_1.rm)(subDir, { force: true, recursive: true });
|
|
155
|
+
await (0, promises_1.rename)((0, node_path_1.join)(pkgDir, pkgRename), pkg.path);
|
|
156
|
+
if (errorMsg) {
|
|
157
|
+
handleErrorAndExit(errorMsg);
|
|
158
|
+
}
|
|
153
159
|
}
|
|
154
160
|
if (success) {
|
|
155
161
|
const filenames = await (0, glob_1.glob)(`${absoluteDualOutDir}/**/*{.js,.d.ts}`, {
|
|
156
162
|
ignore: 'node_modules/**',
|
|
157
163
|
});
|
|
158
164
|
await updateSpecifiersAndFileExtensions(filenames);
|
|
159
|
-
// Copy over and cleanup
|
|
160
|
-
await (0, promises_1.cp)(absoluteDualOutDir, (0, node_path_1.join)(absoluteOutDir, isCjsBuild ? 'cjs' : 'esm'), {
|
|
161
|
-
recursive: true,
|
|
162
|
-
});
|
|
163
|
-
await (0, promises_1.rm)(paraTempDir, { force: true, recursive: true });
|
|
164
165
|
logSuccess(startTime);
|
|
165
166
|
}
|
|
166
167
|
}
|
|
167
|
-
else {
|
|
168
|
-
(0, util_js_1.log)('Starting primary build...');
|
|
169
|
-
let success = false;
|
|
170
|
-
const startTime = node_perf_hooks_1.performance.now();
|
|
171
|
-
try {
|
|
172
|
-
await runPrimaryBuild();
|
|
173
|
-
success = true;
|
|
174
|
-
}
|
|
175
|
-
catch ({ message }) {
|
|
176
|
-
handleErrorAndExit(message);
|
|
177
|
-
}
|
|
178
|
-
if (success) {
|
|
179
|
-
const dualConfigPath = (0, node_path_1.join)(projectDir, `tsconfig.${hex}.json`);
|
|
180
|
-
const absoluteDualOutDir = (0, node_path_1.join)(projectDir, isCjsBuild ? (0, node_path_1.join)(outDir, 'cjs') : (0, node_path_1.join)(outDir, 'esm'));
|
|
181
|
-
const tsconfigDual = getOverrideTsConfig();
|
|
182
|
-
const pkgRename = 'package.json.bak';
|
|
183
|
-
let errorMsg = '';
|
|
184
|
-
/**
|
|
185
|
-
* Create a new package.json with updated `type` field.
|
|
186
|
-
* Create a new tsconfig.json.
|
|
187
|
-
*
|
|
188
|
-
* The need to create a new package.json makes doing
|
|
189
|
-
* the builds in parallel difficult.
|
|
190
|
-
*/
|
|
191
|
-
await (0, promises_1.rename)(pkg.path, (0, node_path_1.join)(pkgDir, pkgRename));
|
|
192
|
-
await (0, promises_1.writeFile)(pkg.path, JSON.stringify({
|
|
193
|
-
type: isCjsBuild ? 'commonjs' : 'module',
|
|
194
|
-
}));
|
|
195
|
-
await (0, promises_1.writeFile)(dualConfigPath, JSON.stringify(tsconfigDual));
|
|
196
|
-
// Build dual
|
|
197
|
-
(0, util_js_1.log)('Starting dual build...');
|
|
198
|
-
try {
|
|
199
|
-
await runBuild(dualConfigPath, absoluteDualOutDir);
|
|
200
|
-
}
|
|
201
|
-
catch ({ message }) {
|
|
202
|
-
success = false;
|
|
203
|
-
errorMsg = message;
|
|
204
|
-
}
|
|
205
|
-
finally {
|
|
206
|
-
// Cleanup and restore
|
|
207
|
-
await (0, promises_1.rm)(dualConfigPath, { force: true });
|
|
208
|
-
await (0, promises_1.rm)(pkg.path, { force: true });
|
|
209
|
-
await (0, promises_1.rename)((0, node_path_1.join)(pkgDir, pkgRename), pkg.path);
|
|
210
|
-
if (errorMsg) {
|
|
211
|
-
handleErrorAndExit(errorMsg);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
if (success) {
|
|
215
|
-
const filenames = await (0, glob_1.glob)(`${absoluteDualOutDir}/**/*{.js,.d.ts}`, {
|
|
216
|
-
ignore: 'node_modules/**',
|
|
217
|
-
});
|
|
218
|
-
await updateSpecifiersAndFileExtensions(filenames);
|
|
219
|
-
logSuccess(startTime);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
168
|
}
|
|
224
169
|
};
|
|
225
170
|
exports.duel = duel;
|
|
226
171
|
const realFileUrlArgv1 = await (0, util_js_1.getRealPathAsFileUrl)(node_process_1.argv[1]);
|
|
227
|
-
if (
|
|
172
|
+
if (require("node:url").pathToFileURL(__filename).toString() === realFileUrlArgv1) {
|
|
228
173
|
await duel();
|
|
229
174
|
}
|
package/dist/cjs/init.cjs
CHANGED
|
@@ -32,11 +32,6 @@ const init = async (args) => {
|
|
|
32
32
|
short: 'k',
|
|
33
33
|
default: (0, node_process_1.cwd)(),
|
|
34
34
|
},
|
|
35
|
-
parallel: {
|
|
36
|
-
type: 'boolean',
|
|
37
|
-
short: 'l',
|
|
38
|
-
default: false,
|
|
39
|
-
},
|
|
40
35
|
dirs: {
|
|
41
36
|
type: 'boolean',
|
|
42
37
|
short: 'd',
|
|
@@ -61,11 +56,10 @@ const init = async (args) => {
|
|
|
61
56
|
(0, util_js_1.log)("--project, -p [path] \t Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.");
|
|
62
57
|
(0, util_js_1.log)('--pkg-dir, -k [path] \t The directory to start looking for a package.json file. Defaults to cwd.');
|
|
63
58
|
(0, util_js_1.log)('--dirs, -d \t\t Output both builds to directories inside of outDir. [esm, cjs].');
|
|
64
|
-
(0, util_js_1.log)('--parallel, -l \t\t Run the builds in parallel.');
|
|
65
59
|
(0, util_js_1.log)('--help, -h \t\t Print this message.');
|
|
66
60
|
}
|
|
67
61
|
else {
|
|
68
|
-
const { project, 'target-extension': targetExt, 'pkg-dir': pkgDir,
|
|
62
|
+
const { project, 'target-extension': targetExt, 'pkg-dir': pkgDir, dirs } = parsed;
|
|
69
63
|
let configPath = (0, node_path_1.resolve)(project);
|
|
70
64
|
let stats = null;
|
|
71
65
|
let pkg = null;
|
|
@@ -114,7 +108,6 @@ const init = async (args) => {
|
|
|
114
108
|
return {
|
|
115
109
|
pkg,
|
|
116
110
|
dirs,
|
|
117
|
-
parallel,
|
|
118
111
|
tsconfig,
|
|
119
112
|
projectDir,
|
|
120
113
|
configPath,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export function init(args: any): Promise<false | {
|
|
2
2
|
pkg: import("read-package-up", { with: { "resolution-mode": "import" } }).NormalizedReadResult;
|
|
3
3
|
dirs: boolean | undefined;
|
|
4
|
-
parallel: boolean | undefined;
|
|
5
4
|
tsconfig: any;
|
|
6
5
|
projectDir: string;
|
|
7
6
|
configPath: string;
|
package/dist/cjs/util.cjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getRealPathAsFileUrl = exports.logError = exports.log = void 0;
|
|
3
|
+
exports.getCompileFiles = exports.getRealPathAsFileUrl = exports.logError = exports.log = void 0;
|
|
4
4
|
const node_url_1 = require("node:url");
|
|
5
5
|
const promises_1 = require("node:fs/promises");
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
const node_process_1 = require("node:process");
|
|
6
8
|
const log = (color = '\x1b[30m', msg = '') => {
|
|
7
9
|
// eslint-disable-next-line no-console
|
|
8
10
|
console.log(`${color}%s\x1b[0m`, msg);
|
|
@@ -16,3 +18,12 @@ const getRealPathAsFileUrl = async (path) => {
|
|
|
16
18
|
return asFileUrl;
|
|
17
19
|
};
|
|
18
20
|
exports.getRealPathAsFileUrl = getRealPathAsFileUrl;
|
|
21
|
+
const getCompileFiles = (tscBinPath, wd = (0, node_process_1.cwd)()) => {
|
|
22
|
+
const { stdout } = (0, node_child_process_1.spawnSync)(tscBinPath, ['--listFilesOnly'], { cwd: wd });
|
|
23
|
+
// Exclude node_modules and empty strings.
|
|
24
|
+
return stdout
|
|
25
|
+
.toString()
|
|
26
|
+
.split('\n')
|
|
27
|
+
.filter(path => !/node_modules|^$/.test(path));
|
|
28
|
+
};
|
|
29
|
+
exports.getCompileFiles = getCompileFiles;
|
package/dist/cjs/util.d.cts
CHANGED
package/dist/esm/duel.js
CHANGED
|
@@ -1,36 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { argv } from 'node:process';
|
|
3
|
-
import { join, dirname, resolve } from 'node:path';
|
|
3
|
+
import { join, dirname, resolve, relative } from 'node:path';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
|
-
import { writeFile, rm,
|
|
5
|
+
import { writeFile, rm, rename, cp, mkdir } from 'node:fs/promises';
|
|
6
6
|
import { randomBytes } from 'node:crypto';
|
|
7
7
|
import { performance } from 'node:perf_hooks';
|
|
8
8
|
import { glob } from 'glob';
|
|
9
9
|
import { findUp, pathExists } from 'find-up';
|
|
10
10
|
import { specifier } from '@knighted/specifier';
|
|
11
|
+
import { transform } from '@knighted/module';
|
|
11
12
|
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, { stdio: 'inherit' });
|
|
23
|
-
build.on('error', err => {
|
|
24
|
-
reject(new Error(`Failed to compile: ${err.message}`));
|
|
25
|
-
});
|
|
26
|
-
build.on('exit', code => {
|
|
27
|
-
if (code > 0) {
|
|
28
|
-
return reject(new Error(code));
|
|
29
|
-
}
|
|
30
|
-
resolve(code);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
};
|
|
13
|
+
import { getRealPathAsFileUrl, getCompileFiles, logError, log } from './util.js';
|
|
34
14
|
const handleErrorAndExit = message => {
|
|
35
15
|
const exitCode = Number(message);
|
|
36
16
|
if (isNaN(exitCode)) {
|
|
@@ -45,7 +25,28 @@ const handleErrorAndExit = message => {
|
|
|
45
25
|
const duel = async (args) => {
|
|
46
26
|
const ctx = await init(args);
|
|
47
27
|
if (ctx) {
|
|
48
|
-
const { projectDir, tsconfig, configPath,
|
|
28
|
+
const { projectDir, tsconfig, configPath, dirs, pkg } = ctx;
|
|
29
|
+
const tsc = await findUp(async (dir) => {
|
|
30
|
+
const tscBin = join(dir, 'node_modules', '.bin', 'tsc');
|
|
31
|
+
if (await pathExists(tscBin)) {
|
|
32
|
+
return tscBin;
|
|
33
|
+
}
|
|
34
|
+
}, { cwd: projectDir });
|
|
35
|
+
const runBuild = (project, outDir) => {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const args = outDir ? ['-p', project, '--outDir', outDir] : ['-p', project];
|
|
38
|
+
const build = spawn(tsc, args, { stdio: 'inherit' });
|
|
39
|
+
build.on('error', err => {
|
|
40
|
+
reject(new Error(`Failed to compile: ${err.message}`));
|
|
41
|
+
});
|
|
42
|
+
build.on('exit', code => {
|
|
43
|
+
if (code > 0) {
|
|
44
|
+
return reject(new Error(code));
|
|
45
|
+
}
|
|
46
|
+
resolve(code);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
};
|
|
49
50
|
const pkgDir = dirname(pkg.path);
|
|
50
51
|
const outDir = tsconfig.compilerOptions?.outDir ?? 'dist';
|
|
51
52
|
const absoluteOutDir = resolve(projectDir, outDir);
|
|
@@ -94,129 +95,73 @@ const duel = async (args) => {
|
|
|
94
95
|
const logSuccess = start => {
|
|
95
96
|
log(`Successfully created a dual ${isCjsBuild ? 'CJS' : 'ESM'} build in ${Math.round(performance.now() - start)}ms.`);
|
|
96
97
|
};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
isDirWritable = false;
|
|
113
|
-
}
|
|
114
|
-
if (!isDirWritable) {
|
|
115
|
-
logError('No writable directory to prepare parallel builds. Exiting.');
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
log('Preparing parallel build...');
|
|
119
|
-
const prepStart = performance.now();
|
|
120
|
-
await cp(projectDir, paraTempDir, {
|
|
121
|
-
recursive: true,
|
|
122
|
-
/**
|
|
123
|
-
* Ignore common .gitignored directories in Node.js projects.
|
|
124
|
-
* Except node_modules.
|
|
125
|
-
*
|
|
126
|
-
* @see https://github.com/github/gitignore/blob/main/Node.gitignore
|
|
127
|
-
*/
|
|
128
|
-
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),
|
|
129
|
-
});
|
|
130
|
-
const dualConfigPath = join(paraTempDir, 'tsconfig.json');
|
|
131
|
-
const absoluteDualOutDir = join(paraTempDir, isCjsBuild ? join(outDir, 'cjs') : join(outDir, 'esm'));
|
|
98
|
+
log('Starting primary build...');
|
|
99
|
+
let success = false;
|
|
100
|
+
const startTime = performance.now();
|
|
101
|
+
try {
|
|
102
|
+
await runPrimaryBuild();
|
|
103
|
+
success = true;
|
|
104
|
+
}
|
|
105
|
+
catch ({ message }) {
|
|
106
|
+
handleErrorAndExit(message);
|
|
107
|
+
}
|
|
108
|
+
if (success) {
|
|
109
|
+
const compileFiles = getCompileFiles(tsc, projectDir);
|
|
110
|
+
const subDir = join(projectDir, `_${hex}_`);
|
|
111
|
+
const dualConfigPath = join(subDir, `tsconfig.${hex}.json`);
|
|
112
|
+
const absoluteDualOutDir = join(projectDir, isCjsBuild ? join(outDir, 'cjs') : join(outDir, 'esm'));
|
|
132
113
|
const tsconfigDual = getOverrideTsConfig();
|
|
133
|
-
|
|
134
|
-
|
|
114
|
+
const pkgRename = 'package.json.bak';
|
|
115
|
+
let errorMsg = '';
|
|
116
|
+
// Copy project directory as a subdirectory
|
|
117
|
+
await mkdir(subDir);
|
|
118
|
+
await Promise.all(compileFiles.map(file => cp(file, resolve(subDir, relative(projectDir, file)))));
|
|
119
|
+
/**
|
|
120
|
+
* Create a new package.json with updated `type` field.
|
|
121
|
+
* Create a new tsconfig.json.
|
|
122
|
+
*/
|
|
123
|
+
await rename(pkg.path, join(pkgDir, pkgRename));
|
|
124
|
+
await writeFile(pkg.path, JSON.stringify({
|
|
135
125
|
type: isCjsBuild ? 'commonjs' : 'module',
|
|
136
126
|
}));
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
127
|
+
await writeFile(dualConfigPath, JSON.stringify(tsconfigDual));
|
|
128
|
+
/**
|
|
129
|
+
* Transform ambiguous modules for the target dual build.
|
|
130
|
+
* @see https://github.com/microsoft/TypeScript/issues/58658
|
|
131
|
+
*/
|
|
132
|
+
const toTransform = await glob(`${subDir}/**/*{.js,.jsx,.ts,.tsx}`, {
|
|
133
|
+
ignore: 'node_modules/**',
|
|
134
|
+
});
|
|
135
|
+
for (const file of toTransform) {
|
|
136
|
+
await transform(file, { out: file, type: isCjsBuild ? 'commonjs' : 'module' });
|
|
137
|
+
}
|
|
138
|
+
// Build dual
|
|
139
|
+
log('Starting dual build...');
|
|
141
140
|
try {
|
|
142
|
-
await
|
|
143
|
-
runPrimaryBuild(),
|
|
144
|
-
runBuild(dualConfigPath, absoluteDualOutDir),
|
|
145
|
-
]);
|
|
146
|
-
success = true;
|
|
141
|
+
await runBuild(dualConfigPath, absoluteDualOutDir);
|
|
147
142
|
}
|
|
148
143
|
catch ({ message }) {
|
|
149
|
-
|
|
144
|
+
success = false;
|
|
145
|
+
errorMsg = message;
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
// Cleanup and restore
|
|
149
|
+
await rm(dualConfigPath, { force: true });
|
|
150
|
+
await rm(pkg.path, { force: true });
|
|
151
|
+
await rm(subDir, { force: true, recursive: true });
|
|
152
|
+
await rename(join(pkgDir, pkgRename), pkg.path);
|
|
153
|
+
if (errorMsg) {
|
|
154
|
+
handleErrorAndExit(errorMsg);
|
|
155
|
+
}
|
|
150
156
|
}
|
|
151
157
|
if (success) {
|
|
152
158
|
const filenames = await glob(`${absoluteDualOutDir}/**/*{.js,.d.ts}`, {
|
|
153
159
|
ignore: 'node_modules/**',
|
|
154
160
|
});
|
|
155
161
|
await updateSpecifiersAndFileExtensions(filenames);
|
|
156
|
-
// Copy over and cleanup
|
|
157
|
-
await cp(absoluteDualOutDir, join(absoluteOutDir, isCjsBuild ? 'cjs' : 'esm'), {
|
|
158
|
-
recursive: true,
|
|
159
|
-
});
|
|
160
|
-
await rm(paraTempDir, { force: true, recursive: true });
|
|
161
162
|
logSuccess(startTime);
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
|
-
else {
|
|
165
|
-
log('Starting primary build...');
|
|
166
|
-
let success = false;
|
|
167
|
-
const startTime = performance.now();
|
|
168
|
-
try {
|
|
169
|
-
await runPrimaryBuild();
|
|
170
|
-
success = true;
|
|
171
|
-
}
|
|
172
|
-
catch ({ message }) {
|
|
173
|
-
handleErrorAndExit(message);
|
|
174
|
-
}
|
|
175
|
-
if (success) {
|
|
176
|
-
const dualConfigPath = join(projectDir, `tsconfig.${hex}.json`);
|
|
177
|
-
const absoluteDualOutDir = join(projectDir, isCjsBuild ? join(outDir, 'cjs') : join(outDir, 'esm'));
|
|
178
|
-
const tsconfigDual = getOverrideTsConfig();
|
|
179
|
-
const pkgRename = 'package.json.bak';
|
|
180
|
-
let errorMsg = '';
|
|
181
|
-
/**
|
|
182
|
-
* Create a new package.json with updated `type` field.
|
|
183
|
-
* Create a new tsconfig.json.
|
|
184
|
-
*
|
|
185
|
-
* The need to create a new package.json makes doing
|
|
186
|
-
* the builds in parallel difficult.
|
|
187
|
-
*/
|
|
188
|
-
await rename(pkg.path, join(pkgDir, pkgRename));
|
|
189
|
-
await writeFile(pkg.path, JSON.stringify({
|
|
190
|
-
type: isCjsBuild ? 'commonjs' : 'module',
|
|
191
|
-
}));
|
|
192
|
-
await writeFile(dualConfigPath, JSON.stringify(tsconfigDual));
|
|
193
|
-
// Build dual
|
|
194
|
-
log('Starting dual build...');
|
|
195
|
-
try {
|
|
196
|
-
await runBuild(dualConfigPath, absoluteDualOutDir);
|
|
197
|
-
}
|
|
198
|
-
catch ({ message }) {
|
|
199
|
-
success = false;
|
|
200
|
-
errorMsg = message;
|
|
201
|
-
}
|
|
202
|
-
finally {
|
|
203
|
-
// Cleanup and restore
|
|
204
|
-
await rm(dualConfigPath, { force: true });
|
|
205
|
-
await rm(pkg.path, { force: true });
|
|
206
|
-
await rename(join(pkgDir, pkgRename), pkg.path);
|
|
207
|
-
if (errorMsg) {
|
|
208
|
-
handleErrorAndExit(errorMsg);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (success) {
|
|
212
|
-
const filenames = await glob(`${absoluteDualOutDir}/**/*{.js,.d.ts}`, {
|
|
213
|
-
ignore: 'node_modules/**',
|
|
214
|
-
});
|
|
215
|
-
await updateSpecifiersAndFileExtensions(filenames);
|
|
216
|
-
logSuccess(startTime);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
165
|
}
|
|
221
166
|
};
|
|
222
167
|
const realFileUrlArgv1 = await getRealPathAsFileUrl(argv[1]);
|
package/dist/esm/init.d.ts
CHANGED
package/dist/esm/init.js
CHANGED
|
@@ -26,11 +26,6 @@ const init = async (args) => {
|
|
|
26
26
|
short: 'k',
|
|
27
27
|
default: cwd(),
|
|
28
28
|
},
|
|
29
|
-
parallel: {
|
|
30
|
-
type: 'boolean',
|
|
31
|
-
short: 'l',
|
|
32
|
-
default: false,
|
|
33
|
-
},
|
|
34
29
|
dirs: {
|
|
35
30
|
type: 'boolean',
|
|
36
31
|
short: 'd',
|
|
@@ -55,11 +50,10 @@ const init = async (args) => {
|
|
|
55
50
|
log("--project, -p [path] \t Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.");
|
|
56
51
|
log('--pkg-dir, -k [path] \t The directory to start looking for a package.json file. Defaults to cwd.');
|
|
57
52
|
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
53
|
log('--help, -h \t\t Print this message.');
|
|
60
54
|
}
|
|
61
55
|
else {
|
|
62
|
-
const { project, 'target-extension': targetExt, 'pkg-dir': pkgDir,
|
|
56
|
+
const { project, 'target-extension': targetExt, 'pkg-dir': pkgDir, dirs } = parsed;
|
|
63
57
|
let configPath = resolve(project);
|
|
64
58
|
let stats = null;
|
|
65
59
|
let pkg = null;
|
|
@@ -108,7 +102,6 @@ const init = async (args) => {
|
|
|
108
102
|
return {
|
|
109
103
|
pkg,
|
|
110
104
|
dirs,
|
|
111
|
-
parallel,
|
|
112
105
|
tsconfig,
|
|
113
106
|
projectDir,
|
|
114
107
|
configPath,
|
package/dist/esm/util.d.ts
CHANGED
package/dist/esm/util.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { pathToFileURL } from 'node:url';
|
|
2
2
|
import { realpath } from 'node:fs/promises';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { cwd } from 'node:process';
|
|
3
5
|
const log = (color = '\x1b[30m', msg = '') => {
|
|
4
6
|
// eslint-disable-next-line no-console
|
|
5
7
|
console.log(`${color}%s\x1b[0m`, msg);
|
|
@@ -10,4 +12,12 @@ const getRealPathAsFileUrl = async (path) => {
|
|
|
10
12
|
const asFileUrl = pathToFileURL(realPath).href;
|
|
11
13
|
return asFileUrl;
|
|
12
14
|
};
|
|
13
|
-
|
|
15
|
+
const getCompileFiles = (tscBinPath, wd = cwd()) => {
|
|
16
|
+
const { stdout } = spawnSync(tscBinPath, ['--listFilesOnly'], { cwd: wd });
|
|
17
|
+
// Exclude node_modules and empty strings.
|
|
18
|
+
return stdout
|
|
19
|
+
.toString()
|
|
20
|
+
.split('\n')
|
|
21
|
+
.filter(path => !/node_modules|^$/.test(path));
|
|
22
|
+
};
|
|
23
|
+
export { log, logError, getRealPathAsFileUrl, getCompileFiles };
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knighted/duel",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-rc.0",
|
|
4
4
|
"description": "TypeScript dual packages.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist",
|
|
6
|
+
"main": "dist/esm/duel.js",
|
|
7
7
|
"bin": "dist/esm/duel.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"./package.json": "./package.json"
|
|
15
15
|
},
|
|
16
16
|
"engines": {
|
|
17
|
-
"node": ">=
|
|
17
|
+
"node": ">=20.11.0"
|
|
18
18
|
},
|
|
19
19
|
"engineStrict": true,
|
|
20
20
|
"scripts": {
|
|
@@ -51,19 +51,20 @@
|
|
|
51
51
|
"url": "https://github.com/knightedcodemonkey/duel/issues"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"typescript": ">=4.0.0 || >=4.9.0-dev || >=5.3.0-dev || 5.4.0-dev || 5.5.0-dev"
|
|
54
|
+
"typescript": ">=4.0.0 || >=4.9.0-dev || >=5.3.0-dev || >=5.4.0-dev || >=5.5.0-dev || next"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@types/node": "^20.
|
|
57
|
+
"@types/node": "^20.11.0",
|
|
58
58
|
"c8": "^8.0.1",
|
|
59
59
|
"eslint": "^8.45.0",
|
|
60
60
|
"eslint-plugin-n": "^16.0.1",
|
|
61
61
|
"prettier": "^3.2.4",
|
|
62
|
-
"typescript": "^5.5.0-dev.
|
|
63
|
-
"vite": "^5.
|
|
62
|
+
"typescript": "^5.5.0-dev.20240525",
|
|
63
|
+
"vite": "^5.2.8"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@knighted/
|
|
66
|
+
"@knighted/module": "^1.0.0-alpha.3",
|
|
67
|
+
"@knighted/specifier": "^2.0.0-rc.1",
|
|
67
68
|
"find-up": "^6.3.0",
|
|
68
69
|
"glob": "^10.3.3",
|
|
69
70
|
"jsonc-parser": "^3.2.0",
|