@@ -8,11 +8,82 @@ import { blue, dim } from 'ansis'
88import Debug from 'debug'
99import { fsRemove } from '../utils/fs'
1010import { logger } from '../utils/logger'
11- import type { ResolvedOptions } from '../options'
11+ import type { AttwOptions , ResolvedOptions } from '../options'
12+ import type { Problem } from '@arethetypeswrong/core'
1213
1314const debug = Debug ( 'tsdown:attw' )
1415const exec = promisify ( child_process . exec )
1516
17+ /**
18+ * ATTW profiles.
19+ * Defines the resolution modes to ignore for each profile.
20+ *
21+ * @see https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/packages/cli/README.md#profiles
22+ */
23+ const profiles : Record < Required < AttwOptions > [ 'profile' ] , string [ ] > = {
24+ strict : [ ] ,
25+ node16 : [ 'node10' ] ,
26+ esmOnly : [ 'node10' , 'node16-cjs' ] ,
27+ }
28+
29+ /**
30+ * Format an ATTW problem for display
31+ */
32+ function formatProblem ( problem : Problem ) : string {
33+ const resolutionKind =
34+ 'resolutionKind' in problem ? ` (${ problem . resolutionKind } )` : ''
35+ const entrypoint = 'entrypoint' in problem ? ` at ${ problem . entrypoint } ` : ''
36+
37+ switch ( problem . kind ) {
38+ case 'NoResolution' :
39+ return ` ❌ No resolution${ resolutionKind } ${ entrypoint } `
40+
41+ case 'UntypedResolution' :
42+ return ` ⚠️ Untyped resolution${ resolutionKind } ${ entrypoint } `
43+
44+ case 'FalseESM' :
45+ return ` 🔄 False ESM: Types indicate ESM (${ problem . typesModuleKind } ) but implementation is CJS (${ problem . implementationModuleKind } )\n Types: ${ problem . typesFileName } | Implementation: ${ problem . implementationFileName } `
46+
47+ case 'FalseCJS' :
48+ return ` 🔄 False CJS: Types indicate CJS (${ problem . typesModuleKind } ) but implementation is ESM (${ problem . implementationModuleKind } )\n Types: ${ problem . typesFileName } | Implementation: ${ problem . implementationFileName } `
49+
50+ case 'CJSResolvesToESM' :
51+ return ` ⚡ CJS resolves to ESM${ resolutionKind } ${ entrypoint } `
52+
53+ case 'NamedExports' : {
54+ const missingExports =
55+ problem . missing ?. length > 0
56+ ? ` Missing: ${ problem . missing . join ( ', ' ) } `
57+ : ''
58+ const allMissing = problem . isMissingAllNamed
59+ ? ' (all named exports missing)'
60+ : ''
61+ return ` 📤 Named exports problem${ allMissing } ${ missingExports } \n Types: ${ problem . typesFileName } | Implementation: ${ problem . implementationFileName } `
62+ }
63+
64+ case 'FallbackCondition' :
65+ return ` 🎯 Fallback condition used${ resolutionKind } ${ entrypoint } `
66+
67+ case 'FalseExportDefault' :
68+ return ` 🎭 False export default\n Types: ${ problem . typesFileName } | Implementation: ${ problem . implementationFileName } `
69+
70+ case 'MissingExportEquals' :
71+ return ` 📝 Missing export equals\n Types: ${ problem . typesFileName } | Implementation: ${ problem . implementationFileName } `
72+
73+ case 'InternalResolutionError' :
74+ return ` 💥 Internal resolution error in ${ problem . fileName } (${ problem . resolutionOption } )\n Module: ${ problem . moduleSpecifier } | Mode: ${ problem . resolutionMode } `
75+
76+ case 'UnexpectedModuleSyntax' :
77+ return ` 📋 Unexpected module syntax in ${ problem . fileName } \n Expected: ${ problem . moduleKind } | Found: ${ problem . syntax === 99 ? 'ESM' : 'CJS' } `
78+
79+ case 'CJSOnlyExportsDefault' :
80+ return ` 🏷️ CJS only exports default in ${ problem . fileName } `
81+
82+ default :
83+ return ` ❓ Unknown problem: ${ JSON . stringify ( problem ) } `
84+ }
85+ }
86+
1687export async function attw ( options : ResolvedOptions ) : Promise < void > {
1788 if ( ! options . attw ) return
1889 if ( ! options . pkg ) {
@@ -38,7 +109,7 @@ export async function attw(options: ResolvedOptions): Promise<void> {
38109 try {
39110 const { stdout : tarballInfo } = await exec (
40111 `npm pack --json ----pack-destination ${ tempDir } ` ,
41- { encoding : 'utf-8' } ,
112+ { encoding : 'utf-8' , cwd : options . cwd } ,
42113 )
43114 const parsed = JSON . parse ( tarballInfo )
44115 if ( ! Array . isArray ( parsed ) || ! parsed [ 0 ] ?. filename ) {
@@ -48,14 +119,29 @@ export async function attw(options: ResolvedOptions): Promise<void> {
48119 const tarball = await readFile ( tarballPath )
49120
50121 const pkg = attwCore . createPackageFromTarballData ( tarball )
51- const checkResult = await attwCore . checkPackage (
52- pkg ,
53- options . attw === true ? { } : options . attw ,
54- )
122+ const attwOptions = options . attw === true ? { } : options . attw
123+ const checkResult = await attwCore . checkPackage ( pkg , attwOptions )
124+ const profile = attwOptions . profile ?? 'strict'
125+ const level = attwOptions . level ?? 'warn'
55126
56127 if ( checkResult . types !== false && checkResult . problems ) {
57- for ( const problem of checkResult . problems ) {
58- logger . warn ( 'Are the types wrong problem:' , problem )
128+ const problems = checkResult . problems . filter ( ( problem ) => {
129+ // Only apply profile filter to problems that have resolutionKind
130+ if ( 'resolutionKind' in problem ) {
131+ return ! profiles [ profile ] ?. includes ( problem . resolutionKind )
132+ }
133+ // Include all other problem types
134+ return true
135+ } )
136+ if ( problems . length ) {
137+ const problemList = problems . map ( formatProblem ) . join ( '\n' )
138+ const problemMessage = `Are the types wrong problems found:\n${ problemList } `
139+
140+ if ( level === 'error' ) {
141+ throw new Error ( problemMessage )
142+ }
143+
144+ logger . warn ( problemMessage )
59145 }
60146 } else {
61147 logger . success (
0 commit comments