@knighted/duel 2.1.7 → 3.0.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 +8 -1
- package/dist/cjs/duel.cjs +51 -24
- package/dist/esm/duel.js +51 -24
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Tool for building a Node.js [dual package](https://nodejs.org/api/packages.html#
|
|
|
16
16
|
|
|
17
17
|
## Requirements
|
|
18
18
|
|
|
19
|
-
- Node >=
|
|
19
|
+
- Node >= 22.21.1 (<23) or >= 24 (<25)
|
|
20
20
|
|
|
21
21
|
## Example
|
|
22
22
|
|
|
@@ -73,6 +73,13 @@ Assuming an `outDir` of `dist`, running the above will create `dist/esm` and `di
|
|
|
73
73
|
|
|
74
74
|
TypeScript will throw compiler errors when using `import.meta` globals while targeting a CommonJS dual build, but _will not_ throw compiler errors when the inverse is true, i.e. using CommonJS globals (`__filename`, `__dirname`, etc.) while targeting an ES module dual build. There is an [open issue](https://github.com/microsoft/TypeScript/issues/58658) regarding this unexpected behavior. You can use the `--modules` option to have the [differences between ES modules and CommonJS](https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs) transformed by `duel` prior to running compilation with `tsc` so that there are no compilation or runtime errors.
|
|
75
75
|
|
|
76
|
+
`duel` infers the primary vs dual build orientation from your `package.json` `type`:
|
|
77
|
+
|
|
78
|
+
- `"type": "module"` → primary ESM, dual CJS
|
|
79
|
+
- `"type": "commonjs"` → primary CJS, dual ESM
|
|
80
|
+
|
|
81
|
+
The `--dirs` flag nests outputs under `outDir/esm` and `outDir/cjs` accordingly.
|
|
82
|
+
|
|
76
83
|
Note, there is a slight performance penalty since your project needs to be copied first to run the transforms before compiling with `tsc`.
|
|
77
84
|
|
|
78
85
|
```json
|
package/dist/cjs/duel.cjs
CHANGED
|
@@ -10,7 +10,6 @@ const node_crypto_1 = require("node:crypto");
|
|
|
10
10
|
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
|
-
const specifier_1 = require("@knighted/specifier");
|
|
14
13
|
const module_1 = require("@knighted/module");
|
|
15
14
|
const init_js_1 = require("./init.cjs");
|
|
16
15
|
const util_js_1 = require("./util.cjs");
|
|
@@ -50,7 +49,11 @@ const duel = async (args) => {
|
|
|
50
49
|
const absoluteOutDir = (0, node_path_1.resolve)(projectDir, outDir);
|
|
51
50
|
const originalType = pkg.packageJson.type ?? 'commonjs';
|
|
52
51
|
const isCjsBuild = originalType !== 'commonjs';
|
|
53
|
-
const
|
|
52
|
+
const primaryOutDir = dirs
|
|
53
|
+
? isCjsBuild
|
|
54
|
+
? (0, node_path_1.join)(absoluteOutDir, 'esm')
|
|
55
|
+
: (0, node_path_1.join)(absoluteOutDir, 'cjs')
|
|
56
|
+
: absoluteOutDir;
|
|
54
57
|
const hex = (0, node_crypto_1.randomBytes)(4).toString('hex');
|
|
55
58
|
const getOverrideTsConfig = () => {
|
|
56
59
|
return {
|
|
@@ -63,29 +66,39 @@ const duel = async (args) => {
|
|
|
63
66
|
};
|
|
64
67
|
};
|
|
65
68
|
const runPrimaryBuild = () => {
|
|
66
|
-
return runBuild(configPath,
|
|
67
|
-
? isCjsBuild
|
|
68
|
-
? (0, node_path_1.join)(absoluteOutDir, 'esm')
|
|
69
|
-
: (0, node_path_1.join)(absoluteOutDir, 'cjs')
|
|
70
|
-
: absoluteOutDir);
|
|
69
|
+
return runBuild(configPath, primaryOutDir);
|
|
71
70
|
};
|
|
72
|
-
const updateSpecifiersAndFileExtensions = async (filenames) => {
|
|
71
|
+
const updateSpecifiersAndFileExtensions = async (filenames, target, ext) => {
|
|
73
72
|
for (const filename of filenames) {
|
|
74
73
|
const dts = /(\.d\.ts)$/;
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
const isDts = dts.test(filename);
|
|
75
|
+
const outFilename = isDts
|
|
76
|
+
? filename.replace(dts, target === 'commonjs' ? '.d.cts' : '.d.mts')
|
|
77
|
+
: filename.replace(/\.js$/, ext);
|
|
78
|
+
if (isDts) {
|
|
79
|
+
const source = await (0, promises_1.readFile)(filename, 'utf8');
|
|
80
|
+
const rewritten = source.replace(/(?<=['"])(\.\.?(?:\/[\w.-]+)*)\.js(?=['"])/g, `$1${ext}`);
|
|
81
|
+
await (0, promises_1.writeFile)(outFilename, rewritten);
|
|
82
|
+
if (outFilename !== filename) {
|
|
83
|
+
await (0, promises_1.rm)(filename, { force: true });
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const rewriteSpecifier = (value = '') => {
|
|
80
88
|
const collapsed = value.replace(/['"`+)\s]|new String\(/g, '');
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// $2 is for any closing quotation/parens around BE or NE
|
|
84
|
-
return value.replace(/(.+)\.js([)'"`]*)?$/, `$1${targetExt}$2`);
|
|
89
|
+
if (/^(?:\.|\.\.)\//.test(collapsed)) {
|
|
90
|
+
return value.replace(/(.+)\.js([)"'`]*)?$/, `$1${ext}$2`);
|
|
85
91
|
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
};
|
|
93
|
+
const writeOptions = {
|
|
94
|
+
target,
|
|
95
|
+
rewriteSpecifier,
|
|
96
|
+
...(outFilename === filename ? { inPlace: true } : { out: outFilename }),
|
|
97
|
+
};
|
|
98
|
+
await (0, module_1.transform)(filename, writeOptions);
|
|
99
|
+
if (outFilename !== filename) {
|
|
100
|
+
await (0, promises_1.rm)(filename, { force: true });
|
|
101
|
+
}
|
|
89
102
|
}
|
|
90
103
|
};
|
|
91
104
|
const logSuccess = start => {
|
|
@@ -112,7 +125,11 @@ const duel = async (args) => {
|
|
|
112
125
|
const compileFiles = (0, util_js_1.getCompileFiles)(tsc, projectDir);
|
|
113
126
|
dualConfigPath = (0, node_path_1.join)(subDir, `tsconfig.${hex}.json`);
|
|
114
127
|
await (0, promises_1.mkdir)(subDir);
|
|
115
|
-
await Promise.all(compileFiles.map(
|
|
128
|
+
await Promise.all(compileFiles.map(async (file) => {
|
|
129
|
+
const dest = (0, node_path_1.join)(subDir, (0, node_path_1.relative)(projectDir, file).replace(/^(\.\.\/)+/, ''));
|
|
130
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(dest), { recursive: true });
|
|
131
|
+
await (0, promises_1.cp)(file, dest);
|
|
132
|
+
}));
|
|
116
133
|
/**
|
|
117
134
|
* Transform ambiguous modules for the target dual build.
|
|
118
135
|
* @see https://github.com/microsoft/TypeScript/issues/58658
|
|
@@ -129,7 +146,11 @@ const duel = async (args) => {
|
|
|
129
146
|
*
|
|
130
147
|
* @see https://github.com/microsoft/TypeScript/issues/58658
|
|
131
148
|
*/
|
|
132
|
-
await (0, module_1.transform)(file, {
|
|
149
|
+
await (0, module_1.transform)(file, {
|
|
150
|
+
out: file,
|
|
151
|
+
target: isCjsBuild ? 'commonjs' : 'module',
|
|
152
|
+
transformSyntax: false,
|
|
153
|
+
});
|
|
133
154
|
}
|
|
134
155
|
}
|
|
135
156
|
/**
|
|
@@ -161,10 +182,16 @@ const duel = async (args) => {
|
|
|
161
182
|
}
|
|
162
183
|
}
|
|
163
184
|
if (success) {
|
|
185
|
+
const dualTarget = isCjsBuild ? 'commonjs' : 'module';
|
|
186
|
+
const dualTargetExt = isCjsBuild ? '.cjs' : dirs ? '.js' : '.mjs';
|
|
164
187
|
const filenames = await (0, glob_1.glob)(`${absoluteDualOutDir.replace(/\\/g, '/')}/**/*{.js,.d.ts}`, {
|
|
165
188
|
ignore: 'node_modules/**',
|
|
166
189
|
});
|
|
167
|
-
await updateSpecifiersAndFileExtensions(filenames);
|
|
190
|
+
await updateSpecifiersAndFileExtensions(filenames, dualTarget, dualTargetExt);
|
|
191
|
+
if (dirs && originalType === 'commonjs') {
|
|
192
|
+
const primaryFiles = await (0, glob_1.glob)(`${primaryOutDir.replace(/\\/g, '/')}/**/*{.js,.d.ts}`, { ignore: 'node_modules/**' });
|
|
193
|
+
await updateSpecifiersAndFileExtensions(primaryFiles, 'commonjs', '.cjs');
|
|
194
|
+
}
|
|
168
195
|
logSuccess(startTime);
|
|
169
196
|
}
|
|
170
197
|
}
|
|
@@ -173,7 +200,7 @@ const duel = async (args) => {
|
|
|
173
200
|
exports.duel = duel;
|
|
174
201
|
(async () => {
|
|
175
202
|
const realFileUrlArgv1 = await (0, util_js_1.getRealPathAsFileUrl)(node_process_1.argv[1] ?? '');
|
|
176
|
-
if (require("node:url").pathToFileURL(__filename).
|
|
203
|
+
if (require("node:url").pathToFileURL(__filename).href === realFileUrlArgv1) {
|
|
177
204
|
await duel();
|
|
178
205
|
}
|
|
179
206
|
})();
|
package/dist/esm/duel.js
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
import { argv, platform } from 'node:process';
|
|
3
3
|
import { join, dirname, resolve, relative } from 'node:path';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
|
-
import { writeFile, rm, rename, mkdir, cp, access } from 'node:fs/promises';
|
|
5
|
+
import { writeFile, rm, rename, mkdir, cp, access, readFile } 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 } from 'find-up';
|
|
10
|
-
import { specifier } from '@knighted/specifier';
|
|
11
10
|
import { transform } from '@knighted/module';
|
|
12
11
|
import { init } from './init.js';
|
|
13
12
|
import { getRealPathAsFileUrl, getCompileFiles, logError, log } from './util.js';
|
|
@@ -47,7 +46,11 @@ const duel = async (args) => {
|
|
|
47
46
|
const absoluteOutDir = resolve(projectDir, outDir);
|
|
48
47
|
const originalType = pkg.packageJson.type ?? 'commonjs';
|
|
49
48
|
const isCjsBuild = originalType !== 'commonjs';
|
|
50
|
-
const
|
|
49
|
+
const primaryOutDir = dirs
|
|
50
|
+
? isCjsBuild
|
|
51
|
+
? join(absoluteOutDir, 'esm')
|
|
52
|
+
: join(absoluteOutDir, 'cjs')
|
|
53
|
+
: absoluteOutDir;
|
|
51
54
|
const hex = randomBytes(4).toString('hex');
|
|
52
55
|
const getOverrideTsConfig = () => {
|
|
53
56
|
return {
|
|
@@ -60,29 +63,39 @@ const duel = async (args) => {
|
|
|
60
63
|
};
|
|
61
64
|
};
|
|
62
65
|
const runPrimaryBuild = () => {
|
|
63
|
-
return runBuild(configPath,
|
|
64
|
-
? isCjsBuild
|
|
65
|
-
? join(absoluteOutDir, 'esm')
|
|
66
|
-
: join(absoluteOutDir, 'cjs')
|
|
67
|
-
: absoluteOutDir);
|
|
66
|
+
return runBuild(configPath, primaryOutDir);
|
|
68
67
|
};
|
|
69
|
-
const updateSpecifiersAndFileExtensions = async (filenames) => {
|
|
68
|
+
const updateSpecifiersAndFileExtensions = async (filenames, target, ext) => {
|
|
70
69
|
for (const filename of filenames) {
|
|
71
70
|
const dts = /(\.d\.ts)$/;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
const isDts = dts.test(filename);
|
|
72
|
+
const outFilename = isDts
|
|
73
|
+
? filename.replace(dts, target === 'commonjs' ? '.d.cts' : '.d.mts')
|
|
74
|
+
: filename.replace(/\.js$/, ext);
|
|
75
|
+
if (isDts) {
|
|
76
|
+
const source = await readFile(filename, 'utf8');
|
|
77
|
+
const rewritten = source.replace(/(?<=['"])(\.\.?(?:\/[\w.-]+)*)\.js(?=['"])/g, `$1${ext}`);
|
|
78
|
+
await writeFile(outFilename, rewritten);
|
|
79
|
+
if (outFilename !== filename) {
|
|
80
|
+
await rm(filename, { force: true });
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const rewriteSpecifier = (value = '') => {
|
|
77
85
|
const collapsed = value.replace(/['"`+)\s]|new String\(/g, '');
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// $2 is for any closing quotation/parens around BE or NE
|
|
81
|
-
return value.replace(/(.+)\.js([)'"`]*)?$/, `$1${targetExt}$2`);
|
|
86
|
+
if (/^(?:\.|\.\.)\//.test(collapsed)) {
|
|
87
|
+
return value.replace(/(.+)\.js([)"'`]*)?$/, `$1${ext}$2`);
|
|
82
88
|
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
};
|
|
90
|
+
const writeOptions = {
|
|
91
|
+
target,
|
|
92
|
+
rewriteSpecifier,
|
|
93
|
+
...(outFilename === filename ? { inPlace: true } : { out: outFilename }),
|
|
94
|
+
};
|
|
95
|
+
await transform(filename, writeOptions);
|
|
96
|
+
if (outFilename !== filename) {
|
|
97
|
+
await rm(filename, { force: true });
|
|
98
|
+
}
|
|
86
99
|
}
|
|
87
100
|
};
|
|
88
101
|
const logSuccess = start => {
|
|
@@ -109,7 +122,11 @@ const duel = async (args) => {
|
|
|
109
122
|
const compileFiles = getCompileFiles(tsc, projectDir);
|
|
110
123
|
dualConfigPath = join(subDir, `tsconfig.${hex}.json`);
|
|
111
124
|
await mkdir(subDir);
|
|
112
|
-
await Promise.all(compileFiles.map(
|
|
125
|
+
await Promise.all(compileFiles.map(async (file) => {
|
|
126
|
+
const dest = join(subDir, relative(projectDir, file).replace(/^(\.\.\/)+/, ''));
|
|
127
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
128
|
+
await cp(file, dest);
|
|
129
|
+
}));
|
|
113
130
|
/**
|
|
114
131
|
* Transform ambiguous modules for the target dual build.
|
|
115
132
|
* @see https://github.com/microsoft/TypeScript/issues/58658
|
|
@@ -126,7 +143,11 @@ const duel = async (args) => {
|
|
|
126
143
|
*
|
|
127
144
|
* @see https://github.com/microsoft/TypeScript/issues/58658
|
|
128
145
|
*/
|
|
129
|
-
await transform(file, {
|
|
146
|
+
await transform(file, {
|
|
147
|
+
out: file,
|
|
148
|
+
target: isCjsBuild ? 'commonjs' : 'module',
|
|
149
|
+
transformSyntax: false,
|
|
150
|
+
});
|
|
130
151
|
}
|
|
131
152
|
}
|
|
132
153
|
/**
|
|
@@ -158,10 +179,16 @@ const duel = async (args) => {
|
|
|
158
179
|
}
|
|
159
180
|
}
|
|
160
181
|
if (success) {
|
|
182
|
+
const dualTarget = isCjsBuild ? 'commonjs' : 'module';
|
|
183
|
+
const dualTargetExt = isCjsBuild ? '.cjs' : dirs ? '.js' : '.mjs';
|
|
161
184
|
const filenames = await glob(`${absoluteDualOutDir.replace(/\\/g, '/')}/**/*{.js,.d.ts}`, {
|
|
162
185
|
ignore: 'node_modules/**',
|
|
163
186
|
});
|
|
164
|
-
await updateSpecifiersAndFileExtensions(filenames);
|
|
187
|
+
await updateSpecifiersAndFileExtensions(filenames, dualTarget, dualTargetExt);
|
|
188
|
+
if (dirs && originalType === 'commonjs') {
|
|
189
|
+
const primaryFiles = await glob(`${primaryOutDir.replace(/\\/g, '/')}/**/*{.js,.d.ts}`, { ignore: 'node_modules/**' });
|
|
190
|
+
await updateSpecifiersAndFileExtensions(primaryFiles, 'commonjs', '.cjs');
|
|
191
|
+
}
|
|
165
192
|
logSuccess(startTime);
|
|
166
193
|
}
|
|
167
194
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knighted/duel",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "TypeScript dual packages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/esm/duel.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"./package.json": "./package.json"
|
|
17
17
|
},
|
|
18
18
|
"engines": {
|
|
19
|
-
"node": ">=
|
|
19
|
+
"node": ">=22.21.1 <23 || >=24 <25"
|
|
20
20
|
},
|
|
21
21
|
"engineStrict": true,
|
|
22
22
|
"scripts": {
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@eslint/js": "^9.39.1",
|
|
70
|
+
"@knighted/module": "^1.0.0-rc.2",
|
|
70
71
|
"@tsconfig/recommended": "^1.0.10",
|
|
71
72
|
"@types/node": "^24.10.1",
|
|
72
73
|
"c8": "^10.1.3",
|
|
@@ -82,8 +83,6 @@
|
|
|
82
83
|
"vite": "^7.2.4"
|
|
83
84
|
},
|
|
84
85
|
"dependencies": {
|
|
85
|
-
"@knighted/module": "^1.0.0-alpha.10",
|
|
86
|
-
"@knighted/specifier": "^2.0.9",
|
|
87
86
|
"find-up": "^8.0.0",
|
|
88
87
|
"get-tsconfig": "^4.13.0",
|
|
89
88
|
"glob": "^13.0.0",
|