@@ -3,6 +3,7 @@ import { dirname, join, resolve } from 'path';
3
3
import { type ConsolaInstance } from 'consola' ;
4
4
import { execa } from 'execa' ;
5
5
import fse from 'fs-extra' ;
6
+ import { getTsconfig , parseTsconfig } from 'get-tsconfig' ;
6
7
import { globby } from 'globby' ;
7
8
import get from 'lodash.get' ;
8
9
import pLimit from 'p-limit' ;
@@ -41,21 +42,10 @@ const filesToExcludeFromDist = [
41
42
'**/temp' ,
42
43
] ;
43
44
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
-
57
45
function 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 } ` ] ) ;
59
49
}
60
50
61
51
function assertTypeScriptBuildResult (
@@ -70,36 +60,62 @@ function assertTypeScriptBuildResult(
70
60
71
61
async function buildTypeScript (
72
62
buildPath : string ,
73
- options : { cwd : string ; tsconfig ?: string ; incremental ?: boolean } ,
63
+ options : {
64
+ cwd : string ;
65
+ tsconfig ?: string ;
66
+ incremental ?: boolean ;
67
+ } ,
74
68
reporter : ConsolaInstance ,
75
69
) {
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 ) ;
79
73
}
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
- ) ;
91
74
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' ) ;
103
119
}
104
120
105
121
export const buildCommand = createCommand <
@@ -479,6 +495,77 @@ export function validatePackageJson(
479
495
}
480
496
}
481
497
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
+
482
569
async function executeCopy ( sourcePath : string , destPath : string ) {
483
570
await fse . mkdirp ( dirname ( destPath ) ) ;
484
571
await fse . copyFile ( sourcePath , destPath ) ;
0 commit comments