@knighted/duel 4.0.0-rc.0 → 4.0.0-rc.2
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 +7 -1
- package/dist/cjs/duel.cjs +287 -93
- package/dist/cjs/init.cjs +2 -1
- package/dist/cjs/init.d.cts +23 -14
- package/dist/cjs/util.cjs +84 -3
- package/dist/cjs/util.d.cts +15 -4
- package/dist/esm/duel.js +290 -96
- package/dist/esm/init.js +2 -1
- package/dist/esm/util.d.ts +9 -0
- package/dist/esm/util.js +84 -5
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
Tool for building a Node.js [dual package](https://nodejs.org/api/packages.html#dual-commonjses-module-packages) with TypeScript. Supports CommonJS and ES module projects.
|
|
8
8
|
|
|
9
|
+
> [!NOTE]
|
|
10
|
+
> I wish this tool were unnecessary, but dual emit was declared out of scope by the TypeScript team, so `duel` exists to fill that gap.
|
|
11
|
+
|
|
9
12
|
## Features
|
|
10
13
|
|
|
11
14
|
- Bidirectional ESM ↔️ CJS dual builds inferred from the package.json `type`.
|
|
@@ -60,6 +63,9 @@ It should work similarly for a CJS-first project. Except, your package.json file
|
|
|
60
63
|
> [!IMPORTANT]
|
|
61
64
|
> This works best if your CJS-first project uses file extensions in _relative_ specifiers. That is acceptable in CJS and [required in ESM](https://nodejs.org/api/esm.html#import-specifiers). `duel` does not rewrite bare specifiers or remap relative specifiers to directory indexes.
|
|
62
65
|
|
|
66
|
+
> [!TIP]
|
|
67
|
+
> `duel` creates a hash-named temp workspace (`.duel-cache/_duel_<hash>_`) inside your project during a build. The `_duel_<hash>_` temp directory is removed on success/failure unless `DUEL_KEEP_TEMP=1` is set. The `.duel-cache/` folder itself (which also holds incremental caches) is not automatically deleted—add it to your `.gitignore`. If a temp folder is ever left behind (e.g., abrupt kill), it is safe to delete.
|
|
68
|
+
|
|
63
69
|
### Build orientation
|
|
64
70
|
|
|
65
71
|
`duel` infers the primary vs dual build orientation from your `package.json` `type`:
|
|
@@ -84,7 +90,7 @@ Assuming an `outDir` of `dist`, running the above will create `dist/esm` and `di
|
|
|
84
90
|
`tsc` is asymmetric: `import.meta` globals fail in a CJS-targeted build, but CommonJS globals like `__filename`/`__dirname` pass when targeting ESM, causing runtime errors in the compiled output. See [TypeScript#58658](https://github.com/microsoft/TypeScript/issues/58658). Use `--mode` to mitigate:
|
|
85
91
|
|
|
86
92
|
- `--mode globals` [rewrites module globals](https://github.com/knightedcodemonkey/module/blob/main/docs/globals-only.md#rewrites-at-a-glance).
|
|
87
|
-
- `--mode full` adds syntax lowering _in addition to_ the globals rewrite.
|
|
93
|
+
- `--mode full` adds syntax lowering _in addition to_ the globals rewrite. TS sources keep a pre-`tsc` guard (`transformSyntax: "globals-only"`) so TypeScript controls declaration emit; JS/JSX and the dual CJS rewrite path are fully lowered. See the [mode matrix](docs/mode-matrix.md) for details.
|
|
88
94
|
|
|
89
95
|
```json
|
|
90
96
|
"scripts": {
|
package/dist/cjs/duel.cjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.duel = void 0;
|
|
5
5
|
const node_process_1 = require("node:process");
|
|
6
|
+
const node_url_1 = require("node:url");
|
|
6
7
|
const node_path_1 = require("node:path");
|
|
7
8
|
const node_child_process_1 = require("node:child_process");
|
|
8
9
|
const promises_1 = require("node:fs/promises");
|
|
@@ -53,7 +54,7 @@ const duel = async (args) => {
|
|
|
53
54
|
/* continue */
|
|
54
55
|
}
|
|
55
56
|
}, { cwd: projectDir });
|
|
56
|
-
const runBuild = (project, outDir) => {
|
|
57
|
+
const runBuild = (project, outDir, tsBuildInfoFile, cwdForBuild) => {
|
|
57
58
|
return new Promise((fulfill, rejectBuild) => {
|
|
58
59
|
const useBuildMode = hasReferences;
|
|
59
60
|
const tsArgs = useBuildMode
|
|
@@ -61,7 +62,16 @@ const duel = async (args) => {
|
|
|
61
62
|
: outDir
|
|
62
63
|
? [tsc, '-p', project, '--outDir', outDir]
|
|
63
64
|
: [tsc, '-p', project];
|
|
64
|
-
|
|
65
|
+
if (!useBuildMode) {
|
|
66
|
+
tsArgs.push('--incremental');
|
|
67
|
+
if (tsBuildInfoFile) {
|
|
68
|
+
tsArgs.push('--tsBuildInfoFile', tsBuildInfoFile);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const build = (0, node_child_process_1.spawn)(process.execPath, tsArgs, {
|
|
72
|
+
stdio: 'inherit',
|
|
73
|
+
cwd: cwdForBuild ?? process.cwd(),
|
|
74
|
+
});
|
|
65
75
|
build.on('exit', code => {
|
|
66
76
|
if (code > 0) {
|
|
67
77
|
return rejectBuild(new Error(code));
|
|
@@ -77,27 +87,94 @@ const duel = async (args) => {
|
|
|
77
87
|
const absoluteOutDir = (0, node_path_1.resolve)(projectDir, outDir);
|
|
78
88
|
const originalType = pkg.packageJson.type ?? 'commonjs';
|
|
79
89
|
const isCjsBuild = originalType !== 'commonjs';
|
|
90
|
+
const absoluteDualOutDir = (0, node_path_1.join)(projectDir, isCjsBuild ? (0, node_path_1.join)(outDir, 'cjs') : (0, node_path_1.join)(outDir, 'esm'));
|
|
91
|
+
const projectRoot = (0, node_path_1.dirname)(projectDir);
|
|
80
92
|
const primaryOutDir = dirs
|
|
81
93
|
? isCjsBuild
|
|
82
94
|
? (0, node_path_1.join)(absoluteOutDir, 'esm')
|
|
83
95
|
: (0, node_path_1.join)(absoluteOutDir, 'cjs')
|
|
84
96
|
: absoluteOutDir;
|
|
85
|
-
const
|
|
97
|
+
const { type, exports, imports, main, module, types, typings, typesVersions, sideEffects, } = pkg.packageJson ?? {};
|
|
98
|
+
const pkgHashInputs = {
|
|
99
|
+
type,
|
|
100
|
+
exports,
|
|
101
|
+
imports,
|
|
102
|
+
main,
|
|
103
|
+
module,
|
|
104
|
+
types,
|
|
105
|
+
typings,
|
|
106
|
+
typesVersions,
|
|
107
|
+
sideEffects,
|
|
108
|
+
};
|
|
109
|
+
const hash = (0, node_crypto_1.createHash)('sha1')
|
|
110
|
+
.update(JSON.stringify({
|
|
111
|
+
configPath,
|
|
112
|
+
tsconfig,
|
|
113
|
+
packageJson: pkgHashInputs,
|
|
114
|
+
dualTarget: isCjsBuild ? 'cjs' : 'esm',
|
|
115
|
+
}))
|
|
116
|
+
.digest('hex')
|
|
117
|
+
.slice(0, 8);
|
|
118
|
+
const cacheDir = (0, node_path_1.join)(projectDir, '.duel-cache');
|
|
119
|
+
const primaryTsBuildInfoFile = (0, node_path_1.join)(cacheDir, `primary.${hash}.tsbuildinfo`);
|
|
120
|
+
const dualTsBuildInfoFile = (0, node_path_1.join)(cacheDir, `dual.${hash}.tsbuildinfo`);
|
|
121
|
+
const subDir = (0, node_path_1.join)(cacheDir, `_duel_${hash}_`);
|
|
122
|
+
const shadowDualOutDir = (0, node_path_1.join)(subDir, (0, node_path_1.relative)(projectRoot, absoluteDualOutDir));
|
|
86
123
|
const hazardMode = detectDualPackageHazard ?? 'warn';
|
|
87
124
|
const hazardScope = dualPackageHazardScope ?? 'file';
|
|
88
|
-
|
|
125
|
+
function mapReferencesToShadow(references = [], options) {
|
|
126
|
+
const { resolveRefPath, toShadowPathFn, fromDir } = options;
|
|
127
|
+
return references.map(ref => {
|
|
128
|
+
if (!ref?.path)
|
|
129
|
+
return ref;
|
|
130
|
+
const refAbs = resolveRefPath(ref.path);
|
|
131
|
+
const shadowRef = toShadowPathFn(refAbs);
|
|
132
|
+
return {
|
|
133
|
+
...ref,
|
|
134
|
+
path: (0, node_path_1.relative)(fromDir, shadowRef),
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const getOverrideTsConfig = dualConfigDir => {
|
|
139
|
+
const shadowReferences = mapReferencesToShadow(tsconfig.references ?? [], {
|
|
140
|
+
resolveRefPath: refPath => (0, node_path_1.resolve)(projectDir, refPath),
|
|
141
|
+
toShadowPathFn: abs => (0, node_path_1.join)(subDir, (0, node_path_1.relative)(projectRoot, abs)),
|
|
142
|
+
fromDir: dualConfigDir,
|
|
143
|
+
});
|
|
89
144
|
return {
|
|
90
145
|
...tsconfig,
|
|
146
|
+
references: shadowReferences,
|
|
91
147
|
compilerOptions: {
|
|
92
|
-
...tsconfig.compilerOptions,
|
|
148
|
+
...(tsconfig.compilerOptions ?? {}),
|
|
93
149
|
module: 'NodeNext',
|
|
94
150
|
moduleResolution: 'NodeNext',
|
|
151
|
+
target: tsconfig.compilerOptions?.target ?? 'ES2022',
|
|
152
|
+
// Emit dual build into the shadow workspace, then copy to real outDir
|
|
153
|
+
outDir: shadowDualOutDir,
|
|
154
|
+
incremental: true,
|
|
155
|
+
tsBuildInfoFile: dualTsBuildInfoFile,
|
|
95
156
|
},
|
|
96
157
|
};
|
|
97
158
|
};
|
|
98
159
|
const hasReferences = Array.isArray(tsconfig.references) && tsconfig.references.length > 0;
|
|
99
160
|
const runPrimaryBuild = () => {
|
|
100
|
-
return runBuild(configPath, hasReferences ? undefined : primaryOutDir);
|
|
161
|
+
return runBuild(configPath, hasReferences ? undefined : primaryOutDir, hasReferences ? undefined : primaryTsBuildInfoFile, projectDir);
|
|
162
|
+
};
|
|
163
|
+
const refreshDualBuildInfo = async () => {
|
|
164
|
+
try {
|
|
165
|
+
await (0, promises_1.access)(shadowDualOutDir);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
await (0, promises_1.rm)(dualTsBuildInfoFile, { force: true });
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const refreshPrimaryBuildInfo = async () => {
|
|
172
|
+
try {
|
|
173
|
+
await (0, promises_1.access)(primaryOutDir);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
await (0, promises_1.rm)(primaryTsBuildInfoFile, { force: true });
|
|
177
|
+
}
|
|
101
178
|
};
|
|
102
179
|
const resolveReferenceConfigPath = (baseDir, refPath) => {
|
|
103
180
|
const abs = (0, node_path_1.resolve)(baseDir, refPath);
|
|
@@ -233,6 +310,7 @@ const duel = async (args) => {
|
|
|
233
310
|
let success = false;
|
|
234
311
|
const startTime = node_perf_hooks_1.performance.now();
|
|
235
312
|
try {
|
|
313
|
+
await refreshPrimaryBuildInfo();
|
|
236
314
|
await runPrimaryBuild();
|
|
237
315
|
success = true;
|
|
238
316
|
}
|
|
@@ -240,15 +318,19 @@ const duel = async (args) => {
|
|
|
240
318
|
handleErrorAndExit(message);
|
|
241
319
|
}
|
|
242
320
|
if (success) {
|
|
243
|
-
const projectRoot = (0, node_path_1.dirname)(projectDir);
|
|
244
321
|
const parentRoot = (0, node_path_1.dirname)(projectRoot);
|
|
245
|
-
const subDir = (0, node_path_1.join)(projectRoot, `_${hex}_`);
|
|
246
|
-
const absoluteDualOutDir = (0, node_path_1.join)(projectDir, isCjsBuild ? (0, node_path_1.join)(outDir, 'cjs') : (0, node_path_1.join)(outDir, 'esm'));
|
|
247
|
-
const tsconfigDual = getOverrideTsConfig();
|
|
248
322
|
const tsconfigRel = (0, node_path_1.relative)(projectRoot, configPath);
|
|
249
|
-
const tsconfigDualRel = tsconfigRel.replace(/tsconfig\.json$/i, `tsconfig.${
|
|
323
|
+
const tsconfigDualRel = tsconfigRel.replace(/tsconfig\.json$/i, `tsconfig.${hash}.json`);
|
|
250
324
|
const dualConfigPath = (0, node_path_1.join)(subDir, tsconfigDualRel);
|
|
251
325
|
const dualConfigDir = (0, node_path_1.dirname)(dualConfigPath);
|
|
326
|
+
const tsconfigDual = getOverrideTsConfig(dualConfigDir);
|
|
327
|
+
const keepTemp = process.env.DUEL_KEEP_TEMP === '1';
|
|
328
|
+
const { cleanupTemp, cleanupTempSync } = (0, util_js_1.createTempCleanup)({
|
|
329
|
+
subDir,
|
|
330
|
+
keepTemp,
|
|
331
|
+
logWarnFn: util_js_1.logWarn,
|
|
332
|
+
});
|
|
333
|
+
const unregisterCleanupHandlers = (0, util_js_1.registerCleanupHandlers)(cleanupTempSync);
|
|
252
334
|
let errorMsg = '';
|
|
253
335
|
let exportsConfigData = null;
|
|
254
336
|
if (exportsConfig) {
|
|
@@ -285,11 +367,16 @@ const duel = async (args) => {
|
|
|
285
367
|
process.exit(1);
|
|
286
368
|
}
|
|
287
369
|
}
|
|
288
|
-
await
|
|
289
|
-
|
|
370
|
+
await Promise.all([
|
|
371
|
+
(0, promises_1.mkdir)(subDir, { recursive: true }),
|
|
372
|
+
(0, promises_1.mkdir)(cacheDir, { recursive: true }),
|
|
373
|
+
]);
|
|
374
|
+
const linkNodeModulesPromise = (0, util_js_1.maybeLinkNodeModules)(projectDir, subDir);
|
|
290
375
|
const projectRel = (0, node_path_1.relative)(projectRoot, projectDir);
|
|
291
376
|
const projectCopyDest = (0, node_path_1.join)(subDir, projectRel);
|
|
292
377
|
const makeCopyFilter = (rootDir, allowDist) => src => {
|
|
378
|
+
if (src.split(/[/\\]/).includes('.duel-cache'))
|
|
379
|
+
return false;
|
|
293
380
|
if (src.split(/[/\\]/).includes('node_modules'))
|
|
294
381
|
return false;
|
|
295
382
|
if (allowDist)
|
|
@@ -300,92 +387,170 @@ const duel = async (args) => {
|
|
|
300
387
|
const [segment] = rel.split(node_path_1.sep);
|
|
301
388
|
return segment !== outDir;
|
|
302
389
|
};
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
recursive: true
|
|
306
|
-
filter
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (!
|
|
390
|
+
const copyFilesToTemp = async () => {
|
|
391
|
+
const copyDirContents = async (sourceDir, destDir, allowDist) => {
|
|
392
|
+
await (0, promises_1.mkdir)(destDir, { recursive: true });
|
|
393
|
+
const filter = makeCopyFilter(sourceDir, allowDist);
|
|
394
|
+
const entries = await (0, promises_1.readdir)(sourceDir, { withFileTypes: true });
|
|
395
|
+
for (const entry of entries) {
|
|
396
|
+
const srcPath = (0, node_path_1.join)(sourceDir, entry.name);
|
|
397
|
+
if (!filter(srcPath))
|
|
311
398
|
continue;
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
const refDest = (0, node_path_1.join)(subDir, refRel);
|
|
315
|
-
await (0, promises_1.cp)(refAbs, refDest, {
|
|
399
|
+
const dstPath = (0, node_path_1.join)(destDir, entry.name);
|
|
400
|
+
await (0, promises_1.cp)(srcPath, dstPath, {
|
|
316
401
|
recursive: true,
|
|
317
|
-
filter
|
|
402
|
+
filter,
|
|
318
403
|
});
|
|
319
404
|
}
|
|
405
|
+
};
|
|
406
|
+
if (copyMode === 'full') {
|
|
407
|
+
const allowDist = hasReferences;
|
|
408
|
+
await copyDirContents(projectDir, projectCopyDest, allowDist);
|
|
409
|
+
if (hasReferences) {
|
|
410
|
+
for (const ref of tsconfig.references ?? []) {
|
|
411
|
+
if (!ref.path)
|
|
412
|
+
continue;
|
|
413
|
+
const refAbs = (0, node_path_1.resolve)(projectDir, ref.path);
|
|
414
|
+
const refRel = (0, node_path_1.relative)(projectRoot, refAbs);
|
|
415
|
+
const refDest = (0, node_path_1.join)(subDir, refRel);
|
|
416
|
+
await copyDirContents(refAbs, refDest, allowDist);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
320
419
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
420
|
+
else {
|
|
421
|
+
const filesToCopy = new Set([...compileFiles, ...configFiles, ...packageJsons]);
|
|
422
|
+
for (const file of filesToCopy) {
|
|
423
|
+
let rel = (0, node_path_1.relative)(projectRoot, file);
|
|
424
|
+
rel = (0, node_path_1.normalize)(rel);
|
|
425
|
+
if (rel.startsWith('..')) {
|
|
426
|
+
const altRel = hasReferences ? (0, node_path_1.normalize)((0, node_path_1.relative)(parentRoot, file)) : rel;
|
|
427
|
+
if (!altRel.startsWith('..')) {
|
|
428
|
+
rel = altRel;
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
(0, util_js_1.logWarn)(`Skipping copy for ${file} outside of project root ${projectRoot}`);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
335
434
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
435
|
+
const dest = (0, node_path_1.join)(subDir, rel);
|
|
436
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(dest), { recursive: true });
|
|
437
|
+
await (0, promises_1.cp)(file, dest);
|
|
438
|
+
}
|
|
439
|
+
const missingConfigs = [];
|
|
440
|
+
for (const configFile of configFiles) {
|
|
441
|
+
const dest = (0, node_path_1.join)(subDir, (0, node_path_1.relative)(projectRoot, configFile));
|
|
442
|
+
try {
|
|
443
|
+
await (0, promises_1.access)(dest);
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
missingConfigs.push({ src: configFile, dest });
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (missingConfigs.length) {
|
|
450
|
+
(0, util_js_1.logWarn)(`Copying ${missingConfigs.length} missing referenced config(s) into temp workspace: ${missingConfigs
|
|
451
|
+
.map(entry => entry.src)
|
|
452
|
+
.join(', ')}`);
|
|
453
|
+
for (const { src, dest } of missingConfigs) {
|
|
454
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(dest), { recursive: true });
|
|
455
|
+
await (0, promises_1.cp)(src, dest);
|
|
339
456
|
}
|
|
340
457
|
}
|
|
341
|
-
const dest = (0, node_path_1.join)(subDir, rel);
|
|
342
|
-
await (0, promises_1.mkdir)((0, node_path_1.dirname)(dest), { recursive: true });
|
|
343
|
-
await (0, promises_1.cp)(file, dest);
|
|
344
458
|
}
|
|
345
|
-
|
|
459
|
+
};
|
|
460
|
+
const toShadowPath = absPath => (0, node_path_1.join)(subDir, (0, node_path_1.relative)(projectRoot, absPath));
|
|
461
|
+
// Patch referenced tsconfig files in the shadow workspace to emit dual outputs
|
|
462
|
+
const patchReferencedConfigs = async () => {
|
|
346
463
|
for (const configFile of configFiles) {
|
|
464
|
+
if (configFile === configPath)
|
|
465
|
+
continue;
|
|
347
466
|
const dest = (0, node_path_1.join)(subDir, (0, node_path_1.relative)(projectRoot, configFile));
|
|
467
|
+
let parsed = null;
|
|
348
468
|
try {
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
catch {
|
|
352
|
-
missingConfigs.push({ src: configFile, dest });
|
|
469
|
+
parsed = (0, get_tsconfig_1.parseTsconfig)(dest);
|
|
353
470
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
.map(entry => entry.src)
|
|
358
|
-
.join(', ')}`);
|
|
359
|
-
for (const { src, dest } of missingConfigs) {
|
|
360
|
-
await (0, promises_1.mkdir)((0, node_path_1.dirname)(dest), { recursive: true });
|
|
361
|
-
await (0, promises_1.cp)(src, dest);
|
|
471
|
+
catch (err) {
|
|
472
|
+
(0, util_js_1.logWarn)(`Skipping referenced tsconfig at ${dest} (parse failed): ${err?.message ?? err}`);
|
|
473
|
+
continue;
|
|
362
474
|
}
|
|
475
|
+
const cfg = parsed?.tsconfig ?? parsed;
|
|
476
|
+
if (!cfg || typeof cfg !== 'object')
|
|
477
|
+
continue;
|
|
478
|
+
const cfgDir = (0, node_path_1.dirname)(configFile);
|
|
479
|
+
const baseOut = cfg.compilerOptions?.outDir
|
|
480
|
+
? (0, node_path_1.resolve)(cfgDir, cfg.compilerOptions.outDir)
|
|
481
|
+
: (0, node_path_1.resolve)(cfgDir, 'dist');
|
|
482
|
+
const dualOutReal = (0, node_path_1.join)(baseOut, isCjsBuild ? 'cjs' : 'esm');
|
|
483
|
+
const dualOut = toShadowPath(dualOutReal);
|
|
484
|
+
const tsbuildReal = cfg.compilerOptions?.tsBuildInfoFile
|
|
485
|
+
? (0, node_path_1.resolve)(cfgDir, cfg.compilerOptions.tsBuildInfoFile)
|
|
486
|
+
: (0, node_path_1.join)(baseOut, 'tsconfig.tsbuildinfo');
|
|
487
|
+
const dualTsbuild = toShadowPath((0, node_path_1.join)((0, node_path_1.dirname)(tsbuildReal), 'tsconfig.dual.tsbuildinfo'));
|
|
488
|
+
const shadowReferences = mapReferencesToShadow(cfg.references ?? [], {
|
|
489
|
+
resolveRefPath: refPath => resolveReferenceConfigPath(cfgDir, refPath),
|
|
490
|
+
toShadowPathFn: toShadowPath,
|
|
491
|
+
fromDir: (0, node_path_1.dirname)(dest),
|
|
492
|
+
});
|
|
493
|
+
const patched = {
|
|
494
|
+
...cfg,
|
|
495
|
+
references: shadowReferences,
|
|
496
|
+
compilerOptions: {
|
|
497
|
+
...(cfg.compilerOptions ?? {}),
|
|
498
|
+
module: 'NodeNext',
|
|
499
|
+
moduleResolution: 'NodeNext',
|
|
500
|
+
outDir: dualOut,
|
|
501
|
+
incremental: cfg.compilerOptions?.incremental ?? true,
|
|
502
|
+
tsBuildInfoFile: dualTsbuild,
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
await (0, promises_1.writeFile)(dest, JSON.stringify(patched, null, 2));
|
|
363
506
|
}
|
|
364
|
-
}
|
|
507
|
+
};
|
|
365
508
|
/**
|
|
366
509
|
* Write dual package.json and tsconfig into temp dir; avoid mutating root package.json.
|
|
367
510
|
*/
|
|
368
|
-
await (
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
511
|
+
await copyFilesToTemp();
|
|
512
|
+
await patchReferencedConfigs();
|
|
513
|
+
const writeDualPackage = async () => {
|
|
514
|
+
const pkgDest = (0, node_path_1.join)(subDir, (0, node_path_1.relative)(projectRoot, pkg.path));
|
|
515
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(pkgDest), { recursive: true });
|
|
516
|
+
await (0, promises_1.writeFile)(pkgDest, JSON.stringify({
|
|
517
|
+
name: pkg.packageJson?.name,
|
|
518
|
+
version: pkg.packageJson?.version,
|
|
519
|
+
type: isCjsBuild ? 'commonjs' : 'module',
|
|
520
|
+
exports: pkg.packageJson?.exports,
|
|
521
|
+
imports: pkg.packageJson?.imports,
|
|
522
|
+
main: pkg.packageJson?.main,
|
|
523
|
+
module: pkg.packageJson?.module,
|
|
524
|
+
types: pkg.packageJson?.types ?? pkg.packageJson?.typings,
|
|
525
|
+
typesVersions: pkg.packageJson?.typesVersions,
|
|
526
|
+
sideEffects: pkg.packageJson?.sideEffects,
|
|
527
|
+
}, null, 2));
|
|
528
|
+
};
|
|
529
|
+
const writeDualConfig = async () => {
|
|
530
|
+
await (0, promises_1.mkdir)(dualConfigDir, { recursive: true });
|
|
531
|
+
await (0, promises_1.writeFile)(dualConfigPath, JSON.stringify({
|
|
532
|
+
...tsconfigDual,
|
|
533
|
+
compilerOptions: {
|
|
534
|
+
...tsconfigDual.compilerOptions,
|
|
535
|
+
outDir: shadowDualOutDir,
|
|
536
|
+
incremental: true,
|
|
537
|
+
tsBuildInfoFile: dualTsBuildInfoFile,
|
|
538
|
+
},
|
|
539
|
+
}, null, 2));
|
|
540
|
+
};
|
|
541
|
+
await Promise.all([linkNodeModulesPromise, writeDualPackage(), writeDualConfig()]);
|
|
379
542
|
if (modules) {
|
|
380
543
|
/**
|
|
381
544
|
* Transform ambiguous modules for the target dual build.
|
|
382
545
|
* @see https://github.com/microsoft/TypeScript/issues/58658
|
|
383
546
|
*/
|
|
384
547
|
const toTransform = await (0, glob_1.glob)(`${subDir.replace(/\\/g, '/')}/**/*{.js,.jsx,.ts,.tsx}`, {
|
|
385
|
-
ignore: 'node_modules
|
|
548
|
+
ignore: `${subDir.replace(/\\/g, '/')}/**/node_modules/**`,
|
|
386
549
|
});
|
|
387
550
|
let transformDiagnosticsError = false;
|
|
388
551
|
for (const file of toTransform) {
|
|
552
|
+
if (file.split(/[/\\]/).includes('node_modules'))
|
|
553
|
+
continue;
|
|
389
554
|
const isTsLike = /\.[cm]?tsx?$/.test(file);
|
|
390
555
|
const transformSyntaxMode = syntaxMode === true && isTsLike ? 'globals-only' : syntaxMode;
|
|
391
556
|
const diagnostics = [];
|
|
@@ -407,22 +572,16 @@ const duel = async (args) => {
|
|
|
407
572
|
// Build dual
|
|
408
573
|
(0, util_js_1.log)('Starting dual build...');
|
|
409
574
|
try {
|
|
410
|
-
await
|
|
575
|
+
await refreshDualBuildInfo();
|
|
576
|
+
await runBuild(dualConfigPath, hasReferences ? undefined : shadowDualOutDir, hasReferences ? undefined : dualTsBuildInfoFile, subDir);
|
|
411
577
|
}
|
|
412
578
|
catch ({ message }) {
|
|
413
579
|
success = false;
|
|
414
580
|
errorMsg = message;
|
|
415
581
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if (!keepTemp) {
|
|
420
|
-
await (0, promises_1.rm)(dualConfigPath, { force: true });
|
|
421
|
-
await (0, promises_1.rm)(subDir, { force: true, recursive: true });
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
(0, util_js_1.logWarn)(`DUEL_KEEP_TEMP=1 set; temp workspace preserved at ${subDir}`);
|
|
425
|
-
}
|
|
582
|
+
if (!success) {
|
|
583
|
+
await cleanupTemp();
|
|
584
|
+
unregisterCleanupHandlers();
|
|
426
585
|
if (errorMsg) {
|
|
427
586
|
handleErrorAndExit(errorMsg);
|
|
428
587
|
}
|
|
@@ -430,24 +589,41 @@ const duel = async (args) => {
|
|
|
430
589
|
if (success) {
|
|
431
590
|
const dualTarget = isCjsBuild ? 'commonjs' : 'module';
|
|
432
591
|
const dualTargetExt = isCjsBuild ? '.cjs' : dirs ? '.js' : '.mjs';
|
|
433
|
-
|
|
434
|
-
|
|
592
|
+
await (0, promises_1.rm)(absoluteDualOutDir, { force: true, recursive: true });
|
|
593
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(absoluteDualOutDir), { recursive: true });
|
|
594
|
+
// Only copy if the shadow dual outDir was produced; absent indicates a failed emit
|
|
595
|
+
try {
|
|
596
|
+
await (0, promises_1.cp)(shadowDualOutDir, absoluteDualOutDir, { recursive: true });
|
|
597
|
+
}
|
|
598
|
+
catch (err) {
|
|
599
|
+
if (err?.code === 'ENOENT') {
|
|
600
|
+
throw new Error(`Dual build output not found at ${shadowDualOutDir}`);
|
|
601
|
+
}
|
|
602
|
+
throw err;
|
|
603
|
+
}
|
|
604
|
+
const dualGlob = dualTarget === 'commonjs' ? '**/*{.js,.cjs,.d.ts}' : '**/*{.js,.mjs,.d.ts}';
|
|
605
|
+
const filenames = await (0, glob_1.glob)(`${absoluteDualOutDir.replace(/\\/g, '/')}/${dualGlob}`, {
|
|
606
|
+
ignore: `${absoluteDualOutDir.replace(/\\/g, '/')}/**/node_modules/**`,
|
|
435
607
|
});
|
|
608
|
+
const rewriteSyntaxMode = dualTarget === 'commonjs' ? true : syntaxMode;
|
|
436
609
|
await (0, resolver_js_1.rewriteSpecifiersAndExtensions)(filenames, {
|
|
437
610
|
target: dualTarget,
|
|
438
611
|
ext: dualTargetExt,
|
|
439
|
-
syntaxMode,
|
|
612
|
+
syntaxMode: rewriteSyntaxMode,
|
|
440
613
|
rewritePolicy,
|
|
441
614
|
validateSpecifiers,
|
|
442
615
|
onWarn: message => (0, util_js_1.logWarn)(message),
|
|
443
616
|
onRewrite: (from, to) => logVerbose(`Rewrote specifiers in ${from} -> ${to}`),
|
|
444
617
|
});
|
|
445
618
|
if (dirs && originalType === 'commonjs') {
|
|
446
|
-
const primaryFiles = await (0, glob_1.glob)(`${primaryOutDir.replace(/\\/g, '/')}/**/*{.js,.d.ts}`, {
|
|
619
|
+
const primaryFiles = await (0, glob_1.glob)(`${primaryOutDir.replace(/\\/g, '/')}/**/*{.js,.cjs,.d.ts}`, {
|
|
620
|
+
ignore: `${primaryOutDir.replace(/\\/g, '/')}/**/node_modules/**`,
|
|
621
|
+
});
|
|
447
622
|
await (0, resolver_js_1.rewriteSpecifiersAndExtensions)(primaryFiles, {
|
|
448
623
|
target: 'commonjs',
|
|
449
624
|
ext: '.cjs',
|
|
450
|
-
|
|
625
|
+
// Always lower syntax for primary CJS output when dirs mode rewrites primary build.
|
|
626
|
+
syntaxMode: true,
|
|
451
627
|
rewritePolicy,
|
|
452
628
|
validateSpecifiers,
|
|
453
629
|
onWarn: message => (0, util_js_1.logWarn)(message),
|
|
@@ -467,15 +643,33 @@ const duel = async (args) => {
|
|
|
467
643
|
mainDefaultKind,
|
|
468
644
|
mainPath,
|
|
469
645
|
});
|
|
646
|
+
await cleanupTemp();
|
|
647
|
+
unregisterCleanupHandlers();
|
|
470
648
|
logSuccess(startTime);
|
|
471
649
|
}
|
|
472
650
|
}
|
|
473
651
|
}
|
|
474
652
|
};
|
|
475
653
|
exports.duel = duel;
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
654
|
+
const getCurrentHref = () => {
|
|
655
|
+
if (typeof module !== 'undefined' && require("node:url").pathToFileURL(__filename).href)
|
|
656
|
+
return require("node:url").pathToFileURL(__filename).href;
|
|
657
|
+
if (typeof module !== 'undefined' && module?.filename) {
|
|
658
|
+
return (0, node_url_1.pathToFileURL)(module.filename).href;
|
|
659
|
+
}
|
|
660
|
+
return null;
|
|
661
|
+
};
|
|
662
|
+
const runIfEntry = async () => {
|
|
663
|
+
try {
|
|
664
|
+
const realFileUrlArgv1 = await (0, util_js_1.getRealPathAsFileUrl)(node_process_1.argv[1] ?? '');
|
|
665
|
+
const currentHref = getCurrentHref();
|
|
666
|
+
if (currentHref && currentHref === realFileUrlArgv1) {
|
|
667
|
+
await duel();
|
|
668
|
+
}
|
|
480
669
|
}
|
|
481
|
-
|
|
670
|
+
catch (err) {
|
|
671
|
+
(0, util_js_1.logError)(err?.message ?? err);
|
|
672
|
+
process.exit(1);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
runIfEntry();
|
package/dist/cjs/init.cjs
CHANGED
|
@@ -189,7 +189,8 @@ const init = async (args) => {
|
|
|
189
189
|
(0, util_js_1.logError)(`Provided --project '${project}' resolves to ${configPath} which is not a file or directory.`);
|
|
190
190
|
return false;
|
|
191
191
|
}
|
|
192
|
-
|
|
192
|
+
const pkgSearchCwd = pkgDir ?? (stats.isDirectory() ? configPath : (0, node_path_1.dirname)(configPath));
|
|
193
|
+
pkg = await (0, read_package_up_1.readPackageUp)({ cwd: pkgSearchCwd });
|
|
193
194
|
if (!pkg) {
|
|
194
195
|
(0, util_js_1.logError)('No package.json file found.');
|
|
195
196
|
return false;
|
package/dist/cjs/init.d.cts
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
export function init(args: any): Promise<false | {
|
|
2
|
-
pkg:
|
|
3
|
-
dirs:
|
|
2
|
+
pkg: import("read-package-up", { with: { "resolution-mode": "import" } }).NormalizedReadResult;
|
|
3
|
+
dirs: boolean;
|
|
4
4
|
modules: boolean;
|
|
5
5
|
transformSyntax: boolean;
|
|
6
|
-
exports:
|
|
7
|
-
exportsConfig:
|
|
8
|
-
exportsValidate:
|
|
9
|
-
rewritePolicy:
|
|
10
|
-
validateSpecifiers:
|
|
11
|
-
detectDualPackageHazard:
|
|
12
|
-
dualPackageHazardScope:
|
|
13
|
-
verbose:
|
|
14
|
-
copyMode:
|
|
15
|
-
tsconfig:
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
exports: string | undefined;
|
|
7
|
+
exportsConfig: string | undefined;
|
|
8
|
+
exportsValidate: boolean;
|
|
9
|
+
rewritePolicy: string;
|
|
10
|
+
validateSpecifiers: boolean;
|
|
11
|
+
detectDualPackageHazard: string;
|
|
12
|
+
dualPackageHazardScope: string;
|
|
13
|
+
verbose: boolean;
|
|
14
|
+
copyMode: string;
|
|
15
|
+
tsconfig: {
|
|
16
|
+
compilerOptions?: import("get-tsconfig").TsConfigJson.CompilerOptions | undefined;
|
|
17
|
+
watchOptions?: import("get-tsconfig").TsConfigJson.WatchOptions | undefined;
|
|
18
|
+
typeAcquisition?: import("get-tsconfig").TsConfigJson.TypeAcquisition | undefined;
|
|
19
|
+
compileOnSave?: boolean | undefined;
|
|
20
|
+
files?: string[] | undefined;
|
|
21
|
+
exclude?: string[] | undefined;
|
|
22
|
+
include?: string[] | undefined;
|
|
23
|
+
references?: import("get-tsconfig").TsConfigJson.References[] | undefined;
|
|
24
|
+
};
|
|
25
|
+
projectDir: string;
|
|
26
|
+
configPath: string;
|
|
18
27
|
}>;
|