@knighted/module 1.4.0 → 1.5.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -114,6 +114,7 @@ type ModuleOptions = {
114
114
  target: 'module' | 'commonjs'
115
115
  sourceType?: 'auto' | 'module' | 'commonjs'
116
116
  transformSyntax?: boolean | 'globals-only'
117
+ sourceMap?: boolean
117
118
  liveBindings?: 'strict' | 'loose' | 'off'
118
119
  appendJsExtension?: 'off' | 'relative-only' | 'all'
119
120
  appendDirectoryIndex?: string | false
@@ -167,6 +168,7 @@ type ModuleOptions = {
167
168
  - `cjsDefault` (`auto`): bundler-style default interop vs direct `module.exports`.
168
169
  - `idiomaticExports` (`safe`): when raising CJS to ESM, attempt to synthesize `export` statements directly when it is safe. `off` always uses the helper bag; `aggressive` currently matches `safe` heuristics.
169
170
  - `out`/`inPlace`: choose output location. Default returns the transformed string (CLI emits to stdout). `out` writes to the provided path. `inPlace` overwrites the input files on disk and does not return/emit the code.
171
+ - `sourceMap` (`false`): when true, returns `{ code, map }` from `transform` and writes the map if you also set `out`/`inPlace`. Maps are generated from the same MagicString pipeline used for the code.
170
172
  - `cwd` (`process.cwd()`): Base directory used to resolve relative `out` paths.
171
173
 
172
174
  > [!NOTE]
package/dist/cjs/cli.cjs CHANGED
@@ -38,6 +38,7 @@ const defaultOptions = {
38
38
  idiomaticExports: 'safe',
39
39
  importMetaPrelude: 'auto',
40
40
  topLevelAwait: 'error',
41
+ sourceMap: false,
41
42
  cwd: undefined,
42
43
  out: undefined,
43
44
  inPlace: false
@@ -183,6 +184,11 @@ const optionsTable = [{
183
184
  short: 'm',
184
185
  type: 'string',
185
186
  desc: 'Emit import.meta prelude (off|auto|on)'
187
+ }, {
188
+ long: 'source-map',
189
+ short: undefined,
190
+ type: 'boolean',
191
+ desc: 'Emit a source map alongside transformed output (use --source-map=inline for stdout)'
186
192
  }, {
187
193
  long: 'nested-require-strategy',
188
194
  short: 'n',
@@ -305,6 +311,7 @@ const toModuleOptions = values => {
305
311
  nestedRequireStrategy: parseEnum(values['nested-require-strategy'], ['create-require', 'dynamic-import']) ?? defaultOptions.nestedRequireStrategy,
306
312
  requireMainStrategy: parseEnum(values['require-main-strategy'], ['import-meta-main', 'realpath']) ?? defaultOptions.requireMainStrategy,
307
313
  liveBindings: parseEnum(values['live-bindings'], ['strict', 'loose', 'off']) ?? defaultOptions.liveBindings,
314
+ sourceMap: Boolean(values['source-map']),
308
315
  cwd: values.cwd ? (0, _nodePath.resolve)(String(values.cwd)) : defaultOptions.cwd
309
316
  };
310
317
  return opts;
@@ -316,6 +323,55 @@ const readStdin = async stdin => {
316
323
  }
317
324
  return Buffer.concat(chunks).toString('utf8');
318
325
  };
326
+ const normalizeSourceMapArgv = argv => {
327
+ let sourceMapInline = false;
328
+ let invalidSourceMapValue = null;
329
+ const normalized = [];
330
+ const recordInvalid = value => {
331
+ if (!invalidSourceMapValue) invalidSourceMapValue = value;
332
+ };
333
+ for (let i = 0; i < argv.length; i += 1) {
334
+ const arg = argv[i];
335
+ if (arg === '--source-map') {
336
+ const next = argv[i + 1];
337
+ if (next === 'inline') {
338
+ sourceMapInline = true;
339
+ normalized.push('--source-map');
340
+ i += 1;
341
+ continue;
342
+ }
343
+ if (next === 'true' || next === 'false') {
344
+ normalized.push(`--source-map=${next}`);
345
+ i += 1;
346
+ continue;
347
+ }
348
+ }
349
+ if (arg.startsWith('--source-map=')) {
350
+ const value = arg.slice('--source-map='.length);
351
+ if (value === 'inline') {
352
+ sourceMapInline = true;
353
+ normalized.push('--source-map');
354
+ continue;
355
+ }
356
+ if (value === 'true' || value === 'false') {
357
+ normalized.push(arg);
358
+ continue;
359
+ }
360
+ recordInvalid(value);
361
+ continue;
362
+ }
363
+ if (arg === '--source-map' && argv[i + 1] && argv[i + 1].startsWith('--')) {
364
+ normalized.push('--source-map');
365
+ continue;
366
+ }
367
+ normalized.push(arg);
368
+ }
369
+ return {
370
+ argv: normalized,
371
+ sourceMapInline,
372
+ invalidSourceMapValue
373
+ };
374
+ };
319
375
  const expandFiles = async (patterns, cwd, ignore) => {
320
376
  const files = new Set();
321
377
  for (const pattern of patterns) {
@@ -403,8 +459,10 @@ const runFiles = async (files, moduleOpts, io, flags) => {
403
459
  filePath: file,
404
460
  detectDualPackageHazard: hazardScope === 'project' ? 'off' : moduleOpts.detectDualPackageHazard
405
461
  };
462
+ const allowWrites = !flags.dryRun && !flags.list;
463
+ const writeInPlace = allowWrites && flags.inPlace;
406
464
  let writeTarget;
407
- if (!flags.dryRun && !flags.list) {
465
+ if (allowWrites) {
408
466
  if (flags.inPlace) {
409
467
  perFileOpts.inPlace = true;
410
468
  } else if (outPath) {
@@ -421,8 +479,15 @@ const runFiles = async (files, moduleOpts, io, flags) => {
421
479
  };
422
480
  }
423
481
  }
424
- const output = await (0, _module.transform)(file, perFileOpts);
482
+ if (moduleOpts.sourceMap && (writeTarget || writeInPlace)) {
483
+ perFileOpts.out = undefined;
484
+ perFileOpts.inPlace = false;
485
+ }
486
+ const transformed = await (0, _module.transform)(file, perFileOpts);
487
+ const output = typeof transformed === 'string' ? transformed : transformed.code;
488
+ const map = typeof transformed === 'string' ? null : transformed.map;
425
489
  const changed = output !== original;
490
+ let finalOutput = output;
426
491
  if (projectHazards) {
427
492
  const extras = projectHazards.get(file);
428
493
  if (extras?.length) diagnostics.push(...extras);
@@ -430,8 +495,24 @@ const runFiles = async (files, moduleOpts, io, flags) => {
430
495
  if (flags.list && changed) {
431
496
  logger.info(file);
432
497
  }
433
- if (!flags.dryRun && !flags.list && !writeTarget && !perFileOpts.inPlace) {
434
- io.stdout.write(output);
498
+ if (map && flags.sourceMapInline && !writeTarget && !writeInPlace) {
499
+ const mapUri = Buffer.from(JSON.stringify(map)).toString('base64');
500
+ finalOutput = `${output.replace(/\/\/# sourceMappingURL=.*/g, '').trimEnd()}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${mapUri}\n`;
501
+ } else if (map && (writeTarget || writeInPlace)) {
502
+ const target = writeTarget ?? file;
503
+ const mapPath = `${target}.map`;
504
+ const mapFile = (0, _nodePath.basename)(mapPath);
505
+ map.file = (0, _nodePath.basename)(target);
506
+ const updated = `${output.replace(/\/\/# sourceMappingURL=.*/g, '').trimEnd()}\n//# sourceMappingURL=${mapFile}\n`;
507
+ await (0, _promises.writeFile)(mapPath, JSON.stringify(map));
508
+ if (writeTarget) {
509
+ await (0, _promises.writeFile)(writeTarget, updated);
510
+ } else if (writeInPlace) {
511
+ await (0, _promises.writeFile)(file, updated);
512
+ }
513
+ }
514
+ if (!flags.dryRun && !flags.list && !writeTarget && !writeInPlace) {
515
+ io.stdout.write(finalOutput);
435
516
  }
436
517
  results.push({
437
518
  filePath: file,
@@ -468,11 +549,21 @@ const runCli = async ({
468
549
  stdout = _nodeProcess.stdout,
469
550
  stderr = _nodeProcess.stderr
470
551
  } = {}) => {
552
+ const logger = makeLogger(stdout, stderr);
553
+ const {
554
+ argv: normalizedArgv,
555
+ sourceMapInline,
556
+ invalidSourceMapValue
557
+ } = normalizeSourceMapArgv(argv);
558
+ if (invalidSourceMapValue) {
559
+ logger.error(`Invalid --source-map value: ${invalidSourceMapValue}`);
560
+ return 2;
561
+ }
471
562
  const {
472
563
  values,
473
564
  positionals
474
565
  } = (0, _nodeUtil.parseArgs)({
475
- args: argv,
566
+ args: normalizedArgv,
476
567
  allowPositionals: true,
477
568
  options: Object.fromEntries(optionsTable.map(opt => [opt.long, {
478
569
  type: opt.type,
@@ -481,7 +572,6 @@ const runCli = async ({
481
572
  } : {})
482
573
  }]))
483
574
  });
484
- const logger = makeLogger(stdout, stderr);
485
575
  if (values.help) {
486
576
  stdout.write(buildHelp(stdout.isTTY ?? false));
487
577
  return 0;
@@ -492,6 +582,7 @@ const runCli = async ({
492
582
  return 0;
493
583
  }
494
584
  const moduleOpts = toModuleOptions(values);
585
+ if (sourceMapInline) moduleOpts.sourceMap = true;
495
586
  const cwd = moduleOpts.cwd ?? process.cwd();
496
587
  const allowStdout = positionals.length <= 1;
497
588
  const fromStdin = positionals.length === 0 || positionals.includes('-');
@@ -504,6 +595,10 @@ const runCli = async ({
504
595
  const list = Boolean(values.list);
505
596
  const summary = Boolean(values.summary);
506
597
  const json = Boolean(values.json);
598
+ if (sourceMapInline && (outDir || inPlace)) {
599
+ logger.error('Inline source maps are only supported when writing to stdout');
600
+ return 2;
601
+ }
507
602
  if (outDir && inPlace) {
508
603
  logger.error('Choose either --out-dir or --in-place, not both');
509
604
  return 2;
@@ -558,7 +653,8 @@ const runCli = async ({
558
653
  json,
559
654
  outDir,
560
655
  inPlace,
561
- allowStdout
656
+ allowStdout,
657
+ sourceMapInline
562
658
  });
563
659
  if (typeof result.code === 'number' && result.code !== 0) return result.code;
564
660
  tasks.push(...result.results);
@@ -3,7 +3,8 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.format = exports.dualPackageHazardDiagnostics = exports.collectDualPackageUsage = void 0;
6
+ exports.dualPackageHazardDiagnostics = exports.collectDualPackageUsage = void 0;
7
+ exports.format = format;
7
8
  var _nodePath = require("node:path");
8
9
  var _promises = require("node:fs/promises");
9
10
  var _magicString = _interopRequireDefault(require("magic-string"));
@@ -291,13 +292,7 @@ const detectDualPackageHazards = async params => {
291
292
  diagOnce(diag.level, diag.code, diag.message, diag.loc);
292
293
  }
293
294
  };
294
-
295
- /**
296
- * Node added support for import.meta.main.
297
- * Added in: v24.2.0, v22.18.0
298
- * @see https://nodejs.org/api/esm.html#importmetamain
299
- */
300
- const format = async (src, ast, opts) => {
295
+ async function format(src, ast, opts) {
301
296
  const code = new _magicString.default(src);
302
297
  const exportsMeta = {
303
298
  hasExportsBeenReassigned: false,
@@ -522,15 +517,15 @@ const format = async (src, ast, opts) => {
522
517
  code.prepend(prelude);
523
518
  }
524
519
  if (opts.target === 'commonjs' && fullTransform && containsTopLevelAwait) {
525
- const body = code.toString();
526
520
  if (opts.topLevelAwait === 'wrap') {
527
- const tlaPromise = `const __tla = (async () => {\n${body}\nreturn module.exports;\n})();\n`;
528
- const setPromise = `const __setTla = target => {\n if (!target) return;\n const type = typeof target;\n if (type !== 'object' && type !== 'function') return;\n target.__tla = __tla;\n};\n`;
529
- const attach = `__setTla(module.exports);\n__tla.then(resolved => __setTla(resolved), err => { throw err; });\n`;
530
- return `${tlaPromise}${setPromise}${attach}`;
521
+ code.prepend('const __tla = (async () => {\n');
522
+ code.append('\nreturn module.exports;\n})();\n');
523
+ code.append('const __setTla = target => {\n if (!target) return;\n const type = typeof target;\n if (type !== "object" && type !== "function") return;\n target.__tla = __tla;\n};\n');
524
+ code.append('__setTla(module.exports);\n__tla.then(resolved => __setTla(resolved), err => { throw err; });\n');
525
+ } else {
526
+ code.prepend(';(async () => {\n');
527
+ code.append('\n})();\n');
531
528
  }
532
- return `;(async () => {\n${body}\n})();\n`;
533
529
  }
534
- return code.toString();
535
- };
536
- exports.format = format;
530
+ return opts.sourceMap ? code : code.toString();
531
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Node, ParseResult } from 'oxc-parser';
2
+ import MagicString from 'magic-string';
2
3
  import type { Diagnostic, FormatterOptions } from './types.cjs';
3
4
  type HazardLevel = 'warning' | 'error';
4
5
  export type PackageUse = {
@@ -22,10 +23,8 @@ declare const dualPackageHazardDiagnostics: (params: {
22
23
  cwd?: string;
23
24
  manifestCache?: Map<string, any | null>;
24
25
  }) => Promise<Diagnostic[]>;
25
- /**
26
- * Node added support for import.meta.main.
27
- * Added in: v24.2.0, v22.18.0
28
- * @see https://nodejs.org/api/esm.html#importmetamain
29
- */
30
- declare const format: (src: string, ast: ParseResult, opts: FormatterOptions) => Promise<string>;
26
+ declare function format(src: string, ast: ParseResult, opts: FormatterOptions & {
27
+ sourceMap: true;
28
+ }): Promise<MagicString>;
29
+ declare function format(src: string, ast: ParseResult, opts: FormatterOptions): Promise<string>;
31
30
  export { format, collectDualPackageUsage, dualPackageHazardDiagnostics };
@@ -3,7 +3,8 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.transform = exports.collectProjectDualPackageHazards = void 0;
6
+ exports.collectProjectDualPackageHazards = void 0;
7
+ exports.transform = transform;
7
8
  var _nodePath = require("node:path");
8
9
  var _promises = require("node:fs/promises");
9
10
  var _specifier = require("./specifier.cjs");
@@ -205,11 +206,12 @@ const createDefaultOptions = () => ({
205
206
  idiomaticExports: 'safe',
206
207
  importMetaPrelude: 'auto',
207
208
  topLevelAwait: 'error',
209
+ sourceMap: false,
208
210
  cwd: undefined,
209
211
  out: undefined,
210
212
  inPlace: false
211
213
  });
212
- const transform = async (filename, options) => {
214
+ async function transform(filename, options) {
213
215
  const base = createDefaultOptions();
214
216
  const opts = options ? {
215
217
  ...base,
@@ -226,9 +228,19 @@ const transform = async (filename, options) => {
226
228
  const file = (0, _nodePath.resolve)(cwdBase, filename);
227
229
  const code = (await (0, _promises.readFile)(file)).toString();
228
230
  const ast = (0, _parse.parse)(filename, code);
229
- let source = await (0, _format.format)(code, ast, opts);
231
+ let sourceCode = null;
232
+ let source;
233
+ if (opts.sourceMap) {
234
+ sourceCode = await (0, _format.format)(code, ast, {
235
+ ...opts,
236
+ sourceMap: true
237
+ });
238
+ source = sourceCode.toString();
239
+ } else {
240
+ source = await (0, _format.format)(code, ast, opts);
241
+ }
230
242
  if (opts.rewriteSpecifier || appendMode !== 'off' || dirIndex) {
231
- const code = await _specifier.specifier.updateSrc(source, (0, _lang.getLangFromExt)(filename), spec => {
243
+ const applyRewrite = spec => {
232
244
  if (spec.type === 'TemplateLiteral' && opts.rewriteTemplateLiterals === 'static-only') {
233
245
  const node = spec.node;
234
246
  if (node.expressions.length > 0) return;
@@ -238,8 +250,14 @@ const transform = async (filename, options) => {
238
250
  const baseValue = rewritten ?? normalized ?? spec.value;
239
251
  const appended = appendExtensionIfNeeded(spec, appendMode, dirIndex, baseValue);
240
252
  return appended ?? rewritten ?? normalized ?? undefined;
241
- });
242
- source = code;
253
+ };
254
+ if (opts.sourceMap && sourceCode) {
255
+ await _specifier.specifier.updateMagicString(sourceCode, code, ast, applyRewrite);
256
+ source = sourceCode.toString();
257
+ } else {
258
+ const rewritten = await _specifier.specifier.updateSrc(source, (0, _lang.getLangFromExt)(filename), applyRewrite);
259
+ source = rewritten;
260
+ }
243
261
  }
244
262
  if (detectCycles !== 'off' && opts.target === 'module' && opts.transformSyntax) {
245
263
  await detectCircularRequireGraph(file, detectCycles, dirIndex || 'index.js');
@@ -248,6 +266,17 @@ const transform = async (filename, options) => {
248
266
  if (outputPath) {
249
267
  await (0, _promises.writeFile)(outputPath, source);
250
268
  }
269
+ if (opts.sourceMap && sourceCode) {
270
+ const map = sourceCode.generateMap({
271
+ hires: true,
272
+ includeContent: true,
273
+ file: outputPath ?? filename,
274
+ source: opts.filePath ?? filename
275
+ });
276
+ return {
277
+ code: source,
278
+ map
279
+ };
280
+ }
251
281
  return source;
252
- };
253
- exports.transform = transform;
282
+ }
@@ -1,4 +1,17 @@
1
1
  import type { ModuleOptions, Diagnostic } from './types.cjs';
2
+ import type { SourceMap } from 'magic-string';
2
3
  declare const collectProjectDualPackageHazards: (files: string[], opts: ModuleOptions) => Promise<Map<string, Diagnostic[]>>;
3
- declare const transform: (filename: string, options?: ModuleOptions) => Promise<string>;
4
+ declare function transform(filename: string, options: ModuleOptions & {
5
+ sourceMap: true;
6
+ }): Promise<{
7
+ code: string;
8
+ map: SourceMap;
9
+ }>;
10
+ declare function transform(filename: string, options?: ModuleOptions & {
11
+ sourceMap?: false | undefined;
12
+ }): Promise<string>;
13
+ declare function transform(filename: string, options?: ModuleOptions): Promise<string | {
14
+ code: string;
15
+ map: SourceMap;
16
+ }>;
4
17
  export { transform, collectProjectDualPackageHazards };
@@ -20,8 +20,7 @@ const isBinaryExpression = node => {
20
20
  const isCallExpression = node => {
21
21
  return node.type === 'CallExpression' && node.callee !== undefined;
22
22
  };
23
- const formatSpecifiers = async (src, ast, cb) => {
24
- const code = new _magicString.default(src);
23
+ const formatSpecifiers = async (src, ast, cb, code = new _magicString.default(src)) => {
25
24
  const formatExpression = expression => {
26
25
  const node = isCallExpression(expression) ? expression.arguments[0] : expression.source;
27
26
  const {
@@ -235,7 +234,7 @@ const formatSpecifiers = async (src, ast, cb) => {
235
234
  }
236
235
  }
237
236
  });
238
- return code.toString();
237
+ return code;
239
238
  };
240
239
  const isValidFilename = async filename => {
241
240
  let stats;
@@ -258,11 +257,17 @@ const specifier = exports.specifier = {
258
257
  }
259
258
  const src = (await (0, _promises.readFile)(filename)).toString();
260
259
  const ast = (0, _oxcParser.parseSync)(filename, src);
261
- return await formatSpecifiers(src, ast, callback);
260
+ const code = await formatSpecifiers(src, ast, callback);
261
+ return code.toString();
262
262
  },
263
263
  async updateSrc(src, lang, callback) {
264
264
  const filename = lang === 'ts' ? 'file.ts' : lang === 'tsx' ? 'file.tsx' : lang === 'js' ? 'file.js' : 'file.jsx';
265
265
  const ast = (0, _oxcParser.parseSync)(filename, src);
266
- return await formatSpecifiers(src, ast, callback);
266
+ const code = await formatSpecifiers(src, ast, callback);
267
+ return code.toString();
268
+ },
269
+ async updateMagicString(code, src, ast, callback) {
270
+ await formatSpecifiers(src, ast, callback, code);
271
+ return code;
267
272
  }
268
273
  };
@@ -1,4 +1,5 @@
1
- import type { ParserOptions, StringLiteral, TemplateLiteral, BinaryExpression, NewExpression, ImportDeclaration, ExportNamedDeclaration, ExportAllDeclaration, TSImportType, ImportExpression, CallExpression } from 'oxc-parser';
1
+ import MagicString from 'magic-string';
2
+ import type { ParserOptions, ParseResult, StringLiteral, TemplateLiteral, BinaryExpression, NewExpression, ImportDeclaration, ExportNamedDeclaration, ExportAllDeclaration, TSImportType, ImportExpression, CallExpression } from 'oxc-parser';
2
3
  type Spec = {
3
4
  type: 'StringLiteral' | 'TemplateLiteral' | 'BinaryExpression' | 'NewExpression';
4
5
  node: StringLiteral | TemplateLiteral | BinaryExpression | NewExpression;
@@ -11,6 +12,7 @@ type Callback = (spec: Spec) => string | void;
11
12
  declare const specifier: {
12
13
  update(path: string, callback: Callback): Promise<string>;
13
14
  updateSrc(src: string, lang: ParserOptions["lang"], callback: Callback): Promise<string>;
15
+ updateMagicString(code: MagicString, src: string, ast: ParseResult, callback: Callback): Promise<MagicString>;
14
16
  };
15
17
  export { specifier };
16
18
  export type { Spec, Callback };
@@ -6,6 +6,8 @@ export type ModuleOptions = {
6
6
  target: 'module' | 'commonjs';
7
7
  /** Explicit source type; auto infers from file extension. */
8
8
  sourceType?: 'auto' | 'module' | 'commonjs';
9
+ /** Emit a source map alongside the transformed code. */
10
+ sourceMap?: boolean;
9
11
  /**
10
12
  * Enable syntax transforms beyond parsing.
11
13
  * - true: full CJS↔ESM lowering/raising
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { stdin as defaultStdin, stdout as defaultStdout, stderr as defaultStderr } from 'node:process';
3
3
  import { parseArgs } from 'node:util';
4
- import { readFile, mkdir } from 'node:fs/promises';
5
- import { dirname, resolve, relative, join } from 'node:path';
4
+ import { readFile, mkdir, writeFile } from 'node:fs/promises';
5
+ import { dirname, resolve, relative, join, basename } from 'node:path';
6
6
  import { glob } from 'glob';
7
7
  import { transform, collectProjectDualPackageHazards } from './module.js';
8
8
  import { parse } from './parse.js';
@@ -32,6 +32,7 @@ const defaultOptions = {
32
32
  idiomaticExports: 'safe',
33
33
  importMetaPrelude: 'auto',
34
34
  topLevelAwait: 'error',
35
+ sourceMap: false,
35
36
  cwd: undefined,
36
37
  out: undefined,
37
38
  inPlace: false
@@ -177,6 +178,11 @@ const optionsTable = [{
177
178
  short: 'm',
178
179
  type: 'string',
179
180
  desc: 'Emit import.meta prelude (off|auto|on)'
181
+ }, {
182
+ long: 'source-map',
183
+ short: undefined,
184
+ type: 'boolean',
185
+ desc: 'Emit a source map alongside transformed output (use --source-map=inline for stdout)'
180
186
  }, {
181
187
  long: 'nested-require-strategy',
182
188
  short: 'n',
@@ -299,6 +305,7 @@ const toModuleOptions = values => {
299
305
  nestedRequireStrategy: parseEnum(values['nested-require-strategy'], ['create-require', 'dynamic-import']) ?? defaultOptions.nestedRequireStrategy,
300
306
  requireMainStrategy: parseEnum(values['require-main-strategy'], ['import-meta-main', 'realpath']) ?? defaultOptions.requireMainStrategy,
301
307
  liveBindings: parseEnum(values['live-bindings'], ['strict', 'loose', 'off']) ?? defaultOptions.liveBindings,
308
+ sourceMap: Boolean(values['source-map']),
302
309
  cwd: values.cwd ? resolve(String(values.cwd)) : defaultOptions.cwd
303
310
  };
304
311
  return opts;
@@ -310,6 +317,55 @@ const readStdin = async stdin => {
310
317
  }
311
318
  return Buffer.concat(chunks).toString('utf8');
312
319
  };
320
+ const normalizeSourceMapArgv = argv => {
321
+ let sourceMapInline = false;
322
+ let invalidSourceMapValue = null;
323
+ const normalized = [];
324
+ const recordInvalid = value => {
325
+ if (!invalidSourceMapValue) invalidSourceMapValue = value;
326
+ };
327
+ for (let i = 0; i < argv.length; i += 1) {
328
+ const arg = argv[i];
329
+ if (arg === '--source-map') {
330
+ const next = argv[i + 1];
331
+ if (next === 'inline') {
332
+ sourceMapInline = true;
333
+ normalized.push('--source-map');
334
+ i += 1;
335
+ continue;
336
+ }
337
+ if (next === 'true' || next === 'false') {
338
+ normalized.push(`--source-map=${next}`);
339
+ i += 1;
340
+ continue;
341
+ }
342
+ }
343
+ if (arg.startsWith('--source-map=')) {
344
+ const value = arg.slice('--source-map='.length);
345
+ if (value === 'inline') {
346
+ sourceMapInline = true;
347
+ normalized.push('--source-map');
348
+ continue;
349
+ }
350
+ if (value === 'true' || value === 'false') {
351
+ normalized.push(arg);
352
+ continue;
353
+ }
354
+ recordInvalid(value);
355
+ continue;
356
+ }
357
+ if (arg === '--source-map' && argv[i + 1] && argv[i + 1].startsWith('--')) {
358
+ normalized.push('--source-map');
359
+ continue;
360
+ }
361
+ normalized.push(arg);
362
+ }
363
+ return {
364
+ argv: normalized,
365
+ sourceMapInline,
366
+ invalidSourceMapValue
367
+ };
368
+ };
313
369
  const expandFiles = async (patterns, cwd, ignore) => {
314
370
  const files = new Set();
315
371
  for (const pattern of patterns) {
@@ -397,8 +453,10 @@ const runFiles = async (files, moduleOpts, io, flags) => {
397
453
  filePath: file,
398
454
  detectDualPackageHazard: hazardScope === 'project' ? 'off' : moduleOpts.detectDualPackageHazard
399
455
  };
456
+ const allowWrites = !flags.dryRun && !flags.list;
457
+ const writeInPlace = allowWrites && flags.inPlace;
400
458
  let writeTarget;
401
- if (!flags.dryRun && !flags.list) {
459
+ if (allowWrites) {
402
460
  if (flags.inPlace) {
403
461
  perFileOpts.inPlace = true;
404
462
  } else if (outPath) {
@@ -415,8 +473,15 @@ const runFiles = async (files, moduleOpts, io, flags) => {
415
473
  };
416
474
  }
417
475
  }
418
- const output = await transform(file, perFileOpts);
476
+ if (moduleOpts.sourceMap && (writeTarget || writeInPlace)) {
477
+ perFileOpts.out = undefined;
478
+ perFileOpts.inPlace = false;
479
+ }
480
+ const transformed = await transform(file, perFileOpts);
481
+ const output = typeof transformed === 'string' ? transformed : transformed.code;
482
+ const map = typeof transformed === 'string' ? null : transformed.map;
419
483
  const changed = output !== original;
484
+ let finalOutput = output;
420
485
  if (projectHazards) {
421
486
  const extras = projectHazards.get(file);
422
487
  if (extras?.length) diagnostics.push(...extras);
@@ -424,8 +489,24 @@ const runFiles = async (files, moduleOpts, io, flags) => {
424
489
  if (flags.list && changed) {
425
490
  logger.info(file);
426
491
  }
427
- if (!flags.dryRun && !flags.list && !writeTarget && !perFileOpts.inPlace) {
428
- io.stdout.write(output);
492
+ if (map && flags.sourceMapInline && !writeTarget && !writeInPlace) {
493
+ const mapUri = Buffer.from(JSON.stringify(map)).toString('base64');
494
+ finalOutput = `${output.replace(/\/\/# sourceMappingURL=.*/g, '').trimEnd()}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${mapUri}\n`;
495
+ } else if (map && (writeTarget || writeInPlace)) {
496
+ const target = writeTarget ?? file;
497
+ const mapPath = `${target}.map`;
498
+ const mapFile = basename(mapPath);
499
+ map.file = basename(target);
500
+ const updated = `${output.replace(/\/\/# sourceMappingURL=.*/g, '').trimEnd()}\n//# sourceMappingURL=${mapFile}\n`;
501
+ await writeFile(mapPath, JSON.stringify(map));
502
+ if (writeTarget) {
503
+ await writeFile(writeTarget, updated);
504
+ } else if (writeInPlace) {
505
+ await writeFile(file, updated);
506
+ }
507
+ }
508
+ if (!flags.dryRun && !flags.list && !writeTarget && !writeInPlace) {
509
+ io.stdout.write(finalOutput);
429
510
  }
430
511
  results.push({
431
512
  filePath: file,
@@ -462,11 +543,21 @@ const runCli = async ({
462
543
  stdout = defaultStdout,
463
544
  stderr = defaultStderr
464
545
  } = {}) => {
546
+ const logger = makeLogger(stdout, stderr);
547
+ const {
548
+ argv: normalizedArgv,
549
+ sourceMapInline,
550
+ invalidSourceMapValue
551
+ } = normalizeSourceMapArgv(argv);
552
+ if (invalidSourceMapValue) {
553
+ logger.error(`Invalid --source-map value: ${invalidSourceMapValue}`);
554
+ return 2;
555
+ }
465
556
  const {
466
557
  values,
467
558
  positionals
468
559
  } = parseArgs({
469
- args: argv,
560
+ args: normalizedArgv,
470
561
  allowPositionals: true,
471
562
  options: Object.fromEntries(optionsTable.map(opt => [opt.long, {
472
563
  type: opt.type,
@@ -475,7 +566,6 @@ const runCli = async ({
475
566
  } : {})
476
567
  }]))
477
568
  });
478
- const logger = makeLogger(stdout, stderr);
479
569
  if (values.help) {
480
570
  stdout.write(buildHelp(stdout.isTTY ?? false));
481
571
  return 0;
@@ -486,6 +576,7 @@ const runCli = async ({
486
576
  return 0;
487
577
  }
488
578
  const moduleOpts = toModuleOptions(values);
579
+ if (sourceMapInline) moduleOpts.sourceMap = true;
489
580
  const cwd = moduleOpts.cwd ?? process.cwd();
490
581
  const allowStdout = positionals.length <= 1;
491
582
  const fromStdin = positionals.length === 0 || positionals.includes('-');
@@ -498,6 +589,10 @@ const runCli = async ({
498
589
  const list = Boolean(values.list);
499
590
  const summary = Boolean(values.summary);
500
591
  const json = Boolean(values.json);
592
+ if (sourceMapInline && (outDir || inPlace)) {
593
+ logger.error('Inline source maps are only supported when writing to stdout');
594
+ return 2;
595
+ }
501
596
  if (outDir && inPlace) {
502
597
  logger.error('Choose either --out-dir or --in-place, not both');
503
598
  return 2;
@@ -552,7 +647,8 @@ const runCli = async ({
552
647
  json,
553
648
  outDir,
554
649
  inPlace,
555
- allowStdout
650
+ allowStdout,
651
+ sourceMapInline
556
652
  });
557
653
  if (typeof result.code === 'number' && result.code !== 0) return result.code;
558
654
  tasks.push(...result.results);
package/dist/format.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Node, ParseResult } from 'oxc-parser';
2
+ import MagicString from 'magic-string';
2
3
  import type { Diagnostic, FormatterOptions } from './types.js';
3
4
  type HazardLevel = 'warning' | 'error';
4
5
  export type PackageUse = {
@@ -22,10 +23,8 @@ declare const dualPackageHazardDiagnostics: (params: {
22
23
  cwd?: string;
23
24
  manifestCache?: Map<string, any | null>;
24
25
  }) => Promise<Diagnostic[]>;
25
- /**
26
- * Node added support for import.meta.main.
27
- * Added in: v24.2.0, v22.18.0
28
- * @see https://nodejs.org/api/esm.html#importmetamain
29
- */
30
- declare const format: (src: string, ast: ParseResult, opts: FormatterOptions) => Promise<string>;
26
+ declare function format(src: string, ast: ParseResult, opts: FormatterOptions & {
27
+ sourceMap: true;
28
+ }): Promise<MagicString>;
29
+ declare function format(src: string, ast: ParseResult, opts: FormatterOptions): Promise<string>;
31
30
  export { format, collectDualPackageUsage, dualPackageHazardDiagnostics };
package/dist/format.js CHANGED
@@ -282,13 +282,7 @@ const detectDualPackageHazards = async params => {
282
282
  diagOnce(diag.level, diag.code, diag.message, diag.loc);
283
283
  }
284
284
  };
285
-
286
- /**
287
- * Node added support for import.meta.main.
288
- * Added in: v24.2.0, v22.18.0
289
- * @see https://nodejs.org/api/esm.html#importmetamain
290
- */
291
- const format = async (src, ast, opts) => {
285
+ async function format(src, ast, opts) {
292
286
  const code = new MagicString(src);
293
287
  const exportsMeta = {
294
288
  hasExportsBeenReassigned: false,
@@ -513,15 +507,16 @@ const format = async (src, ast, opts) => {
513
507
  code.prepend(prelude);
514
508
  }
515
509
  if (opts.target === 'commonjs' && fullTransform && containsTopLevelAwait) {
516
- const body = code.toString();
517
510
  if (opts.topLevelAwait === 'wrap') {
518
- const tlaPromise = `const __tla = (async () => {\n${body}\nreturn module.exports;\n})();\n`;
519
- const setPromise = `const __setTla = target => {\n if (!target) return;\n const type = typeof target;\n if (type !== 'object' && type !== 'function') return;\n target.__tla = __tla;\n};\n`;
520
- const attach = `__setTla(module.exports);\n__tla.then(resolved => __setTla(resolved), err => { throw err; });\n`;
521
- return `${tlaPromise}${setPromise}${attach}`;
511
+ code.prepend('const __tla = (async () => {\n');
512
+ code.append('\nreturn module.exports;\n})();\n');
513
+ code.append('const __setTla = target => {\n if (!target) return;\n const type = typeof target;\n if (type !== "object" && type !== "function") return;\n target.__tla = __tla;\n};\n');
514
+ code.append('__setTla(module.exports);\n__tla.then(resolved => __setTla(resolved), err => { throw err; });\n');
515
+ } else {
516
+ code.prepend(';(async () => {\n');
517
+ code.append('\n})();\n');
522
518
  }
523
- return `;(async () => {\n${body}\n})();\n`;
524
519
  }
525
- return code.toString();
526
- };
520
+ return opts.sourceMap ? code : code.toString();
521
+ }
527
522
  export { format, collectDualPackageUsage, dualPackageHazardDiagnostics };
package/dist/module.d.ts CHANGED
@@ -1,4 +1,17 @@
1
1
  import type { ModuleOptions, Diagnostic } from './types.js';
2
+ import type { SourceMap } from 'magic-string';
2
3
  declare const collectProjectDualPackageHazards: (files: string[], opts: ModuleOptions) => Promise<Map<string, Diagnostic[]>>;
3
- declare const transform: (filename: string, options?: ModuleOptions) => Promise<string>;
4
+ declare function transform(filename: string, options: ModuleOptions & {
5
+ sourceMap: true;
6
+ }): Promise<{
7
+ code: string;
8
+ map: SourceMap;
9
+ }>;
10
+ declare function transform(filename: string, options?: ModuleOptions & {
11
+ sourceMap?: false | undefined;
12
+ }): Promise<string>;
13
+ declare function transform(filename: string, options?: ModuleOptions): Promise<string | {
14
+ code: string;
15
+ map: SourceMap;
16
+ }>;
4
17
  export { transform, collectProjectDualPackageHazards };
package/dist/module.js CHANGED
@@ -201,11 +201,12 @@ const createDefaultOptions = () => ({
201
201
  idiomaticExports: 'safe',
202
202
  importMetaPrelude: 'auto',
203
203
  topLevelAwait: 'error',
204
+ sourceMap: false,
204
205
  cwd: undefined,
205
206
  out: undefined,
206
207
  inPlace: false
207
208
  });
208
- const transform = async (filename, options) => {
209
+ async function transform(filename, options) {
209
210
  const base = createDefaultOptions();
210
211
  const opts = options ? {
211
212
  ...base,
@@ -222,9 +223,19 @@ const transform = async (filename, options) => {
222
223
  const file = resolve(cwdBase, filename);
223
224
  const code = (await readFile(file)).toString();
224
225
  const ast = parse(filename, code);
225
- let source = await format(code, ast, opts);
226
+ let sourceCode = null;
227
+ let source;
228
+ if (opts.sourceMap) {
229
+ sourceCode = await format(code, ast, {
230
+ ...opts,
231
+ sourceMap: true
232
+ });
233
+ source = sourceCode.toString();
234
+ } else {
235
+ source = await format(code, ast, opts);
236
+ }
226
237
  if (opts.rewriteSpecifier || appendMode !== 'off' || dirIndex) {
227
- const code = await specifier.updateSrc(source, getLangFromExt(filename), spec => {
238
+ const applyRewrite = spec => {
228
239
  if (spec.type === 'TemplateLiteral' && opts.rewriteTemplateLiterals === 'static-only') {
229
240
  const node = spec.node;
230
241
  if (node.expressions.length > 0) return;
@@ -234,8 +245,14 @@ const transform = async (filename, options) => {
234
245
  const baseValue = rewritten ?? normalized ?? spec.value;
235
246
  const appended = appendExtensionIfNeeded(spec, appendMode, dirIndex, baseValue);
236
247
  return appended ?? rewritten ?? normalized ?? undefined;
237
- });
238
- source = code;
248
+ };
249
+ if (opts.sourceMap && sourceCode) {
250
+ await specifier.updateMagicString(sourceCode, code, ast, applyRewrite);
251
+ source = sourceCode.toString();
252
+ } else {
253
+ const rewritten = await specifier.updateSrc(source, getLangFromExt(filename), applyRewrite);
254
+ source = rewritten;
255
+ }
239
256
  }
240
257
  if (detectCycles !== 'off' && opts.target === 'module' && opts.transformSyntax) {
241
258
  await detectCircularRequireGraph(file, detectCycles, dirIndex || 'index.js');
@@ -244,6 +261,18 @@ const transform = async (filename, options) => {
244
261
  if (outputPath) {
245
262
  await writeFile(outputPath, source);
246
263
  }
264
+ if (opts.sourceMap && sourceCode) {
265
+ const map = sourceCode.generateMap({
266
+ hires: true,
267
+ includeContent: true,
268
+ file: outputPath ?? filename,
269
+ source: opts.filePath ?? filename
270
+ });
271
+ return {
272
+ code: source,
273
+ map
274
+ };
275
+ }
247
276
  return source;
248
- };
277
+ }
249
278
  export { transform, collectProjectDualPackageHazards };
@@ -1,4 +1,5 @@
1
- import type { ParserOptions, StringLiteral, TemplateLiteral, BinaryExpression, NewExpression, ImportDeclaration, ExportNamedDeclaration, ExportAllDeclaration, TSImportType, ImportExpression, CallExpression } from 'oxc-parser';
1
+ import MagicString from 'magic-string';
2
+ import type { ParserOptions, ParseResult, StringLiteral, TemplateLiteral, BinaryExpression, NewExpression, ImportDeclaration, ExportNamedDeclaration, ExportAllDeclaration, TSImportType, ImportExpression, CallExpression } from 'oxc-parser';
2
3
  type Spec = {
3
4
  type: 'StringLiteral' | 'TemplateLiteral' | 'BinaryExpression' | 'NewExpression';
4
5
  node: StringLiteral | TemplateLiteral | BinaryExpression | NewExpression;
@@ -11,6 +12,7 @@ type Callback = (spec: Spec) => string | void;
11
12
  declare const specifier: {
12
13
  update(path: string, callback: Callback): Promise<string>;
13
14
  updateSrc(src: string, lang: ParserOptions["lang"], callback: Callback): Promise<string>;
15
+ updateMagicString(code: MagicString, src: string, ast: ParseResult, callback: Callback): Promise<MagicString>;
14
16
  };
15
17
  export { specifier };
16
18
  export type { Spec, Callback };
package/dist/specifier.js CHANGED
@@ -13,8 +13,7 @@ const isBinaryExpression = node => {
13
13
  const isCallExpression = node => {
14
14
  return node.type === 'CallExpression' && node.callee !== undefined;
15
15
  };
16
- const formatSpecifiers = async (src, ast, cb) => {
17
- const code = new MagicString(src);
16
+ const formatSpecifiers = async (src, ast, cb, code = new MagicString(src)) => {
18
17
  const formatExpression = expression => {
19
18
  const node = isCallExpression(expression) ? expression.arguments[0] : expression.source;
20
19
  const {
@@ -228,7 +227,7 @@ const formatSpecifiers = async (src, ast, cb) => {
228
227
  }
229
228
  }
230
229
  });
231
- return code.toString();
230
+ return code;
232
231
  };
233
232
  const isValidFilename = async filename => {
234
233
  let stats;
@@ -251,12 +250,18 @@ const specifier = {
251
250
  }
252
251
  const src = (await readFile(filename)).toString();
253
252
  const ast = parseSync(filename, src);
254
- return await formatSpecifiers(src, ast, callback);
253
+ const code = await formatSpecifiers(src, ast, callback);
254
+ return code.toString();
255
255
  },
256
256
  async updateSrc(src, lang, callback) {
257
257
  const filename = lang === 'ts' ? 'file.ts' : lang === 'tsx' ? 'file.tsx' : lang === 'js' ? 'file.js' : 'file.jsx';
258
258
  const ast = parseSync(filename, src);
259
- return await formatSpecifiers(src, ast, callback);
259
+ const code = await formatSpecifiers(src, ast, callback);
260
+ return code.toString();
261
+ },
262
+ async updateMagicString(code, src, ast, callback) {
263
+ await formatSpecifiers(src, ast, callback, code);
264
+ return code;
260
265
  }
261
266
  };
262
267
  export { specifier };
package/dist/types.d.ts CHANGED
@@ -6,6 +6,8 @@ export type ModuleOptions = {
6
6
  target: 'module' | 'commonjs';
7
7
  /** Explicit source type; auto infers from file extension. */
8
8
  sourceType?: 'auto' | 'module' | 'commonjs';
9
+ /** Emit a source map alongside the transformed code. */
10
+ sourceMap?: boolean;
9
11
  /**
10
12
  * Enable syntax transforms beyond parsing.
11
13
  * - true: full CJS↔ESM lowering/raising
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/module",
3
- "version": "1.4.0",
3
+ "version": "1.5.0-rc.0",
4
4
  "description": "Bidirectional transform for ES modules and CommonJS.",
5
5
  "type": "module",
6
6
  "main": "dist/module.js",