@@ -3,7 +3,6 @@ import execa from "execa";
33import * as fse from "fs-extra" ;
44import globby from "globby" ;
55import pLimit from "p-limit" ;
6- import fs from "fs-extra" ;
76import { resolve , join , dirname } from "path" ;
87import { Consola } from "consola" ;
98import get from "lodash.get" ;
@@ -124,9 +123,9 @@ export const buildCommand = createCommand<{}, {}>((api) => {
124123 const cwd = rootPackageJSONPath . replace ( "/package.json" , "" ) ;
125124 const buildPath = join ( cwd , ".bob" ) ;
126125
127- await fs . remove ( buildPath ) ;
126+ await fse . remove ( buildPath ) ;
128127 await buildTypeScript ( buildPath ) ;
129- const pkg = await fs . readJSON ( resolve ( cwd , "package.json" ) ) ;
128+ const pkg = await fse . readJSON ( resolve ( cwd , "package.json" ) ) ;
130129 const fullName : string = pkg . name ;
131130
132131 const distPath = join ( cwd , "dist" ) ;
@@ -160,15 +159,15 @@ export const buildCommand = createCommand<{}, {}>((api) => {
160159 packages . map ( ( packagePath ) =>
161160 limit ( async ( ) => {
162161 const cwd = packagePath . replace ( "/package.json" , "" ) ;
163- const pkg = await fs . readJSON ( resolve ( cwd , "package.json" ) ) ;
162+ const pkg = await fse . readJSON ( resolve ( cwd , "package.json" ) ) ;
164163 const fullName : string = pkg . name ;
165164 return { packagePath, cwd, pkg, fullName } ;
166165 } )
167166 )
168167 ) ;
169168
170169 const bobBuildPath = join ( cwd , ".bob" ) ;
171- await fs . remove ( bobBuildPath ) ;
170+ await fse . remove ( bobBuildPath ) ;
172171 await buildTypeScript ( bobBuildPath ) ;
173172
174173 await Promise . all (
@@ -210,6 +209,7 @@ async function build({
210209 pkg : {
211210 name : string ;
212211 buildOptions : BuildOptions ;
212+ bin ?: Record < string , string > ;
213213 } ;
214214 fullName : string ;
215215 config : BobConfig ;
@@ -225,10 +225,10 @@ async function build({
225225 validatePackageJson ( pkg ) ;
226226
227227 // remove <project>/dist
228- await fs . remove ( distPath ) ;
228+ await fse . remove ( distPath ) ;
229229
230230 // Copy type definitions
231- await fs . ensureDir ( join ( distPath , "typings" ) ) ;
231+ await fse . ensureDir ( join ( distPath , "typings" ) ) ;
232232
233233 const declarations = await globby ( "**/*.d.ts" , {
234234 cwd : getBuildPath ( "esm" ) ,
@@ -239,7 +239,7 @@ async function build({
239239 await Promise . all (
240240 declarations . map ( ( filePath ) =>
241241 limit ( ( ) =>
242- fs . copy (
242+ fse . copy (
243243 join ( getBuildPath ( "esm" ) , filePath ) ,
244244 join ( distPath , "typings" , filePath )
245245 )
@@ -248,7 +248,7 @@ async function build({
248248 ) ;
249249
250250 // Move ESM to dist/esm
251- await fs . ensureDir ( join ( distPath , "esm" ) ) ;
251+ await fse . ensureDir ( join ( distPath , "esm" ) ) ;
252252
253253 const esmFiles = await globby ( "**/*.js" , {
254254 cwd : getBuildPath ( "esm" ) ,
@@ -259,7 +259,7 @@ async function build({
259259 await Promise . all (
260260 esmFiles . map ( ( filePath ) =>
261261 limit ( ( ) =>
262- fs . copy (
262+ fse . copy (
263263 join ( getBuildPath ( "esm" ) , filePath ) ,
264264 join ( distPath , "esm" , filePath )
265265 )
@@ -268,7 +268,7 @@ async function build({
268268 ) ;
269269
270270 // Transpile ESM to CJS and move CJS to dist/cjs
271- await fs . ensureDir ( join ( distPath , "cjs" ) ) ;
271+ await fse . ensureDir ( join ( distPath , "cjs" ) ) ;
272272
273273 const cjsFiles = await globby ( "**/*.js" , {
274274 cwd : getBuildPath ( "cjs" ) ,
@@ -279,7 +279,7 @@ async function build({
279279 await Promise . all (
280280 cjsFiles . map ( ( filePath ) =>
281281 limit ( ( ) =>
282- fs . copy (
282+ fse . copy (
283283 join ( getBuildPath ( "cjs" ) , filePath ) ,
284284 join ( distPath , "cjs" , filePath )
285285 )
@@ -288,13 +288,13 @@ async function build({
288288 ) ;
289289
290290 // Add package.json to dist/cjs to ensure files are interpreted as commonjs
291- await fs . writeFile (
291+ await fse . writeFile (
292292 join ( distPath , "cjs" , "package.json" ) ,
293293 JSON . stringify ( { type : "commonjs" } )
294294 ) ;
295295
296296 // move the package.json to dist
297- await fs . writeFile (
297+ await fse . writeFile (
298298 join ( distPath , "package.json" ) ,
299299 JSON . stringify ( rewritePackageJson ( pkg ) , null , 2 )
300300 ) ;
@@ -306,6 +306,21 @@ async function build({
306306 distPath
307307 ) ;
308308
309+ if ( pkg . bin ) {
310+ if ( globalThis . process . platform === "win32" ) {
311+ console . warn (
312+ "Package includes bin files, but cannot set the executable bit on Windows.\n" +
313+ "Please manually set the executable bit on the bin files before publishing."
314+ ) ;
315+ } else {
316+ await Promise . all (
317+ Object . values ( pkg . bin ) . map ( ( filePath ) =>
318+ execa ( "chmod" , [ "+x" , join ( cwd , filePath ) ] )
319+ )
320+ ) ;
321+ }
322+ }
323+
309324 reporter . success ( `Built ${ pkg . name } ` ) ;
310325}
311326
@@ -378,29 +393,46 @@ export function validatePackageJson(pkg: any) {
378393 ) ;
379394 }
380395
381- expect ( "main" , presetFields . main ) ;
382- expect ( "module" , presetFields . module ) ;
383- expect ( "typings" , presetFields . typings ) ;
384- expect ( "typescript.definition" , presetFields . typescript . definition ) ;
385-
386- expect ( "exports['.'].require" , presetFields . exports [ "." ] . require ) ;
387- expect ( "exports['.'].import" , presetFields . exports [ "." ] . import ) ;
388- expect ( "exports['.'].default" , presetFields . exports [ "." ] . default ) ;
389- expect ( "exports['./*'].require" , presetFields . exports [ "./*" ] . require ) ;
390- expect ( "exports['./*'].import" , presetFields . exports [ "./*" ] . import ) ;
391- expect ( "exports['./*'].default" , presetFields . exports [ "./*" ] . default ) ;
396+ // If the package has NO binary we need to check the exports map.
397+ // a package should either
398+ // 1. have a bin property
399+ // 2. have a exports property
400+ // 3. have an exports and bin property
401+ if ( Object . keys ( pkg . bin ?? { } ) . length === 0 ) {
402+ expect ( "main" , presetFields . main ) ;
403+ expect ( "module" , presetFields . module ) ;
404+ expect ( "typings" , presetFields . typings ) ;
405+ expect ( "typescript.definition" , presetFields . typescript . definition ) ;
406+ } else if (
407+ pkg . main !== undefined ||
408+ pkg . module !== undefined ||
409+ pkg . exports !== undefined ||
410+ pkg . typings !== undefined ||
411+ pkg . typescript !== undefined
412+ ) {
413+ // if there is no bin property, we NEED to check the exports.
414+ expect ( "main" , presetFields . main ) ;
415+ expect ( "module" , presetFields . module ) ;
416+ expect ( "typings" , presetFields . typings ) ;
417+ expect ( "typescript.definition" , presetFields . typescript . definition ) ;
418+
419+ // For now we enforce a top level exports property
420+ expect ( "exports['.'].require" , presetFields . exports [ "." ] . require ) ;
421+ expect ( "exports['.'].import" , presetFields . exports [ "." ] . import ) ;
422+ expect ( "exports['.'].default" , presetFields . exports [ "." ] . default ) ;
423+ }
392424}
393425
394426async function copyToDist ( cwd : string , files : string [ ] , distDir : string ) {
395427 const allFiles = await globby ( files , { cwd } ) ;
396428
397429 return Promise . all (
398430 allFiles . map ( async ( file ) => {
399- if ( await fs . pathExists ( join ( cwd , file ) ) ) {
431+ if ( await fse . pathExists ( join ( cwd , file ) ) ) {
400432 const sourcePath = join ( cwd , file ) ;
401433 const destPath = join ( cwd , distDir , file . replace ( "src/" , "" ) ) ;
402434 await mkdirp ( dirname ( destPath ) ) ;
403- await fs . copyFile ( sourcePath , destPath ) ;
435+ await fse . copyFile ( sourcePath , destPath ) ;
404436 }
405437 } )
406438 ) ;
0 commit comments