@knighted/module 1.3.1 → 1.4.0-rc.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 +4 -0
- package/dist/cjs/cli.cjs +28 -4
- package/dist/cjs/format.cjs +288 -4
- package/dist/cjs/format.d.cts +25 -3
- package/dist/cjs/module.cjs +45 -1
- package/dist/cjs/module.d.cts +3 -2
- package/dist/cjs/types.d.cts +4 -0
- package/dist/cli.js +29 -5
- package/dist/format.d.ts +25 -3
- package/dist/format.js +286 -4
- package/dist/module.d.ts +3 -2
- package/dist/module.js +45 -2
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -130,6 +130,8 @@ type ModuleOptions = {
|
|
|
130
130
|
importMetaMain?: 'shim' | 'warn' | 'error'
|
|
131
131
|
requireMainStrategy?: 'import-meta-main' | 'realpath'
|
|
132
132
|
detectCircularRequires?: 'off' | 'warn' | 'error'
|
|
133
|
+
detectDualPackageHazard?: 'off' | 'warn' | 'error'
|
|
134
|
+
dualPackageHazardScope?: 'file' | 'project'
|
|
133
135
|
requireSource?: 'builtin' | 'create-require'
|
|
134
136
|
importMetaPrelude?: 'off' | 'auto' | 'on'
|
|
135
137
|
cjsDefault?: 'module-exports' | 'auto' | 'none'
|
|
@@ -155,6 +157,8 @@ type ModuleOptions = {
|
|
|
155
157
|
- `requireMainStrategy` (`import-meta-main`): use `import.meta.main` or the realpath-based `pathToFileURL(realpathSync(process.argv[1])).href` check.
|
|
156
158
|
- `importMetaPrelude` (`auto`): emit a no-op `void import.meta.filename;` touch. `on` always emits; `off` never emits; `auto` emits only when helpers that reference `import.meta.*` are synthesized (e.g., `__dirname`/`__filename` in CJS→ESM, require-main shims, createRequire helpers). Useful for bundlers/transpilers that do usage-based `import.meta` polyfilling.
|
|
157
159
|
- `detectCircularRequires` (`off`): optionally detect relative static require cycles and warn/throw.
|
|
160
|
+
- `detectDualPackageHazard` (`warn`): flag when `import` and `require` mix for the same package or root/subpath are combined in ways that can resolve to separate module instances (dual packages). Set to `error` to fail the transform.
|
|
161
|
+
- `dualPackageHazardScope` (`file`): `file` preserves the legacy per-file detector; `project` aggregates package usage across all CLI inputs (useful in monorepos/hoisted installs) and emits one diagnostic per package.
|
|
158
162
|
- `topLevelAwait` (`error`): throw, wrap, or preserve when TLA appears in CommonJS output.
|
|
159
163
|
- `rewriteSpecifier` (off): rewrite relative specifiers to a chosen extension or via a callback. Precedence: the callback (if provided) runs first; if it returns a string, that wins. If it returns `undefined` or `null`, the appenders still apply.
|
|
160
164
|
- `requireSource` (`builtin`): whether `require` comes from Node or `createRequire`.
|
package/dist/cjs/cli.cjs
CHANGED
|
@@ -29,6 +29,8 @@ const defaultOptions = {
|
|
|
29
29
|
importMetaMain: 'shim',
|
|
30
30
|
requireMainStrategy: 'import-meta-main',
|
|
31
31
|
detectCircularRequires: 'off',
|
|
32
|
+
detectDualPackageHazard: 'warn',
|
|
33
|
+
dualPackageHazardScope: 'file',
|
|
32
34
|
requireSource: 'builtin',
|
|
33
35
|
nestedRequireStrategy: 'create-require',
|
|
34
36
|
cjsDefault: 'auto',
|
|
@@ -150,6 +152,16 @@ const optionsTable = [{
|
|
|
150
152
|
short: 'c',
|
|
151
153
|
type: 'string',
|
|
152
154
|
desc: 'Warn/error on circular require (off|warn|error)'
|
|
155
|
+
}, {
|
|
156
|
+
long: 'detect-dual-package-hazard',
|
|
157
|
+
short: 'H',
|
|
158
|
+
type: 'string',
|
|
159
|
+
desc: 'Warn/error on mixed import/require of dual packages (off|warn|error)'
|
|
160
|
+
}, {
|
|
161
|
+
long: 'dual-package-hazard-scope',
|
|
162
|
+
short: undefined,
|
|
163
|
+
type: 'string',
|
|
164
|
+
desc: 'Scope for dual package hazard detection (file|project)'
|
|
153
165
|
}, {
|
|
154
166
|
long: 'top-level-await',
|
|
155
167
|
short: 'a',
|
|
@@ -243,10 +255,10 @@ const optionsTable = [{
|
|
|
243
255
|
}];
|
|
244
256
|
const buildHelp = enableColor => {
|
|
245
257
|
const c = colorize(enableColor);
|
|
246
|
-
const maxFlagLength = Math.max(...optionsTable.map(opt => ` -${opt.short}, --${opt.long}`.length));
|
|
258
|
+
const maxFlagLength = Math.max(...optionsTable.map(opt => opt.short ? ` -${opt.short}, --${opt.long}`.length : ` --${opt.long}`.length));
|
|
247
259
|
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
260
|
for (const opt of optionsTable) {
|
|
249
|
-
const flag = ` -${opt.short}, --${opt.long}`;
|
|
261
|
+
const flag = opt.short ? ` -${opt.short}, --${opt.long}` : ` --${opt.long}`;
|
|
250
262
|
const pad = ' '.repeat(Math.max(2, maxFlagLength - flag.length + 2));
|
|
251
263
|
lines.push(`${c.bold(flag)}${pad}${opt.desc}`);
|
|
252
264
|
}
|
|
@@ -281,6 +293,8 @@ const toModuleOptions = values => {
|
|
|
281
293
|
appendJsExtension: appendJsExtension,
|
|
282
294
|
appendDirectoryIndex,
|
|
283
295
|
detectCircularRequires: parseEnum(values['detect-circular-requires'], ['off', 'warn', 'error']) ?? defaultOptions.detectCircularRequires,
|
|
296
|
+
detectDualPackageHazard: parseEnum(values['detect-dual-package-hazard'], ['off', 'warn', 'error']) ?? defaultOptions.detectDualPackageHazard,
|
|
297
|
+
dualPackageHazardScope: parseEnum(values['dual-package-hazard-scope'], ['file', 'project']) ?? defaultOptions.dualPackageHazardScope,
|
|
284
298
|
topLevelAwait: parseEnum(values['top-level-await'], ['error', 'wrap', 'preserve']) ?? defaultOptions.topLevelAwait,
|
|
285
299
|
cjsDefault: parseEnum(values['cjs-default'], ['module-exports', 'auto', 'none']) ?? defaultOptions.cjsDefault,
|
|
286
300
|
idiomaticExports: parseEnum(values['idiomatic-exports'], ['off', 'safe', 'aggressive']) ?? defaultOptions.idiomaticExports,
|
|
@@ -367,6 +381,9 @@ const summarizeDiagnostics = diags => {
|
|
|
367
381
|
const runFiles = async (files, moduleOpts, io, flags) => {
|
|
368
382
|
const results = [];
|
|
369
383
|
const logger = makeLogger(io.stdout, io.stderr);
|
|
384
|
+
const hazardScope = moduleOpts.dualPackageHazardScope ?? 'file';
|
|
385
|
+
const hazardMode = moduleOpts.detectDualPackageHazard ?? 'warn';
|
|
386
|
+
const projectHazards = hazardScope === 'project' && hazardMode !== 'off' ? await (0, _module.collectProjectDualPackageHazards)(files, moduleOpts) : null;
|
|
370
387
|
for (const file of files) {
|
|
371
388
|
const diagnostics = [];
|
|
372
389
|
const original = await (0, _promises.readFile)(file, 'utf8');
|
|
@@ -376,7 +393,8 @@ const runFiles = async (files, moduleOpts, io, flags) => {
|
|
|
376
393
|
diagnostics: diag => diagnostics.push(diag),
|
|
377
394
|
out: undefined,
|
|
378
395
|
inPlace: false,
|
|
379
|
-
filePath: file
|
|
396
|
+
filePath: file,
|
|
397
|
+
detectDualPackageHazard: hazardScope === 'project' ? 'off' : moduleOpts.detectDualPackageHazard
|
|
380
398
|
};
|
|
381
399
|
let writeTarget;
|
|
382
400
|
if (!flags.dryRun && !flags.list) {
|
|
@@ -398,6 +416,10 @@ const runFiles = async (files, moduleOpts, io, flags) => {
|
|
|
398
416
|
}
|
|
399
417
|
const output = await (0, _module.transform)(file, perFileOpts);
|
|
400
418
|
const changed = output !== original;
|
|
419
|
+
if (projectHazards) {
|
|
420
|
+
const extras = projectHazards.get(file);
|
|
421
|
+
if (extras?.length) diagnostics.push(...extras);
|
|
422
|
+
}
|
|
401
423
|
if (flags.list && changed) {
|
|
402
424
|
logger.info(file);
|
|
403
425
|
}
|
|
@@ -447,7 +469,9 @@ const runCli = async ({
|
|
|
447
469
|
allowPositionals: true,
|
|
448
470
|
options: Object.fromEntries(optionsTable.map(opt => [opt.long, {
|
|
449
471
|
type: opt.type,
|
|
450
|
-
|
|
472
|
+
...(opt.short ? {
|
|
473
|
+
short: opt.short
|
|
474
|
+
} : {})
|
|
451
475
|
}]))
|
|
452
476
|
});
|
|
453
477
|
const logger = makeLogger(stdout, stderr);
|
package/dist/cjs/format.cjs
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.format = void 0;
|
|
6
|
+
exports.format = exports.dualPackageHazardDiagnostics = exports.collectDualPackageUsage = void 0;
|
|
7
|
+
var _nodeModule = require("node:module");
|
|
8
|
+
var _nodePath = require("node:path");
|
|
9
|
+
var _promises = require("node:fs/promises");
|
|
7
10
|
var _magicString = _interopRequireDefault(require("magic-string"));
|
|
8
11
|
var _async = require("./helpers/async.cjs");
|
|
9
12
|
var _identifier = require("./helpers/identifier.cjs");
|
|
@@ -24,6 +27,274 @@ var _url = require("./utils/url.cjs");
|
|
|
24
27
|
var _walk = require("./walk.cjs");
|
|
25
28
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
26
29
|
const isRequireMainMember = (node, shadowed) => node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'require' && !shadowed.has('require') && node.property.type === 'Identifier' && node.property.name === 'main';
|
|
30
|
+
const builtinSpecifiers = new Set(_nodeModule.builtinModules.map(mod => mod.startsWith('node:') ? mod.slice(5) : mod).flatMap(mod => {
|
|
31
|
+
const parts = mod.split('/');
|
|
32
|
+
const base = parts[0];
|
|
33
|
+
return parts.length > 1 ? [mod, base] : [mod];
|
|
34
|
+
}));
|
|
35
|
+
const stripQuery = value => value.includes('?') || value.includes('#') ? value.split(/[?#]/)[0] ?? value : value;
|
|
36
|
+
const packageFromSpecifier = spec => {
|
|
37
|
+
const cleaned = stripQuery(spec);
|
|
38
|
+
if (!cleaned) return null;
|
|
39
|
+
if (cleaned.startsWith('node:')) return null;
|
|
40
|
+
if (/^(?:\.?\.?\/|\/)/.test(cleaned)) return null;
|
|
41
|
+
if (/^[a-zA-Z][a-zA-Z+.-]*:/.test(cleaned)) return null;
|
|
42
|
+
const parts = cleaned.split('/');
|
|
43
|
+
if (cleaned.startsWith('@')) {
|
|
44
|
+
if (parts.length < 2) return null;
|
|
45
|
+
const pkg = `${parts[0]}/${parts[1]}`;
|
|
46
|
+
if (builtinSpecifiers.has(pkg) || builtinSpecifiers.has(parts[1] ?? '')) return null;
|
|
47
|
+
const subpath = parts.slice(2).join('/');
|
|
48
|
+
return {
|
|
49
|
+
pkg,
|
|
50
|
+
subpath
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const pkg = parts[0] ?? '';
|
|
54
|
+
if (!pkg || builtinSpecifiers.has(pkg)) return null;
|
|
55
|
+
const subpath = parts.slice(1).join('/');
|
|
56
|
+
return {
|
|
57
|
+
pkg,
|
|
58
|
+
subpath
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
const fileExists = async filename => {
|
|
62
|
+
try {
|
|
63
|
+
const stats = await (0, _promises.stat)(filename);
|
|
64
|
+
return stats.isFile();
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const findPackageManifest = async (pkg, filePath, cwd) => {
|
|
70
|
+
const startDir = filePath ? (0, _nodePath.dirname)((0, _nodePath.resolve)(filePath)) : (0, _nodePath.resolve)(cwd ?? process.cwd());
|
|
71
|
+
const seen = new Set();
|
|
72
|
+
let dir = startDir;
|
|
73
|
+
while (!seen.has(dir)) {
|
|
74
|
+
seen.add(dir);
|
|
75
|
+
const candidate = (0, _nodePath.join)(dir, 'node_modules', pkg, 'package.json');
|
|
76
|
+
if (await fileExists(candidate)) return candidate;
|
|
77
|
+
const parent = (0, _nodePath.dirname)(dir);
|
|
78
|
+
if (parent === dir) break;
|
|
79
|
+
dir = parent;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
};
|
|
83
|
+
const readPackageManifest = async (pkg, filePath, cwd, cache) => {
|
|
84
|
+
const start = (0, _nodePath.resolve)(filePath ? (0, _nodePath.dirname)(filePath) : cwd ?? process.cwd());
|
|
85
|
+
const cacheKey = `${pkg}@${start}`;
|
|
86
|
+
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
|
87
|
+
const manifestPath = await findPackageManifest(pkg, filePath, cwd);
|
|
88
|
+
if (!manifestPath) {
|
|
89
|
+
cache.set(cacheKey, null);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const raw = await (0, _promises.readFile)(manifestPath, 'utf8');
|
|
94
|
+
const json = JSON.parse(raw);
|
|
95
|
+
cache.set(cacheKey, json);
|
|
96
|
+
return json;
|
|
97
|
+
} catch {
|
|
98
|
+
cache.set(cacheKey, null);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const analyzeExportsTargets = exportsField => {
|
|
103
|
+
const root = exportsField && typeof exportsField === 'object' && !Array.isArray(exportsField) ?
|
|
104
|
+
// @ts-expect-error -- loose lookup of root export condition
|
|
105
|
+
exportsField['.'] ?? exportsField : exportsField;
|
|
106
|
+
if (typeof root === 'string') {
|
|
107
|
+
return {
|
|
108
|
+
importTarget: root,
|
|
109
|
+
requireTarget: root
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (root && typeof root === 'object') {
|
|
113
|
+
const record = root;
|
|
114
|
+
const importTarget = typeof record.import === 'string' ? record.import : undefined;
|
|
115
|
+
const requireTarget = typeof record.require === 'string' ? record.require : undefined;
|
|
116
|
+
const defaultTarget = typeof record.default === 'string' ? record.default : undefined;
|
|
117
|
+
return {
|
|
118
|
+
importTarget: importTarget ?? defaultTarget,
|
|
119
|
+
requireTarget: requireTarget ?? defaultTarget
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
importTarget: undefined,
|
|
124
|
+
requireTarget: undefined
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
const describeDualPackage = pkgJson => {
|
|
128
|
+
const {
|
|
129
|
+
importTarget,
|
|
130
|
+
requireTarget
|
|
131
|
+
} = analyzeExportsTargets(pkgJson?.exports);
|
|
132
|
+
const moduleField = typeof pkgJson?.module === 'string' ? pkgJson.module : undefined;
|
|
133
|
+
const mainField = typeof pkgJson?.main === 'string' ? pkgJson.main : undefined;
|
|
134
|
+
const typeField = typeof pkgJson?.type === 'string' ? pkgJson.type : undefined;
|
|
135
|
+
const divergentExports = importTarget && requireTarget && importTarget !== requireTarget;
|
|
136
|
+
const divergentModuleMain = moduleField && mainField && moduleField !== mainField;
|
|
137
|
+
const typeModuleMainCjs = typeField === 'module' && typeof mainField === 'string' && mainField.endsWith('.cjs');
|
|
138
|
+
const hasHazardSignals = divergentExports || divergentModuleMain || typeModuleMainCjs;
|
|
139
|
+
const details = [];
|
|
140
|
+
if (divergentExports) {
|
|
141
|
+
details.push(`exports import -> ${importTarget}, require -> ${requireTarget}`);
|
|
142
|
+
}
|
|
143
|
+
if (divergentModuleMain) {
|
|
144
|
+
details.push(`module -> ${moduleField}, main -> ${mainField}`);
|
|
145
|
+
}
|
|
146
|
+
if (typeModuleMainCjs) {
|
|
147
|
+
details.push(`type: module with CommonJS main (${mainField})`);
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
hasHazardSignals,
|
|
151
|
+
details,
|
|
152
|
+
importTarget,
|
|
153
|
+
requireTarget
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
const recordUsage = (usages, pkg, kind, spec, subpath, loc, filePath) => {
|
|
157
|
+
const existing = usages.get(pkg) ?? {
|
|
158
|
+
imports: [],
|
|
159
|
+
requires: []
|
|
160
|
+
};
|
|
161
|
+
const bucket = kind === 'import' ? existing.imports : existing.requires;
|
|
162
|
+
bucket.push({
|
|
163
|
+
spec,
|
|
164
|
+
subpath,
|
|
165
|
+
loc,
|
|
166
|
+
filePath
|
|
167
|
+
});
|
|
168
|
+
usages.set(pkg, existing);
|
|
169
|
+
};
|
|
170
|
+
const collectDualPackageUsage = async (program, shadowedBindings, filePath) => {
|
|
171
|
+
const usages = new Map();
|
|
172
|
+
await (0, _walk.ancestorWalk)(program, {
|
|
173
|
+
enter(node) {
|
|
174
|
+
if (node.type === 'ImportDeclaration' && node.source.type === 'Literal' && typeof node.source.value === 'string') {
|
|
175
|
+
const pkg = packageFromSpecifier(node.source.value);
|
|
176
|
+
if (pkg) recordUsage(usages, pkg.pkg, 'import', node.source.value, pkg.subpath, {
|
|
177
|
+
start: node.source.start,
|
|
178
|
+
end: node.source.end
|
|
179
|
+
}, filePath);
|
|
180
|
+
}
|
|
181
|
+
if (node.type === 'ExportNamedDeclaration' && node.source && node.source.type === 'Literal' && typeof node.source.value === 'string') {
|
|
182
|
+
const pkg = packageFromSpecifier(node.source.value);
|
|
183
|
+
if (pkg) recordUsage(usages, pkg.pkg, 'import', node.source.value, pkg.subpath, {
|
|
184
|
+
start: node.source.start,
|
|
185
|
+
end: node.source.end
|
|
186
|
+
}, filePath);
|
|
187
|
+
}
|
|
188
|
+
if (node.type === 'ExportAllDeclaration' && node.source.type === 'Literal' && typeof node.source.value === 'string') {
|
|
189
|
+
const pkg = packageFromSpecifier(node.source.value);
|
|
190
|
+
if (pkg) recordUsage(usages, pkg.pkg, 'import', node.source.value, pkg.subpath, {
|
|
191
|
+
start: node.source.start,
|
|
192
|
+
end: node.source.end
|
|
193
|
+
}, filePath);
|
|
194
|
+
}
|
|
195
|
+
if (node.type === 'ImportExpression' && node.source.type === 'Literal' && typeof node.source.value === 'string') {
|
|
196
|
+
const pkg = packageFromSpecifier(node.source.value);
|
|
197
|
+
if (pkg) recordUsage(usages, pkg.pkg, 'import', node.source.value, pkg.subpath, {
|
|
198
|
+
start: node.source.start,
|
|
199
|
+
end: node.source.end
|
|
200
|
+
}, filePath);
|
|
201
|
+
}
|
|
202
|
+
if (node.type === 'CallExpression' && (0, _lowerCjsRequireToImports.isStaticRequire)(node, shadowedBindings)) {
|
|
203
|
+
const arg = node.arguments[0];
|
|
204
|
+
if (arg?.type === 'Literal' && typeof arg.value === 'string') {
|
|
205
|
+
const pkg = packageFromSpecifier(arg.value);
|
|
206
|
+
if (pkg) recordUsage(usages, pkg.pkg, 'require', arg.value, pkg.subpath, {
|
|
207
|
+
start: arg.start,
|
|
208
|
+
end: arg.end
|
|
209
|
+
}, filePath);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return usages;
|
|
215
|
+
};
|
|
216
|
+
exports.collectDualPackageUsage = collectDualPackageUsage;
|
|
217
|
+
const dualPackageHazardDiagnostics = async params => {
|
|
218
|
+
const {
|
|
219
|
+
usages,
|
|
220
|
+
hazardLevel,
|
|
221
|
+
filePath,
|
|
222
|
+
cwd
|
|
223
|
+
} = params;
|
|
224
|
+
const manifestCache = params.manifestCache ?? new Map();
|
|
225
|
+
const diags = [];
|
|
226
|
+
for (const [pkg, usage] of usages) {
|
|
227
|
+
const hasImport = usage.imports.length > 0;
|
|
228
|
+
const hasRequire = usage.requires.length > 0;
|
|
229
|
+
const combined = [...usage.imports, ...usage.requires];
|
|
230
|
+
const hasRoot = combined.some(entry => !entry.subpath);
|
|
231
|
+
const hasSubpath = combined.some(entry => Boolean(entry.subpath));
|
|
232
|
+
const origin = usage.imports[0] ?? usage.requires[0];
|
|
233
|
+
const diagFile = origin?.filePath ?? filePath;
|
|
234
|
+
if (hasImport && hasRequire) {
|
|
235
|
+
const importSpecs = usage.imports.map(u => u.subpath ? `${pkg}/${u.subpath}` : pkg);
|
|
236
|
+
const requireSpecs = usage.requires.map(u => u.subpath ? `${pkg}/${u.subpath}` : pkg);
|
|
237
|
+
diags.push({
|
|
238
|
+
level: hazardLevel,
|
|
239
|
+
code: 'dual-package-mixed-specifiers',
|
|
240
|
+
message: `Package '${pkg}' is loaded via import (${importSpecs.join(', ')}) and require (${requireSpecs.join(', ')}); conditional exports can instantiate it twice.`,
|
|
241
|
+
filePath: diagFile,
|
|
242
|
+
loc: origin?.loc
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
if (hasRoot && hasSubpath) {
|
|
246
|
+
const subpaths = combined.filter(entry => entry.subpath).map(entry => `${pkg}/${entry.subpath}`);
|
|
247
|
+
const originSubpath = combined.find(entry => entry.subpath) ?? combined[0];
|
|
248
|
+
diags.push({
|
|
249
|
+
level: hazardLevel,
|
|
250
|
+
code: 'dual-package-subpath',
|
|
251
|
+
message: `Package '${pkg}' is referenced via root specifier '${pkg}' and subpath(s) ${subpaths.join(', ')}; mixing them loads separate module instances.`,
|
|
252
|
+
filePath: originSubpath?.filePath ?? filePath,
|
|
253
|
+
loc: originSubpath?.loc
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (hasImport && hasRequire) {
|
|
257
|
+
const manifest = await readPackageManifest(pkg, diagFile, cwd, manifestCache);
|
|
258
|
+
if (manifest) {
|
|
259
|
+
const meta = describeDualPackage(manifest);
|
|
260
|
+
if (meta.hasHazardSignals) {
|
|
261
|
+
const detail = meta.details.length ? ` (${meta.details.join('; ')})` : '';
|
|
262
|
+
diags.push({
|
|
263
|
+
level: hazardLevel,
|
|
264
|
+
code: 'dual-package-conditional-exports',
|
|
265
|
+
message: `Package '${pkg}' exposes different entry points for import vs require${detail}. Mixed usage can produce distinct instances.`,
|
|
266
|
+
filePath: diagFile,
|
|
267
|
+
loc: origin?.loc
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return diags;
|
|
274
|
+
};
|
|
275
|
+
exports.dualPackageHazardDiagnostics = dualPackageHazardDiagnostics;
|
|
276
|
+
const detectDualPackageHazards = async params => {
|
|
277
|
+
const {
|
|
278
|
+
program,
|
|
279
|
+
shadowedBindings,
|
|
280
|
+
hazardLevel,
|
|
281
|
+
filePath,
|
|
282
|
+
cwd,
|
|
283
|
+
diagOnce
|
|
284
|
+
} = params;
|
|
285
|
+
const manifestCache = new Map();
|
|
286
|
+
const usages = await collectDualPackageUsage(program, shadowedBindings, filePath);
|
|
287
|
+
const diags = await dualPackageHazardDiagnostics({
|
|
288
|
+
usages,
|
|
289
|
+
hazardLevel,
|
|
290
|
+
filePath,
|
|
291
|
+
cwd,
|
|
292
|
+
manifestCache
|
|
293
|
+
});
|
|
294
|
+
for (const diag of diags) {
|
|
295
|
+
diagOnce(diag.level, diag.code, diag.message, diag.loc);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
27
298
|
|
|
28
299
|
/**
|
|
29
300
|
* Node added support for import.meta.main.
|
|
@@ -53,22 +324,35 @@ const format = async (src, ast, opts) => {
|
|
|
53
324
|
// eslint-disable-next-line no-console -- used for opt-in diagnostics
|
|
54
325
|
console.error(diag.message);
|
|
55
326
|
};
|
|
56
|
-
const
|
|
57
|
-
const key = `${codeId}:${loc?.start ?? ''}`;
|
|
327
|
+
const diagOnce = (level, codeId, message, loc) => {
|
|
328
|
+
const key = `${level}:${codeId}:${loc?.start ?? ''}`;
|
|
58
329
|
if (warned.has(key)) return;
|
|
59
330
|
warned.add(key);
|
|
60
331
|
emitDiagnostic({
|
|
61
|
-
level
|
|
332
|
+
level,
|
|
62
333
|
code: codeId,
|
|
63
334
|
message,
|
|
64
335
|
filePath: opts.filePath,
|
|
65
336
|
loc
|
|
66
337
|
});
|
|
67
338
|
};
|
|
339
|
+
const warnOnce = (codeId, message, loc) => diagOnce('warning', codeId, message, loc);
|
|
68
340
|
const transformMode = opts.transformSyntax;
|
|
69
341
|
const fullTransform = transformMode === true;
|
|
70
342
|
const moduleIdentifiers = await (0, _identifiers.collectModuleIdentifiers)(ast.program);
|
|
71
343
|
const shadowedBindings = new Set([...moduleIdentifiers.entries()].filter(([, meta]) => meta.declare.length > 0).map(([name]) => name));
|
|
344
|
+
const hazardMode = opts.detectDualPackageHazard ?? 'warn';
|
|
345
|
+
if (hazardMode !== 'off') {
|
|
346
|
+
const hazardLevel = hazardMode === 'error' ? 'error' : 'warning';
|
|
347
|
+
await detectDualPackageHazards({
|
|
348
|
+
program: ast.program,
|
|
349
|
+
shadowedBindings,
|
|
350
|
+
hazardLevel,
|
|
351
|
+
filePath: opts.filePath,
|
|
352
|
+
cwd: opts.cwd,
|
|
353
|
+
diagOnce
|
|
354
|
+
});
|
|
355
|
+
}
|
|
72
356
|
if (opts.target === 'module' && fullTransform) {
|
|
73
357
|
if (shadowedBindings.has('module') || shadowedBindings.has('exports')) {
|
|
74
358
|
throw new Error('Cannot transform to ESM: module or exports is shadowed in module scope.');
|
package/dist/cjs/format.d.cts
CHANGED
|
@@ -1,9 +1,31 @@
|
|
|
1
|
-
import type { ParseResult } from 'oxc-parser';
|
|
2
|
-
import type { FormatterOptions } from './types.cjs';
|
|
1
|
+
import type { Node, ParseResult } from 'oxc-parser';
|
|
2
|
+
import type { Diagnostic, FormatterOptions } from './types.cjs';
|
|
3
|
+
type HazardLevel = 'warning' | 'error';
|
|
4
|
+
export type PackageUse = {
|
|
5
|
+
spec: string;
|
|
6
|
+
subpath: string;
|
|
7
|
+
loc?: {
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
};
|
|
11
|
+
filePath?: string;
|
|
12
|
+
};
|
|
13
|
+
export type PackageUsage = {
|
|
14
|
+
imports: PackageUse[];
|
|
15
|
+
requires: PackageUse[];
|
|
16
|
+
};
|
|
17
|
+
declare const collectDualPackageUsage: (program: Node, shadowedBindings: Set<string>, filePath?: string) => Promise<Map<string, PackageUsage>>;
|
|
18
|
+
declare const dualPackageHazardDiagnostics: (params: {
|
|
19
|
+
usages: Map<string, PackageUsage>;
|
|
20
|
+
hazardLevel: HazardLevel;
|
|
21
|
+
filePath?: string;
|
|
22
|
+
cwd?: string;
|
|
23
|
+
manifestCache?: Map<string, any | null>;
|
|
24
|
+
}) => Promise<Diagnostic[]>;
|
|
3
25
|
/**
|
|
4
26
|
* Node added support for import.meta.main.
|
|
5
27
|
* Added in: v24.2.0, v22.18.0
|
|
6
28
|
* @see https://nodejs.org/api/esm.html#importmetamain
|
|
7
29
|
*/
|
|
8
30
|
declare const format: (src: string, ast: ParseResult, opts: FormatterOptions) => Promise<string>;
|
|
9
|
-
export { format };
|
|
31
|
+
export { format, collectDualPackageUsage, dualPackageHazardDiagnostics };
|
package/dist/cjs/module.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.transform = void 0;
|
|
6
|
+
exports.transform = exports.collectProjectDualPackageHazards = void 0;
|
|
7
7
|
var _nodePath = require("node:path");
|
|
8
8
|
var _promises = require("node:fs/promises");
|
|
9
9
|
var _specifier = require("./specifier.cjs");
|
|
@@ -12,6 +12,7 @@ var _format = require("./format.cjs");
|
|
|
12
12
|
var _lang = require("./utils/lang.cjs");
|
|
13
13
|
var _nodeModule = require("node:module");
|
|
14
14
|
var _walk = require("./walk.cjs");
|
|
15
|
+
var _identifiers = require("./utils/identifiers.cjs");
|
|
15
16
|
const collapseSpecifier = value => value.replace(/['"`+)\s]|new String\(/g, '');
|
|
16
17
|
const builtinSpecifiers = new Set(_nodeModule.builtinModules.map(mod => mod.startsWith('node:') ? mod.slice(5) : mod).flatMap(mod => {
|
|
17
18
|
const parts = mod.split('/');
|
|
@@ -144,6 +145,47 @@ const detectCircularRequireGraph = async (entryFile, mode, dirIndex) => {
|
|
|
144
145
|
};
|
|
145
146
|
await dfs(entryFile, []);
|
|
146
147
|
};
|
|
148
|
+
const mergeUsageMaps = (target, source) => {
|
|
149
|
+
for (const [pkg, usage] of source) {
|
|
150
|
+
const existing = target.get(pkg) ?? {
|
|
151
|
+
imports: [],
|
|
152
|
+
requires: []
|
|
153
|
+
};
|
|
154
|
+
existing.imports.push(...usage.imports);
|
|
155
|
+
existing.requires.push(...usage.requires);
|
|
156
|
+
target.set(pkg, existing);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const collectProjectDualPackageHazards = async (files, opts) => {
|
|
160
|
+
const hazardMode = opts.detectDualPackageHazard ?? 'warn';
|
|
161
|
+
if (hazardMode === 'off') return new Map();
|
|
162
|
+
const hazardLevel = hazardMode === 'error' ? 'error' : 'warning';
|
|
163
|
+
const usages = new Map();
|
|
164
|
+
const manifestCache = new Map();
|
|
165
|
+
for (const file of files) {
|
|
166
|
+
const code = await (0, _promises.readFile)(file, 'utf8');
|
|
167
|
+
const ast = (0, _parse.parse)(file, code);
|
|
168
|
+
const moduleIdentifiers = await (0, _identifiers.collectModuleIdentifiers)(ast.program);
|
|
169
|
+
const shadowedBindings = new Set([...moduleIdentifiers.entries()].filter(([, meta]) => meta.declare.length > 0).map(([name]) => name));
|
|
170
|
+
const perFileUsage = await (0, _format.collectDualPackageUsage)(ast.program, shadowedBindings, file);
|
|
171
|
+
mergeUsageMaps(usages, perFileUsage);
|
|
172
|
+
}
|
|
173
|
+
const diags = await (0, _format.dualPackageHazardDiagnostics)({
|
|
174
|
+
usages,
|
|
175
|
+
hazardLevel,
|
|
176
|
+
cwd: opts.cwd,
|
|
177
|
+
manifestCache
|
|
178
|
+
});
|
|
179
|
+
const byFile = new Map();
|
|
180
|
+
for (const diag of diags) {
|
|
181
|
+
const key = diag.filePath ?? files[0];
|
|
182
|
+
const existing = byFile.get(key) ?? [];
|
|
183
|
+
existing.push(diag);
|
|
184
|
+
byFile.set(key, existing);
|
|
185
|
+
}
|
|
186
|
+
return byFile;
|
|
187
|
+
};
|
|
188
|
+
exports.collectProjectDualPackageHazards = collectProjectDualPackageHazards;
|
|
147
189
|
const defaultOptions = {
|
|
148
190
|
target: 'commonjs',
|
|
149
191
|
sourceType: 'auto',
|
|
@@ -157,6 +199,8 @@ const defaultOptions = {
|
|
|
157
199
|
importMetaMain: 'shim',
|
|
158
200
|
requireMainStrategy: 'import-meta-main',
|
|
159
201
|
detectCircularRequires: 'off',
|
|
202
|
+
detectDualPackageHazard: 'warn',
|
|
203
|
+
dualPackageHazardScope: 'file',
|
|
160
204
|
requireSource: 'builtin',
|
|
161
205
|
nestedRequireStrategy: 'create-require',
|
|
162
206
|
cjsDefault: 'auto',
|
package/dist/cjs/module.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import type { ModuleOptions } from './types.cjs';
|
|
1
|
+
import type { ModuleOptions, Diagnostic } from './types.cjs';
|
|
2
|
+
declare const collectProjectDualPackageHazards: (files: string[], opts: ModuleOptions) => Promise<Map<string, Diagnostic[]>>;
|
|
2
3
|
declare const transform: (filename: string, options?: ModuleOptions) => Promise<string>;
|
|
3
|
-
export { transform };
|
|
4
|
+
export { transform, collectProjectDualPackageHazards };
|
package/dist/cjs/types.d.cts
CHANGED
|
@@ -32,6 +32,10 @@ export type ModuleOptions = {
|
|
|
32
32
|
requireMainStrategy?: 'import-meta-main' | 'realpath';
|
|
33
33
|
/** Detect circular require usage level. */
|
|
34
34
|
detectCircularRequires?: 'off' | 'warn' | 'error';
|
|
35
|
+
/** Detect divergent import/require usage of the same dual package (default warn). */
|
|
36
|
+
detectDualPackageHazard?: 'off' | 'warn' | 'error';
|
|
37
|
+
/** Scope for dual package hazard detection. */
|
|
38
|
+
dualPackageHazardScope?: 'file' | 'project';
|
|
35
39
|
/** Source used to provide require in ESM output. */
|
|
36
40
|
requireSource?: 'builtin' | 'create-require';
|
|
37
41
|
/** How to rewrite nested or non-hoistable require calls. */
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { readFile, mkdir } from 'node:fs/promises';
|
|
|
5
5
|
import { dirname, resolve, relative, join } from 'node:path';
|
|
6
6
|
import { builtinModules } from 'node:module';
|
|
7
7
|
import { glob } from 'glob';
|
|
8
|
-
import { transform } from './module.js';
|
|
8
|
+
import { transform, collectProjectDualPackageHazards } from './module.js';
|
|
9
9
|
import { parse } from './parse.js';
|
|
10
10
|
import { format } from './format.js';
|
|
11
11
|
import { specifier } from './specifier.js';
|
|
@@ -23,6 +23,8 @@ const defaultOptions = {
|
|
|
23
23
|
importMetaMain: 'shim',
|
|
24
24
|
requireMainStrategy: 'import-meta-main',
|
|
25
25
|
detectCircularRequires: 'off',
|
|
26
|
+
detectDualPackageHazard: 'warn',
|
|
27
|
+
dualPackageHazardScope: 'file',
|
|
26
28
|
requireSource: 'builtin',
|
|
27
29
|
nestedRequireStrategy: 'create-require',
|
|
28
30
|
cjsDefault: 'auto',
|
|
@@ -144,6 +146,16 @@ const optionsTable = [{
|
|
|
144
146
|
short: 'c',
|
|
145
147
|
type: 'string',
|
|
146
148
|
desc: 'Warn/error on circular require (off|warn|error)'
|
|
149
|
+
}, {
|
|
150
|
+
long: 'detect-dual-package-hazard',
|
|
151
|
+
short: 'H',
|
|
152
|
+
type: 'string',
|
|
153
|
+
desc: 'Warn/error on mixed import/require of dual packages (off|warn|error)'
|
|
154
|
+
}, {
|
|
155
|
+
long: 'dual-package-hazard-scope',
|
|
156
|
+
short: undefined,
|
|
157
|
+
type: 'string',
|
|
158
|
+
desc: 'Scope for dual package hazard detection (file|project)'
|
|
147
159
|
}, {
|
|
148
160
|
long: 'top-level-await',
|
|
149
161
|
short: 'a',
|
|
@@ -237,10 +249,10 @@ const optionsTable = [{
|
|
|
237
249
|
}];
|
|
238
250
|
const buildHelp = enableColor => {
|
|
239
251
|
const c = colorize(enableColor);
|
|
240
|
-
const maxFlagLength = Math.max(...optionsTable.map(opt => ` -${opt.short}, --${opt.long}`.length));
|
|
252
|
+
const maxFlagLength = Math.max(...optionsTable.map(opt => opt.short ? ` -${opt.short}, --${opt.long}`.length : ` --${opt.long}`.length));
|
|
241
253
|
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
254
|
for (const opt of optionsTable) {
|
|
243
|
-
const flag = ` -${opt.short}, --${opt.long}`;
|
|
255
|
+
const flag = opt.short ? ` -${opt.short}, --${opt.long}` : ` --${opt.long}`;
|
|
244
256
|
const pad = ' '.repeat(Math.max(2, maxFlagLength - flag.length + 2));
|
|
245
257
|
lines.push(`${c.bold(flag)}${pad}${opt.desc}`);
|
|
246
258
|
}
|
|
@@ -275,6 +287,8 @@ const toModuleOptions = values => {
|
|
|
275
287
|
appendJsExtension: appendJsExtension,
|
|
276
288
|
appendDirectoryIndex,
|
|
277
289
|
detectCircularRequires: parseEnum(values['detect-circular-requires'], ['off', 'warn', 'error']) ?? defaultOptions.detectCircularRequires,
|
|
290
|
+
detectDualPackageHazard: parseEnum(values['detect-dual-package-hazard'], ['off', 'warn', 'error']) ?? defaultOptions.detectDualPackageHazard,
|
|
291
|
+
dualPackageHazardScope: parseEnum(values['dual-package-hazard-scope'], ['file', 'project']) ?? defaultOptions.dualPackageHazardScope,
|
|
278
292
|
topLevelAwait: parseEnum(values['top-level-await'], ['error', 'wrap', 'preserve']) ?? defaultOptions.topLevelAwait,
|
|
279
293
|
cjsDefault: parseEnum(values['cjs-default'], ['module-exports', 'auto', 'none']) ?? defaultOptions.cjsDefault,
|
|
280
294
|
idiomaticExports: parseEnum(values['idiomatic-exports'], ['off', 'safe', 'aggressive']) ?? defaultOptions.idiomaticExports,
|
|
@@ -361,6 +375,9 @@ const summarizeDiagnostics = diags => {
|
|
|
361
375
|
const runFiles = async (files, moduleOpts, io, flags) => {
|
|
362
376
|
const results = [];
|
|
363
377
|
const logger = makeLogger(io.stdout, io.stderr);
|
|
378
|
+
const hazardScope = moduleOpts.dualPackageHazardScope ?? 'file';
|
|
379
|
+
const hazardMode = moduleOpts.detectDualPackageHazard ?? 'warn';
|
|
380
|
+
const projectHazards = hazardScope === 'project' && hazardMode !== 'off' ? await collectProjectDualPackageHazards(files, moduleOpts) : null;
|
|
364
381
|
for (const file of files) {
|
|
365
382
|
const diagnostics = [];
|
|
366
383
|
const original = await readFile(file, 'utf8');
|
|
@@ -370,7 +387,8 @@ const runFiles = async (files, moduleOpts, io, flags) => {
|
|
|
370
387
|
diagnostics: diag => diagnostics.push(diag),
|
|
371
388
|
out: undefined,
|
|
372
389
|
inPlace: false,
|
|
373
|
-
filePath: file
|
|
390
|
+
filePath: file,
|
|
391
|
+
detectDualPackageHazard: hazardScope === 'project' ? 'off' : moduleOpts.detectDualPackageHazard
|
|
374
392
|
};
|
|
375
393
|
let writeTarget;
|
|
376
394
|
if (!flags.dryRun && !flags.list) {
|
|
@@ -392,6 +410,10 @@ const runFiles = async (files, moduleOpts, io, flags) => {
|
|
|
392
410
|
}
|
|
393
411
|
const output = await transform(file, perFileOpts);
|
|
394
412
|
const changed = output !== original;
|
|
413
|
+
if (projectHazards) {
|
|
414
|
+
const extras = projectHazards.get(file);
|
|
415
|
+
if (extras?.length) diagnostics.push(...extras);
|
|
416
|
+
}
|
|
395
417
|
if (flags.list && changed) {
|
|
396
418
|
logger.info(file);
|
|
397
419
|
}
|
|
@@ -441,7 +463,9 @@ const runCli = async ({
|
|
|
441
463
|
allowPositionals: true,
|
|
442
464
|
options: Object.fromEntries(optionsTable.map(opt => [opt.long, {
|
|
443
465
|
type: opt.type,
|
|
444
|
-
|
|
466
|
+
...(opt.short ? {
|
|
467
|
+
short: opt.short
|
|
468
|
+
} : {})
|
|
445
469
|
}]))
|
|
446
470
|
});
|
|
447
471
|
const logger = makeLogger(stdout, stderr);
|
package/dist/format.d.ts
CHANGED
|
@@ -1,9 +1,31 @@
|
|
|
1
|
-
import type { ParseResult } from 'oxc-parser';
|
|
2
|
-
import type { FormatterOptions } from './types.js';
|
|
1
|
+
import type { Node, ParseResult } from 'oxc-parser';
|
|
2
|
+
import type { Diagnostic, FormatterOptions } from './types.js';
|
|
3
|
+
type HazardLevel = 'warning' | 'error';
|
|
4
|
+
export type PackageUse = {
|
|
5
|
+
spec: string;
|
|
6
|
+
subpath: string;
|
|
7
|
+
loc?: {
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
};
|
|
11
|
+
filePath?: string;
|
|
12
|
+
};
|
|
13
|
+
export type PackageUsage = {
|
|
14
|
+
imports: PackageUse[];
|
|
15
|
+
requires: PackageUse[];
|
|
16
|
+
};
|
|
17
|
+
declare const collectDualPackageUsage: (program: Node, shadowedBindings: Set<string>, filePath?: string) => Promise<Map<string, PackageUsage>>;
|
|
18
|
+
declare const dualPackageHazardDiagnostics: (params: {
|
|
19
|
+
usages: Map<string, PackageUsage>;
|
|
20
|
+
hazardLevel: HazardLevel;
|
|
21
|
+
filePath?: string;
|
|
22
|
+
cwd?: string;
|
|
23
|
+
manifestCache?: Map<string, any | null>;
|
|
24
|
+
}) => Promise<Diagnostic[]>;
|
|
3
25
|
/**
|
|
4
26
|
* Node added support for import.meta.main.
|
|
5
27
|
* Added in: v24.2.0, v22.18.0
|
|
6
28
|
* @see https://nodejs.org/api/esm.html#importmetamain
|
|
7
29
|
*/
|
|
8
30
|
declare const format: (src: string, ast: ParseResult, opts: FormatterOptions) => Promise<string>;
|
|
9
|
-
export { format };
|
|
31
|
+
export { format, collectDualPackageUsage, dualPackageHazardDiagnostics };
|
package/dist/format.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { builtinModules } from 'node:module';
|
|
2
|
+
import { dirname, join, resolve as pathResolve } from 'node:path';
|
|
3
|
+
import { readFile as fsReadFile, stat as fsStat } from 'node:fs/promises';
|
|
1
4
|
import MagicString from 'magic-string';
|
|
2
5
|
import { hasTopLevelAwait, isAsyncContext } from './helpers/async.js';
|
|
3
6
|
import { isIdentifierName } from './helpers/identifier.js';
|
|
@@ -17,6 +20,272 @@ import { collectModuleIdentifiers } from './utils/identifiers.js';
|
|
|
17
20
|
import { isValidUrl } from './utils/url.js';
|
|
18
21
|
import { ancestorWalk } from './walk.js';
|
|
19
22
|
const isRequireMainMember = (node, shadowed) => node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'require' && !shadowed.has('require') && node.property.type === 'Identifier' && node.property.name === 'main';
|
|
23
|
+
const builtinSpecifiers = new Set(builtinModules.map(mod => mod.startsWith('node:') ? mod.slice(5) : mod).flatMap(mod => {
|
|
24
|
+
const parts = mod.split('/');
|
|
25
|
+
const base = parts[0];
|
|
26
|
+
return parts.length > 1 ? [mod, base] : [mod];
|
|
27
|
+
}));
|
|
28
|
+
const stripQuery = value => value.includes('?') || value.includes('#') ? value.split(/[?#]/)[0] ?? value : value;
|
|
29
|
+
const packageFromSpecifier = spec => {
|
|
30
|
+
const cleaned = stripQuery(spec);
|
|
31
|
+
if (!cleaned) return null;
|
|
32
|
+
if (cleaned.startsWith('node:')) return null;
|
|
33
|
+
if (/^(?:\.?\.?\/|\/)/.test(cleaned)) return null;
|
|
34
|
+
if (/^[a-zA-Z][a-zA-Z+.-]*:/.test(cleaned)) return null;
|
|
35
|
+
const parts = cleaned.split('/');
|
|
36
|
+
if (cleaned.startsWith('@')) {
|
|
37
|
+
if (parts.length < 2) return null;
|
|
38
|
+
const pkg = `${parts[0]}/${parts[1]}`;
|
|
39
|
+
if (builtinSpecifiers.has(pkg) || builtinSpecifiers.has(parts[1] ?? '')) return null;
|
|
40
|
+
const subpath = parts.slice(2).join('/');
|
|
41
|
+
return {
|
|
42
|
+
pkg,
|
|
43
|
+
subpath
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const pkg = parts[0] ?? '';
|
|
47
|
+
if (!pkg || builtinSpecifiers.has(pkg)) return null;
|
|
48
|
+
const subpath = parts.slice(1).join('/');
|
|
49
|
+
return {
|
|
50
|
+
pkg,
|
|
51
|
+
subpath
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
const fileExists = async filename => {
|
|
55
|
+
try {
|
|
56
|
+
const stats = await fsStat(filename);
|
|
57
|
+
return stats.isFile();
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const findPackageManifest = async (pkg, filePath, cwd) => {
|
|
63
|
+
const startDir = filePath ? dirname(pathResolve(filePath)) : pathResolve(cwd ?? process.cwd());
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
let dir = startDir;
|
|
66
|
+
while (!seen.has(dir)) {
|
|
67
|
+
seen.add(dir);
|
|
68
|
+
const candidate = join(dir, 'node_modules', pkg, 'package.json');
|
|
69
|
+
if (await fileExists(candidate)) return candidate;
|
|
70
|
+
const parent = dirname(dir);
|
|
71
|
+
if (parent === dir) break;
|
|
72
|
+
dir = parent;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
};
|
|
76
|
+
const readPackageManifest = async (pkg, filePath, cwd, cache) => {
|
|
77
|
+
const start = pathResolve(filePath ? dirname(filePath) : cwd ?? process.cwd());
|
|
78
|
+
const cacheKey = `${pkg}@${start}`;
|
|
79
|
+
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
|
80
|
+
const manifestPath = await findPackageManifest(pkg, filePath, cwd);
|
|
81
|
+
if (!manifestPath) {
|
|
82
|
+
cache.set(cacheKey, null);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const raw = await fsReadFile(manifestPath, 'utf8');
|
|
87
|
+
const json = JSON.parse(raw);
|
|
88
|
+
cache.set(cacheKey, json);
|
|
89
|
+
return json;
|
|
90
|
+
} catch {
|
|
91
|
+
cache.set(cacheKey, null);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const analyzeExportsTargets = exportsField => {
|
|
96
|
+
const root = exportsField && typeof exportsField === 'object' && !Array.isArray(exportsField) ?
|
|
97
|
+
// @ts-expect-error -- loose lookup of root export condition
|
|
98
|
+
exportsField['.'] ?? exportsField : exportsField;
|
|
99
|
+
if (typeof root === 'string') {
|
|
100
|
+
return {
|
|
101
|
+
importTarget: root,
|
|
102
|
+
requireTarget: root
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (root && typeof root === 'object') {
|
|
106
|
+
const record = root;
|
|
107
|
+
const importTarget = typeof record.import === 'string' ? record.import : undefined;
|
|
108
|
+
const requireTarget = typeof record.require === 'string' ? record.require : undefined;
|
|
109
|
+
const defaultTarget = typeof record.default === 'string' ? record.default : undefined;
|
|
110
|
+
return {
|
|
111
|
+
importTarget: importTarget ?? defaultTarget,
|
|
112
|
+
requireTarget: requireTarget ?? defaultTarget
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
importTarget: undefined,
|
|
117
|
+
requireTarget: undefined
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
const describeDualPackage = pkgJson => {
|
|
121
|
+
const {
|
|
122
|
+
importTarget,
|
|
123
|
+
requireTarget
|
|
124
|
+
} = analyzeExportsTargets(pkgJson?.exports);
|
|
125
|
+
const moduleField = typeof pkgJson?.module === 'string' ? pkgJson.module : undefined;
|
|
126
|
+
const mainField = typeof pkgJson?.main === 'string' ? pkgJson.main : undefined;
|
|
127
|
+
const typeField = typeof pkgJson?.type === 'string' ? pkgJson.type : undefined;
|
|
128
|
+
const divergentExports = importTarget && requireTarget && importTarget !== requireTarget;
|
|
129
|
+
const divergentModuleMain = moduleField && mainField && moduleField !== mainField;
|
|
130
|
+
const typeModuleMainCjs = typeField === 'module' && typeof mainField === 'string' && mainField.endsWith('.cjs');
|
|
131
|
+
const hasHazardSignals = divergentExports || divergentModuleMain || typeModuleMainCjs;
|
|
132
|
+
const details = [];
|
|
133
|
+
if (divergentExports) {
|
|
134
|
+
details.push(`exports import -> ${importTarget}, require -> ${requireTarget}`);
|
|
135
|
+
}
|
|
136
|
+
if (divergentModuleMain) {
|
|
137
|
+
details.push(`module -> ${moduleField}, main -> ${mainField}`);
|
|
138
|
+
}
|
|
139
|
+
if (typeModuleMainCjs) {
|
|
140
|
+
details.push(`type: module with CommonJS main (${mainField})`);
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
hasHazardSignals,
|
|
144
|
+
details,
|
|
145
|
+
importTarget,
|
|
146
|
+
requireTarget
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
const recordUsage = (usages, pkg, kind, spec, subpath, loc, filePath) => {
|
|
150
|
+
const existing = usages.get(pkg) ?? {
|
|
151
|
+
imports: [],
|
|
152
|
+
requires: []
|
|
153
|
+
};
|
|
154
|
+
const bucket = kind === 'import' ? existing.imports : existing.requires;
|
|
155
|
+
bucket.push({
|
|
156
|
+
spec,
|
|
157
|
+
subpath,
|
|
158
|
+
loc,
|
|
159
|
+
filePath
|
|
160
|
+
});
|
|
161
|
+
usages.set(pkg, existing);
|
|
162
|
+
};
|
|
163
|
+
const collectDualPackageUsage = async (program, shadowedBindings, filePath) => {
|
|
164
|
+
const usages = new Map();
|
|
165
|
+
await ancestorWalk(program, {
|
|
166
|
+
enter(node) {
|
|
167
|
+
if (node.type === 'ImportDeclaration' && node.source.type === 'Literal' && typeof node.source.value === 'string') {
|
|
168
|
+
const pkg = packageFromSpecifier(node.source.value);
|
|
169
|
+
if (pkg) recordUsage(usages, pkg.pkg, 'import', node.source.value, pkg.subpath, {
|
|
170
|
+
start: node.source.start,
|
|
171
|
+
end: node.source.end
|
|
172
|
+
}, filePath);
|
|
173
|
+
}
|
|
174
|
+
if (node.type === 'ExportNamedDeclaration' && node.source && node.source.type === 'Literal' && typeof node.source.value === 'string') {
|
|
175
|
+
const pkg = packageFromSpecifier(node.source.value);
|
|
176
|
+
if (pkg) recordUsage(usages, pkg.pkg, 'import', node.source.value, pkg.subpath, {
|
|
177
|
+
start: node.source.start,
|
|
178
|
+
end: node.source.end
|
|
179
|
+
}, filePath);
|
|
180
|
+
}
|
|
181
|
+
if (node.type === 'ExportAllDeclaration' && node.source.type === 'Literal' && typeof node.source.value === 'string') {
|
|
182
|
+
const pkg = packageFromSpecifier(node.source.value);
|
|
183
|
+
if (pkg) recordUsage(usages, pkg.pkg, 'import', node.source.value, pkg.subpath, {
|
|
184
|
+
start: node.source.start,
|
|
185
|
+
end: node.source.end
|
|
186
|
+
}, filePath);
|
|
187
|
+
}
|
|
188
|
+
if (node.type === 'ImportExpression' && node.source.type === 'Literal' && typeof node.source.value === 'string') {
|
|
189
|
+
const pkg = packageFromSpecifier(node.source.value);
|
|
190
|
+
if (pkg) recordUsage(usages, pkg.pkg, 'import', node.source.value, pkg.subpath, {
|
|
191
|
+
start: node.source.start,
|
|
192
|
+
end: node.source.end
|
|
193
|
+
}, filePath);
|
|
194
|
+
}
|
|
195
|
+
if (node.type === 'CallExpression' && isStaticRequire(node, shadowedBindings)) {
|
|
196
|
+
const arg = node.arguments[0];
|
|
197
|
+
if (arg?.type === 'Literal' && typeof arg.value === 'string') {
|
|
198
|
+
const pkg = packageFromSpecifier(arg.value);
|
|
199
|
+
if (pkg) recordUsage(usages, pkg.pkg, 'require', arg.value, pkg.subpath, {
|
|
200
|
+
start: arg.start,
|
|
201
|
+
end: arg.end
|
|
202
|
+
}, filePath);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
return usages;
|
|
208
|
+
};
|
|
209
|
+
const dualPackageHazardDiagnostics = async params => {
|
|
210
|
+
const {
|
|
211
|
+
usages,
|
|
212
|
+
hazardLevel,
|
|
213
|
+
filePath,
|
|
214
|
+
cwd
|
|
215
|
+
} = params;
|
|
216
|
+
const manifestCache = params.manifestCache ?? new Map();
|
|
217
|
+
const diags = [];
|
|
218
|
+
for (const [pkg, usage] of usages) {
|
|
219
|
+
const hasImport = usage.imports.length > 0;
|
|
220
|
+
const hasRequire = usage.requires.length > 0;
|
|
221
|
+
const combined = [...usage.imports, ...usage.requires];
|
|
222
|
+
const hasRoot = combined.some(entry => !entry.subpath);
|
|
223
|
+
const hasSubpath = combined.some(entry => Boolean(entry.subpath));
|
|
224
|
+
const origin = usage.imports[0] ?? usage.requires[0];
|
|
225
|
+
const diagFile = origin?.filePath ?? filePath;
|
|
226
|
+
if (hasImport && hasRequire) {
|
|
227
|
+
const importSpecs = usage.imports.map(u => u.subpath ? `${pkg}/${u.subpath}` : pkg);
|
|
228
|
+
const requireSpecs = usage.requires.map(u => u.subpath ? `${pkg}/${u.subpath}` : pkg);
|
|
229
|
+
diags.push({
|
|
230
|
+
level: hazardLevel,
|
|
231
|
+
code: 'dual-package-mixed-specifiers',
|
|
232
|
+
message: `Package '${pkg}' is loaded via import (${importSpecs.join(', ')}) and require (${requireSpecs.join(', ')}); conditional exports can instantiate it twice.`,
|
|
233
|
+
filePath: diagFile,
|
|
234
|
+
loc: origin?.loc
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (hasRoot && hasSubpath) {
|
|
238
|
+
const subpaths = combined.filter(entry => entry.subpath).map(entry => `${pkg}/${entry.subpath}`);
|
|
239
|
+
const originSubpath = combined.find(entry => entry.subpath) ?? combined[0];
|
|
240
|
+
diags.push({
|
|
241
|
+
level: hazardLevel,
|
|
242
|
+
code: 'dual-package-subpath',
|
|
243
|
+
message: `Package '${pkg}' is referenced via root specifier '${pkg}' and subpath(s) ${subpaths.join(', ')}; mixing them loads separate module instances.`,
|
|
244
|
+
filePath: originSubpath?.filePath ?? filePath,
|
|
245
|
+
loc: originSubpath?.loc
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (hasImport && hasRequire) {
|
|
249
|
+
const manifest = await readPackageManifest(pkg, diagFile, cwd, manifestCache);
|
|
250
|
+
if (manifest) {
|
|
251
|
+
const meta = describeDualPackage(manifest);
|
|
252
|
+
if (meta.hasHazardSignals) {
|
|
253
|
+
const detail = meta.details.length ? ` (${meta.details.join('; ')})` : '';
|
|
254
|
+
diags.push({
|
|
255
|
+
level: hazardLevel,
|
|
256
|
+
code: 'dual-package-conditional-exports',
|
|
257
|
+
message: `Package '${pkg}' exposes different entry points for import vs require${detail}. Mixed usage can produce distinct instances.`,
|
|
258
|
+
filePath: diagFile,
|
|
259
|
+
loc: origin?.loc
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return diags;
|
|
266
|
+
};
|
|
267
|
+
const detectDualPackageHazards = async params => {
|
|
268
|
+
const {
|
|
269
|
+
program,
|
|
270
|
+
shadowedBindings,
|
|
271
|
+
hazardLevel,
|
|
272
|
+
filePath,
|
|
273
|
+
cwd,
|
|
274
|
+
diagOnce
|
|
275
|
+
} = params;
|
|
276
|
+
const manifestCache = new Map();
|
|
277
|
+
const usages = await collectDualPackageUsage(program, shadowedBindings, filePath);
|
|
278
|
+
const diags = await dualPackageHazardDiagnostics({
|
|
279
|
+
usages,
|
|
280
|
+
hazardLevel,
|
|
281
|
+
filePath,
|
|
282
|
+
cwd,
|
|
283
|
+
manifestCache
|
|
284
|
+
});
|
|
285
|
+
for (const diag of diags) {
|
|
286
|
+
diagOnce(diag.level, diag.code, diag.message, diag.loc);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
20
289
|
|
|
21
290
|
/**
|
|
22
291
|
* Node added support for import.meta.main.
|
|
@@ -46,22 +315,35 @@ const format = async (src, ast, opts) => {
|
|
|
46
315
|
// eslint-disable-next-line no-console -- used for opt-in diagnostics
|
|
47
316
|
console.error(diag.message);
|
|
48
317
|
};
|
|
49
|
-
const
|
|
50
|
-
const key = `${codeId}:${loc?.start ?? ''}`;
|
|
318
|
+
const diagOnce = (level, codeId, message, loc) => {
|
|
319
|
+
const key = `${level}:${codeId}:${loc?.start ?? ''}`;
|
|
51
320
|
if (warned.has(key)) return;
|
|
52
321
|
warned.add(key);
|
|
53
322
|
emitDiagnostic({
|
|
54
|
-
level
|
|
323
|
+
level,
|
|
55
324
|
code: codeId,
|
|
56
325
|
message,
|
|
57
326
|
filePath: opts.filePath,
|
|
58
327
|
loc
|
|
59
328
|
});
|
|
60
329
|
};
|
|
330
|
+
const warnOnce = (codeId, message, loc) => diagOnce('warning', codeId, message, loc);
|
|
61
331
|
const transformMode = opts.transformSyntax;
|
|
62
332
|
const fullTransform = transformMode === true;
|
|
63
333
|
const moduleIdentifiers = await collectModuleIdentifiers(ast.program);
|
|
64
334
|
const shadowedBindings = new Set([...moduleIdentifiers.entries()].filter(([, meta]) => meta.declare.length > 0).map(([name]) => name));
|
|
335
|
+
const hazardMode = opts.detectDualPackageHazard ?? 'warn';
|
|
336
|
+
if (hazardMode !== 'off') {
|
|
337
|
+
const hazardLevel = hazardMode === 'error' ? 'error' : 'warning';
|
|
338
|
+
await detectDualPackageHazards({
|
|
339
|
+
program: ast.program,
|
|
340
|
+
shadowedBindings,
|
|
341
|
+
hazardLevel,
|
|
342
|
+
filePath: opts.filePath,
|
|
343
|
+
cwd: opts.cwd,
|
|
344
|
+
diagOnce
|
|
345
|
+
});
|
|
346
|
+
}
|
|
65
347
|
if (opts.target === 'module' && fullTransform) {
|
|
66
348
|
if (shadowedBindings.has('module') || shadowedBindings.has('exports')) {
|
|
67
349
|
throw new Error('Cannot transform to ESM: module or exports is shadowed in module scope.');
|
|
@@ -246,4 +528,4 @@ const format = async (src, ast, opts) => {
|
|
|
246
528
|
}
|
|
247
529
|
return code.toString();
|
|
248
530
|
};
|
|
249
|
-
export { format };
|
|
531
|
+
export { format, collectDualPackageUsage, dualPackageHazardDiagnostics };
|
package/dist/module.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import type { ModuleOptions } from './types.js';
|
|
1
|
+
import type { ModuleOptions, Diagnostic } from './types.js';
|
|
2
|
+
declare const collectProjectDualPackageHazards: (files: string[], opts: ModuleOptions) => Promise<Map<string, Diagnostic[]>>;
|
|
2
3
|
declare const transform: (filename: string, options?: ModuleOptions) => Promise<string>;
|
|
3
|
-
export { transform };
|
|
4
|
+
export { transform, collectProjectDualPackageHazards };
|
package/dist/module.js
CHANGED
|
@@ -2,13 +2,14 @@ import { resolve } from 'node:path';
|
|
|
2
2
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { specifier } from './specifier.js';
|
|
4
4
|
import { parse } from './parse.js';
|
|
5
|
-
import { format } from './format.js';
|
|
5
|
+
import { format, collectDualPackageUsage, dualPackageHazardDiagnostics } from './format.js';
|
|
6
6
|
import { getLangFromExt } from './utils/lang.js';
|
|
7
7
|
import { builtinModules } from 'node:module';
|
|
8
8
|
import { resolve as pathResolve, dirname as pathDirname, extname, join } from 'node:path';
|
|
9
9
|
import { readFile as fsReadFile, stat } from 'node:fs/promises';
|
|
10
10
|
import { parse as parseModule } from './parse.js';
|
|
11
11
|
import { walk } from './walk.js';
|
|
12
|
+
import { collectModuleIdentifiers } from './utils/identifiers.js';
|
|
12
13
|
const collapseSpecifier = value => value.replace(/['"`+)\s]|new String\(/g, '');
|
|
13
14
|
const builtinSpecifiers = new Set(builtinModules.map(mod => mod.startsWith('node:') ? mod.slice(5) : mod).flatMap(mod => {
|
|
14
15
|
const parts = mod.split('/');
|
|
@@ -141,6 +142,46 @@ const detectCircularRequireGraph = async (entryFile, mode, dirIndex) => {
|
|
|
141
142
|
};
|
|
142
143
|
await dfs(entryFile, []);
|
|
143
144
|
};
|
|
145
|
+
const mergeUsageMaps = (target, source) => {
|
|
146
|
+
for (const [pkg, usage] of source) {
|
|
147
|
+
const existing = target.get(pkg) ?? {
|
|
148
|
+
imports: [],
|
|
149
|
+
requires: []
|
|
150
|
+
};
|
|
151
|
+
existing.imports.push(...usage.imports);
|
|
152
|
+
existing.requires.push(...usage.requires);
|
|
153
|
+
target.set(pkg, existing);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const collectProjectDualPackageHazards = async (files, opts) => {
|
|
157
|
+
const hazardMode = opts.detectDualPackageHazard ?? 'warn';
|
|
158
|
+
if (hazardMode === 'off') return new Map();
|
|
159
|
+
const hazardLevel = hazardMode === 'error' ? 'error' : 'warning';
|
|
160
|
+
const usages = new Map();
|
|
161
|
+
const manifestCache = new Map();
|
|
162
|
+
for (const file of files) {
|
|
163
|
+
const code = await readFile(file, 'utf8');
|
|
164
|
+
const ast = parseModule(file, code);
|
|
165
|
+
const moduleIdentifiers = await collectModuleIdentifiers(ast.program);
|
|
166
|
+
const shadowedBindings = new Set([...moduleIdentifiers.entries()].filter(([, meta]) => meta.declare.length > 0).map(([name]) => name));
|
|
167
|
+
const perFileUsage = await collectDualPackageUsage(ast.program, shadowedBindings, file);
|
|
168
|
+
mergeUsageMaps(usages, perFileUsage);
|
|
169
|
+
}
|
|
170
|
+
const diags = await dualPackageHazardDiagnostics({
|
|
171
|
+
usages,
|
|
172
|
+
hazardLevel,
|
|
173
|
+
cwd: opts.cwd,
|
|
174
|
+
manifestCache
|
|
175
|
+
});
|
|
176
|
+
const byFile = new Map();
|
|
177
|
+
for (const diag of diags) {
|
|
178
|
+
const key = diag.filePath ?? files[0];
|
|
179
|
+
const existing = byFile.get(key) ?? [];
|
|
180
|
+
existing.push(diag);
|
|
181
|
+
byFile.set(key, existing);
|
|
182
|
+
}
|
|
183
|
+
return byFile;
|
|
184
|
+
};
|
|
144
185
|
const defaultOptions = {
|
|
145
186
|
target: 'commonjs',
|
|
146
187
|
sourceType: 'auto',
|
|
@@ -154,6 +195,8 @@ const defaultOptions = {
|
|
|
154
195
|
importMetaMain: 'shim',
|
|
155
196
|
requireMainStrategy: 'import-meta-main',
|
|
156
197
|
detectCircularRequires: 'off',
|
|
198
|
+
detectDualPackageHazard: 'warn',
|
|
199
|
+
dualPackageHazardScope: 'file',
|
|
157
200
|
requireSource: 'builtin',
|
|
158
201
|
nestedRequireStrategy: 'create-require',
|
|
159
202
|
cjsDefault: 'auto',
|
|
@@ -197,4 +240,4 @@ const transform = async (filename, options = defaultOptions) => {
|
|
|
197
240
|
}
|
|
198
241
|
return source;
|
|
199
242
|
};
|
|
200
|
-
export { transform };
|
|
243
|
+
export { transform, collectProjectDualPackageHazards };
|
package/dist/types.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ export type ModuleOptions = {
|
|
|
32
32
|
requireMainStrategy?: 'import-meta-main' | 'realpath';
|
|
33
33
|
/** Detect circular require usage level. */
|
|
34
34
|
detectCircularRequires?: 'off' | 'warn' | 'error';
|
|
35
|
+
/** Detect divergent import/require usage of the same dual package (default warn). */
|
|
36
|
+
detectDualPackageHazard?: 'off' | 'warn' | 'error';
|
|
37
|
+
/** Scope for dual package hazard detection. */
|
|
38
|
+
dualPackageHazardScope?: 'file' | 'project';
|
|
35
39
|
/** Source used to provide require in ESM output. */
|
|
36
40
|
requireSource?: 'builtin' | 'create-require';
|
|
37
41
|
/** How to rewrite nested or non-hoistable require calls. */
|