11import path from 'node:path'
22import type { ExtractorMessage } from '@microsoft/api-extractor'
3- import type { BuildFailure , Message } from 'esbuild'
4- import { createConsoleSpy } from './consoleSpy.ts'
53import { loadConfig } from './core/config/loadConfig.ts'
64import type { BuildContext } from './core/contexts/index.ts'
75import { loadPkgWithReporting } from './core/pkg/loadPkgWithReporting.ts'
@@ -56,41 +54,12 @@ export async function check(options: {
5654 logger . error ( `missing files: ${ missingFiles . join ( ', ' ) } ` )
5755 process . exit ( 1 )
5856 }
59-
60- // Check if the files are resolved
61- const exportPaths : { require : string [ ] ; import : string [ ] } = {
62- require : [ ] ,
63- import : [ ] ,
64- }
65-
66- for ( const exp of Object . values ( ctx . exports || { } ) ) {
67- if ( ! exp . _exported ) continue
68- if ( exp . require ) exportPaths . require . push ( exp . require )
69- if ( exp . import ) exportPaths . import . push ( exp . import )
70- }
71-
72- const external = [
73- ...Object . keys ( pkg . dependencies || { } ) ,
74- ...Object . keys ( pkg . devDependencies || { } ) ,
75- ]
76-
77- const consoleSpy = createConsoleSpy ( )
78-
79- const checks = [ ]
80- if ( exportPaths . import . length ) {
81- checks . push ( checkExports ( exportPaths . import , { cwd, external, format : 'esm' , logger} ) )
82- }
83-
84- if ( exportPaths . require . length ) {
85- checks . push ( checkExports ( exportPaths . require , { cwd, external, format : 'cjs' , logger} ) )
86- }
87-
88- await Promise . all ( checks )
89-
90- consoleSpy . restore ( )
9157 }
9258
93- if ( ctx . dts === 'rolldown' && ctx . config ?. extract ?. enabled !== false ) {
59+ // Now use publint to check the package
60+ await checkWithPublint ( cwd , logger )
61+
62+ if ( ctx . config ?. extract ?. enabled !== false ) {
9463 await checkApiExtractorReleaseTags ( ctx )
9564 }
9665
@@ -109,114 +78,40 @@ export async function check(options: {
10978 }
11079}
11180
112- async function checkExports (
113- exportPaths : string [ ] ,
114- options : { cwd : string ; external : string [ ] ; format : 'esm' | 'cjs' ; logger : Logger } ,
115- ) {
116- const { build} = await import ( 'esbuild' )
117- const { cwd, external, format, logger} = options
118-
119- const code = exportPaths
120- . map ( ( id ) => ( format ? `import('${ id } ');` : `require('${ id } ');` ) )
121- . join ( '\n' )
122-
123- try {
124- const esbuildResult = await build ( {
125- bundle : true ,
126- external,
127- format,
128- logLevel : 'silent' ,
129- // otherwise output maps to stdout as we're using stdin
130- outfile : '/dev/null' ,
131- platform : 'node' ,
132- // We're not interested in CSS files that might be imported as a side effect, so we'll treat them as empty
133- loader : { '.css' : 'empty' } ,
134- stdin : {
135- contents : code ,
136- loader : 'js' ,
137- resolveDir : cwd ,
138- } ,
139- } )
140-
141- if ( esbuildResult . errors . length > 0 ) {
142- for ( const msg of esbuildResult . errors ) {
143- printEsbuildMessage ( logger . warn , msg )
144-
145- logger . log ( )
146- }
147-
148- process . exit ( 1 )
149- }
81+ async function checkWithPublint ( cwd : string , logger : Logger ) {
82+ const { publint} = await import ( 'publint' )
83+ const { formatMessage} = await import ( 'publint/utils' )
84+ const { readFileSync} = await import ( 'node:fs' )
15085
151- const esbuildWarnings = esbuildResult . warnings . filter ( ( msg ) => {
152- return ! ( msg . detail || msg . text ) . includes ( `does not affect esbuild's own target setting` )
153- } )
86+ const pkgJsonPath = path . resolve ( cwd , 'package.json' )
87+ const pkg = JSON . parse ( readFileSync ( pkgJsonPath , 'utf-8' ) )
15488
155- for ( const msg of esbuildWarnings ) {
156- printEsbuildMessage ( logger . warn , msg )
89+ const { messages} = await publint ( { pkgDir : cwd } )
15790
158- logger . log ( )
159- }
160- } catch ( err ) {
161- if ( isEsbuildFailure ( err ) ) {
162- const { errors} = err
91+ if ( messages . length > 0 ) {
92+ for ( const message of messages ) {
93+ const formatted = formatMessage ( message , pkg )
16394
164- for ( const msg of errors ) {
165- printEsbuildMessage ( logger . error , msg )
95+ if ( ! formatted ) continue
16696
167- logger . log ( )
97+ if ( message . type === 'error' ) {
98+ logger . error ( formatted )
99+ } else if ( message . type === 'warning' ) {
100+ logger . warn ( formatted )
101+ } else {
102+ logger . info ( formatted )
168103 }
169- } else if ( err instanceof Error ) {
170- logger . error ( err . stack || err . message )
171-
172- logger . log ( )
173- } else {
174- logger . error ( String ( err ) )
175104
176105 logger . log ( )
177106 }
178107
179- process . exit ( 1 )
180- }
181- }
182-
183- function printEsbuildMessage ( log : ( ...args : unknown [ ] ) => void , msg : Message ) {
184- if ( msg . location ) {
185- log (
186- [
187- `${ msg . detail || msg . text } \n` ,
188- `${ msg . location . line } | ${ msg . location . lineText } \n` ,
189- `in ./${ msg . location . file } :${ msg . location . line } :${ msg . location . column } ` ,
190- ] . join ( '' ) ,
191- )
192- } else {
193- log ( msg . detail || msg . text )
108+ const hasErrors = messages . some ( ( m ) => m . type === 'error' )
109+ if ( hasErrors ) {
110+ process . exit ( 1 )
111+ }
194112 }
195113}
196114
197- function isEsbuildFailure ( err : unknown ) : err is BuildFailure {
198- return (
199- err instanceof Error &&
200- 'errors' in err &&
201- Array . isArray ( err . errors ) &&
202- err . errors . every ( isEsbuildMessage ) &&
203- 'warnings' in err &&
204- Array . isArray ( err . warnings ) &&
205- err . warnings . every ( isEsbuildMessage )
206- )
207- }
208-
209- function isEsbuildMessage ( msg : unknown ) : msg is Message {
210- return (
211- typeof msg === 'object' &&
212- msg !== null &&
213- 'text' in msg &&
214- typeof msg . text === 'string' &&
215- 'location' in msg &&
216- ( msg . location === null || typeof msg . location === 'object' )
217- )
218- }
219-
220115async function checkApiExtractorReleaseTags ( ctx : BuildContext ) {
221116 const [
222117 { Extractor, ExtractorConfig} ,
@@ -235,18 +130,20 @@ async function checkApiExtractorReleaseTags(ctx: BuildContext) {
235130 const customTags = ctx . config ?. extract ?. customTags || [ ]
236131 const bundledPackages = ctx . bundledPackages
237132 const distPath = ctx . distPath
238- const outDir = ctx . ts . config ?. options . outDir
133+ const outDir = ctx . ts . config ?. options . outDir || distPath
239134 const rules = ctx . config ?. extract ?. rules || { }
240135
241- if ( ! outDir ) {
242- throw new Error ( 'tsconfig.json is missing `compilerOptions.outDir`' )
243- }
244-
245136 for ( const exp of Object . values ( ctx . exports || { } ) ) {
246137 if ( ! exp . _exported || ! exp . default . endsWith ( '.js' ) ) continue
247138 const dtsPath = exp . default . replace ( / \. j s $ / , '.d.ts' )
248139 const exportPath = path . resolve ( ctx . cwd , dtsPath )
249140
141+ // Skip if declaration file doesn't exist (e.g., JavaScript-only projects)
142+ const { existsSync} = await import ( 'node:fs' )
143+ if ( ! existsSync ( exportPath ) ) {
144+ continue
145+ }
146+
250147 const tsdocConfigFile = await createTSDocConfig ( {
251148 customTags,
252149 } )
0 commit comments