@knighted/duel 1.0.0-rc.9 → 1.0.1
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 -3
- package/dist/duel.cjs +18 -28
- package/dist/duel.js +19 -29
- package/dist/init.cjs +3 -1
- package/dist/init.js +2 -1
- package/dist/util.cjs +0 -1
- package/dist/util.js +0 -1
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Node.js tool for building a TypeScript dual package.
|
|
|
21
21
|
First, install this package to create the `duel` executable inside your `node_modules/.bin` directory.
|
|
22
22
|
|
|
23
23
|
```console
|
|
24
|
-
user@comp ~ $ npm i @knighted/duel
|
|
24
|
+
user@comp ~ $ npm i @knighted/duel --save-dev
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
Then, given a `package.json` that defines `"type": "module"` and a `tsconfig.json` file that looks something like the following:
|
|
@@ -86,7 +86,7 @@ The available options are limited, because you should define most of them inside
|
|
|
86
86
|
|
|
87
87
|
* `--project, -p` The path to the project's configuration file. Defaults to `tsconfig.json`.
|
|
88
88
|
* `--pkg-dir, -k` The directory to start looking for a package.json file. Defaults to the cwd.
|
|
89
|
-
* `--dirs, -d` Outputs both builds to directories inside of `outDir`.
|
|
89
|
+
* `--dirs, -d` Outputs both builds to directories inside of `outDir`. Defaults to `false`.
|
|
90
90
|
* `--parallel, -l` Run the builds in parallel. Defaults to `false`.
|
|
91
91
|
|
|
92
92
|
You can run `duel --help` to get the same info. Below is the output of that:
|
|
@@ -106,7 +106,7 @@ Options:
|
|
|
106
106
|
|
|
107
107
|
These are definitely edge cases, and would only really come up if your project mixes file extensions. For example, if you have `.ts` files combined with `.mts`, and/or `.cts`. For most projects, things should just work as expected.
|
|
108
108
|
|
|
109
|
-
* This is going to work best if your CJS-first project uses file extensions in
|
|
109
|
+
* This is going to work best if your CJS-first project uses file extensions in _relative_ specifiers. This is completely acceptable in CJS projects, and [required in ESM projects](https://nodejs.org/api/esm.html#import-specifiers). This package makes no attempt to rewrite bare specifiers, or remap any relative specifiers to a directory index.
|
|
110
110
|
|
|
111
111
|
* Unfortunately, TypeScript doesn't really build [dual packages](https://nodejs.org/api/packages.html#dual-commonjses-module-packages) very well in regards to preserving module system by file extension. For instance, there doesn't appear to be a way to convert an arbitrary `.ts` file into another module system, _while also preserving the module system of `.mts` and `.cts` files_, without requiring **multiple** package.json files. In my opinion, the `tsc` compiler is fundamentally broken in this regard, and at best is enforcing usage patterns it shouldn't. This is only mentioned for transparency, `duel` will correct for this and produce files with the module system you would expect based on the file's extension, so that it works with [how Node.js determines module systems](https://nodejs.org/api/packages.html#determining-module-system).
|
|
112
112
|
|
package/dist/duel.cjs
CHANGED
|
@@ -12,10 +12,16 @@ var _promises = require("node:fs/promises");
|
|
|
12
12
|
var _nodeCrypto = require("node:crypto");
|
|
13
13
|
var _nodePerf_hooks = require("node:perf_hooks");
|
|
14
14
|
var _glob = require("glob");
|
|
15
|
+
var _findUp = require("find-up");
|
|
15
16
|
var _specifier = require("@knighted/specifier");
|
|
16
17
|
var _init = require("./init.cjs");
|
|
17
18
|
var _util = require("./util.cjs");
|
|
18
|
-
const tsc =
|
|
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
|
+
});
|
|
19
25
|
const runBuild = (project, outDir) => {
|
|
20
26
|
return new Promise((resolve, reject) => {
|
|
21
27
|
const args = outDir ? ['-p', project, '--outDir', outDir] : ['-p', project];
|
|
@@ -71,21 +77,24 @@ const duel = async args => {
|
|
|
71
77
|
for (const filename of filenames) {
|
|
72
78
|
const dts = /(\.d\.ts)$/;
|
|
73
79
|
const outFilename = dts.test(filename) ? filename.replace(dts, isCjsBuild ? '.d.cts' : '.d.mts') : filename.replace(/\.js$/, targetExt);
|
|
74
|
-
const
|
|
80
|
+
const {
|
|
81
|
+
code,
|
|
82
|
+
error
|
|
83
|
+
} = await _specifier.specifier.update(filename, ({
|
|
75
84
|
value
|
|
76
85
|
}) => {
|
|
77
|
-
// Collapse any BinaryExpression or NewExpression to test for a relative specifier
|
|
78
86
|
const collapsed = value.replace(/['"`+)\s]|new String\(/g, '');
|
|
79
87
|
const relative = /^(?:\.|\.\.)\//;
|
|
80
88
|
if (relative.test(collapsed)) {
|
|
81
|
-
// $2 is for any closing quotation/parens around BE or NE
|
|
82
89
|
return value.replace(/(.+)\.js([)'"`]*)?$/, `$1${targetExt}$2`);
|
|
83
90
|
}
|
|
84
91
|
});
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
if (code && !error) {
|
|
93
|
+
await (0, _promises.writeFile)(outFilename, code);
|
|
94
|
+
await (0, _promises.rm)(filename, {
|
|
95
|
+
force: true
|
|
96
|
+
});
|
|
97
|
+
}
|
|
89
98
|
}
|
|
90
99
|
};
|
|
91
100
|
const logSuccess = start => {
|
|
@@ -114,13 +123,7 @@ const duel = async args => {
|
|
|
114
123
|
const prepStart = _nodePerf_hooks.performance.now();
|
|
115
124
|
await (0, _promises.cp)(projectDir, paraTempDir, {
|
|
116
125
|
recursive: true,
|
|
117
|
-
|
|
118
|
-
* Ignore common .gitignored directories in Node.js projects.
|
|
119
|
-
* Except node_modules.
|
|
120
|
-
*
|
|
121
|
-
* @see https://github.com/github/gitignore/blob/main/Node.gitignore
|
|
122
|
-
*/
|
|
123
|
-
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/i.test(src)
|
|
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)
|
|
124
127
|
});
|
|
125
128
|
const dualConfigPath = (0, _nodePath.join)(paraTempDir, 'tsconfig.json');
|
|
126
129
|
const absoluteDualOutDir = (0, _nodePath.join)(paraTempDir, isCjsBuild ? (0, _nodePath.join)(outDir, 'cjs') : (0, _nodePath.join)(outDir, 'esm'));
|
|
@@ -146,7 +149,6 @@ const duel = async args => {
|
|
|
146
149
|
ignore: 'node_modules/**'
|
|
147
150
|
});
|
|
148
151
|
await updateSpecifiersAndFileExtensions(filenames);
|
|
149
|
-
// Copy over and cleanup
|
|
150
152
|
await (0, _promises.cp)(absoluteDualOutDir, (0, _nodePath.join)(absoluteOutDir, isCjsBuild ? 'cjs' : 'esm'), {
|
|
151
153
|
recursive: true
|
|
152
154
|
});
|
|
@@ -173,21 +175,11 @@ const duel = async args => {
|
|
|
173
175
|
const absoluteDualOutDir = (0, _nodePath.join)(projectDir, isCjsBuild ? (0, _nodePath.join)(outDir, 'cjs') : (0, _nodePath.join)(outDir, 'esm'));
|
|
174
176
|
const tsconfigDual = getOverrideTsConfig();
|
|
175
177
|
const pkgRename = 'package.json.bak';
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Create a new package.json with updated `type` field.
|
|
179
|
-
* Create a new tsconfig.json.
|
|
180
|
-
*
|
|
181
|
-
* The need to create a new package.json makes doing
|
|
182
|
-
* the builds in parallel difficult.
|
|
183
|
-
*/
|
|
184
178
|
await (0, _promises.rename)(pkg.path, (0, _nodePath.join)(pkgDir, pkgRename));
|
|
185
179
|
await (0, _promises.writeFile)(pkg.path, JSON.stringify({
|
|
186
180
|
type: isCjsBuild ? 'commonjs' : 'module'
|
|
187
181
|
}));
|
|
188
182
|
await (0, _promises.writeFile)(dualConfigPath, JSON.stringify(tsconfigDual));
|
|
189
|
-
|
|
190
|
-
// Build dual
|
|
191
183
|
(0, _util.log)('Starting dual build...');
|
|
192
184
|
try {
|
|
193
185
|
await runBuild(dualConfigPath, absoluteDualOutDir);
|
|
@@ -197,8 +189,6 @@ const duel = async args => {
|
|
|
197
189
|
success = false;
|
|
198
190
|
(0, _util.logError)(message);
|
|
199
191
|
}
|
|
200
|
-
|
|
201
|
-
// Cleanup and restore
|
|
202
192
|
await (0, _promises.rm)(dualConfigPath, {
|
|
203
193
|
force: true
|
|
204
194
|
});
|
package/dist/duel.js
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { argv
|
|
2
|
+
import { argv } from 'node:process';
|
|
3
3
|
import { join, dirname, resolve } from 'node:path';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { writeFile, rm, cp, rename, stat, access, constants } 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
|
+
import { findUp, pathExists } from 'find-up';
|
|
9
10
|
import { specifier } from '@knighted/specifier';
|
|
10
11
|
import { init } from './init.js';
|
|
11
12
|
import { getRealPathAsFileUrl, logError, log } from './util.js';
|
|
12
|
-
const tsc =
|
|
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
|
+
});
|
|
13
19
|
const runBuild = (project, outDir) => {
|
|
14
20
|
return new Promise((resolve, reject) => {
|
|
15
21
|
const args = outDir ? ['-p', project, '--outDir', outDir] : ['-p', project];
|
|
@@ -65,21 +71,24 @@ const duel = async args => {
|
|
|
65
71
|
for (const filename of filenames) {
|
|
66
72
|
const dts = /(\.d\.ts)$/;
|
|
67
73
|
const outFilename = dts.test(filename) ? filename.replace(dts, isCjsBuild ? '.d.cts' : '.d.mts') : filename.replace(/\.js$/, targetExt);
|
|
68
|
-
const
|
|
74
|
+
const {
|
|
75
|
+
code,
|
|
76
|
+
error
|
|
77
|
+
} = await specifier.update(filename, ({
|
|
69
78
|
value
|
|
70
79
|
}) => {
|
|
71
|
-
// Collapse any BinaryExpression or NewExpression to test for a relative specifier
|
|
72
80
|
const collapsed = value.replace(/['"`+)\s]|new String\(/g, '');
|
|
73
81
|
const relative = /^(?:\.|\.\.)\//;
|
|
74
82
|
if (relative.test(collapsed)) {
|
|
75
|
-
// $2 is for any closing quotation/parens around BE or NE
|
|
76
83
|
return value.replace(/(.+)\.js([)'"`]*)?$/, `$1${targetExt}$2`);
|
|
77
84
|
}
|
|
78
85
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
if (code && !error) {
|
|
87
|
+
await writeFile(outFilename, code);
|
|
88
|
+
await rm(filename, {
|
|
89
|
+
force: true
|
|
90
|
+
});
|
|
91
|
+
}
|
|
83
92
|
}
|
|
84
93
|
};
|
|
85
94
|
const logSuccess = start => {
|
|
@@ -108,13 +117,7 @@ const duel = async args => {
|
|
|
108
117
|
const prepStart = performance.now();
|
|
109
118
|
await cp(projectDir, paraTempDir, {
|
|
110
119
|
recursive: true,
|
|
111
|
-
|
|
112
|
-
* Ignore common .gitignored directories in Node.js projects.
|
|
113
|
-
* Except node_modules.
|
|
114
|
-
*
|
|
115
|
-
* @see https://github.com/github/gitignore/blob/main/Node.gitignore
|
|
116
|
-
*/
|
|
117
|
-
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/i.test(src)
|
|
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)
|
|
118
121
|
});
|
|
119
122
|
const dualConfigPath = join(paraTempDir, 'tsconfig.json');
|
|
120
123
|
const absoluteDualOutDir = join(paraTempDir, isCjsBuild ? join(outDir, 'cjs') : join(outDir, 'esm'));
|
|
@@ -140,7 +143,6 @@ const duel = async args => {
|
|
|
140
143
|
ignore: 'node_modules/**'
|
|
141
144
|
});
|
|
142
145
|
await updateSpecifiersAndFileExtensions(filenames);
|
|
143
|
-
// Copy over and cleanup
|
|
144
146
|
await cp(absoluteDualOutDir, join(absoluteOutDir, isCjsBuild ? 'cjs' : 'esm'), {
|
|
145
147
|
recursive: true
|
|
146
148
|
});
|
|
@@ -167,21 +169,11 @@ const duel = async args => {
|
|
|
167
169
|
const absoluteDualOutDir = join(projectDir, isCjsBuild ? join(outDir, 'cjs') : join(outDir, 'esm'));
|
|
168
170
|
const tsconfigDual = getOverrideTsConfig();
|
|
169
171
|
const pkgRename = 'package.json.bak';
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Create a new package.json with updated `type` field.
|
|
173
|
-
* Create a new tsconfig.json.
|
|
174
|
-
*
|
|
175
|
-
* The need to create a new package.json makes doing
|
|
176
|
-
* the builds in parallel difficult.
|
|
177
|
-
*/
|
|
178
172
|
await rename(pkg.path, join(pkgDir, pkgRename));
|
|
179
173
|
await writeFile(pkg.path, JSON.stringify({
|
|
180
174
|
type: isCjsBuild ? 'commonjs' : 'module'
|
|
181
175
|
}));
|
|
182
176
|
await writeFile(dualConfigPath, JSON.stringify(tsconfigDual));
|
|
183
|
-
|
|
184
|
-
// Build dual
|
|
185
177
|
log('Starting dual build...');
|
|
186
178
|
try {
|
|
187
179
|
await runBuild(dualConfigPath, absoluteDualOutDir);
|
|
@@ -191,8 +183,6 @@ const duel = async args => {
|
|
|
191
183
|
success = false;
|
|
192
184
|
logError(message);
|
|
193
185
|
}
|
|
194
|
-
|
|
195
|
-
// Cleanup and restore
|
|
196
186
|
await rm(dualConfigPath, {
|
|
197
187
|
force: true
|
|
198
188
|
});
|
package/dist/init.cjs
CHANGED
|
@@ -8,8 +8,10 @@ var _nodeProcess = require("node:process");
|
|
|
8
8
|
var _nodeUtil = require("node:util");
|
|
9
9
|
var _nodePath = require("node:path");
|
|
10
10
|
var _promises = require("node:fs/promises");
|
|
11
|
+
var _stripJsonComments = _interopRequireDefault(require("strip-json-comments"));
|
|
11
12
|
var _readPkgUp = require("read-pkg-up");
|
|
12
13
|
var _util = require("./util.cjs");
|
|
14
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
15
|
const init = async args => {
|
|
14
16
|
let parsed = null;
|
|
15
17
|
try {
|
|
@@ -103,7 +105,7 @@ const init = async args => {
|
|
|
103
105
|
if (stats.isFile()) {
|
|
104
106
|
let tsconfig = null;
|
|
105
107
|
try {
|
|
106
|
-
tsconfig = JSON.parse((await (0, _promises.readFile)(configPath)).toString());
|
|
108
|
+
tsconfig = JSON.parse((0, _stripJsonComments.default)((await (0, _promises.readFile)(configPath)).toString()));
|
|
107
109
|
} catch (err) {
|
|
108
110
|
(0, _util.logError)(`The config file found at ${configPath} is not parsable as JSON.`);
|
|
109
111
|
return false;
|
package/dist/init.js
CHANGED
|
@@ -2,6 +2,7 @@ import { cwd } from 'node:process';
|
|
|
2
2
|
import { parseArgs } from 'node:util';
|
|
3
3
|
import { resolve, join, dirname } from 'node:path';
|
|
4
4
|
import { stat, readFile } from 'node:fs/promises';
|
|
5
|
+
import stripJsonComments from 'strip-json-comments';
|
|
5
6
|
import { readPackageUp } from 'read-pkg-up';
|
|
6
7
|
import { logError, log } from './util.js';
|
|
7
8
|
const init = async args => {
|
|
@@ -97,7 +98,7 @@ const init = async args => {
|
|
|
97
98
|
if (stats.isFile()) {
|
|
98
99
|
let tsconfig = null;
|
|
99
100
|
try {
|
|
100
|
-
tsconfig = JSON.parse((await readFile(configPath)).toString());
|
|
101
|
+
tsconfig = JSON.parse(stripJsonComments((await readFile(configPath)).toString()));
|
|
101
102
|
} catch (err) {
|
|
102
103
|
logError(`The config file found at ${configPath} is not parsable as JSON.`);
|
|
103
104
|
return false;
|
package/dist/util.cjs
CHANGED
|
@@ -7,7 +7,6 @@ exports.logError = exports.log = exports.getRealPathAsFileUrl = void 0;
|
|
|
7
7
|
var _nodeUrl = require("node:url");
|
|
8
8
|
var _promises = require("node:fs/promises");
|
|
9
9
|
const log = (color = '\x1b[30m', msg = '') => {
|
|
10
|
-
// eslint-disable-next-line no-console
|
|
11
10
|
console.log(`${color}%s\x1b[0m`, msg);
|
|
12
11
|
};
|
|
13
12
|
exports.log = log;
|
package/dist/util.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { pathToFileURL } from 'node:url';
|
|
2
2
|
import { realpath } from 'node:fs/promises';
|
|
3
3
|
const log = (color = '\x1b[30m', msg = '') => {
|
|
4
|
-
// eslint-disable-next-line no-console
|
|
5
4
|
console.log(`${color}%s\x1b[0m`, msg);
|
|
6
5
|
};
|
|
7
6
|
const logError = log.bind(null, '\x1b[31m');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knighted/duel",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "TypeScript dual packages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist",
|
|
@@ -21,7 +21,7 @@
|
|
|
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": "babel-dual-package --no-cjs-dir src",
|
|
24
|
+
"build": "babel-dual-package --no-cjs-dir --no-comments src",
|
|
25
25
|
"prepack": "npm run build"
|
|
26
26
|
},
|
|
27
27
|
"keywords": [
|
|
@@ -44,22 +44,24 @@
|
|
|
44
44
|
"url": "https://github.com/knightedcodemonkey/duel/issues"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"typescript": ">=4.0.0 || >=4.
|
|
47
|
+
"typescript": ">=4.0.0 || >=4.9.0-dev || >=5.2.0-dev || >=5.3.0-dev"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^20.4.6",
|
|
51
|
-
"babel-dual-package": "^1.
|
|
51
|
+
"babel-dual-package": "^1.1.2",
|
|
52
52
|
"c8": "^8.0.1",
|
|
53
53
|
"eslint": "^8.45.0",
|
|
54
54
|
"eslint-plugin-n": "^16.0.1",
|
|
55
55
|
"prettier": "^3.0.1",
|
|
56
|
-
"typescript": "^5.
|
|
56
|
+
"typescript": "^5.3.0-dev.20230906",
|
|
57
57
|
"vite": "^4.4.8"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@knighted/specifier": "^1.0.
|
|
60
|
+
"@knighted/specifier": "^1.0.1",
|
|
61
|
+
"find-up": "^6.3.0",
|
|
61
62
|
"glob": "^10.3.3",
|
|
62
|
-
"read-pkg-up": "^10.0.0"
|
|
63
|
+
"read-pkg-up": "^10.0.0",
|
|
64
|
+
"strip-json-comments": "^5.0.1"
|
|
63
65
|
},
|
|
64
66
|
"prettier": {
|
|
65
67
|
"arrowParens": "avoid",
|