@@ -3,6 +3,7 @@ import { dirname, join, resolve } from 'path';
33import { type ConsolaInstance } from 'consola' ;
44import { execa } from 'execa' ;
55import fse from 'fs-extra' ;
6+ import { getTsconfig , parseTsconfig } from 'get-tsconfig' ;
67import { globby } from 'globby' ;
78import get from 'lodash.get' ;
89import pLimit from 'p-limit' ;
@@ -41,21 +42,10 @@ const filesToExcludeFromDist = [
4142 '**/temp' ,
4243] ;
4344
44- const moduleMappings = {
45- esm : 'es2022' ,
46- cjs : 'commonjs' ,
47- } as const ;
48-
49- function typeScriptCompilerOptions ( target : 'esm' | 'cjs' ) : Record < string , unknown > {
50- return {
51- module : moduleMappings [ target ] ,
52- sourceMap : false ,
53- inlineSourceMap : false ,
54- } ;
55- }
56-
5745function compilerOptionsToArgs ( options : Record < string , unknown > ) : string [ ] {
58- return Object . entries ( options ) . flatMap ( ( [ key , value ] ) => [ `--${ key } ` , `${ value } ` ] ) ;
46+ return Object . entries ( options )
47+ . filter ( ( [ , value ] ) => ! ! value )
48+ . flatMap ( ( [ key , value ] ) => [ `--${ key } ` , `${ value } ` ] ) ;
5949}
6050
6151function assertTypeScriptBuildResult (
@@ -70,36 +60,62 @@ function assertTypeScriptBuildResult(
7060
7161async function buildTypeScript (
7262 buildPath : string ,
73- options : { cwd : string ; tsconfig ?: string ; incremental ?: boolean } ,
63+ options : {
64+ cwd : string ;
65+ tsconfig ?: string ;
66+ incremental ?: boolean ;
67+ } ,
7468 reporter : ConsolaInstance ,
7569) {
76- let tsconfig = options . tsconfig ;
77- if ( ! tsconfig && ( await fse . exists ( join ( options . cwd , DEFAULT_TS_BUILD_CONFIG ) ) ) ) {
78- tsconfig = join ( options . cwd , DEFAULT_TS_BUILD_CONFIG ) ;
70+ let project = options . tsconfig ;
71+ if ( ! project && ( await fse . exists ( join ( options . cwd , DEFAULT_TS_BUILD_CONFIG ) ) ) ) {
72+ project = join ( options . cwd , DEFAULT_TS_BUILD_CONFIG ) ;
7973 }
80- assertTypeScriptBuildResult (
81- await execa ( 'npx' , [
82- 'tsc' ,
83- ...( tsconfig ? [ '--project' , tsconfig ] : [ ] ) ,
84- ...compilerOptionsToArgs ( typeScriptCompilerOptions ( 'esm' ) ) ,
85- ...( options . incremental ? [ '--incremental' ] : [ ] ) ,
86- '--outDir' ,
87- join ( buildPath , 'esm' ) ,
88- ] ) ,
89- reporter ,
90- ) ;
9174
92- assertTypeScriptBuildResult (
93- await execa ( 'npx' , [
94- 'tsc' ,
95- ...( tsconfig ? [ '--project' , tsconfig ] : [ ] ) ,
96- ...compilerOptionsToArgs ( typeScriptCompilerOptions ( 'cjs' ) ) ,
97- ...( options . incremental ? [ '--incremental' ] : [ ] ) ,
98- '--outDir' ,
99- join ( buildPath , 'cjs' ) ,
100- ] ) ,
101- reporter ,
102- ) ;
75+ const tsconfig = project ? parseTsconfig ( project ) : getTsconfig ( options . cwd ) ?. config ;
76+
77+ const moduleResolution = ( tsconfig ?. compilerOptions ?. moduleResolution || '' ) . toLowerCase ( ) ;
78+ const isModernNodeModuleResolution = [ 'node16' , 'nodenext' ] . includes ( moduleResolution ) ;
79+ const isOldNodeModuleResolution = [ 'classic' , 'node' , 'node10' ] . includes ( moduleResolution ) ;
80+ if ( moduleResolution && ! isOldNodeModuleResolution && ! isModernNodeModuleResolution ) {
81+ throw new Error (
82+ `'moduleResolution' option '${ moduleResolution } ' cannot be used to build CommonJS"` ,
83+ ) ;
84+ }
85+
86+ async function build ( out : PackageJsonType ) {
87+ const revertPackageJsonsType = await setPackageJsonsType (
88+ { cwd : options . cwd , ignore : [ ...filesToExcludeFromDist , ...( tsconfig ?. exclude || [ ] ) ] } ,
89+ out ,
90+ ) ;
91+ try {
92+ assertTypeScriptBuildResult (
93+ await execa ( 'npx' , [
94+ 'tsc' ,
95+ ...compilerOptionsToArgs ( {
96+ project,
97+ module : isModernNodeModuleResolution
98+ ? moduleResolution // match module with moduleResolution for modern node (nodenext and node16)
99+ : out === 'module'
100+ ? 'es2022'
101+ : isOldNodeModuleResolution
102+ ? 'commonjs' // old commonjs
103+ : 'node16' , // modern commonjs
104+ sourceMap : false ,
105+ inlineSourceMap : false ,
106+ incremental : options . incremental ,
107+ outDir : out === 'module' ? join ( buildPath , 'esm' ) : join ( buildPath , 'cjs' ) ,
108+ } ) ,
109+ ] ) ,
110+ reporter ,
111+ ) ;
112+ } finally {
113+ await revertPackageJsonsType ( ) ;
114+ }
115+ }
116+
117+ await build ( 'module' ) ;
118+ await build ( 'commonjs' ) ;
103119}
104120
105121export const buildCommand = createCommand <
@@ -479,6 +495,77 @@ export function validatePackageJson(
479495 }
480496}
481497
498+ type PackageJsonType = 'module' | 'commonjs' ;
499+
500+ /**
501+ * Sets the {@link cwd workspaces} package.json(s) `"type"` field to the defined {@link type}
502+ * returning a "revert" function which puts the original `"type"` back.
503+ *
504+ * @returns A revert function that reverts the original value of the `"type"` field.
505+ */
506+ async function setPackageJsonsType (
507+ { cwd, ignore } : { cwd : string ; ignore : string [ ] } ,
508+ type : PackageJsonType ,
509+ ) : Promise < ( ) => Promise < void > > {
510+ const rootPkgJsonPath = join ( cwd , 'package.json' ) ;
511+ const rootContents = await fse . readFile ( rootPkgJsonPath , 'utf8' ) ;
512+ const rootPkg = JSON . parse ( rootContents ) ;
513+ const workspaces = await getWorkspaces ( rootPkg ) ;
514+ const isSinglePackage = workspaces === null ;
515+
516+ const reverts : ( ( ) => Promise < void > ) [ ] = [ ] ;
517+
518+ for ( const pkgJsonPath of [
519+ // we also want to modify the root package.json TODO: do we in single package repos?
520+ rootPkgJsonPath ,
521+ ...( isSinglePackage
522+ ? [ ]
523+ : await globby (
524+ workspaces . map ( ( w : string ) => w + '/package.json' ) ,
525+ { cwd, absolute : true , ignore } ,
526+ ) ) ,
527+ ] ) {
528+ const contents =
529+ pkgJsonPath === rootPkgJsonPath
530+ ? // no need to re-read the root package.json
531+ rootContents
532+ : await fse . readFile ( pkgJsonPath , 'utf8' ) ;
533+ const endsWithNewline = contents . endsWith ( '\n' ) ;
534+
535+ const pkg = JSON . parse ( contents ) ;
536+ if ( pkg . type != null && pkg . type !== 'commonjs' && pkg . type !== 'module' ) {
537+ throw new Error ( `Invalid "type" property value "${ pkg . type } " in ${ pkgJsonPath } ` ) ;
538+ }
539+
540+ const originalPkg = { ...pkg } ;
541+ const differentType =
542+ ( pkg . type ||
543+ // default when the type is not defined
544+ 'commonjs' ) !== type ;
545+
546+ // change only if the provided type is different
547+ if ( differentType ) {
548+ pkg . type = type ;
549+ await fse . writeFile (
550+ pkgJsonPath ,
551+ JSON . stringify ( pkg , null , ' ' ) + ( endsWithNewline ? '\n' : '' ) ,
552+ ) ;
553+
554+ // revert change, of course only if we changed something
555+ reverts . push ( async ( ) => {
556+ await fse . writeFile (
557+ pkgJsonPath ,
558+ JSON . stringify ( originalPkg , null , ' ' ) + ( endsWithNewline ? '\n' : '' ) ,
559+ ) ;
560+ } ) ;
561+ }
562+ }
563+
564+ return async function revert ( ) {
565+ await Promise . all ( reverts . map ( r => r ( ) ) ) ;
566+ } ;
567+ }
568+
482569async function executeCopy ( sourcePath : string , destPath : string ) {
483570 await fse . mkdirp ( dirname ( destPath ) ) ;
484571 await fse . copyFile ( sourcePath , destPath ) ;
0 commit comments