55 stderr as defaultStderr ,
66} from 'node:process'
77import { parseArgs } from 'node:util'
8- import { readFile , mkdir } from 'node:fs/promises'
9- import { dirname , resolve , relative , join } from 'node:path'
8+ import { readFile , mkdir , writeFile } from 'node:fs/promises'
9+ import { dirname , resolve , relative , join , basename } from 'node:path'
1010import { glob } from 'glob'
1111
1212import type { TemplateLiteral } from '@oxc-project/types'
@@ -41,6 +41,7 @@ const defaultOptions: ModuleOptions = {
4141 idiomaticExports : 'safe' ,
4242 importMetaPrelude : 'auto' ,
4343 topLevelAwait : 'error' ,
44+ sourceMap : false ,
4445 cwd : undefined ,
4546 out : undefined ,
4647 inPlace : false ,
@@ -248,6 +249,12 @@ const optionsTable = [
248249 type : 'string' ,
249250 desc : 'Emit import.meta prelude (off|auto|on)' ,
250251 } ,
252+ {
253+ long : 'source-map' ,
254+ short : undefined ,
255+ type : 'boolean' ,
256+ desc : 'Emit a source map alongside transformed output (use --source-map=inline for stdout)' ,
257+ } ,
251258 {
252259 long : 'nested-require-strategy' ,
253260 short : 'n' ,
@@ -448,6 +455,7 @@ const toModuleOptions = (values: ParsedValues): ModuleOptions => {
448455 values [ 'live-bindings' ] as string | undefined ,
449456 [ 'strict' , 'loose' , 'off' ] as const ,
450457 ) ?? defaultOptions . liveBindings ,
458+ sourceMap : Boolean ( values [ 'source-map' ] ) ,
451459 cwd : values . cwd ? resolve ( String ( values . cwd ) ) : defaultOptions . cwd ,
452460 }
453461
@@ -462,6 +470,58 @@ const readStdin = async (stdin: typeof defaultStdin) => {
462470 return Buffer . concat ( chunks ) . toString ( 'utf8' )
463471}
464472
473+ const normalizeSourceMapArgv = ( argv : string [ ] ) = > {
474+ let sourceMapInline = false
475+ let invalidSourceMapValue : string | null = null
476+ const normalized : string [ ] = [ ]
477+ const recordInvalid = ( value : string ) => {
478+ if ( ! invalidSourceMapValue ) invalidSourceMapValue = value
479+ }
480+
481+ for ( let i = 0 ; i < argv . length ; i += 1 ) {
482+ const arg = argv [ i ]
483+
484+ if ( arg === '--source-map' ) {
485+ const next = argv [ i + 1 ]
486+ if ( next === 'inline' ) {
487+ sourceMapInline = true
488+ normalized . push ( '--source-map' )
489+ i += 1
490+ continue
491+ }
492+ if ( next === 'true' || next === 'false' ) {
493+ normalized . push ( `--source-map=${ next } ` )
494+ i += 1
495+ continue
496+ }
497+ }
498+
499+ if ( arg . startsWith ( '--source-map=' ) ) {
500+ const value = arg . slice ( '--source-map=' . length )
501+ if ( value === 'inline' ) {
502+ sourceMapInline = true
503+ normalized . push ( '--source-map' )
504+ continue
505+ }
506+ if ( value === 'true' || value === 'false' ) {
507+ normalized . push ( arg )
508+ continue
509+ }
510+ recordInvalid ( value )
511+ continue
512+ }
513+
514+ if ( arg === '--source-map' && argv [ i + 1 ] && argv [ i + 1 ] . startsWith ( '--' ) ) {
515+ normalized . push ( '--source-map' )
516+ continue
517+ }
518+
519+ normalized . push ( arg )
520+ }
521+
522+ return { argv : normalized , sourceMapInline , invalidSourceMapValue }
523+ }
524+
465525const expandFiles = async ( patterns : string [ ] , cwd : string , ignore ?: string [ ] ) => {
466526 const files = new Set < string > ( )
467527 for ( const pattern of patterns ) {
@@ -577,6 +637,7 @@ const runFiles = async (
577637 outDir ?: string
578638 inPlace : boolean
579639 allowStdout : boolean
640+ sourceMapInline : boolean
580641 } ,
581642) = > {
582643 const results : FileResult [ ] = [ ]
@@ -604,8 +665,11 @@ const runFiles = async (
604665 hazardScope === 'project' ? 'off' : moduleOpts . detectDualPackageHazard ,
605666 }
606667
668+ const allowWrites = ! flags . dryRun && ! flags . list
669+ const writeInPlace = allowWrites && flags . inPlace
607670 let writeTarget : string | undefined
608- if ( ! flags . dryRun && ! flags . list ) {
671+
672+ if ( allowWrites ) {
609673 if ( flags . inPlace ) {
610674 perFileOpts . inPlace = true
611675 } else if ( outPath ) {
@@ -618,8 +682,16 @@ const runFiles = async (
618682 }
619683 }
620684
621- const output = await transform ( file , perFileOpts )
685+ if ( moduleOpts . sourceMap && ( writeTarget || writeInPlace ) ) {
686+ perFileOpts . out = undefined
687+ perFileOpts . inPlace = false
688+ }
689+
690+ const transformed = await transform ( file , perFileOpts )
691+ const output = typeof transformed === 'string' ? transformed : transformed . code
692+ const map = typeof transformed === 'string' ? null : transformed . map
622693 const changed = output !== original
694+ let finalOutput = output
623695
624696 if ( projectHazards ) {
625697 const extras = projectHazards . get ( file )
@@ -630,8 +702,27 @@ const runFiles = async (
630702 logger . info ( file )
631703 }
632704
633- if ( ! flags . dryRun && ! flags . list && ! writeTarget && ! perFileOpts . inPlace ) {
634- io . stdout . write ( output )
705+ if ( map && flags . sourceMapInline && ! writeTarget && ! writeInPlace ) {
706+ const mapUri = Buffer . from ( JSON . stringify ( map ) ) . toString ( 'base64' )
707+ finalOutput = `${ output . replace ( / \/ \/ # s o u r c e M a p p i n g U R L = .* / g, '' ) . trimEnd ( ) } \n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${ mapUri } \n`
708+ } else if ( map && ( writeTarget || writeInPlace ) ) {
709+ const target = writeTarget ?? file
710+ const mapPath = `${ target } .map`
711+ const mapFile = basename ( mapPath )
712+ map . file = basename ( target )
713+
714+ const updated = `${ output . replace ( / \/ \/ # s o u r c e M a p p i n g U R L = .* / g, '' ) . trimEnd ( ) } \n//# sourceMappingURL=${ mapFile } \n`
715+ await writeFile ( mapPath , JSON . stringify ( map ) )
716+
717+ if ( writeTarget ) {
718+ await writeFile ( writeTarget , updated )
719+ } else if ( writeInPlace ) {
720+ await writeFile ( file , updated )
721+ }
722+ }
723+
724+ if ( ! flags . dryRun && ! flags . list && ! writeTarget && ! writeInPlace ) {
725+ io . stdout . write ( finalOutput )
635726 }
636727
637728 results . push ( { filePath : file , changed, diagnostics } )
@@ -664,8 +755,20 @@ const runCli = async ({
664755 stdout = defaultStdout ,
665756 stderr = defaultStderr ,
666757} : CliOptions = { } ) = > {
758+ const logger = makeLogger ( stdout , stderr )
759+ const {
760+ argv : normalizedArgv ,
761+ sourceMapInline,
762+ invalidSourceMapValue,
763+ } = normalizeSourceMapArgv ( argv )
764+
765+ if ( invalidSourceMapValue ) {
766+ logger . error ( `Invalid --source-map value: ${ invalidSourceMapValue } ` )
767+ return 2
768+ }
769+
667770 const { values , positionals } = parseArgs ( {
668- args : argv ,
771+ args : normalizedArgv ,
669772 allowPositionals : true ,
670773 options : Object . fromEntries (
671774 optionsTable . map ( opt => [
@@ -678,8 +781,6 @@ const runCli = async ({
678781 ) ,
679782 } )
680783
681- const logger = makeLogger ( stdout , stderr )
682-
683784 if ( values . help ) {
684785 stdout . write ( buildHelp ( stdout . isTTY ?? false ) )
685786 return 0
@@ -694,6 +795,7 @@ const runCli = async ({
694795 }
695796
696797 const moduleOpts = toModuleOptions ( values )
798+ if ( sourceMapInline ) moduleOpts . sourceMap = true
697799 const cwd = moduleOpts . cwd ?? process . cwd ( )
698800 const allowStdout = positionals . length <= 1
699801 const fromStdin = positionals . length === 0 || positionals . includes ( '-' )
@@ -710,6 +812,11 @@ const runCli = async ({
710812 const summary = Boolean ( values . summary )
711813 const json = Boolean ( values . json )
712814
815+ if ( sourceMapInline && ( outDir || inPlace ) ) {
816+ logger . error ( 'Inline source maps are only supported when writing to stdout' )
817+ return 2
818+ }
819+
713820 if ( outDir && inPlace ) {
714821 logger . error ( 'Choose either --out-dir or --in-place, not both' )
715822 return 2
@@ -769,6 +876,7 @@ const runCli = async ({
769876 outDir,
770877 inPlace,
771878 allowStdout,
879+ sourceMapInline,
772880 } ,
773881 )
774882
0 commit comments