@knighted/module 1.0.0-rc.3 → 1.0.0-rc.5

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
@@ -151,6 +151,36 @@ See [docs/esm-to-cjs.md](docs/esm-to-cjs.md) for deeper notes on live bindings,
151
151
  > [!NOTE]
152
152
  > 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`.
153
153
 
154
+ ### Diagnostics callback example
155
+
156
+ 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):
157
+
158
+ ```ts
159
+ import { transform } from '@knighted/module'
160
+
161
+ const diagnostics: any[] = []
162
+
163
+ await transform('./file.cjs', {
164
+ target: 'module',
165
+ diagnostics: diag => diagnostics.push(diag),
166
+ })
167
+
168
+ console.log(diagnostics)
169
+ // [
170
+ // {
171
+ // level: 'warning',
172
+ // code: 'cjs-mixed-exports',
173
+ // message: 'Both module.exports and exports are assigned in this module; CommonJS shadowing may not match synthesized ESM exports.',
174
+ // filePath: './file.cjs',
175
+ // loc: { start: 12, end: 48 }
176
+ // },
177
+ // ...
178
+ // ]
179
+ ```
180
+
181
+ > [!WARNING]
182
+ > When raising CommonJS to ESM, synthesized named exports rely on literal keys and `const` literal aliases (e.g., `const key = 'foo'; exports[key] = value`). `var`/`let` bindings used as export keys are not tracked, so prefer direct property names or `const` literals when exporting.
183
+
154
184
  ## Pre-`tsc` transforms for TypeScript diagnostics
155
185
 
156
186
  TypeScript reports asymmetric module-global errors (e.g., `import.meta` in CJS, `__dirname` in ESM) as tracked in [microsoft/TypeScript#58658](https://github.com/microsoft/TypeScript/issues/58658). You can mitigate this by running `@knighted/module` **before** `tsc` so the checker sees already-rewritten sources.
@@ -370,6 +370,33 @@ const format = async (src, ast, opts) => {
370
370
  hasDefaultExportBeenReassigned: false,
371
371
  hasDefaultExportBeenAssigned: false
372
372
  };
373
+ const warned = new Set();
374
+ const emitDiagnostic = diag => {
375
+ if (opts.diagnostics) {
376
+ opts.diagnostics(diag);
377
+ return;
378
+ }
379
+ if (diag.level === 'warning') {
380
+ // eslint-disable-next-line no-console -- used for opt-in diagnostics
381
+ console.warn(diag.message);
382
+ return;
383
+ }
384
+
385
+ // eslint-disable-next-line no-console -- used for opt-in diagnostics
386
+ console.error(diag.message);
387
+ };
388
+ const warnOnce = (codeId, message, loc) => {
389
+ const key = `${codeId}:${loc?.start ?? ''}`;
390
+ if (warned.has(key)) return;
391
+ warned.add(key);
392
+ emitDiagnostic({
393
+ level: 'warning',
394
+ code: codeId,
395
+ message,
396
+ filePath: opts.filePath,
397
+ loc
398
+ });
399
+ };
373
400
  const moduleIdentifiers = await (0, _identifiers.collectModuleIdentifiers)(ast.program);
374
401
  const shadowedBindings = new Set([...moduleIdentifiers.entries()].filter(([, meta]) => meta.declare.length > 0).map(([name]) => name));
375
402
  if (opts.target === 'module' && opts.transformSyntax) {
@@ -378,6 +405,18 @@ const format = async (src, ast, opts) => {
378
405
  }
379
406
  }
380
407
  const exportTable = opts.target === 'module' ? await (0, _exports.collectCjsExports)(ast.program) : null;
408
+ if (opts.target === 'module' && exportTable) {
409
+ const hasExportsVia = [...exportTable.values()].some(entry => entry.via.has('exports'));
410
+ const hasModuleExportsVia = [...exportTable.values()].some(entry => entry.via.has('module.exports'));
411
+ if (hasExportsVia && hasModuleExportsVia) {
412
+ const firstExports = [...exportTable.values()].find(entry => entry.via.has('exports'))?.writes[0];
413
+ const firstModule = [...exportTable.values()].find(entry => entry.via.has('module.exports'))?.writes[0];
414
+ warnOnce('cjs-mixed-exports', 'Both module.exports and exports are assigned in this module; CommonJS shadowing may not match synthesized ESM exports.', {
415
+ start: firstModule?.start ?? 0,
416
+ end: firstExports?.end ?? 0
417
+ });
418
+ }
419
+ }
381
420
  const shouldCheckTopLevelAwait = opts.target === 'commonjs' && opts.transformSyntax;
382
421
  const containsTopLevelAwait = shouldCheckTopLevelAwait ? hasTopLevelAwait(ast.program) : false;
383
422
  const requireMainStrategy = opts.requireMainStrategy ?? 'import-meta-main';
@@ -412,6 +451,12 @@ const format = async (src, ast, opts) => {
412
451
  await (0, _walk.ancestorWalk)(ast.program, {
413
452
  async enter(node, ancestors) {
414
453
  const parent = ancestors[ancestors.length - 2] ?? null;
454
+ if (shouldRaiseEsm && node.type === 'ReturnStatement' && parent?.type === 'Program') {
455
+ warnOnce('top-level-return', 'Top-level return is not allowed in ESM; the transformed module will fail to parse.', {
456
+ start: node.start,
457
+ end: node.end
458
+ });
459
+ }
415
460
  if (shouldRaiseEsm && node.type === 'BinaryExpression') {
416
461
  const op = node.operator;
417
462
  const isEquality = op === '===' || op === '==' || op === '!==' || op === '!=';
@@ -525,7 +570,10 @@ const format = async (src, ast, opts) => {
525
570
  onRequireResolve: () => {
526
571
  if (shouldRaiseEsm) needsRequireResolveHelper = true;
527
572
  },
528
- requireResolveName: '__requireResolve'
573
+ requireResolveName: '__requireResolve',
574
+ onDiagnostic: (codeId, message, loc) => {
575
+ if (shouldRaiseEsm) warnOnce(codeId, message, loc);
576
+ }
529
577
  });
530
578
  }
531
579
  if (shouldRaiseEsm && node.type === 'ThisExpression') {
@@ -579,21 +627,52 @@ const format = async (src, ast, opts) => {
579
627
  const isValidExportName = name => /^[$A-Z_a-z][$\w]*$/.test(name);
580
628
  const asExportName = name => isValidExportName(name) ? name : JSON.stringify(name);
581
629
  const accessProp = name => isValidExportName(name) ? `${_exports.exportsRename}.${name}` : `${_exports.exportsRename}[${JSON.stringify(name)}]`;
630
+ const exportValueFor = name => {
631
+ if (name === '__dirname') return 'import.meta.dirname';
632
+ if (name === '__filename') return 'import.meta.filename';
633
+ return name;
634
+ };
582
635
  const tempNameFor = name => {
583
636
  const sanitized = name.replace(/[^$\w]/g, '_') || 'value';
584
637
  const safe = /^[0-9]/.test(sanitized) ? `_${sanitized}` : sanitized;
585
638
  return `__export_${safe}`;
586
639
  };
640
+ for (const [key, entry] of exportTable) {
641
+ if (entry.reassignments.length) {
642
+ const loc = entry.reassignments[0];
643
+ warnOnce(`cjs-export-reassignment:${key}`, `Export '${key}' is reassigned after export; ESM live bindings may change consumer behavior.`, {
644
+ start: loc.start,
645
+ end: loc.end
646
+ });
647
+ }
648
+ }
587
649
  const lines = [];
588
650
  const defaultEntry = exportTable.get('default');
589
651
  if (defaultEntry) {
590
652
  const def = defaultEntry.fromIdentifier ?? _exports.exportsRename;
591
- lines.push(`export default ${def};`);
653
+ const defExpr = exportValueFor(def);
654
+ if (defExpr !== def) {
655
+ const temp = tempNameFor(def);
656
+ lines.push(`const ${temp} = ${defExpr};`);
657
+ lines.push(`export default ${temp};`);
658
+ } else {
659
+ lines.push(`export default ${defExpr};`);
660
+ }
592
661
  }
593
662
  for (const [key, entry] of exportTable) {
594
663
  if (key === 'default') continue;
664
+ if (!isValidExportName(key)) {
665
+ warnOnce(`cjs-string-export:${key}`, `Synthesized string-literal export '${key}'. Some tooling may require bracket access to use it.`);
666
+ }
595
667
  if (entry.fromIdentifier) {
596
- lines.push(`export { ${entry.fromIdentifier} as ${asExportName(key)} };`);
668
+ const resolved = exportValueFor(entry.fromIdentifier);
669
+ if (resolved !== entry.fromIdentifier) {
670
+ const temp = tempNameFor(entry.fromIdentifier);
671
+ lines.push(`const ${temp} = ${resolved};`);
672
+ lines.push(`export { ${temp} as ${asExportName(key)} };`);
673
+ } else {
674
+ lines.push(`export { ${resolved} as ${asExportName(key)} };`);
675
+ }
597
676
  } else {
598
677
  const temp = tempNameFor(key);
599
678
  lines.push(`const ${temp} = ${accessProp(key)};`);
@@ -40,14 +40,31 @@ const memberExpression = (node, parent, src, options, shadowed, extras) => {
40
40
  * Can of worms here. ¯\_(ツ)_/¯
41
41
  * @see https://github.com/nodejs/help/issues/2806
42
42
  */
43
+ extras?.onDiagnostic?.('legacy-require-cache', 'Access to require.cache is not supported when raising to ESM; behavior may differ.', {
44
+ start,
45
+ end
46
+ });
43
47
  src.update(start, end, '{}');
44
48
  break;
49
+ case 'extensions':
50
+ extras?.onDiagnostic?.('legacy-require-extensions', 'Access to require.extensions is not supported when raising to ESM; use loaders instead.', {
51
+ start,
52
+ end
53
+ });
54
+ break;
45
55
  }
46
56
  }
47
57
  if (node.object.type === 'Identifier' && node.property.type === 'Identifier' && node.object.name === 'module' && node.property.name === 'require') {
48
58
  if (!shadowed?.has('module')) {
49
59
  src.update(node.start, node.end, 'require');
50
60
  }
61
+ return;
62
+ }
63
+ if (node.object.type === 'Identifier' && node.property.type === 'Identifier' && node.object.name === 'module' && (node.property.name === 'parent' || node.property.name === 'children')) {
64
+ extras?.onDiagnostic?.(`legacy-module-${node.property.name}`, `Access to module.${node.property.name} may not behave the same in ESM; consider loaders or explicit wiring instead.`, {
65
+ start: node.start,
66
+ end: node.end
67
+ });
51
68
  }
52
69
  }
53
70
  };
@@ -4,6 +4,10 @@ import type { FormatterOptions } from '../types.cjs';
4
4
  type MemberExpressionExtras = {
5
5
  onRequireResolve?: () => void;
6
6
  requireResolveName?: string;
7
+ onDiagnostic?: (code: string, message: string, loc?: {
8
+ start: number;
9
+ end: number;
10
+ }) => void;
7
11
  };
8
12
  export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>, extras?: MemberExpressionExtras) => void;
9
13
  export {};
@@ -167,7 +167,8 @@ const defaultOptions = {
167
167
  const transform = async (filename, options = defaultOptions) => {
168
168
  const opts = {
169
169
  ...defaultOptions,
170
- ...options
170
+ ...options,
171
+ filePath: filename
171
172
  };
172
173
  const appendMode = options?.appendJsExtension ?? (opts.target === 'module' ? 'relative-only' : 'off');
173
174
  const dirIndex = opts.appendDirectoryIndex === undefined ? 'index.js' : opts.appendDirectoryIndex;
@@ -34,11 +34,25 @@ export type ModuleOptions = {
34
34
  cjsDefault?: 'module-exports' | 'auto' | 'none';
35
35
  /** Handling for top-level await constructs. */
36
36
  topLevelAwait?: 'error' | 'wrap' | 'preserve';
37
+ /** Optional diagnostics sink for warnings/errors emitted during transform. */
38
+ diagnostics?: (diag: Diagnostic) => void;
39
+ /** Optional source file path used for diagnostics context. */
40
+ filePath?: string;
37
41
  /** Output directory or file path when writing. */
38
42
  out?: string;
39
43
  /** Overwrite input files instead of writing to out. */
40
44
  inPlace?: boolean;
41
45
  };
46
+ export type Diagnostic = {
47
+ level: 'warning' | 'error';
48
+ code: string;
49
+ message: string;
50
+ filePath?: string;
51
+ loc?: {
52
+ start: number;
53
+ end: number;
54
+ };
55
+ };
42
56
  export type SpannedNode = Node & Span;
43
57
  export type ExportsMeta = {
44
58
  hasExportsBeenReassigned: boolean;
package/dist/format.js CHANGED
@@ -363,6 +363,33 @@ const format = async (src, ast, opts) => {
363
363
  hasDefaultExportBeenReassigned: false,
364
364
  hasDefaultExportBeenAssigned: false
365
365
  };
366
+ const warned = new Set();
367
+ const emitDiagnostic = diag => {
368
+ if (opts.diagnostics) {
369
+ opts.diagnostics(diag);
370
+ return;
371
+ }
372
+ if (diag.level === 'warning') {
373
+ // eslint-disable-next-line no-console -- used for opt-in diagnostics
374
+ console.warn(diag.message);
375
+ return;
376
+ }
377
+
378
+ // eslint-disable-next-line no-console -- used for opt-in diagnostics
379
+ console.error(diag.message);
380
+ };
381
+ const warnOnce = (codeId, message, loc) => {
382
+ const key = `${codeId}:${loc?.start ?? ''}`;
383
+ if (warned.has(key)) return;
384
+ warned.add(key);
385
+ emitDiagnostic({
386
+ level: 'warning',
387
+ code: codeId,
388
+ message,
389
+ filePath: opts.filePath,
390
+ loc
391
+ });
392
+ };
366
393
  const moduleIdentifiers = await collectModuleIdentifiers(ast.program);
367
394
  const shadowedBindings = new Set([...moduleIdentifiers.entries()].filter(([, meta]) => meta.declare.length > 0).map(([name]) => name));
368
395
  if (opts.target === 'module' && opts.transformSyntax) {
@@ -371,6 +398,18 @@ const format = async (src, ast, opts) => {
371
398
  }
372
399
  }
373
400
  const exportTable = opts.target === 'module' ? await collectCjsExports(ast.program) : null;
401
+ if (opts.target === 'module' && exportTable) {
402
+ const hasExportsVia = [...exportTable.values()].some(entry => entry.via.has('exports'));
403
+ const hasModuleExportsVia = [...exportTable.values()].some(entry => entry.via.has('module.exports'));
404
+ if (hasExportsVia && hasModuleExportsVia) {
405
+ const firstExports = [...exportTable.values()].find(entry => entry.via.has('exports'))?.writes[0];
406
+ const firstModule = [...exportTable.values()].find(entry => entry.via.has('module.exports'))?.writes[0];
407
+ warnOnce('cjs-mixed-exports', 'Both module.exports and exports are assigned in this module; CommonJS shadowing may not match synthesized ESM exports.', {
408
+ start: firstModule?.start ?? 0,
409
+ end: firstExports?.end ?? 0
410
+ });
411
+ }
412
+ }
374
413
  const shouldCheckTopLevelAwait = opts.target === 'commonjs' && opts.transformSyntax;
375
414
  const containsTopLevelAwait = shouldCheckTopLevelAwait ? hasTopLevelAwait(ast.program) : false;
376
415
  const requireMainStrategy = opts.requireMainStrategy ?? 'import-meta-main';
@@ -405,6 +444,12 @@ const format = async (src, ast, opts) => {
405
444
  await ancestorWalk(ast.program, {
406
445
  async enter(node, ancestors) {
407
446
  const parent = ancestors[ancestors.length - 2] ?? null;
447
+ if (shouldRaiseEsm && node.type === 'ReturnStatement' && parent?.type === 'Program') {
448
+ warnOnce('top-level-return', 'Top-level return is not allowed in ESM; the transformed module will fail to parse.', {
449
+ start: node.start,
450
+ end: node.end
451
+ });
452
+ }
408
453
  if (shouldRaiseEsm && node.type === 'BinaryExpression') {
409
454
  const op = node.operator;
410
455
  const isEquality = op === '===' || op === '==' || op === '!==' || op === '!=';
@@ -518,7 +563,10 @@ const format = async (src, ast, opts) => {
518
563
  onRequireResolve: () => {
519
564
  if (shouldRaiseEsm) needsRequireResolveHelper = true;
520
565
  },
521
- requireResolveName: '__requireResolve'
566
+ requireResolveName: '__requireResolve',
567
+ onDiagnostic: (codeId, message, loc) => {
568
+ if (shouldRaiseEsm) warnOnce(codeId, message, loc);
569
+ }
522
570
  });
523
571
  }
524
572
  if (shouldRaiseEsm && node.type === 'ThisExpression') {
@@ -572,21 +620,52 @@ const format = async (src, ast, opts) => {
572
620
  const isValidExportName = name => /^[$A-Z_a-z][$\w]*$/.test(name);
573
621
  const asExportName = name => isValidExportName(name) ? name : JSON.stringify(name);
574
622
  const accessProp = name => isValidExportName(name) ? `${exportsRename}.${name}` : `${exportsRename}[${JSON.stringify(name)}]`;
623
+ const exportValueFor = name => {
624
+ if (name === '__dirname') return 'import.meta.dirname';
625
+ if (name === '__filename') return 'import.meta.filename';
626
+ return name;
627
+ };
575
628
  const tempNameFor = name => {
576
629
  const sanitized = name.replace(/[^$\w]/g, '_') || 'value';
577
630
  const safe = /^[0-9]/.test(sanitized) ? `_${sanitized}` : sanitized;
578
631
  return `__export_${safe}`;
579
632
  };
633
+ for (const [key, entry] of exportTable) {
634
+ if (entry.reassignments.length) {
635
+ const loc = entry.reassignments[0];
636
+ warnOnce(`cjs-export-reassignment:${key}`, `Export '${key}' is reassigned after export; ESM live bindings may change consumer behavior.`, {
637
+ start: loc.start,
638
+ end: loc.end
639
+ });
640
+ }
641
+ }
580
642
  const lines = [];
581
643
  const defaultEntry = exportTable.get('default');
582
644
  if (defaultEntry) {
583
645
  const def = defaultEntry.fromIdentifier ?? exportsRename;
584
- lines.push(`export default ${def};`);
646
+ const defExpr = exportValueFor(def);
647
+ if (defExpr !== def) {
648
+ const temp = tempNameFor(def);
649
+ lines.push(`const ${temp} = ${defExpr};`);
650
+ lines.push(`export default ${temp};`);
651
+ } else {
652
+ lines.push(`export default ${defExpr};`);
653
+ }
585
654
  }
586
655
  for (const [key, entry] of exportTable) {
587
656
  if (key === 'default') continue;
657
+ if (!isValidExportName(key)) {
658
+ warnOnce(`cjs-string-export:${key}`, `Synthesized string-literal export '${key}'. Some tooling may require bracket access to use it.`);
659
+ }
588
660
  if (entry.fromIdentifier) {
589
- lines.push(`export { ${entry.fromIdentifier} as ${asExportName(key)} };`);
661
+ const resolved = exportValueFor(entry.fromIdentifier);
662
+ if (resolved !== entry.fromIdentifier) {
663
+ const temp = tempNameFor(entry.fromIdentifier);
664
+ lines.push(`const ${temp} = ${resolved};`);
665
+ lines.push(`export { ${temp} as ${asExportName(key)} };`);
666
+ } else {
667
+ lines.push(`export { ${resolved} as ${asExportName(key)} };`);
668
+ }
590
669
  } else {
591
670
  const temp = tempNameFor(key);
592
671
  lines.push(`const ${temp} = ${accessProp(key)};`);
@@ -34,14 +34,31 @@ export const memberExpression = (node, parent, src, options, shadowed, extras) =
34
34
  * Can of worms here. ¯\_(ツ)_/¯
35
35
  * @see https://github.com/nodejs/help/issues/2806
36
36
  */
37
+ extras?.onDiagnostic?.('legacy-require-cache', 'Access to require.cache is not supported when raising to ESM; behavior may differ.', {
38
+ start,
39
+ end
40
+ });
37
41
  src.update(start, end, '{}');
38
42
  break;
43
+ case 'extensions':
44
+ extras?.onDiagnostic?.('legacy-require-extensions', 'Access to require.extensions is not supported when raising to ESM; use loaders instead.', {
45
+ start,
46
+ end
47
+ });
48
+ break;
39
49
  }
40
50
  }
41
51
  if (node.object.type === 'Identifier' && node.property.type === 'Identifier' && node.object.name === 'module' && node.property.name === 'require') {
42
52
  if (!shadowed?.has('module')) {
43
53
  src.update(node.start, node.end, 'require');
44
54
  }
55
+ return;
56
+ }
57
+ if (node.object.type === 'Identifier' && node.property.type === 'Identifier' && node.object.name === 'module' && (node.property.name === 'parent' || node.property.name === 'children')) {
58
+ extras?.onDiagnostic?.(`legacy-module-${node.property.name}`, `Access to module.${node.property.name} may not behave the same in ESM; consider loaders or explicit wiring instead.`, {
59
+ start: node.start,
60
+ end: node.end
61
+ });
45
62
  }
46
63
  }
47
64
  };
@@ -4,6 +4,10 @@ import type { FormatterOptions } from '../types.cjs';
4
4
  type MemberExpressionExtras = {
5
5
  onRequireResolve?: () => void;
6
6
  requireResolveName?: string;
7
+ onDiagnostic?: (code: string, message: string, loc?: {
8
+ start: number;
9
+ end: number;
10
+ }) => void;
7
11
  };
8
12
  export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>, extras?: MemberExpressionExtras) => void;
9
13
  export {};
@@ -4,6 +4,10 @@ import type { FormatterOptions } from '../types.js';
4
4
  type MemberExpressionExtras = {
5
5
  onRequireResolve?: () => void;
6
6
  requireResolveName?: string;
7
+ onDiagnostic?: (code: string, message: string, loc?: {
8
+ start: number;
9
+ end: number;
10
+ }) => void;
7
11
  };
8
12
  export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>, extras?: MemberExpressionExtras) => void;
9
13
  export {};
package/dist/module.js CHANGED
@@ -164,7 +164,8 @@ const defaultOptions = {
164
164
  const transform = async (filename, options = defaultOptions) => {
165
165
  const opts = {
166
166
  ...defaultOptions,
167
- ...options
167
+ ...options,
168
+ filePath: filename
168
169
  };
169
170
  const appendMode = options?.appendJsExtension ?? (opts.target === 'module' ? 'relative-only' : 'off');
170
171
  const dirIndex = opts.appendDirectoryIndex === undefined ? 'index.js' : opts.appendDirectoryIndex;
@@ -4,6 +4,10 @@ import type { FormatterOptions } from '../types.js';
4
4
  type MemberExpressionExtras = {
5
5
  onRequireResolve?: () => void;
6
6
  requireResolveName?: string;
7
+ onDiagnostic?: (code: string, message: string, loc?: {
8
+ start: number;
9
+ end: number;
10
+ }) => void;
7
11
  };
8
12
  export declare const memberExpression: (node: MemberExpression, parent: Node | null, src: MagicString, options: FormatterOptions, shadowed?: Set<string>, extras?: MemberExpressionExtras) => void;
9
13
  export {};
@@ -34,11 +34,25 @@ export type ModuleOptions = {
34
34
  cjsDefault?: 'module-exports' | 'auto' | 'none';
35
35
  /** Handling for top-level await constructs. */
36
36
  topLevelAwait?: 'error' | 'wrap' | 'preserve';
37
+ /** Optional diagnostics sink for warnings/errors emitted during transform. */
38
+ diagnostics?: (diag: Diagnostic) => void;
39
+ /** Optional source file path used for diagnostics context. */
40
+ filePath?: string;
37
41
  /** Output directory or file path when writing. */
38
42
  out?: string;
39
43
  /** Overwrite input files instead of writing to out. */
40
44
  inPlace?: boolean;
41
45
  };
46
+ export type Diagnostic = {
47
+ level: 'warning' | 'error';
48
+ code: string;
49
+ message: string;
50
+ filePath?: string;
51
+ loc?: {
52
+ start: number;
53
+ end: number;
54
+ };
55
+ };
42
56
  export type SpannedNode = Node & Span;
43
57
  export type ExportsMeta = {
44
58
  hasExportsBeenReassigned: boolean;
package/dist/types.d.cts CHANGED
@@ -34,11 +34,25 @@ export type ModuleOptions = {
34
34
  cjsDefault?: 'module-exports' | 'auto' | 'none';
35
35
  /** Handling for top-level await constructs. */
36
36
  topLevelAwait?: 'error' | 'wrap' | 'preserve';
37
+ /** Optional diagnostics sink for warnings/errors emitted during transform. */
38
+ diagnostics?: (diag: Diagnostic) => void;
39
+ /** Optional source file path used for diagnostics context. */
40
+ filePath?: string;
37
41
  /** Output directory or file path when writing. */
38
42
  out?: string;
39
43
  /** Overwrite input files instead of writing to out. */
40
44
  inPlace?: boolean;
41
45
  };
46
+ export type Diagnostic = {
47
+ level: 'warning' | 'error';
48
+ code: string;
49
+ message: string;
50
+ filePath?: string;
51
+ loc?: {
52
+ start: number;
53
+ end: number;
54
+ };
55
+ };
42
56
  export type SpannedNode = Node & Span;
43
57
  export type ExportsMeta = {
44
58
  hasExportsBeenReassigned: boolean;
package/dist/types.d.ts CHANGED
@@ -34,11 +34,25 @@ export type ModuleOptions = {
34
34
  cjsDefault?: 'module-exports' | 'auto' | 'none';
35
35
  /** Handling for top-level await constructs. */
36
36
  topLevelAwait?: 'error' | 'wrap' | 'preserve';
37
+ /** Optional diagnostics sink for warnings/errors emitted during transform. */
38
+ diagnostics?: (diag: Diagnostic) => void;
39
+ /** Optional source file path used for diagnostics context. */
40
+ filePath?: string;
37
41
  /** Output directory or file path when writing. */
38
42
  out?: string;
39
43
  /** Overwrite input files instead of writing to out. */
40
44
  inPlace?: boolean;
41
45
  };
46
+ export type Diagnostic = {
47
+ level: 'warning' | 'error';
48
+ code: string;
49
+ message: string;
50
+ filePath?: string;
51
+ loc?: {
52
+ start: number;
53
+ end: number;
54
+ };
55
+ };
42
56
  export type SpannedNode = Node & Span;
43
57
  export type ExportsMeta = {
44
58
  hasExportsBeenReassigned: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@knighted/module",
3
- "version": "1.0.0-rc.3",
4
- "description": "Transforms differences between ES modules and CommonJS.",
3
+ "version": "1.0.0-rc.5",
4
+ "description": "Bidirectional transform for ES modules and CommonJS.",
5
5
  "type": "module",
6
6
  "main": "dist/module.js",
7
7
  "exports": {
@@ -44,13 +44,12 @@
44
44
  "postpack": "node scripts/restoreImportsToSrc.js"
45
45
  },
46
46
  "keywords": [
47
- "transform",
48
- "es module",
47
+ "esm",
49
48
  "commonjs",
50
- "require",
51
- "require.resolve",
52
- "import.meta.url",
53
- "import.meta.dirname",
49
+ "transform",
50
+ "cjs-to-esm",
51
+ "esm-to-cjs",
52
+ "import.meta",
54
53
  "__dirname",
55
54
  "__filename"
56
55
  ],