@knighted/module 1.5.0-rc.0 → 1.5.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 CHANGED
@@ -4,14 +4,14 @@
4
4
  [![codecov](https://codecov.io/gh/knightedcodemonkey/module/graph/badge.svg?token=AjayQQxghy)](https://codecov.io/gh/knightedcodemonkey/module)
5
5
  [![NPM version](https://img.shields.io/npm/v/@knighted/module.svg)](https://www.npmjs.com/package/@knighted/module)
6
6
 
7
- Node.js utility for transforming a JavaScript or TypeScript file from an ES module to CommonJS, or vice versa.
7
+ Node.js utility for transforming a JavaScript or TypeScript file from CommonJS to an ES module, or vice versa.
8
8
 
9
- - ES module ➡️ CommonJS
10
9
  - CommonJS ➡️ ES module
10
+ - ES module ➡️ CommonJS
11
11
 
12
12
  Highlights
13
13
 
14
- - ESM ➡️ CJS and CJS ➡️ ESM with one function call.
14
+ - CJS ➡️ ESM and ESM ➡️ CJS with one function call.
15
15
  - Defaults to safe CommonJS output: strict live bindings, import.meta shims, and specifier preservation.
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.
@@ -25,7 +25,7 @@ By default `@knighted/module` transforms the one-to-one [differences between ES
25
25
 
26
26
  ## Requirements
27
27
 
28
- - Node 22 or 24 (tested on 22.21.1 and 24.11.1)
28
+ - Node 22.21.1+ or 24+
29
29
 
30
30
  ## Install
31
31
 
@@ -134,6 +134,7 @@ type ModuleOptions = {
134
134
  detectCircularRequires?: 'off' | 'warn' | 'error'
135
135
  detectDualPackageHazard?: 'off' | 'warn' | 'error'
136
136
  dualPackageHazardScope?: 'file' | 'project'
137
+ dualPackageHazardAllowlist?: string[]
137
138
  requireSource?: 'builtin' | 'create-require'
138
139
  importMetaPrelude?: 'off' | 'auto' | 'on'
139
140
  cjsDefault?: 'module-exports' | 'auto' | 'none'
@@ -162,6 +163,7 @@ type ModuleOptions = {
162
163
  - `detectCircularRequires` (`off`): optionally detect relative static require cycles across `.js`/`.mjs`/`.cjs`/`.ts`/`.mts`/`.cts` (realpath-normalized) and warn/throw.
163
164
  - `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.
164
165
  - `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.
166
+ - `dualPackageHazardAllowlist` (`[]`): suppress dual-package hazard diagnostics for the listed packages. Accepts an array in the API; entries are trimmed and empty values dropped. The CLI flag `--dual-package-hazard-allowlist pkg1,pkg2` parses a comma- or space-separated string into this array. Applies to both `file` and `project` scopes.
165
167
  - `topLevelAwait` (`error`): throw, wrap, or preserve when TLA appears in CommonJS output. `wrap` runs the file body inside an async IIFE (exports may resolve after the initial tick); `preserve` leaves `await` at top level, which Node will reject for CJS.
166
168
  - `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.
167
169
  - `requireSource` (`builtin`): whether `require` comes from Node or `createRequire`.
package/dist/cjs/cli.cjs CHANGED
@@ -32,6 +32,7 @@ const defaultOptions = {
32
32
  detectCircularRequires: 'off',
33
33
  detectDualPackageHazard: 'warn',
34
34
  dualPackageHazardScope: 'file',
35
+ dualPackageHazardAllowlist: [],
35
36
  requireSource: 'builtin',
36
37
  nestedRequireStrategy: 'create-require',
37
38
  cjsDefault: 'auto',
@@ -164,6 +165,11 @@ const optionsTable = [{
164
165
  short: undefined,
165
166
  type: 'string',
166
167
  desc: 'Scope for dual package hazard detection (file|project)'
168
+ }, {
169
+ long: 'dual-package-hazard-allowlist',
170
+ short: undefined,
171
+ type: 'string',
172
+ desc: 'Comma-separated packages to ignore for dual package hazard checks'
167
173
  }, {
168
174
  long: 'top-level-await',
169
175
  short: 'a',
@@ -287,12 +293,17 @@ const parseAppendDirectoryIndex = value => {
287
293
  if (value === 'false') return false;
288
294
  return value;
289
295
  };
296
+ const parseAllowlist = value => {
297
+ const values = value === undefined ? [] : Array.isArray(value) ? value : [value];
298
+ return values.flatMap(entry => String(entry).split(',')).map(item => item.trim()).filter(Boolean);
299
+ };
290
300
  const toModuleOptions = values => {
291
301
  const target = parseEnum(values.target, ['module', 'commonjs']) ?? defaultOptions.target;
292
302
  const transformSyntax = parseTransformSyntax(values['transform-syntax']);
293
303
  const rewriteTemplateLiterals = parseEnum(values['rewrite-template-literals'], ['allow', 'static-only']) ?? defaultOptions.rewriteTemplateLiterals;
294
304
  const appendJsExtension = parseEnum(values['append-js-extension'], ['off', 'relative-only', 'all']);
295
305
  const appendDirectoryIndex = parseAppendDirectoryIndex(values['append-directory-index']);
306
+ const dualPackageHazardAllowlist = parseAllowlist(values['dual-package-hazard-allowlist']);
296
307
  const opts = {
297
308
  ...defaultOptions,
298
309
  target,
@@ -304,6 +315,7 @@ const toModuleOptions = values => {
304
315
  detectCircularRequires: parseEnum(values['detect-circular-requires'], ['off', 'warn', 'error']) ?? defaultOptions.detectCircularRequires,
305
316
  detectDualPackageHazard: parseEnum(values['detect-dual-package-hazard'], ['off', 'warn', 'error']) ?? defaultOptions.detectDualPackageHazard,
306
317
  dualPackageHazardScope: parseEnum(values['dual-package-hazard-scope'], ['file', 'project']) ?? defaultOptions.dualPackageHazardScope,
318
+ dualPackageHazardAllowlist,
307
319
  topLevelAwait: parseEnum(values['top-level-await'], ['error', 'wrap', 'preserve']) ?? defaultOptions.topLevelAwait,
308
320
  cjsDefault: parseEnum(values['cjs-default'], ['module-exports', 'auto', 'none']) ?? defaultOptions.cjsDefault,
309
321
  idiomaticExports: parseEnum(values['idiomatic-exports'], ['off', 'safe', 'aggressive']) ?? defaultOptions.idiomaticExports,
@@ -149,6 +149,9 @@ const describeDualPackage = pkgJson => {
149
149
  requireTarget
150
150
  };
151
151
  };
152
+ const normalizeAllowlist = allowlist => {
153
+ return new Set([...(allowlist ?? [])].map(item => item.trim()).filter(item => item.length > 0));
154
+ };
152
155
  const recordUsage = (usages, pkg, kind, spec, subpath, loc, filePath) => {
153
156
  const existing = usages.get(pkg) ?? {
154
157
  imports: [],
@@ -218,8 +221,10 @@ const dualPackageHazardDiagnostics = async params => {
218
221
  cwd
219
222
  } = params;
220
223
  const manifestCache = params.manifestCache ?? new Map();
224
+ const allowlist = normalizeAllowlist(params.hazardAllowlist);
221
225
  const diags = [];
222
226
  for (const [pkg, usage] of usages) {
227
+ if (allowlist.has(pkg)) continue;
223
228
  const hasImport = usage.imports.length > 0;
224
229
  const hasRequire = usage.requires.length > 0;
225
230
  const combined = [...usage.imports, ...usage.requires];
@@ -286,7 +291,8 @@ const detectDualPackageHazards = async params => {
286
291
  hazardLevel,
287
292
  filePath,
288
293
  cwd,
289
- manifestCache
294
+ manifestCache,
295
+ hazardAllowlist: params.hazardAllowlist
290
296
  });
291
297
  for (const diag of diags) {
292
298
  diagOnce(diag.level, diag.code, diag.message, diag.loc);
@@ -341,7 +347,8 @@ async function format(src, ast, opts) {
341
347
  hazardLevel,
342
348
  filePath: opts.filePath,
343
349
  cwd: opts.cwd,
344
- diagOnce
350
+ diagOnce,
351
+ hazardAllowlist: opts.dualPackageHazardAllowlist
345
352
  });
346
353
  }
347
354
  if (opts.target === 'module' && fullTransform) {
@@ -22,6 +22,7 @@ declare const dualPackageHazardDiagnostics: (params: {
22
22
  filePath?: string;
23
23
  cwd?: string;
24
24
  manifestCache?: Map<string, any | null>;
25
+ hazardAllowlist?: Iterable<string>;
25
26
  }) => Promise<Diagnostic[]>;
26
27
  declare function format(src: string, ast: ParseResult, opts: FormatterOptions & {
27
28
  sourceMap: true;
@@ -171,8 +171,10 @@ const collectProjectDualPackageHazards = async (files, opts) => {
171
171
  const diags = await (0, _format.dualPackageHazardDiagnostics)({
172
172
  usages,
173
173
  hazardLevel,
174
+ filePath: opts.filePath,
174
175
  cwd: opts.cwd,
175
- manifestCache
176
+ manifestCache,
177
+ hazardAllowlist: opts.dualPackageHazardAllowlist
176
178
  });
177
179
  const byFile = new Map();
178
180
  for (const diag of diags) {
@@ -200,6 +202,7 @@ const createDefaultOptions = () => ({
200
202
  detectCircularRequires: 'off',
201
203
  detectDualPackageHazard: 'warn',
202
204
  dualPackageHazardScope: 'file',
205
+ dualPackageHazardAllowlist: [],
203
206
  requireSource: 'builtin',
204
207
  nestedRequireStrategy: 'create-require',
205
208
  cjsDefault: 'auto',
@@ -40,6 +40,8 @@ export type ModuleOptions = {
40
40
  detectDualPackageHazard?: 'off' | 'warn' | 'error';
41
41
  /** Scope for dual package hazard detection. */
42
42
  dualPackageHazardScope?: 'file' | 'project';
43
+ /** Packages to ignore for dual package hazard diagnostics. */
44
+ dualPackageHazardAllowlist?: string[];
43
45
  /** Source used to provide require in ESM output. */
44
46
  requireSource?: 'builtin' | 'create-require';
45
47
  /** How to rewrite nested or non-hoistable require calls. */
package/dist/cli.js CHANGED
@@ -26,6 +26,7 @@ const defaultOptions = {
26
26
  detectCircularRequires: 'off',
27
27
  detectDualPackageHazard: 'warn',
28
28
  dualPackageHazardScope: 'file',
29
+ dualPackageHazardAllowlist: [],
29
30
  requireSource: 'builtin',
30
31
  nestedRequireStrategy: 'create-require',
31
32
  cjsDefault: 'auto',
@@ -158,6 +159,11 @@ const optionsTable = [{
158
159
  short: undefined,
159
160
  type: 'string',
160
161
  desc: 'Scope for dual package hazard detection (file|project)'
162
+ }, {
163
+ long: 'dual-package-hazard-allowlist',
164
+ short: undefined,
165
+ type: 'string',
166
+ desc: 'Comma-separated packages to ignore for dual package hazard checks'
161
167
  }, {
162
168
  long: 'top-level-await',
163
169
  short: 'a',
@@ -281,12 +287,17 @@ const parseAppendDirectoryIndex = value => {
281
287
  if (value === 'false') return false;
282
288
  return value;
283
289
  };
290
+ const parseAllowlist = value => {
291
+ const values = value === undefined ? [] : Array.isArray(value) ? value : [value];
292
+ return values.flatMap(entry => String(entry).split(',')).map(item => item.trim()).filter(Boolean);
293
+ };
284
294
  const toModuleOptions = values => {
285
295
  const target = parseEnum(values.target, ['module', 'commonjs']) ?? defaultOptions.target;
286
296
  const transformSyntax = parseTransformSyntax(values['transform-syntax']);
287
297
  const rewriteTemplateLiterals = parseEnum(values['rewrite-template-literals'], ['allow', 'static-only']) ?? defaultOptions.rewriteTemplateLiterals;
288
298
  const appendJsExtension = parseEnum(values['append-js-extension'], ['off', 'relative-only', 'all']);
289
299
  const appendDirectoryIndex = parseAppendDirectoryIndex(values['append-directory-index']);
300
+ const dualPackageHazardAllowlist = parseAllowlist(values['dual-package-hazard-allowlist']);
290
301
  const opts = {
291
302
  ...defaultOptions,
292
303
  target,
@@ -298,6 +309,7 @@ const toModuleOptions = values => {
298
309
  detectCircularRequires: parseEnum(values['detect-circular-requires'], ['off', 'warn', 'error']) ?? defaultOptions.detectCircularRequires,
299
310
  detectDualPackageHazard: parseEnum(values['detect-dual-package-hazard'], ['off', 'warn', 'error']) ?? defaultOptions.detectDualPackageHazard,
300
311
  dualPackageHazardScope: parseEnum(values['dual-package-hazard-scope'], ['file', 'project']) ?? defaultOptions.dualPackageHazardScope,
312
+ dualPackageHazardAllowlist,
301
313
  topLevelAwait: parseEnum(values['top-level-await'], ['error', 'wrap', 'preserve']) ?? defaultOptions.topLevelAwait,
302
314
  cjsDefault: parseEnum(values['cjs-default'], ['module-exports', 'auto', 'none']) ?? defaultOptions.cjsDefault,
303
315
  idiomaticExports: parseEnum(values['idiomatic-exports'], ['off', 'safe', 'aggressive']) ?? defaultOptions.idiomaticExports,
package/dist/format.d.ts CHANGED
@@ -22,6 +22,7 @@ declare const dualPackageHazardDiagnostics: (params: {
22
22
  filePath?: string;
23
23
  cwd?: string;
24
24
  manifestCache?: Map<string, any | null>;
25
+ hazardAllowlist?: Iterable<string>;
25
26
  }) => Promise<Diagnostic[]>;
26
27
  declare function format(src: string, ast: ParseResult, opts: FormatterOptions & {
27
28
  sourceMap: true;
package/dist/format.js CHANGED
@@ -141,6 +141,9 @@ const describeDualPackage = pkgJson => {
141
141
  requireTarget
142
142
  };
143
143
  };
144
+ const normalizeAllowlist = allowlist => {
145
+ return new Set([...(allowlist ?? [])].map(item => item.trim()).filter(item => item.length > 0));
146
+ };
144
147
  const recordUsage = (usages, pkg, kind, spec, subpath, loc, filePath) => {
145
148
  const existing = usages.get(pkg) ?? {
146
149
  imports: [],
@@ -209,8 +212,10 @@ const dualPackageHazardDiagnostics = async params => {
209
212
  cwd
210
213
  } = params;
211
214
  const manifestCache = params.manifestCache ?? new Map();
215
+ const allowlist = normalizeAllowlist(params.hazardAllowlist);
212
216
  const diags = [];
213
217
  for (const [pkg, usage] of usages) {
218
+ if (allowlist.has(pkg)) continue;
214
219
  const hasImport = usage.imports.length > 0;
215
220
  const hasRequire = usage.requires.length > 0;
216
221
  const combined = [...usage.imports, ...usage.requires];
@@ -276,7 +281,8 @@ const detectDualPackageHazards = async params => {
276
281
  hazardLevel,
277
282
  filePath,
278
283
  cwd,
279
- manifestCache
284
+ manifestCache,
285
+ hazardAllowlist: params.hazardAllowlist
280
286
  });
281
287
  for (const diag of diags) {
282
288
  diagOnce(diag.level, diag.code, diag.message, diag.loc);
@@ -331,7 +337,8 @@ async function format(src, ast, opts) {
331
337
  hazardLevel,
332
338
  filePath: opts.filePath,
333
339
  cwd: opts.cwd,
334
- diagOnce
340
+ diagOnce,
341
+ hazardAllowlist: opts.dualPackageHazardAllowlist
335
342
  });
336
343
  }
337
344
  if (opts.target === 'module' && fullTransform) {
package/dist/module.js CHANGED
@@ -167,8 +167,10 @@ const collectProjectDualPackageHazards = async (files, opts) => {
167
167
  const diags = await dualPackageHazardDiagnostics({
168
168
  usages,
169
169
  hazardLevel,
170
+ filePath: opts.filePath,
170
171
  cwd: opts.cwd,
171
- manifestCache
172
+ manifestCache,
173
+ hazardAllowlist: opts.dualPackageHazardAllowlist
172
174
  });
173
175
  const byFile = new Map();
174
176
  for (const diag of diags) {
@@ -195,6 +197,7 @@ const createDefaultOptions = () => ({
195
197
  detectCircularRequires: 'off',
196
198
  detectDualPackageHazard: 'warn',
197
199
  dualPackageHazardScope: 'file',
200
+ dualPackageHazardAllowlist: [],
198
201
  requireSource: 'builtin',
199
202
  nestedRequireStrategy: 'create-require',
200
203
  cjsDefault: 'auto',
package/dist/types.d.ts CHANGED
@@ -40,6 +40,8 @@ export type ModuleOptions = {
40
40
  detectDualPackageHazard?: 'off' | 'warn' | 'error';
41
41
  /** Scope for dual package hazard detection. */
42
42
  dualPackageHazardScope?: 'file' | 'project';
43
+ /** Packages to ignore for dual package hazard diagnostics. */
44
+ dualPackageHazardAllowlist?: string[];
43
45
  /** Source used to provide require in ESM output. */
44
46
  requireSource?: 'builtin' | 'create-require';
45
47
  /** How to rewrite nested or non-hoistable require calls. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/module",
3
- "version": "1.5.0-rc.0",
3
+ "version": "1.5.1",
4
4
  "description": "Bidirectional transform for ES modules and CommonJS.",
5
5
  "type": "module",
6
6
  "main": "dist/module.js",
@@ -38,7 +38,7 @@
38
38
  "build:types": "tsc --emitDeclarationOnly",
39
39
  "build:dual": "babel-dual-package src --extensions .ts",
40
40
  "build": "npm run build:types && npm run build:dual",
41
- "cycles": "madge --circular src --extensions ts,js --ts-config tsconfig.json",
41
+ "cycles": "oxlint --import-plugin --tsconfig tsconfig.json -A all -D import/no-cycle src test",
42
42
  "prepack": "npm run build"
43
43
  },
44
44
  "keywords": [
@@ -64,29 +64,22 @@
64
64
  "url": "https://github.com/knightedcodemonkey/module/issues"
65
65
  },
66
66
  "devDependencies": {
67
- "@knighted/dump": "^1.0.3",
68
67
  "@types/node": "^22.19.3",
69
68
  "babel-dual-package": "^1.2.3",
70
- "c8": "^10.1.3",
69
+ "c8": "^11.0.0",
71
70
  "husky": "^9.1.7",
72
71
  "lint-staged": "^16.2.7",
73
- "madge": "^8.0.0",
74
- "oxlint": "^1.35.0",
72
+ "oxlint": "^1.51.0",
75
73
  "prettier": "^3.7.4",
76
74
  "tsx": "^4.21.0",
77
75
  "typescript": "^5.9.3"
78
76
  },
79
77
  "dependencies": {
80
- "glob": "^13.0.0",
78
+ "glob": "^13.0.6",
81
79
  "magic-string": "^0.30.21",
82
- "oxc-parser": "^0.105.0",
80
+ "oxc-parser": "^0.116.0",
83
81
  "periscopic": "^4.0.2"
84
82
  },
85
- "overrides": {
86
- "module-lookup-amd": {
87
- "glob": "^9.0.0"
88
- }
89
- },
90
83
  "prettier": {
91
84
  "arrowParens": "avoid",
92
85
  "printWidth": 90,