@@ -33,6 +33,28 @@ export default async function runExecutor(
3333 context : ExecutorContext
3434) {
3535 const pm = detectPackageManager ( ) ;
36+
37+ // Check if npm is installed (needed for dist-tag management and as fallback for view command)
38+ let isNpmInstalled = false ;
39+ try {
40+ isNpmInstalled =
41+ execSync ( 'npm --version' , {
42+ encoding : 'utf-8' ,
43+ windowsHide : true ,
44+ stdio : [ 'ignore' , 'pipe' , 'ignore' ] ,
45+ } ) . trim ( ) !== '' ;
46+ } catch {
47+ // Allow missing npm only when using bun
48+ if ( pm !== 'bun' ) {
49+ console . error (
50+ `npm was not found in the current environment. This is only supported when using \`bun\` as a package manager, but your detected package manager is "${ pm } "`
51+ ) ;
52+ return {
53+ success : false ,
54+ } ;
55+ }
56+ }
57+
3658 /**
3759 * We need to check both the env var and the option because the executor may have been triggered
3860 * indirectly via dependsOn, in which case the env var will be set, but the option will not.
@@ -126,9 +148,14 @@ Please update the local dependency on "${depName}" to be a valid semantic versio
126148 warnFn
127149 ) ;
128150
129- const npmViewCommandSegments = [
130- `npm view ${ packageName } versions dist-tags --json --"${ registryConfigKey } =${ registry } "` ,
131- ] ;
151+ // Use bun info when bun is the package manager, otherwise use npm view
152+ // (npm view works across npm/pnpm/yarn environments and is the established default)
153+ const npmViewCommandSegments =
154+ pm === 'bun'
155+ ? [ 'bun info' , packageName , `--json --"${ registryConfigKey } =${ registry } "` ]
156+ : [
157+ `npm view ${ packageName } versions dist-tags --json --"${ registryConfigKey } =${ registry } "` ,
158+ ] ;
132159 const npmDistTagAddCommandSegments = [
133160 `npm dist-tag add ${ packageName } @${ packageJson . version } ${ tag } --"${ registryConfigKey } =${ registry } "` ,
134161 ] ;
@@ -162,103 +189,134 @@ Please update the local dependency on "${depName}" to be a valid semantic versio
162189 } ;
163190 }
164191
165- // If only one version of a package exists in the registry, versions will be a string instead of an array.
166- const versions = Array . isArray ( resultJson . versions )
167- ? resultJson . versions
168- : [ resultJson . versions ] ;
169-
170- if ( versions . includes ( currentVersion ) ) {
171- try {
172- if ( ! isDryRun ) {
173- execSync ( npmDistTagAddCommandSegments . join ( ' ' ) , {
174- env : processEnv ( true ) ,
175- cwd : context . root ,
176- stdio : 'ignore' ,
177- windowsHide : false ,
178- } ) ;
179- console . log (
180- `Added the dist-tag ${ tag } to v${ currentVersion } for registry ${ registry } .\n`
181- ) ;
182- } else {
183- console . log (
184- `Would add the dist-tag ${ tag } to v${ currentVersion } for registry ${ registry } , but ${ chalk . keyword (
185- 'orange'
186- ) ( '[dry-run]' ) } was set.\n`
187- ) ;
188- }
189- return {
190- success : true ,
191- } ;
192- } catch ( err ) {
193- try {
194- const stdoutData = JSON . parse ( err . stdout ?. toString ( ) || '{}' ) ;
195-
196- // If the error is that the package doesn't exist, then we can ignore it because we will be publishing it for the first time in the next step
197- if (
198- ! (
199- stdoutData . error ?. code ?. includes ( 'E404' ) &&
200- stdoutData . error ?. summary ?. includes ( 'no such package available' )
201- ) &&
202- ! (
203- err . stderr ?. toString ( ) . includes ( 'E404' ) &&
204- err . stderr ?. toString ( ) . includes ( 'no such package available' )
205- )
206- ) {
207- console . error ( 'npm dist-tag add error:' ) ;
208- // npm returns error.summary and error.detail
209- if ( stdoutData . error ?. summary ) {
210- console . error ( stdoutData . error . summary ) ;
211- }
212- if ( stdoutData . error ?. detail ) {
213- console . error ( stdoutData . error . detail ) ;
214- }
215- // pnpm returns error.code and error.message
216- if ( stdoutData . error ?. code && ! stdoutData . error ?. summary ) {
217- console . error ( `Error code: ${ stdoutData . error . code } ` ) ;
218- }
219- if ( stdoutData . error ?. message && ! stdoutData . error ?. summary ) {
220- console . error ( stdoutData . error . message ) ;
221- }
192+ if ( isNpmInstalled ) {
193+ // If only one version of a package exists in the registry, versions will be a string instead of an array.
194+ const versions = Array . isArray ( resultJson . versions )
195+ ? resultJson . versions
196+ : [ resultJson . versions ] ;
222197
223- if ( context . isVerbose ) {
224- console . error ( 'npm dist-tag add stdout:' ) ;
225- console . error ( JSON . stringify ( stdoutData , null , 2 ) ) ;
198+ if ( versions . includes ( currentVersion ) ) {
199+ try {
200+ if ( ! isDryRun ) {
201+ execSync ( npmDistTagAddCommandSegments . join ( ' ' ) , {
202+ env : processEnv ( true ) ,
203+ cwd : context . root ,
204+ stdio : 'ignore' ,
205+ windowsHide : false ,
206+ } ) ;
207+ console . log (
208+ `Added the dist-tag ${ tag } to v${ currentVersion } for registry ${ registry } .\n`
209+ ) ;
210+ } else {
211+ console . log (
212+ `Would add the dist-tag ${ tag } to v${ currentVersion } for registry ${ registry } , but ${ chalk . keyword (
213+ 'orange'
214+ ) ( '[dry-run]' ) } was set.\n`
215+ ) ;
216+ }
217+ return {
218+ success : true ,
219+ } ;
220+ } catch ( err ) {
221+ try {
222+ const stdoutData = JSON . parse ( err . stdout ?. toString ( ) || '{}' ) ;
223+
224+ // If the error is that the package doesn't exist, then we can ignore it because we will be publishing it for the first time in the next step
225+ if (
226+ ! (
227+ stdoutData . error ?. code ?. includes ( 'E404' ) &&
228+ stdoutData . error ?. summary ?. includes (
229+ 'no such package available'
230+ )
231+ ) &&
232+ ! (
233+ err . stderr ?. toString ( ) . includes ( 'E404' ) &&
234+ err . stderr ?. toString ( ) . includes ( 'no such package available' )
235+ )
236+ ) {
237+ console . error ( 'npm dist-tag add error:' ) ;
238+ // npm returns error.summary and error.detail
239+ if ( stdoutData . error ?. summary ) {
240+ console . error ( stdoutData . error . summary ) ;
241+ }
242+ if ( stdoutData . error ?. detail ) {
243+ console . error ( stdoutData . error . detail ) ;
244+ }
245+ // pnpm returns error.code and error.message
246+ if ( stdoutData . error ?. code && ! stdoutData . error ?. summary ) {
247+ console . error ( `Error code: ${ stdoutData . error . code } ` ) ;
248+ }
249+ if ( stdoutData . error ?. message && ! stdoutData . error ?. summary ) {
250+ console . error ( stdoutData . error . message ) ;
251+ }
252+
253+ if ( context . isVerbose ) {
254+ console . error ( 'npm dist-tag add stdout:' ) ;
255+ console . error ( JSON . stringify ( stdoutData , null , 2 ) ) ;
256+ }
257+ return {
258+ success : false ,
259+ } ;
226260 }
261+ } catch ( err ) {
262+ console . error (
263+ 'Something unexpected went wrong when processing the npm dist-tag add output\n' ,
264+ err
265+ ) ;
227266 return {
228267 success : false ,
229268 } ;
230269 }
231- } catch ( err ) {
232- console . error (
233- 'Something unexpected went wrong when processing the npm dist-tag add output\n' ,
234- err
235- ) ;
236- return {
237- success : false ,
238- } ;
239270 }
240271 }
241272 }
242273 } catch ( err ) {
243- const stdoutData = JSON . parse ( err . stdout ?. toString ( ) || '{}' ) ;
244- // If the error is that the package doesn't exist, then we can ignore it because we will be publishing it for the first time in the next step
245- if (
246- ! (
247- stdoutData . error ?. code ?. includes ( 'E404' ) &&
248- stdoutData . error ?. summary ?. toLowerCase ( ) . includes ( 'not found' )
249- ) &&
250- ! (
251- err . stderr ?. toString ( ) . includes ( 'E404' ) &&
252- err . stderr ?. toString ( ) . toLowerCase ( ) . includes ( 'not found' )
253- )
254- ) {
255- console . error (
256- `Something unexpected went wrong when checking for existing dist-tags.\n` ,
257- err
258- ) ;
259- return {
260- success : false ,
261- } ;
274+ try {
275+ const stdoutData = JSON . parse ( err . stdout ?. toString ( ) || '{}' ) ;
276+ // If the error is that the package doesn't exist, then we can ignore it because we will be publishing it for the first time in the next step
277+ if (
278+ ! (
279+ stdoutData . error ?. code ?. includes ( 'E404' ) &&
280+ stdoutData . error ?. summary ?. toLowerCase ( ) . includes ( 'not found' )
281+ ) &&
282+ ! (
283+ err . stderr ?. toString ( ) . includes ( 'E404' ) &&
284+ err . stderr ?. toString ( ) . toLowerCase ( ) . includes ( 'not found' )
285+ ) &&
286+ // bun uses plain '404' instead of 'E404'
287+ ! (
288+ err . stderr ?. toString ( ) . includes ( '404' ) &&
289+ err . stderr ?. toString ( ) . toLowerCase ( ) . includes ( 'not found' )
290+ )
291+ ) {
292+ console . error (
293+ `Something unexpected went wrong when checking for existing dist-tags.\n` ,
294+ err
295+ ) ;
296+ return {
297+ success : false ,
298+ } ;
299+ }
300+ } catch {
301+ // JSON parse failed entirely — check stderr/stdout for plain 404
302+ const stderrStr = err . stderr ?. toString ( ) || '' ;
303+ const stdoutStr = err . stdout ?. toString ( ) || '' ;
304+ if (
305+ ! (
306+ ( stderrStr . includes ( '404' ) &&
307+ stderrStr . toLowerCase ( ) . includes ( 'not found' ) ) ||
308+ ( stdoutStr . includes ( '404' ) &&
309+ stdoutStr . toLowerCase ( ) . includes ( 'not found' ) )
310+ )
311+ ) {
312+ console . error (
313+ `Something unexpected went wrong when checking for existing dist-tags.\n` ,
314+ err
315+ ) ;
316+ return {
317+ success : false ,
318+ } ;
319+ }
262320 }
263321 }
264322 }
0 commit comments