@knighted/module 1.2.0 → 1.3.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 +7 -24
- package/dist/cjs/cli.cjs +553 -0
- package/dist/cjs/cli.d.cts +14 -0
- package/dist/cjs/format.cjs +11 -2
- package/dist/cjs/formatters/metaProperty.cjs +3 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +547 -0
- package/dist/format.js +11 -2
- package/dist/formatters/metaProperty.js +3 -0
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ Highlights
|
|
|
16
16
|
- Configurable lowering modes: full syntax transforms or globals-only.
|
|
17
17
|
- Specifier tools: add extensions, add directory indexes, or map with a custom callback.
|
|
18
18
|
- Output control: write to disk (`out`/`inPlace`) or return the transformed string.
|
|
19
|
+
- CLI: `dub` for batch transforms, dry-run/list/summary, stdin/stdout, and colorized diagnostics. See [docs/cli.md](docs/cli.md).
|
|
19
20
|
|
|
20
21
|
> [!IMPORTANT]
|
|
21
22
|
> All parsing logic is applied under the assumption the code is in [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) which [modules run under by default](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#other_differences_between_modules_and_classic_scripts).
|
|
@@ -143,7 +144,7 @@ type ModuleOptions = {
|
|
|
143
144
|
### Behavior notes (defaults in parentheses)
|
|
144
145
|
|
|
145
146
|
- `target` (`commonjs`): output module system.
|
|
146
|
-
- `transformSyntax` (true): enable/disable the ESM↔CJS lowering pass; set to `'globals-only'` to rewrite module globals (`import.meta.*`, `__dirname`, `__filename`, `require.main` shims) while leaving import/export syntax untouched. In `'globals-only'`, no helpers are injected (e.g., `__requireResolve`), `require.resolve` rewrites to `import.meta.resolve`, and `idiomaticExports` is skipped. See [globals-only](
|
|
147
|
+
- `transformSyntax` (`true`): enable/disable the ESM↔CJS lowering pass; set to `'globals-only'` to rewrite module globals (`import.meta.*`, `__dirname`, `__filename`, `require.main` shims) while leaving import/export syntax untouched. In `'globals-only'`, no helpers are injected (e.g., `__requireResolve`), `require.resolve` rewrites to `import.meta.resolve`, and `idiomaticExports` is skipped. See [globals-only](docs/globals-only.md).
|
|
147
148
|
- `liveBindings` (`strict`): getter-based live bindings, or snapshot (`loose`/`off`).
|
|
148
149
|
- `appendJsExtension` (`relative-only` when targeting ESM): append `.js` to relative specifiers; never touches bare specifiers.
|
|
149
150
|
- `appendDirectoryIndex` (`index.js`): when a relative specifier ends with a slash, append this index filename (set `false` to disable).
|
|
@@ -159,7 +160,7 @@ type ModuleOptions = {
|
|
|
159
160
|
- `requireSource` (`builtin`): whether `require` comes from Node or `createRequire`.
|
|
160
161
|
- `cjsDefault` (`auto`): bundler-style default interop vs direct `module.exports`.
|
|
161
162
|
- `idiomaticExports` (`safe`): when raising CJS to ESM, attempt to synthesize `export` statements directly when it is safe. `off` always uses the helper bag; `aggressive` currently matches `safe` heuristics.
|
|
162
|
-
- `out`/`inPlace`:
|
|
163
|
+
- `out`/`inPlace`: choose output location. Default returns the transformed string (CLI emits to stdout). `out` writes to the provided path. `inPlace` overwrites the input files on disk and does not return/emit the code.
|
|
163
164
|
- `cwd` (`process.cwd()`): Base directory used to resolve relative `out` paths.
|
|
164
165
|
|
|
165
166
|
> [!NOTE]
|
|
@@ -170,13 +171,6 @@ See [docs/esm-to-cjs.md](docs/esm-to-cjs.md) for deeper notes on live bindings,
|
|
|
170
171
|
> [!NOTE]
|
|
171
172
|
> Known limitations: `with` and unshadowed `eval` are rejected when raising CJS to ESM because the rewrite would be unsound; bare specifiers are not rewritten—only relative specifiers participate in `rewriteSpecifier`.
|
|
172
173
|
|
|
173
|
-
### Globals-only scope
|
|
174
|
-
|
|
175
|
-
- Rewrites module globals (`import.meta.*`, `__dirname`, `__filename`, `require.main` shims) for the target side.
|
|
176
|
-
- Optional specifier rewrites still run (`rewriteSpecifier`, `appendJsExtension`, `appendDirectoryIndex`).
|
|
177
|
-
- Leaves imports/exports and interop untouched (no export bag, no idiomaticExports, no live-binding synthesis, no helpers like `__requireResolve`).
|
|
178
|
-
- CJS→ESM: `require.resolve` maps to `import.meta.resolve` (URL return, ESM resolver) and may differ from CJS resolution. ESM→CJS: `import.meta` maps to CJS globals; no import lowering.
|
|
179
|
-
|
|
180
174
|
### Diagnostics callback example
|
|
181
175
|
|
|
182
176
|
Pass a `diagnostics` callback to surface CJS→ESM edge cases (mixed `module.exports`/`exports`, top-level `return`, legacy `require.cache`/`require.extensions`, live-binding reassignments, string-literal export names):
|
|
@@ -213,20 +207,9 @@ TypeScript reports asymmetric module-global errors (e.g., `import.meta` in CJS,
|
|
|
213
207
|
|
|
214
208
|
Minimal flow:
|
|
215
209
|
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const files = await glob('src/**/*.{ts,js,mts,cts}', { ignore: 'node_modules/**' })
|
|
221
|
-
|
|
222
|
-
for (const file of files) {
|
|
223
|
-
await transform(file, {
|
|
224
|
-
target: 'commonjs', // or 'module' when raising CJS → ESM
|
|
225
|
-
inPlace: true,
|
|
226
|
-
transformSyntax: true,
|
|
227
|
-
})
|
|
228
|
-
}
|
|
229
|
-
// then run `tsc`
|
|
210
|
+
```bash
|
|
211
|
+
dub -t commonjs "src/**/*.{ts,js,mts,cts}" --ignore node_modules/** --transform-syntax globals-only --in-place
|
|
212
|
+
tsc
|
|
230
213
|
```
|
|
231
214
|
|
|
232
|
-
This pre-`tsc` step
|
|
215
|
+
This pre-`tsc` step rewrites globals-only (keeps import/export syntax) so the TypeScript checker sees already-rewritten sources; runtime semantics still match the target build.
|
package/dist/cjs/cli.cjs
ADDED
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.runCli = void 0;
|
|
8
|
+
var _nodeProcess = require("node:process");
|
|
9
|
+
var _nodeUtil = require("node:util");
|
|
10
|
+
var _promises = require("node:fs/promises");
|
|
11
|
+
var _nodePath = require("node:path");
|
|
12
|
+
var _nodeModule = require("node:module");
|
|
13
|
+
var _glob = require("glob");
|
|
14
|
+
var _module = require("./module.cjs");
|
|
15
|
+
var _parse = require("./parse.cjs");
|
|
16
|
+
var _format = require("./format.cjs");
|
|
17
|
+
var _specifier = require("./specifier.cjs");
|
|
18
|
+
var _lang = require("./utils/lang.cjs");
|
|
19
|
+
const defaultOptions = {
|
|
20
|
+
target: 'commonjs',
|
|
21
|
+
sourceType: 'auto',
|
|
22
|
+
transformSyntax: true,
|
|
23
|
+
liveBindings: 'strict',
|
|
24
|
+
rewriteSpecifier: undefined,
|
|
25
|
+
appendJsExtension: undefined,
|
|
26
|
+
appendDirectoryIndex: 'index.js',
|
|
27
|
+
dirFilename: 'inject',
|
|
28
|
+
importMeta: 'shim',
|
|
29
|
+
importMetaMain: 'shim',
|
|
30
|
+
requireMainStrategy: 'import-meta-main',
|
|
31
|
+
detectCircularRequires: 'off',
|
|
32
|
+
requireSource: 'builtin',
|
|
33
|
+
nestedRequireStrategy: 'create-require',
|
|
34
|
+
cjsDefault: 'auto',
|
|
35
|
+
idiomaticExports: 'safe',
|
|
36
|
+
importMetaPrelude: 'auto',
|
|
37
|
+
topLevelAwait: 'error',
|
|
38
|
+
cwd: undefined,
|
|
39
|
+
out: undefined,
|
|
40
|
+
inPlace: false
|
|
41
|
+
};
|
|
42
|
+
const icons = {
|
|
43
|
+
info: 'i',
|
|
44
|
+
warn: '⚠',
|
|
45
|
+
error: '✖',
|
|
46
|
+
success: '✔'
|
|
47
|
+
};
|
|
48
|
+
const codes = {
|
|
49
|
+
reset: '\u001b[0m',
|
|
50
|
+
bold: '\u001b[1m',
|
|
51
|
+
dim: '\u001b[2m',
|
|
52
|
+
red: '\u001b[31m',
|
|
53
|
+
yellow: '\u001b[33m',
|
|
54
|
+
green: '\u001b[32m',
|
|
55
|
+
cyan: '\u001b[36m'
|
|
56
|
+
};
|
|
57
|
+
const colorize = enabled => {
|
|
58
|
+
if (!enabled) {
|
|
59
|
+
return {
|
|
60
|
+
bold: v => v,
|
|
61
|
+
dim: v => v,
|
|
62
|
+
red: v => v,
|
|
63
|
+
yellow: v => v,
|
|
64
|
+
green: v => v,
|
|
65
|
+
cyan: v => v
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const wrap = code => v => `${code}${v}${codes.reset}`;
|
|
69
|
+
return {
|
|
70
|
+
bold: wrap(codes.bold),
|
|
71
|
+
dim: wrap(codes.dim),
|
|
72
|
+
red: wrap(codes.red),
|
|
73
|
+
yellow: wrap(codes.yellow),
|
|
74
|
+
green: wrap(codes.green),
|
|
75
|
+
cyan: wrap(codes.cyan)
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const builtinSpecifiers = new Set(_nodeModule.builtinModules.map(mod => mod.startsWith('node:') ? mod.slice(5) : mod).flatMap(mod => {
|
|
79
|
+
const parts = mod.split('/');
|
|
80
|
+
const base = parts[0];
|
|
81
|
+
return parts.length > 1 ? [mod, base] : [mod];
|
|
82
|
+
}));
|
|
83
|
+
const collapseSpecifier = value => value.replace(/['"`+)\s]|new String\(/g, '');
|
|
84
|
+
const appendExtensionIfNeeded = (value, mode, dirIndex) => {
|
|
85
|
+
if (mode === 'off') return;
|
|
86
|
+
const collapsed = collapseSpecifier(value);
|
|
87
|
+
const isRelative = /^(?:\.\.?)\//.test(collapsed);
|
|
88
|
+
if (!isRelative) return;
|
|
89
|
+
const base = collapsed.split(/[?#]/)[0];
|
|
90
|
+
if (!base) return;
|
|
91
|
+
if (base.endsWith('/')) {
|
|
92
|
+
if (!dirIndex) return;
|
|
93
|
+
return `${value}${dirIndex}`;
|
|
94
|
+
}
|
|
95
|
+
const lastSegment = base.split('/').pop() ?? '';
|
|
96
|
+
if (lastSegment.includes('.')) return;
|
|
97
|
+
return `${value}.js`;
|
|
98
|
+
};
|
|
99
|
+
const rewriteSpecifierValue = (value, rewriteSpecifier) => {
|
|
100
|
+
if (!rewriteSpecifier) return;
|
|
101
|
+
if (typeof rewriteSpecifier === 'function') {
|
|
102
|
+
return rewriteSpecifier(value) ?? undefined;
|
|
103
|
+
}
|
|
104
|
+
const collapsed = collapseSpecifier(value);
|
|
105
|
+
const relative = /^(?:\.\.?\/)/;
|
|
106
|
+
if (relative.test(collapsed)) {
|
|
107
|
+
return value.replace(/(.+)\.(?:m|c)?(?:j|t)sx?([)'"]*)?$/, `$1${rewriteSpecifier}$2`);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const normalizeBuiltinSpecifier = value => {
|
|
111
|
+
const collapsed = collapseSpecifier(value);
|
|
112
|
+
if (!collapsed) return;
|
|
113
|
+
const specPart = collapsed.split(/[?#]/)[0] ?? '';
|
|
114
|
+
if (/^(?:\.\.?(?:\/)|\/)/.test(specPart)) return;
|
|
115
|
+
if (/^[a-zA-Z][a-zA-Z+.-]*:/.test(specPart) && !specPart.startsWith('node:')) return;
|
|
116
|
+
const bare = specPart.startsWith('node:') ? specPart.slice(5) : specPart;
|
|
117
|
+
const base = bare.split('/')[0] ?? '';
|
|
118
|
+
if (!builtinSpecifiers.has(bare) && !builtinSpecifiers.has(base)) return;
|
|
119
|
+
if (specPart.startsWith('node:')) return;
|
|
120
|
+
const quote = /^['"`]/.exec(value)?.[0] ?? '';
|
|
121
|
+
return quote ? `${quote}node:${value.slice(quote.length)}` : `node:${value}`;
|
|
122
|
+
};
|
|
123
|
+
const optionsTable = [{
|
|
124
|
+
long: 'target',
|
|
125
|
+
short: 't',
|
|
126
|
+
type: 'string',
|
|
127
|
+
desc: 'Output format (module|commonjs)'
|
|
128
|
+
}, {
|
|
129
|
+
long: 'transform-syntax',
|
|
130
|
+
short: 'x',
|
|
131
|
+
type: 'string',
|
|
132
|
+
desc: 'Syntax transforms (true|false|globals-only)'
|
|
133
|
+
}, {
|
|
134
|
+
long: 'rewrite-specifier',
|
|
135
|
+
short: 'r',
|
|
136
|
+
type: 'string',
|
|
137
|
+
desc: 'Rewrite import specifiers (.js/.mjs/.cjs/.ts/.mts/.cts)'
|
|
138
|
+
}, {
|
|
139
|
+
long: 'append-js-extension',
|
|
140
|
+
short: 'j',
|
|
141
|
+
type: 'string',
|
|
142
|
+
desc: 'Append .js to relative imports (off|relative-only|all)'
|
|
143
|
+
}, {
|
|
144
|
+
long: 'append-directory-index',
|
|
145
|
+
short: 'i',
|
|
146
|
+
type: 'string',
|
|
147
|
+
desc: 'Append directory index (e.g. index.js) or false'
|
|
148
|
+
}, {
|
|
149
|
+
long: 'detect-circular-requires',
|
|
150
|
+
short: 'c',
|
|
151
|
+
type: 'string',
|
|
152
|
+
desc: 'Warn/error on circular require (off|warn|error)'
|
|
153
|
+
}, {
|
|
154
|
+
long: 'top-level-await',
|
|
155
|
+
short: 'a',
|
|
156
|
+
type: 'string',
|
|
157
|
+
desc: 'TLA handling (error|wrap|preserve)'
|
|
158
|
+
}, {
|
|
159
|
+
long: 'cjs-default',
|
|
160
|
+
short: 'd',
|
|
161
|
+
type: 'string',
|
|
162
|
+
desc: 'Default interop (module-exports|auto|none)'
|
|
163
|
+
}, {
|
|
164
|
+
long: 'idiomatic-exports',
|
|
165
|
+
short: 'e',
|
|
166
|
+
type: 'string',
|
|
167
|
+
desc: 'Emit idiomatic exports when safe (off|safe|aggressive)'
|
|
168
|
+
}, {
|
|
169
|
+
long: 'import-meta-prelude',
|
|
170
|
+
short: 'm',
|
|
171
|
+
type: 'string',
|
|
172
|
+
desc: 'Emit import.meta prelude (off|auto|on)'
|
|
173
|
+
}, {
|
|
174
|
+
long: 'nested-require-strategy',
|
|
175
|
+
short: 'n',
|
|
176
|
+
type: 'string',
|
|
177
|
+
desc: 'Rewrite nested require (create-require|dynamic-import)'
|
|
178
|
+
}, {
|
|
179
|
+
long: 'require-main-strategy',
|
|
180
|
+
short: 'R',
|
|
181
|
+
type: 'string',
|
|
182
|
+
desc: 'Detect main (import-meta-main|realpath)'
|
|
183
|
+
}, {
|
|
184
|
+
long: 'live-bindings',
|
|
185
|
+
short: 'l',
|
|
186
|
+
type: 'string',
|
|
187
|
+
desc: 'Live binding strategy (strict|loose|off)'
|
|
188
|
+
}, {
|
|
189
|
+
long: 'out-dir',
|
|
190
|
+
short: 'o',
|
|
191
|
+
type: 'string',
|
|
192
|
+
desc: 'Write outputs to a directory mirror'
|
|
193
|
+
}, {
|
|
194
|
+
long: 'in-place',
|
|
195
|
+
short: 'p',
|
|
196
|
+
type: 'boolean',
|
|
197
|
+
desc: 'Rewrite files in place'
|
|
198
|
+
}, {
|
|
199
|
+
long: 'dry-run',
|
|
200
|
+
short: 'y',
|
|
201
|
+
type: 'boolean',
|
|
202
|
+
desc: 'Do not write files; report planned changes'
|
|
203
|
+
}, {
|
|
204
|
+
long: 'list',
|
|
205
|
+
short: 'L',
|
|
206
|
+
type: 'boolean',
|
|
207
|
+
desc: 'List files that would change'
|
|
208
|
+
}, {
|
|
209
|
+
long: 'summary',
|
|
210
|
+
short: 's',
|
|
211
|
+
type: 'boolean',
|
|
212
|
+
desc: 'Print a summary of work performed'
|
|
213
|
+
}, {
|
|
214
|
+
long: 'json',
|
|
215
|
+
short: 'J',
|
|
216
|
+
type: 'boolean',
|
|
217
|
+
desc: 'Emit machine-readable JSON summary/diagnostics'
|
|
218
|
+
}, {
|
|
219
|
+
long: 'cwd',
|
|
220
|
+
short: 'C',
|
|
221
|
+
type: 'string',
|
|
222
|
+
desc: 'Working directory for resolving files/out paths'
|
|
223
|
+
}, {
|
|
224
|
+
long: 'stdin-filename',
|
|
225
|
+
short: 'f',
|
|
226
|
+
type: 'string',
|
|
227
|
+
desc: 'Virtual filename when reading from stdin'
|
|
228
|
+
}, {
|
|
229
|
+
long: 'ignore',
|
|
230
|
+
short: 'g',
|
|
231
|
+
type: 'string',
|
|
232
|
+
desc: 'Glob pattern(s) to ignore (repeatable)'
|
|
233
|
+
}, {
|
|
234
|
+
long: 'help',
|
|
235
|
+
short: 'h',
|
|
236
|
+
type: 'boolean',
|
|
237
|
+
desc: 'Show help'
|
|
238
|
+
}, {
|
|
239
|
+
long: 'version',
|
|
240
|
+
short: 'v',
|
|
241
|
+
type: 'boolean',
|
|
242
|
+
desc: 'Show version'
|
|
243
|
+
}];
|
|
244
|
+
const buildHelp = enableColor => {
|
|
245
|
+
const c = colorize(enableColor);
|
|
246
|
+
const maxFlagLength = Math.max(...optionsTable.map(opt => ` -${opt.short}, --${opt.long}`.length));
|
|
247
|
+
const lines = [`${c.bold('Usage:')} dub [options] <files...>`, '', 'Examples:', ' dub -t module src/index.cjs --out-dir dist', ' dub -t commonjs src/**/*.mjs -p', ' cat input.cjs | dub -t module --stdin-filename input.cjs', '', 'Options:'];
|
|
248
|
+
for (const opt of optionsTable) {
|
|
249
|
+
const flag = ` -${opt.short}, --${opt.long}`;
|
|
250
|
+
const pad = ' '.repeat(Math.max(2, maxFlagLength - flag.length + 2));
|
|
251
|
+
lines.push(`${c.bold(flag)}${pad}${opt.desc}`);
|
|
252
|
+
}
|
|
253
|
+
return `${lines.join('\n')}\n`;
|
|
254
|
+
};
|
|
255
|
+
const parseEnum = (value, allowed) => {
|
|
256
|
+
if (value === undefined) return undefined;
|
|
257
|
+
return allowed.includes(value) ? value : undefined;
|
|
258
|
+
};
|
|
259
|
+
const parseTransformSyntax = value => {
|
|
260
|
+
if (value === undefined) return defaultOptions.transformSyntax;
|
|
261
|
+
if (value === 'globals-only') return 'globals-only';
|
|
262
|
+
if (value === 'false') return false;
|
|
263
|
+
if (value === 'true') return true;
|
|
264
|
+
return defaultOptions.transformSyntax;
|
|
265
|
+
};
|
|
266
|
+
const parseAppendDirectoryIndex = value => {
|
|
267
|
+
if (value === undefined) return undefined;
|
|
268
|
+
if (value === 'false') return false;
|
|
269
|
+
return value;
|
|
270
|
+
};
|
|
271
|
+
const toModuleOptions = values => {
|
|
272
|
+
const target = parseEnum(values.target, ['module', 'commonjs']) ?? defaultOptions.target;
|
|
273
|
+
const transformSyntax = parseTransformSyntax(values['transform-syntax']);
|
|
274
|
+
const appendJsExtension = parseEnum(values['append-js-extension'], ['off', 'relative-only', 'all']);
|
|
275
|
+
const appendDirectoryIndex = parseAppendDirectoryIndex(values['append-directory-index']);
|
|
276
|
+
const opts = {
|
|
277
|
+
...defaultOptions,
|
|
278
|
+
target,
|
|
279
|
+
transformSyntax,
|
|
280
|
+
rewriteSpecifier: values['rewrite-specifier'] ?? undefined,
|
|
281
|
+
appendJsExtension: appendJsExtension,
|
|
282
|
+
appendDirectoryIndex,
|
|
283
|
+
detectCircularRequires: parseEnum(values['detect-circular-requires'], ['off', 'warn', 'error']) ?? defaultOptions.detectCircularRequires,
|
|
284
|
+
topLevelAwait: parseEnum(values['top-level-await'], ['error', 'wrap', 'preserve']) ?? defaultOptions.topLevelAwait,
|
|
285
|
+
cjsDefault: parseEnum(values['cjs-default'], ['module-exports', 'auto', 'none']) ?? defaultOptions.cjsDefault,
|
|
286
|
+
idiomaticExports: parseEnum(values['idiomatic-exports'], ['off', 'safe', 'aggressive']) ?? defaultOptions.idiomaticExports,
|
|
287
|
+
importMetaPrelude: parseEnum(values['import-meta-prelude'], ['off', 'auto', 'on']) ?? defaultOptions.importMetaPrelude,
|
|
288
|
+
nestedRequireStrategy: parseEnum(values['nested-require-strategy'], ['create-require', 'dynamic-import']) ?? defaultOptions.nestedRequireStrategy,
|
|
289
|
+
requireMainStrategy: parseEnum(values['require-main-strategy'], ['import-meta-main', 'realpath']) ?? defaultOptions.requireMainStrategy,
|
|
290
|
+
liveBindings: parseEnum(values['live-bindings'], ['strict', 'loose', 'off']) ?? defaultOptions.liveBindings,
|
|
291
|
+
cwd: values.cwd ? (0, _nodePath.resolve)(String(values.cwd)) : defaultOptions.cwd
|
|
292
|
+
};
|
|
293
|
+
return opts;
|
|
294
|
+
};
|
|
295
|
+
const readStdin = async stdin => {
|
|
296
|
+
const chunks = [];
|
|
297
|
+
for await (const chunk of stdin) {
|
|
298
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
299
|
+
}
|
|
300
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
301
|
+
};
|
|
302
|
+
const expandFiles = async (patterns, cwd, ignore) => {
|
|
303
|
+
const files = new Set();
|
|
304
|
+
for (const pattern of patterns) {
|
|
305
|
+
const matches = await (0, _glob.glob)(pattern, {
|
|
306
|
+
cwd,
|
|
307
|
+
absolute: true,
|
|
308
|
+
nodir: true,
|
|
309
|
+
windowsPathsNoEscape: true,
|
|
310
|
+
ignore
|
|
311
|
+
});
|
|
312
|
+
for (const m of matches) files.add((0, _nodePath.resolve)(m));
|
|
313
|
+
}
|
|
314
|
+
return [...files];
|
|
315
|
+
};
|
|
316
|
+
const makeLogger = (stdout, stderr) => {
|
|
317
|
+
const enableColor = stdout.isTTY ?? stderr.isTTY ?? false;
|
|
318
|
+
const c = colorize(enableColor);
|
|
319
|
+
const log = (kind, message, stream) => {
|
|
320
|
+
const icon = icons[kind];
|
|
321
|
+
const colored = kind === 'error' ? c.red(message) : kind === 'warn' ? c.yellow(message) : kind === 'success' ? c.green(message) : c.cyan(message);
|
|
322
|
+
stream.write(`${icon} ${colored}\n`);
|
|
323
|
+
};
|
|
324
|
+
return {
|
|
325
|
+
info: msg => log('info', msg, stdout),
|
|
326
|
+
warn: msg => log('warn', msg, stderr),
|
|
327
|
+
error: msg => log('error', msg, stderr),
|
|
328
|
+
success: msg => log('success', msg, stdout),
|
|
329
|
+
color: c
|
|
330
|
+
};
|
|
331
|
+
};
|
|
332
|
+
const applySpecifierUpdates = async (source, filename, opts, appendMode, dirIndex) => {
|
|
333
|
+
if (!opts.rewriteSpecifier && appendMode === 'off' && !dirIndex) return source;
|
|
334
|
+
const lang = (0, _lang.getLangFromExt)(filename);
|
|
335
|
+
const updated = await _specifier.specifier.updateSrc(source, lang, spec => {
|
|
336
|
+
const normalized = normalizeBuiltinSpecifier(spec.value);
|
|
337
|
+
const rewritten = rewriteSpecifierValue(normalized ?? spec.value, opts.rewriteSpecifier);
|
|
338
|
+
const baseValue = rewritten ?? normalized ?? spec.value;
|
|
339
|
+
const appended = appendExtensionIfNeeded(baseValue, appendMode, dirIndex);
|
|
340
|
+
return appended ?? rewritten ?? normalized ?? undefined;
|
|
341
|
+
});
|
|
342
|
+
return updated;
|
|
343
|
+
};
|
|
344
|
+
const transformVirtual = async (source, filename, opts) => {
|
|
345
|
+
const ast = (0, _parse.parse)(filename, source);
|
|
346
|
+
let output = await (0, _format.format)(source, ast, {
|
|
347
|
+
...opts,
|
|
348
|
+
filePath: filename
|
|
349
|
+
});
|
|
350
|
+
const appendMode = opts.appendJsExtension ?? (opts.target === 'module' ? 'relative-only' : 'off');
|
|
351
|
+
const dirIndex = opts.appendDirectoryIndex === undefined ? 'index.js' : opts.appendDirectoryIndex;
|
|
352
|
+
output = await applySpecifierUpdates(output, filename, opts, appendMode, dirIndex);
|
|
353
|
+
return output;
|
|
354
|
+
};
|
|
355
|
+
const summarizeDiagnostics = diags => {
|
|
356
|
+
let warnings = 0;
|
|
357
|
+
let errors = 0;
|
|
358
|
+
for (const d of diags) {
|
|
359
|
+
if (d.level === 'warning') warnings += 1;
|
|
360
|
+
if (d.level === 'error') errors += 1;
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
warnings,
|
|
364
|
+
errors
|
|
365
|
+
};
|
|
366
|
+
};
|
|
367
|
+
const runFiles = async (files, moduleOpts, io, flags) => {
|
|
368
|
+
const results = [];
|
|
369
|
+
const logger = makeLogger(io.stdout, io.stderr);
|
|
370
|
+
for (const file of files) {
|
|
371
|
+
const diagnostics = [];
|
|
372
|
+
const original = await (0, _promises.readFile)(file, 'utf8');
|
|
373
|
+
const outPath = flags.outDir ? (0, _nodePath.join)(flags.outDir, (0, _nodePath.relative)(moduleOpts.cwd ?? process.cwd(), file)) : undefined;
|
|
374
|
+
const perFileOpts = {
|
|
375
|
+
...moduleOpts,
|
|
376
|
+
diagnostics: diag => diagnostics.push(diag),
|
|
377
|
+
out: undefined,
|
|
378
|
+
inPlace: false,
|
|
379
|
+
filePath: file
|
|
380
|
+
};
|
|
381
|
+
let writeTarget;
|
|
382
|
+
if (!flags.dryRun && !flags.list) {
|
|
383
|
+
if (flags.inPlace) {
|
|
384
|
+
perFileOpts.inPlace = true;
|
|
385
|
+
} else if (outPath) {
|
|
386
|
+
writeTarget = outPath;
|
|
387
|
+
perFileOpts.out = outPath;
|
|
388
|
+
await (0, _promises.mkdir)((0, _nodePath.dirname)(outPath), {
|
|
389
|
+
recursive: true
|
|
390
|
+
});
|
|
391
|
+
} else if (!flags.allowStdout) {
|
|
392
|
+
logger.error('Specify --out-dir or --in-place when transforming files');
|
|
393
|
+
return {
|
|
394
|
+
code: 2,
|
|
395
|
+
results
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const output = await (0, _module.transform)(file, perFileOpts);
|
|
400
|
+
const changed = output !== original;
|
|
401
|
+
if (flags.list && changed) {
|
|
402
|
+
logger.info(file);
|
|
403
|
+
}
|
|
404
|
+
if (!flags.dryRun && !flags.list && !writeTarget && !perFileOpts.inPlace) {
|
|
405
|
+
io.stdout.write(output);
|
|
406
|
+
}
|
|
407
|
+
results.push({
|
|
408
|
+
filePath: file,
|
|
409
|
+
changed,
|
|
410
|
+
diagnostics
|
|
411
|
+
});
|
|
412
|
+
const counts = summarizeDiagnostics(diagnostics);
|
|
413
|
+
if (!flags.json) {
|
|
414
|
+
for (const diag of diagnostics) {
|
|
415
|
+
const prefix = diag.level === 'error' ? logger.error : logger.warn;
|
|
416
|
+
const loc = diag.loc ? ` [${diag.loc.start}-${diag.loc.end}]` : '';
|
|
417
|
+
prefix(`${diag.code}: ${diag.message}${loc}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (counts.errors > 0) {
|
|
421
|
+
return {
|
|
422
|
+
code: 1,
|
|
423
|
+
results
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (flags.summary && !flags.json) {
|
|
428
|
+
const changedCount = results.filter(r => r.changed).length;
|
|
429
|
+
logger.success(`Processed ${results.length} file(s); changed ${changedCount}`);
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
code: 0,
|
|
433
|
+
results
|
|
434
|
+
};
|
|
435
|
+
};
|
|
436
|
+
const runCli = async ({
|
|
437
|
+
argv = process.argv.slice(2),
|
|
438
|
+
stdin = _nodeProcess.stdin,
|
|
439
|
+
stdout = _nodeProcess.stdout,
|
|
440
|
+
stderr = _nodeProcess.stderr
|
|
441
|
+
} = {}) => {
|
|
442
|
+
const {
|
|
443
|
+
values,
|
|
444
|
+
positionals
|
|
445
|
+
} = (0, _nodeUtil.parseArgs)({
|
|
446
|
+
args: argv,
|
|
447
|
+
allowPositionals: true,
|
|
448
|
+
options: Object.fromEntries(optionsTable.map(opt => [opt.long, {
|
|
449
|
+
type: opt.type,
|
|
450
|
+
short: opt.short
|
|
451
|
+
}]))
|
|
452
|
+
});
|
|
453
|
+
const logger = makeLogger(stdout, stderr);
|
|
454
|
+
if (values.help) {
|
|
455
|
+
stdout.write(buildHelp(stdout.isTTY ?? false));
|
|
456
|
+
return 0;
|
|
457
|
+
}
|
|
458
|
+
if (values.version) {
|
|
459
|
+
const pkg = JSON.parse(await (0, _promises.readFile)(new URL('../package.json', import.meta.url), 'utf8'));
|
|
460
|
+
stdout.write(`${pkg.version}\n`);
|
|
461
|
+
return 0;
|
|
462
|
+
}
|
|
463
|
+
const moduleOpts = toModuleOptions(values);
|
|
464
|
+
const cwd = moduleOpts.cwd ?? process.cwd();
|
|
465
|
+
const allowStdout = positionals.length <= 1;
|
|
466
|
+
const fromStdin = positionals.length === 0 || positionals.includes('-');
|
|
467
|
+
const patterns = positionals.filter(p => p !== '-');
|
|
468
|
+
const ignoreValues = values.ignore;
|
|
469
|
+
const ignore = ignoreValues ? (Array.isArray(ignoreValues) ? ignoreValues : [ignoreValues]).map(String) : undefined;
|
|
470
|
+
const outDir = values['out-dir'] ? (0, _nodePath.resolve)(cwd, String(values['out-dir'])) : undefined;
|
|
471
|
+
const inPlace = Boolean(values['in-place']);
|
|
472
|
+
const dryRun = Boolean(values['dry-run']);
|
|
473
|
+
const list = Boolean(values.list);
|
|
474
|
+
const summary = Boolean(values.summary);
|
|
475
|
+
const json = Boolean(values.json);
|
|
476
|
+
if (outDir && inPlace) {
|
|
477
|
+
logger.error('Choose either --out-dir or --in-place, not both');
|
|
478
|
+
return 2;
|
|
479
|
+
}
|
|
480
|
+
if (fromStdin && (outDir || inPlace)) {
|
|
481
|
+
logger.error('Cannot combine stdin with --out-dir or --in-place; output goes to stdout');
|
|
482
|
+
return 2;
|
|
483
|
+
}
|
|
484
|
+
const files = await expandFiles(patterns, cwd, ignore);
|
|
485
|
+
if (!fromStdin && files.length === 0) {
|
|
486
|
+
logger.error('No input files were provided or matched');
|
|
487
|
+
return 2;
|
|
488
|
+
}
|
|
489
|
+
const tasks = [];
|
|
490
|
+
if (fromStdin) {
|
|
491
|
+
const virtualName = values['stdin-filename'] ?? 'stdin.js';
|
|
492
|
+
const source = await readStdin(stdin);
|
|
493
|
+
const diagnostics = [];
|
|
494
|
+
const output = await transformVirtual(source, virtualName, {
|
|
495
|
+
...moduleOpts,
|
|
496
|
+
diagnostics: diag => diagnostics.push(diag),
|
|
497
|
+
filePath: virtualName,
|
|
498
|
+
cwd
|
|
499
|
+
});
|
|
500
|
+
tasks.push({
|
|
501
|
+
filePath: virtualName,
|
|
502
|
+
changed: true,
|
|
503
|
+
diagnostics
|
|
504
|
+
});
|
|
505
|
+
stdout.write(output);
|
|
506
|
+
if (!json) {
|
|
507
|
+
for (const diag of diagnostics) {
|
|
508
|
+
const prefix = diag.level === 'error' ? logger.error : logger.warn;
|
|
509
|
+
const loc = diag.loc ? ` [${diag.loc.start}-${diag.loc.end}]` : '';
|
|
510
|
+
prefix(`${diag.code}: ${diag.message}${loc}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const diagSummary = summarizeDiagnostics(diagnostics);
|
|
514
|
+
if (diagSummary.errors > 0) return 1;
|
|
515
|
+
}
|
|
516
|
+
if (files.length) {
|
|
517
|
+
const result = await runFiles(files, {
|
|
518
|
+
...moduleOpts,
|
|
519
|
+
cwd
|
|
520
|
+
}, {
|
|
521
|
+
stdout,
|
|
522
|
+
stderr
|
|
523
|
+
}, {
|
|
524
|
+
dryRun,
|
|
525
|
+
list,
|
|
526
|
+
summary,
|
|
527
|
+
json,
|
|
528
|
+
outDir,
|
|
529
|
+
inPlace,
|
|
530
|
+
allowStdout
|
|
531
|
+
});
|
|
532
|
+
if (typeof result.code === 'number' && result.code !== 0) return result.code;
|
|
533
|
+
tasks.push(...result.results);
|
|
534
|
+
}
|
|
535
|
+
if (json) {
|
|
536
|
+
const summaryDiag = summarizeDiagnostics(tasks.flatMap(t => t.diagnostics));
|
|
537
|
+
stdout.write(`${JSON.stringify({
|
|
538
|
+
files: tasks,
|
|
539
|
+
summary: summaryDiag
|
|
540
|
+
}, null, 2)}\n`);
|
|
541
|
+
}
|
|
542
|
+
return 0;
|
|
543
|
+
};
|
|
544
|
+
exports.runCli = runCli;
|
|
545
|
+
if (import.meta.main) {
|
|
546
|
+
runCli().then(code => {
|
|
547
|
+
if (code !== 0) process.exit(code);
|
|
548
|
+
}, err => {
|
|
549
|
+
// eslint-disable-next-line no-console -- CLI surface
|
|
550
|
+
console.error(err);
|
|
551
|
+
process.exit(1);
|
|
552
|
+
});
|
|
553
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { stdin as defaultStdin } from 'node:process';
|
|
3
|
+
type StreamLike = {
|
|
4
|
+
isTTY?: boolean;
|
|
5
|
+
write: (chunk: string | Uint8Array) => unknown;
|
|
6
|
+
};
|
|
7
|
+
type CliOptions = {
|
|
8
|
+
argv?: string[];
|
|
9
|
+
stdin?: typeof defaultStdin;
|
|
10
|
+
stdout?: StreamLike;
|
|
11
|
+
stderr?: StreamLike;
|
|
12
|
+
};
|
|
13
|
+
declare const runCli: ({ argv, stdin, stdout, stderr, }?: CliOptions) => Promise<number>;
|
|
14
|
+
export { runCli };
|
package/dist/cjs/format.cjs
CHANGED
|
@@ -580,7 +580,10 @@ const format = async (src, ast, opts) => {
|
|
|
580
580
|
seen.add(propName);
|
|
581
581
|
if (rhs.type === 'Identifier') {
|
|
582
582
|
const rhsId = rhsSourceFor(rhs);
|
|
583
|
-
|
|
583
|
+
const rhsName = rhs.name;
|
|
584
|
+
if (rhsId === rhsName && rhsName === propName) {
|
|
585
|
+
exportsOut.push(`export { ${propName} };`);
|
|
586
|
+
} else if (rhsId === rhsName) {
|
|
584
587
|
exportsOut.push(`export { ${rhsId} as ${propName} };`);
|
|
585
588
|
} else {
|
|
586
589
|
exportsOut.push(`export const ${propName} = ${rhsId};`);
|
|
@@ -589,9 +592,15 @@ const format = async (src, ast, opts) => {
|
|
|
589
592
|
exportsOut.push(`export const ${propName} = ${rhsSrc};`);
|
|
590
593
|
}
|
|
591
594
|
}
|
|
595
|
+
|
|
596
|
+
// Trim trailing whitespace and one optional semicolon so the idiomatic export
|
|
597
|
+
// replacement does not leave the original `;` behind (avoids emitting `;;`).
|
|
598
|
+
let end = write.end;
|
|
599
|
+
while (end < src.length && (src[end] === ' ' || src[end] === '\t')) end++;
|
|
600
|
+
if (end < src.length && src[end] === ';') end++;
|
|
592
601
|
replacements.push({
|
|
593
602
|
start: write.start,
|
|
594
|
-
end
|
|
603
|
+
end
|
|
595
604
|
});
|
|
596
605
|
}
|
|
597
606
|
if (!seen.size) return {
|
|
@@ -49,6 +49,9 @@ const metaProperty = (node, parent, src, options) => {
|
|
|
49
49
|
case 'main':
|
|
50
50
|
src.update(parent.start, parent.end, importMetaMainExpr(options.importMetaMain));
|
|
51
51
|
break;
|
|
52
|
+
default:
|
|
53
|
+
src.update(parent.start, parent.end, `module.${parent.property.name}`);
|
|
54
|
+
break;
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { stdin as defaultStdin } from 'node:process';
|
|
3
|
+
type StreamLike = {
|
|
4
|
+
isTTY?: boolean;
|
|
5
|
+
write: (chunk: string | Uint8Array) => unknown;
|
|
6
|
+
};
|
|
7
|
+
type CliOptions = {
|
|
8
|
+
argv?: string[];
|
|
9
|
+
stdin?: typeof defaultStdin;
|
|
10
|
+
stdout?: StreamLike;
|
|
11
|
+
stderr?: StreamLike;
|
|
12
|
+
};
|
|
13
|
+
declare const runCli: ({ argv, stdin, stdout, stderr, }?: CliOptions) => Promise<number>;
|
|
14
|
+
export { runCli };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { stdin as defaultStdin, stdout as defaultStdout, stderr as defaultStderr } from 'node:process';
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
import { readFile, mkdir } from 'node:fs/promises';
|
|
5
|
+
import { dirname, resolve, relative, join } from 'node:path';
|
|
6
|
+
import { builtinModules } from 'node:module';
|
|
7
|
+
import { glob } from 'glob';
|
|
8
|
+
import { transform } from './module.js';
|
|
9
|
+
import { parse } from './parse.js';
|
|
10
|
+
import { format } from './format.js';
|
|
11
|
+
import { specifier } from './specifier.js';
|
|
12
|
+
import { getLangFromExt } from './utils/lang.js';
|
|
13
|
+
const defaultOptions = {
|
|
14
|
+
target: 'commonjs',
|
|
15
|
+
sourceType: 'auto',
|
|
16
|
+
transformSyntax: true,
|
|
17
|
+
liveBindings: 'strict',
|
|
18
|
+
rewriteSpecifier: undefined,
|
|
19
|
+
appendJsExtension: undefined,
|
|
20
|
+
appendDirectoryIndex: 'index.js',
|
|
21
|
+
dirFilename: 'inject',
|
|
22
|
+
importMeta: 'shim',
|
|
23
|
+
importMetaMain: 'shim',
|
|
24
|
+
requireMainStrategy: 'import-meta-main',
|
|
25
|
+
detectCircularRequires: 'off',
|
|
26
|
+
requireSource: 'builtin',
|
|
27
|
+
nestedRequireStrategy: 'create-require',
|
|
28
|
+
cjsDefault: 'auto',
|
|
29
|
+
idiomaticExports: 'safe',
|
|
30
|
+
importMetaPrelude: 'auto',
|
|
31
|
+
topLevelAwait: 'error',
|
|
32
|
+
cwd: undefined,
|
|
33
|
+
out: undefined,
|
|
34
|
+
inPlace: false
|
|
35
|
+
};
|
|
36
|
+
const icons = {
|
|
37
|
+
info: 'i',
|
|
38
|
+
warn: '⚠',
|
|
39
|
+
error: '✖',
|
|
40
|
+
success: '✔'
|
|
41
|
+
};
|
|
42
|
+
const codes = {
|
|
43
|
+
reset: '\u001b[0m',
|
|
44
|
+
bold: '\u001b[1m',
|
|
45
|
+
dim: '\u001b[2m',
|
|
46
|
+
red: '\u001b[31m',
|
|
47
|
+
yellow: '\u001b[33m',
|
|
48
|
+
green: '\u001b[32m',
|
|
49
|
+
cyan: '\u001b[36m'
|
|
50
|
+
};
|
|
51
|
+
const colorize = enabled => {
|
|
52
|
+
if (!enabled) {
|
|
53
|
+
return {
|
|
54
|
+
bold: v => v,
|
|
55
|
+
dim: v => v,
|
|
56
|
+
red: v => v,
|
|
57
|
+
yellow: v => v,
|
|
58
|
+
green: v => v,
|
|
59
|
+
cyan: v => v
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const wrap = code => v => `${code}${v}${codes.reset}`;
|
|
63
|
+
return {
|
|
64
|
+
bold: wrap(codes.bold),
|
|
65
|
+
dim: wrap(codes.dim),
|
|
66
|
+
red: wrap(codes.red),
|
|
67
|
+
yellow: wrap(codes.yellow),
|
|
68
|
+
green: wrap(codes.green),
|
|
69
|
+
cyan: wrap(codes.cyan)
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
const builtinSpecifiers = new Set(builtinModules.map(mod => mod.startsWith('node:') ? mod.slice(5) : mod).flatMap(mod => {
|
|
73
|
+
const parts = mod.split('/');
|
|
74
|
+
const base = parts[0];
|
|
75
|
+
return parts.length > 1 ? [mod, base] : [mod];
|
|
76
|
+
}));
|
|
77
|
+
const collapseSpecifier = value => value.replace(/['"`+)\s]|new String\(/g, '');
|
|
78
|
+
const appendExtensionIfNeeded = (value, mode, dirIndex) => {
|
|
79
|
+
if (mode === 'off') return;
|
|
80
|
+
const collapsed = collapseSpecifier(value);
|
|
81
|
+
const isRelative = /^(?:\.\.?)\//.test(collapsed);
|
|
82
|
+
if (!isRelative) return;
|
|
83
|
+
const base = collapsed.split(/[?#]/)[0];
|
|
84
|
+
if (!base) return;
|
|
85
|
+
if (base.endsWith('/')) {
|
|
86
|
+
if (!dirIndex) return;
|
|
87
|
+
return `${value}${dirIndex}`;
|
|
88
|
+
}
|
|
89
|
+
const lastSegment = base.split('/').pop() ?? '';
|
|
90
|
+
if (lastSegment.includes('.')) return;
|
|
91
|
+
return `${value}.js`;
|
|
92
|
+
};
|
|
93
|
+
const rewriteSpecifierValue = (value, rewriteSpecifier) => {
|
|
94
|
+
if (!rewriteSpecifier) return;
|
|
95
|
+
if (typeof rewriteSpecifier === 'function') {
|
|
96
|
+
return rewriteSpecifier(value) ?? undefined;
|
|
97
|
+
}
|
|
98
|
+
const collapsed = collapseSpecifier(value);
|
|
99
|
+
const relative = /^(?:\.\.?\/)/;
|
|
100
|
+
if (relative.test(collapsed)) {
|
|
101
|
+
return value.replace(/(.+)\.(?:m|c)?(?:j|t)sx?([)'"]*)?$/, `$1${rewriteSpecifier}$2`);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const normalizeBuiltinSpecifier = value => {
|
|
105
|
+
const collapsed = collapseSpecifier(value);
|
|
106
|
+
if (!collapsed) return;
|
|
107
|
+
const specPart = collapsed.split(/[?#]/)[0] ?? '';
|
|
108
|
+
if (/^(?:\.\.?(?:\/)|\/)/.test(specPart)) return;
|
|
109
|
+
if (/^[a-zA-Z][a-zA-Z+.-]*:/.test(specPart) && !specPart.startsWith('node:')) return;
|
|
110
|
+
const bare = specPart.startsWith('node:') ? specPart.slice(5) : specPart;
|
|
111
|
+
const base = bare.split('/')[0] ?? '';
|
|
112
|
+
if (!builtinSpecifiers.has(bare) && !builtinSpecifiers.has(base)) return;
|
|
113
|
+
if (specPart.startsWith('node:')) return;
|
|
114
|
+
const quote = /^['"`]/.exec(value)?.[0] ?? '';
|
|
115
|
+
return quote ? `${quote}node:${value.slice(quote.length)}` : `node:${value}`;
|
|
116
|
+
};
|
|
117
|
+
const optionsTable = [{
|
|
118
|
+
long: 'target',
|
|
119
|
+
short: 't',
|
|
120
|
+
type: 'string',
|
|
121
|
+
desc: 'Output format (module|commonjs)'
|
|
122
|
+
}, {
|
|
123
|
+
long: 'transform-syntax',
|
|
124
|
+
short: 'x',
|
|
125
|
+
type: 'string',
|
|
126
|
+
desc: 'Syntax transforms (true|false|globals-only)'
|
|
127
|
+
}, {
|
|
128
|
+
long: 'rewrite-specifier',
|
|
129
|
+
short: 'r',
|
|
130
|
+
type: 'string',
|
|
131
|
+
desc: 'Rewrite import specifiers (.js/.mjs/.cjs/.ts/.mts/.cts)'
|
|
132
|
+
}, {
|
|
133
|
+
long: 'append-js-extension',
|
|
134
|
+
short: 'j',
|
|
135
|
+
type: 'string',
|
|
136
|
+
desc: 'Append .js to relative imports (off|relative-only|all)'
|
|
137
|
+
}, {
|
|
138
|
+
long: 'append-directory-index',
|
|
139
|
+
short: 'i',
|
|
140
|
+
type: 'string',
|
|
141
|
+
desc: 'Append directory index (e.g. index.js) or false'
|
|
142
|
+
}, {
|
|
143
|
+
long: 'detect-circular-requires',
|
|
144
|
+
short: 'c',
|
|
145
|
+
type: 'string',
|
|
146
|
+
desc: 'Warn/error on circular require (off|warn|error)'
|
|
147
|
+
}, {
|
|
148
|
+
long: 'top-level-await',
|
|
149
|
+
short: 'a',
|
|
150
|
+
type: 'string',
|
|
151
|
+
desc: 'TLA handling (error|wrap|preserve)'
|
|
152
|
+
}, {
|
|
153
|
+
long: 'cjs-default',
|
|
154
|
+
short: 'd',
|
|
155
|
+
type: 'string',
|
|
156
|
+
desc: 'Default interop (module-exports|auto|none)'
|
|
157
|
+
}, {
|
|
158
|
+
long: 'idiomatic-exports',
|
|
159
|
+
short: 'e',
|
|
160
|
+
type: 'string',
|
|
161
|
+
desc: 'Emit idiomatic exports when safe (off|safe|aggressive)'
|
|
162
|
+
}, {
|
|
163
|
+
long: 'import-meta-prelude',
|
|
164
|
+
short: 'm',
|
|
165
|
+
type: 'string',
|
|
166
|
+
desc: 'Emit import.meta prelude (off|auto|on)'
|
|
167
|
+
}, {
|
|
168
|
+
long: 'nested-require-strategy',
|
|
169
|
+
short: 'n',
|
|
170
|
+
type: 'string',
|
|
171
|
+
desc: 'Rewrite nested require (create-require|dynamic-import)'
|
|
172
|
+
}, {
|
|
173
|
+
long: 'require-main-strategy',
|
|
174
|
+
short: 'R',
|
|
175
|
+
type: 'string',
|
|
176
|
+
desc: 'Detect main (import-meta-main|realpath)'
|
|
177
|
+
}, {
|
|
178
|
+
long: 'live-bindings',
|
|
179
|
+
short: 'l',
|
|
180
|
+
type: 'string',
|
|
181
|
+
desc: 'Live binding strategy (strict|loose|off)'
|
|
182
|
+
}, {
|
|
183
|
+
long: 'out-dir',
|
|
184
|
+
short: 'o',
|
|
185
|
+
type: 'string',
|
|
186
|
+
desc: 'Write outputs to a directory mirror'
|
|
187
|
+
}, {
|
|
188
|
+
long: 'in-place',
|
|
189
|
+
short: 'p',
|
|
190
|
+
type: 'boolean',
|
|
191
|
+
desc: 'Rewrite files in place'
|
|
192
|
+
}, {
|
|
193
|
+
long: 'dry-run',
|
|
194
|
+
short: 'y',
|
|
195
|
+
type: 'boolean',
|
|
196
|
+
desc: 'Do not write files; report planned changes'
|
|
197
|
+
}, {
|
|
198
|
+
long: 'list',
|
|
199
|
+
short: 'L',
|
|
200
|
+
type: 'boolean',
|
|
201
|
+
desc: 'List files that would change'
|
|
202
|
+
}, {
|
|
203
|
+
long: 'summary',
|
|
204
|
+
short: 's',
|
|
205
|
+
type: 'boolean',
|
|
206
|
+
desc: 'Print a summary of work performed'
|
|
207
|
+
}, {
|
|
208
|
+
long: 'json',
|
|
209
|
+
short: 'J',
|
|
210
|
+
type: 'boolean',
|
|
211
|
+
desc: 'Emit machine-readable JSON summary/diagnostics'
|
|
212
|
+
}, {
|
|
213
|
+
long: 'cwd',
|
|
214
|
+
short: 'C',
|
|
215
|
+
type: 'string',
|
|
216
|
+
desc: 'Working directory for resolving files/out paths'
|
|
217
|
+
}, {
|
|
218
|
+
long: 'stdin-filename',
|
|
219
|
+
short: 'f',
|
|
220
|
+
type: 'string',
|
|
221
|
+
desc: 'Virtual filename when reading from stdin'
|
|
222
|
+
}, {
|
|
223
|
+
long: 'ignore',
|
|
224
|
+
short: 'g',
|
|
225
|
+
type: 'string',
|
|
226
|
+
desc: 'Glob pattern(s) to ignore (repeatable)'
|
|
227
|
+
}, {
|
|
228
|
+
long: 'help',
|
|
229
|
+
short: 'h',
|
|
230
|
+
type: 'boolean',
|
|
231
|
+
desc: 'Show help'
|
|
232
|
+
}, {
|
|
233
|
+
long: 'version',
|
|
234
|
+
short: 'v',
|
|
235
|
+
type: 'boolean',
|
|
236
|
+
desc: 'Show version'
|
|
237
|
+
}];
|
|
238
|
+
const buildHelp = enableColor => {
|
|
239
|
+
const c = colorize(enableColor);
|
|
240
|
+
const maxFlagLength = Math.max(...optionsTable.map(opt => ` -${opt.short}, --${opt.long}`.length));
|
|
241
|
+
const lines = [`${c.bold('Usage:')} dub [options] <files...>`, '', 'Examples:', ' dub -t module src/index.cjs --out-dir dist', ' dub -t commonjs src/**/*.mjs -p', ' cat input.cjs | dub -t module --stdin-filename input.cjs', '', 'Options:'];
|
|
242
|
+
for (const opt of optionsTable) {
|
|
243
|
+
const flag = ` -${opt.short}, --${opt.long}`;
|
|
244
|
+
const pad = ' '.repeat(Math.max(2, maxFlagLength - flag.length + 2));
|
|
245
|
+
lines.push(`${c.bold(flag)}${pad}${opt.desc}`);
|
|
246
|
+
}
|
|
247
|
+
return `${lines.join('\n')}\n`;
|
|
248
|
+
};
|
|
249
|
+
const parseEnum = (value, allowed) => {
|
|
250
|
+
if (value === undefined) return undefined;
|
|
251
|
+
return allowed.includes(value) ? value : undefined;
|
|
252
|
+
};
|
|
253
|
+
const parseTransformSyntax = value => {
|
|
254
|
+
if (value === undefined) return defaultOptions.transformSyntax;
|
|
255
|
+
if (value === 'globals-only') return 'globals-only';
|
|
256
|
+
if (value === 'false') return false;
|
|
257
|
+
if (value === 'true') return true;
|
|
258
|
+
return defaultOptions.transformSyntax;
|
|
259
|
+
};
|
|
260
|
+
const parseAppendDirectoryIndex = value => {
|
|
261
|
+
if (value === undefined) return undefined;
|
|
262
|
+
if (value === 'false') return false;
|
|
263
|
+
return value;
|
|
264
|
+
};
|
|
265
|
+
const toModuleOptions = values => {
|
|
266
|
+
const target = parseEnum(values.target, ['module', 'commonjs']) ?? defaultOptions.target;
|
|
267
|
+
const transformSyntax = parseTransformSyntax(values['transform-syntax']);
|
|
268
|
+
const appendJsExtension = parseEnum(values['append-js-extension'], ['off', 'relative-only', 'all']);
|
|
269
|
+
const appendDirectoryIndex = parseAppendDirectoryIndex(values['append-directory-index']);
|
|
270
|
+
const opts = {
|
|
271
|
+
...defaultOptions,
|
|
272
|
+
target,
|
|
273
|
+
transformSyntax,
|
|
274
|
+
rewriteSpecifier: values['rewrite-specifier'] ?? undefined,
|
|
275
|
+
appendJsExtension: appendJsExtension,
|
|
276
|
+
appendDirectoryIndex,
|
|
277
|
+
detectCircularRequires: parseEnum(values['detect-circular-requires'], ['off', 'warn', 'error']) ?? defaultOptions.detectCircularRequires,
|
|
278
|
+
topLevelAwait: parseEnum(values['top-level-await'], ['error', 'wrap', 'preserve']) ?? defaultOptions.topLevelAwait,
|
|
279
|
+
cjsDefault: parseEnum(values['cjs-default'], ['module-exports', 'auto', 'none']) ?? defaultOptions.cjsDefault,
|
|
280
|
+
idiomaticExports: parseEnum(values['idiomatic-exports'], ['off', 'safe', 'aggressive']) ?? defaultOptions.idiomaticExports,
|
|
281
|
+
importMetaPrelude: parseEnum(values['import-meta-prelude'], ['off', 'auto', 'on']) ?? defaultOptions.importMetaPrelude,
|
|
282
|
+
nestedRequireStrategy: parseEnum(values['nested-require-strategy'], ['create-require', 'dynamic-import']) ?? defaultOptions.nestedRequireStrategy,
|
|
283
|
+
requireMainStrategy: parseEnum(values['require-main-strategy'], ['import-meta-main', 'realpath']) ?? defaultOptions.requireMainStrategy,
|
|
284
|
+
liveBindings: parseEnum(values['live-bindings'], ['strict', 'loose', 'off']) ?? defaultOptions.liveBindings,
|
|
285
|
+
cwd: values.cwd ? resolve(String(values.cwd)) : defaultOptions.cwd
|
|
286
|
+
};
|
|
287
|
+
return opts;
|
|
288
|
+
};
|
|
289
|
+
const readStdin = async stdin => {
|
|
290
|
+
const chunks = [];
|
|
291
|
+
for await (const chunk of stdin) {
|
|
292
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
293
|
+
}
|
|
294
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
295
|
+
};
|
|
296
|
+
const expandFiles = async (patterns, cwd, ignore) => {
|
|
297
|
+
const files = new Set();
|
|
298
|
+
for (const pattern of patterns) {
|
|
299
|
+
const matches = await glob(pattern, {
|
|
300
|
+
cwd,
|
|
301
|
+
absolute: true,
|
|
302
|
+
nodir: true,
|
|
303
|
+
windowsPathsNoEscape: true,
|
|
304
|
+
ignore
|
|
305
|
+
});
|
|
306
|
+
for (const m of matches) files.add(resolve(m));
|
|
307
|
+
}
|
|
308
|
+
return [...files];
|
|
309
|
+
};
|
|
310
|
+
const makeLogger = (stdout, stderr) => {
|
|
311
|
+
const enableColor = stdout.isTTY ?? stderr.isTTY ?? false;
|
|
312
|
+
const c = colorize(enableColor);
|
|
313
|
+
const log = (kind, message, stream) => {
|
|
314
|
+
const icon = icons[kind];
|
|
315
|
+
const colored = kind === 'error' ? c.red(message) : kind === 'warn' ? c.yellow(message) : kind === 'success' ? c.green(message) : c.cyan(message);
|
|
316
|
+
stream.write(`${icon} ${colored}\n`);
|
|
317
|
+
};
|
|
318
|
+
return {
|
|
319
|
+
info: msg => log('info', msg, stdout),
|
|
320
|
+
warn: msg => log('warn', msg, stderr),
|
|
321
|
+
error: msg => log('error', msg, stderr),
|
|
322
|
+
success: msg => log('success', msg, stdout),
|
|
323
|
+
color: c
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
const applySpecifierUpdates = async (source, filename, opts, appendMode, dirIndex) => {
|
|
327
|
+
if (!opts.rewriteSpecifier && appendMode === 'off' && !dirIndex) return source;
|
|
328
|
+
const lang = getLangFromExt(filename);
|
|
329
|
+
const updated = await specifier.updateSrc(source, lang, spec => {
|
|
330
|
+
const normalized = normalizeBuiltinSpecifier(spec.value);
|
|
331
|
+
const rewritten = rewriteSpecifierValue(normalized ?? spec.value, opts.rewriteSpecifier);
|
|
332
|
+
const baseValue = rewritten ?? normalized ?? spec.value;
|
|
333
|
+
const appended = appendExtensionIfNeeded(baseValue, appendMode, dirIndex);
|
|
334
|
+
return appended ?? rewritten ?? normalized ?? undefined;
|
|
335
|
+
});
|
|
336
|
+
return updated;
|
|
337
|
+
};
|
|
338
|
+
const transformVirtual = async (source, filename, opts) => {
|
|
339
|
+
const ast = parse(filename, source);
|
|
340
|
+
let output = await format(source, ast, {
|
|
341
|
+
...opts,
|
|
342
|
+
filePath: filename
|
|
343
|
+
});
|
|
344
|
+
const appendMode = opts.appendJsExtension ?? (opts.target === 'module' ? 'relative-only' : 'off');
|
|
345
|
+
const dirIndex = opts.appendDirectoryIndex === undefined ? 'index.js' : opts.appendDirectoryIndex;
|
|
346
|
+
output = await applySpecifierUpdates(output, filename, opts, appendMode, dirIndex);
|
|
347
|
+
return output;
|
|
348
|
+
};
|
|
349
|
+
const summarizeDiagnostics = diags => {
|
|
350
|
+
let warnings = 0;
|
|
351
|
+
let errors = 0;
|
|
352
|
+
for (const d of diags) {
|
|
353
|
+
if (d.level === 'warning') warnings += 1;
|
|
354
|
+
if (d.level === 'error') errors += 1;
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
warnings,
|
|
358
|
+
errors
|
|
359
|
+
};
|
|
360
|
+
};
|
|
361
|
+
const runFiles = async (files, moduleOpts, io, flags) => {
|
|
362
|
+
const results = [];
|
|
363
|
+
const logger = makeLogger(io.stdout, io.stderr);
|
|
364
|
+
for (const file of files) {
|
|
365
|
+
const diagnostics = [];
|
|
366
|
+
const original = await readFile(file, 'utf8');
|
|
367
|
+
const outPath = flags.outDir ? join(flags.outDir, relative(moduleOpts.cwd ?? process.cwd(), file)) : undefined;
|
|
368
|
+
const perFileOpts = {
|
|
369
|
+
...moduleOpts,
|
|
370
|
+
diagnostics: diag => diagnostics.push(diag),
|
|
371
|
+
out: undefined,
|
|
372
|
+
inPlace: false,
|
|
373
|
+
filePath: file
|
|
374
|
+
};
|
|
375
|
+
let writeTarget;
|
|
376
|
+
if (!flags.dryRun && !flags.list) {
|
|
377
|
+
if (flags.inPlace) {
|
|
378
|
+
perFileOpts.inPlace = true;
|
|
379
|
+
} else if (outPath) {
|
|
380
|
+
writeTarget = outPath;
|
|
381
|
+
perFileOpts.out = outPath;
|
|
382
|
+
await mkdir(dirname(outPath), {
|
|
383
|
+
recursive: true
|
|
384
|
+
});
|
|
385
|
+
} else if (!flags.allowStdout) {
|
|
386
|
+
logger.error('Specify --out-dir or --in-place when transforming files');
|
|
387
|
+
return {
|
|
388
|
+
code: 2,
|
|
389
|
+
results
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const output = await transform(file, perFileOpts);
|
|
394
|
+
const changed = output !== original;
|
|
395
|
+
if (flags.list && changed) {
|
|
396
|
+
logger.info(file);
|
|
397
|
+
}
|
|
398
|
+
if (!flags.dryRun && !flags.list && !writeTarget && !perFileOpts.inPlace) {
|
|
399
|
+
io.stdout.write(output);
|
|
400
|
+
}
|
|
401
|
+
results.push({
|
|
402
|
+
filePath: file,
|
|
403
|
+
changed,
|
|
404
|
+
diagnostics
|
|
405
|
+
});
|
|
406
|
+
const counts = summarizeDiagnostics(diagnostics);
|
|
407
|
+
if (!flags.json) {
|
|
408
|
+
for (const diag of diagnostics) {
|
|
409
|
+
const prefix = diag.level === 'error' ? logger.error : logger.warn;
|
|
410
|
+
const loc = diag.loc ? ` [${diag.loc.start}-${diag.loc.end}]` : '';
|
|
411
|
+
prefix(`${diag.code}: ${diag.message}${loc}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (counts.errors > 0) {
|
|
415
|
+
return {
|
|
416
|
+
code: 1,
|
|
417
|
+
results
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (flags.summary && !flags.json) {
|
|
422
|
+
const changedCount = results.filter(r => r.changed).length;
|
|
423
|
+
logger.success(`Processed ${results.length} file(s); changed ${changedCount}`);
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
code: 0,
|
|
427
|
+
results
|
|
428
|
+
};
|
|
429
|
+
};
|
|
430
|
+
const runCli = async ({
|
|
431
|
+
argv = process.argv.slice(2),
|
|
432
|
+
stdin = defaultStdin,
|
|
433
|
+
stdout = defaultStdout,
|
|
434
|
+
stderr = defaultStderr
|
|
435
|
+
} = {}) => {
|
|
436
|
+
const {
|
|
437
|
+
values,
|
|
438
|
+
positionals
|
|
439
|
+
} = parseArgs({
|
|
440
|
+
args: argv,
|
|
441
|
+
allowPositionals: true,
|
|
442
|
+
options: Object.fromEntries(optionsTable.map(opt => [opt.long, {
|
|
443
|
+
type: opt.type,
|
|
444
|
+
short: opt.short
|
|
445
|
+
}]))
|
|
446
|
+
});
|
|
447
|
+
const logger = makeLogger(stdout, stderr);
|
|
448
|
+
if (values.help) {
|
|
449
|
+
stdout.write(buildHelp(stdout.isTTY ?? false));
|
|
450
|
+
return 0;
|
|
451
|
+
}
|
|
452
|
+
if (values.version) {
|
|
453
|
+
const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
454
|
+
stdout.write(`${pkg.version}\n`);
|
|
455
|
+
return 0;
|
|
456
|
+
}
|
|
457
|
+
const moduleOpts = toModuleOptions(values);
|
|
458
|
+
const cwd = moduleOpts.cwd ?? process.cwd();
|
|
459
|
+
const allowStdout = positionals.length <= 1;
|
|
460
|
+
const fromStdin = positionals.length === 0 || positionals.includes('-');
|
|
461
|
+
const patterns = positionals.filter(p => p !== '-');
|
|
462
|
+
const ignoreValues = values.ignore;
|
|
463
|
+
const ignore = ignoreValues ? (Array.isArray(ignoreValues) ? ignoreValues : [ignoreValues]).map(String) : undefined;
|
|
464
|
+
const outDir = values['out-dir'] ? resolve(cwd, String(values['out-dir'])) : undefined;
|
|
465
|
+
const inPlace = Boolean(values['in-place']);
|
|
466
|
+
const dryRun = Boolean(values['dry-run']);
|
|
467
|
+
const list = Boolean(values.list);
|
|
468
|
+
const summary = Boolean(values.summary);
|
|
469
|
+
const json = Boolean(values.json);
|
|
470
|
+
if (outDir && inPlace) {
|
|
471
|
+
logger.error('Choose either --out-dir or --in-place, not both');
|
|
472
|
+
return 2;
|
|
473
|
+
}
|
|
474
|
+
if (fromStdin && (outDir || inPlace)) {
|
|
475
|
+
logger.error('Cannot combine stdin with --out-dir or --in-place; output goes to stdout');
|
|
476
|
+
return 2;
|
|
477
|
+
}
|
|
478
|
+
const files = await expandFiles(patterns, cwd, ignore);
|
|
479
|
+
if (!fromStdin && files.length === 0) {
|
|
480
|
+
logger.error('No input files were provided or matched');
|
|
481
|
+
return 2;
|
|
482
|
+
}
|
|
483
|
+
const tasks = [];
|
|
484
|
+
if (fromStdin) {
|
|
485
|
+
const virtualName = values['stdin-filename'] ?? 'stdin.js';
|
|
486
|
+
const source = await readStdin(stdin);
|
|
487
|
+
const diagnostics = [];
|
|
488
|
+
const output = await transformVirtual(source, virtualName, {
|
|
489
|
+
...moduleOpts,
|
|
490
|
+
diagnostics: diag => diagnostics.push(diag),
|
|
491
|
+
filePath: virtualName,
|
|
492
|
+
cwd
|
|
493
|
+
});
|
|
494
|
+
tasks.push({
|
|
495
|
+
filePath: virtualName,
|
|
496
|
+
changed: true,
|
|
497
|
+
diagnostics
|
|
498
|
+
});
|
|
499
|
+
stdout.write(output);
|
|
500
|
+
if (!json) {
|
|
501
|
+
for (const diag of diagnostics) {
|
|
502
|
+
const prefix = diag.level === 'error' ? logger.error : logger.warn;
|
|
503
|
+
const loc = diag.loc ? ` [${diag.loc.start}-${diag.loc.end}]` : '';
|
|
504
|
+
prefix(`${diag.code}: ${diag.message}${loc}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
const diagSummary = summarizeDiagnostics(diagnostics);
|
|
508
|
+
if (diagSummary.errors > 0) return 1;
|
|
509
|
+
}
|
|
510
|
+
if (files.length) {
|
|
511
|
+
const result = await runFiles(files, {
|
|
512
|
+
...moduleOpts,
|
|
513
|
+
cwd
|
|
514
|
+
}, {
|
|
515
|
+
stdout,
|
|
516
|
+
stderr
|
|
517
|
+
}, {
|
|
518
|
+
dryRun,
|
|
519
|
+
list,
|
|
520
|
+
summary,
|
|
521
|
+
json,
|
|
522
|
+
outDir,
|
|
523
|
+
inPlace,
|
|
524
|
+
allowStdout
|
|
525
|
+
});
|
|
526
|
+
if (typeof result.code === 'number' && result.code !== 0) return result.code;
|
|
527
|
+
tasks.push(...result.results);
|
|
528
|
+
}
|
|
529
|
+
if (json) {
|
|
530
|
+
const summaryDiag = summarizeDiagnostics(tasks.flatMap(t => t.diagnostics));
|
|
531
|
+
stdout.write(`${JSON.stringify({
|
|
532
|
+
files: tasks,
|
|
533
|
+
summary: summaryDiag
|
|
534
|
+
}, null, 2)}\n`);
|
|
535
|
+
}
|
|
536
|
+
return 0;
|
|
537
|
+
};
|
|
538
|
+
if (import.meta.main) {
|
|
539
|
+
runCli().then(code => {
|
|
540
|
+
if (code !== 0) process.exit(code);
|
|
541
|
+
}, err => {
|
|
542
|
+
// eslint-disable-next-line no-console -- CLI surface
|
|
543
|
+
console.error(err);
|
|
544
|
+
process.exit(1);
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
export { runCli };
|
package/dist/format.js
CHANGED
|
@@ -573,7 +573,10 @@ const format = async (src, ast, opts) => {
|
|
|
573
573
|
seen.add(propName);
|
|
574
574
|
if (rhs.type === 'Identifier') {
|
|
575
575
|
const rhsId = rhsSourceFor(rhs);
|
|
576
|
-
|
|
576
|
+
const rhsName = rhs.name;
|
|
577
|
+
if (rhsId === rhsName && rhsName === propName) {
|
|
578
|
+
exportsOut.push(`export { ${propName} };`);
|
|
579
|
+
} else if (rhsId === rhsName) {
|
|
577
580
|
exportsOut.push(`export { ${rhsId} as ${propName} };`);
|
|
578
581
|
} else {
|
|
579
582
|
exportsOut.push(`export const ${propName} = ${rhsId};`);
|
|
@@ -582,9 +585,15 @@ const format = async (src, ast, opts) => {
|
|
|
582
585
|
exportsOut.push(`export const ${propName} = ${rhsSrc};`);
|
|
583
586
|
}
|
|
584
587
|
}
|
|
588
|
+
|
|
589
|
+
// Trim trailing whitespace and one optional semicolon so the idiomatic export
|
|
590
|
+
// replacement does not leave the original `;` behind (avoids emitting `;;`).
|
|
591
|
+
let end = write.end;
|
|
592
|
+
while (end < src.length && (src[end] === ' ' || src[end] === '\t')) end++;
|
|
593
|
+
if (end < src.length && src[end] === ';') end++;
|
|
585
594
|
replacements.push({
|
|
586
595
|
start: write.start,
|
|
587
|
-
end
|
|
596
|
+
end
|
|
588
597
|
});
|
|
589
598
|
}
|
|
590
599
|
if (!seen.size) return {
|
|
@@ -43,6 +43,9 @@ export const metaProperty = (node, parent, src, options) => {
|
|
|
43
43
|
case 'main':
|
|
44
44
|
src.update(parent.start, parent.end, importMetaMainExpr(options.importMetaMain));
|
|
45
45
|
break;
|
|
46
|
+
default:
|
|
47
|
+
src.update(parent.start, parent.end, `module.${parent.property.name}`);
|
|
48
|
+
break;
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knighted/module",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0-rc.0",
|
|
4
4
|
"description": "Bidirectional transform for ES modules and CommonJS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/module.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dub": "dist/cli.js"
|
|
9
|
+
},
|
|
7
10
|
"exports": {
|
|
8
11
|
".": {
|
|
9
12
|
"import": {
|
|
@@ -28,7 +31,10 @@
|
|
|
28
31
|
"prettier:check": "prettier -c .",
|
|
29
32
|
"lint": "oxlint --config oxlint.json .",
|
|
30
33
|
"prepare": "husky",
|
|
31
|
-
"test": "
|
|
34
|
+
"test:base": "tsx --test --test-reporter=spec",
|
|
35
|
+
"test:cli": "npm run test:base -- test/cli.ts",
|
|
36
|
+
"test:module": "npm run test:base -- test/module.ts",
|
|
37
|
+
"test": "c8 --reporter=text --reporter=text-summary --reporter=lcov npm run test:base -- test/*.ts",
|
|
32
38
|
"build:types": "tsc --emitDeclarationOnly",
|
|
33
39
|
"build:dual": "babel-dual-package src --extensions .ts",
|
|
34
40
|
"build": "npm run build:types && npm run build:dual",
|
|
@@ -59,7 +65,7 @@
|
|
|
59
65
|
},
|
|
60
66
|
"devDependencies": {
|
|
61
67
|
"@knighted/dump": "^1.0.3",
|
|
62
|
-
"@types/node": "^22.
|
|
68
|
+
"@types/node": "^22.19.3",
|
|
63
69
|
"babel-dual-package": "^1.2.3",
|
|
64
70
|
"c8": "^10.1.3",
|
|
65
71
|
"husky": "^9.1.7",
|
|
@@ -71,6 +77,7 @@
|
|
|
71
77
|
"typescript": "^5.9.3"
|
|
72
78
|
},
|
|
73
79
|
"dependencies": {
|
|
80
|
+
"glob": "^13.0.0",
|
|
74
81
|
"magic-string": "^0.30.21",
|
|
75
82
|
"oxc-parser": "^0.105.0",
|
|
76
83
|
"periscopic": "^4.0.2"
|