@b9g/libuild 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/libuild.js CHANGED
@@ -1,4 +1,12 @@
1
1
  /// <reference types="./libuild.d.ts" />
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined")
6
+ return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+
2
10
  // src/libuild.ts
3
11
  import * as FS3 from "fs/promises";
4
12
  import * as Path3 from "path";
@@ -67,6 +75,21 @@ function externalEntrypointsPlugin(options) {
67
75
  };
68
76
  }
69
77
  });
78
+ build3.onResolve({ filter: /^\.\.\/(?:src|bin)\// }, (args) => {
79
+ const withoutExt = args.path.replace(/\.(ts|js)$/, "");
80
+ const match = withoutExt.match(/^\.\.\/(?:src|bin)\/(.+)$/);
81
+ if (match) {
82
+ const entryName = match[1];
83
+ if (externalEntries.includes(entryName)) {
84
+ const dir = withoutExt.match(/^(\.\.\/(?:src|bin))\//)?.[1];
85
+ return {
86
+ path: `${dir}/${entryName}${outputExtension}`,
87
+ external: true
88
+ };
89
+ }
90
+ }
91
+ return void 0;
92
+ });
70
93
  }
71
94
  };
72
95
  }
@@ -92,27 +115,34 @@ function dtsPlugin(options) {
92
115
  let TS;
93
116
  try {
94
117
  TS = await import("typescript");
95
- } catch {
118
+ } catch (error) {
96
119
  return;
97
120
  }
98
121
  const tsFiles = entryFiles.filter((file) => {
99
122
  if (!file.endsWith(".ts"))
100
123
  return false;
101
124
  return options.entryPoints.some((entryPoint) => {
102
- const normalizedEntry = Path2.resolve(entryPoint);
103
- const normalizedFile = Path2.resolve(file);
104
- return normalizedEntry === normalizedFile;
125
+ try {
126
+ const fs = __require("fs");
127
+ const normalizedEntry = fs.realpathSync(Path2.resolve(entryPoint));
128
+ const normalizedFile = fs.realpathSync(Path2.resolve(file));
129
+ return normalizedEntry === normalizedFile;
130
+ } catch {
131
+ const normalizedEntry = Path2.resolve(entryPoint);
132
+ const normalizedFile = Path2.resolve(file);
133
+ return normalizedEntry === normalizedFile;
134
+ }
105
135
  });
106
136
  });
107
137
  if (tsFiles.length === 0) {
108
138
  return;
109
139
  }
110
140
  try {
141
+ await FS2.mkdir(options.outDir, { recursive: true });
111
142
  const compilerOptions = {
112
143
  declaration: true,
113
144
  emitDeclarationOnly: true,
114
145
  outDir: options.outDir,
115
- rootDir: options.rootDir,
116
146
  skipLibCheck: true,
117
147
  esModuleInterop: true,
118
148
  target: TS.ScriptTarget.ES2020,
@@ -187,6 +217,38 @@ ${content}`;
187
217
  }
188
218
 
189
219
  // src/libuild.ts
220
+ function generateRuntimeBanner(pkg) {
221
+ const prefersBun = pkg.engines?.bun !== void 0;
222
+ if (prefersBun) {
223
+ return '//bin/true; exec "$({ [ "${npm_config_user_agent#bun/}" != "$npm_config_user_agent" ] || true; } && command -v bun || command -v node)" "$0" "$@"';
224
+ } else {
225
+ return '//bin/true; exec "$([ "${npm_config_user_agent#bun/}" != "$npm_config_user_agent" ] && command -v bun || command -v node)" "$0" "$@"';
226
+ }
227
+ }
228
+ async function processJavaScriptExecutable(filePath, runtimeBanner) {
229
+ try {
230
+ const contents = await FS3.readFile(filePath, "utf8");
231
+ const lines = contents.split("\n");
232
+ let modified = false;
233
+ if (lines[0]?.startsWith("#!")) {
234
+ const existingShebang = lines[0];
235
+ if (existingShebang.includes("python") || existingShebang.includes("bash") || !existingShebang.includes("node") && !existingShebang.includes("bun") && !existingShebang.includes("sh")) {
236
+ console.warn(`\u26A0\uFE0F WARNING: ${filePath} has non-JavaScript shebang but is a JS file. Adding dual runtime support.`);
237
+ }
238
+ lines[0] = "#!/usr/bin/env sh";
239
+ lines.splice(1, 0, runtimeBanner);
240
+ modified = true;
241
+ } else {
242
+ lines.unshift("#!/usr/bin/env sh", runtimeBanner);
243
+ modified = true;
244
+ }
245
+ if (modified) {
246
+ await FS3.writeFile(filePath, lines.join("\n"));
247
+ }
248
+ } catch (error) {
249
+ console.warn(`\u26A0\uFE0F WARNING: Could not process executable ${filePath}: ${error.message}`);
250
+ }
251
+ }
190
252
  function isValidEntrypoint(filename) {
191
253
  if (!filename.endsWith(".ts") && !filename.endsWith(".js"))
192
254
  return false;
@@ -213,6 +275,22 @@ async function findEntrypoints(srcDir) {
213
275
  return isValidEntrypoint(dirent.name);
214
276
  }).map((dirent) => Path3.basename(dirent.name, Path3.extname(dirent.name))).sort();
215
277
  }
278
+ async function findBinEntrypoints(binDir) {
279
+ try {
280
+ const files = await FS3.readdir(binDir, { withFileTypes: true });
281
+ return files.filter((dirent) => {
282
+ if (dirent.isDirectory()) {
283
+ return false;
284
+ }
285
+ return isValidEntrypoint(dirent.name);
286
+ }).map((dirent) => Path3.basename(dirent.name, Path3.extname(dirent.name))).sort();
287
+ } catch (error) {
288
+ if (error.code === "ENOENT") {
289
+ return [];
290
+ }
291
+ throw error;
292
+ }
293
+ }
216
294
  function detectMainEntry(pkg, entries) {
217
295
  function extractEntryFromPath(path) {
218
296
  const match = path.match(/\.\/src\/([^.]+)/);
@@ -310,18 +388,38 @@ function checkIfExportIsStale(exportKey, exportValue, entries) {
310
388
  }
311
389
  return entryName ? !entries.includes(entryName) : false;
312
390
  }
313
- function generateExports(entries, mainEntry, options, existingExports = {}) {
391
+ async function generateExports(entries, mainEntry, options, existingExports = {}, distDir, allBinEntries = []) {
314
392
  const exports = {};
315
393
  const staleExports = [];
316
- function createExportEntry(entry) {
317
- const exportEntry = {
318
- types: `./src/${entry}.d.ts`,
319
- import: `./src/${entry}.js`
320
- };
321
- if (options.formats.cjs) {
322
- exportEntry.require = `./src/${entry}.cjs`;
394
+ async function createExportEntry(entry) {
395
+ if (entry.startsWith("bin/")) {
396
+ const binEntry = entry.replace("bin/", "");
397
+ const exportEntry = {
398
+ import: `./bin/${binEntry}.js`
399
+ };
400
+ if (distDir) {
401
+ const dtsPath = Path3.join(distDir, "bin", `${binEntry}.d.ts`);
402
+ if (await fileExists(dtsPath)) {
403
+ exportEntry.types = `./bin/${binEntry}.d.ts`;
404
+ }
405
+ }
406
+ return exportEntry;
407
+ } else {
408
+ const exportEntry = {
409
+ import: `./src/${entry}.js`
410
+ };
411
+ if (distDir) {
412
+ const dtsPath = Path3.join(distDir, "src", `${entry}.d.ts`);
413
+ const exists = await fileExists(dtsPath);
414
+ if (exists) {
415
+ exportEntry.types = `./src/${entry}.d.ts`;
416
+ }
417
+ }
418
+ if (options.formats.cjs) {
419
+ exportEntry.require = `./src/${entry}.cjs`;
420
+ }
421
+ return exportEntry;
323
422
  }
324
- return exportEntry;
325
423
  }
326
424
  function expandExistingExport(existing, entryFromPath) {
327
425
  if (typeof existing === "string") {
@@ -395,7 +493,7 @@ function generateExports(entries, mainEntry, options, existingExports = {}) {
395
493
  }
396
494
  }
397
495
  if (!exports["."]) {
398
- exports["."] = createExportEntry(mainEntry);
496
+ exports["."] = await createExportEntry(mainEntry);
399
497
  } else {
400
498
  exports["."] = expandExistingExport(exports["."], mainEntry);
401
499
  }
@@ -404,7 +502,7 @@ function generateExports(entries, mainEntry, options, existingExports = {}) {
404
502
  continue;
405
503
  const key = `./${entry}`;
406
504
  if (!exports[key]) {
407
- exports[key] = createExportEntry(entry);
505
+ exports[key] = await createExportEntry(entry);
408
506
  } else {
409
507
  exports[key] = expandExistingExport(exports[key], entry);
410
508
  }
@@ -442,67 +540,96 @@ function transformSrcToDist(value) {
442
540
  }
443
541
  return value;
444
542
  }
445
- async function validateBinPaths(value, srcDir, fieldName = "bin") {
543
+ async function validateBinPaths(value, cwd, save, fieldName = "bin") {
446
544
  if (typeof value === "string") {
447
- await validateSingleBinPath(value, fieldName, srcDir);
545
+ await validateSingleBinPath(value, fieldName, cwd, save);
448
546
  } else if (typeof value === "object" && value !== null) {
449
547
  for (const [key, val] of Object.entries(value)) {
450
548
  if (typeof val === "string") {
451
- await validateSingleBinPath(val, `${fieldName}.${key}`, srcDir);
549
+ await validateSingleBinPath(val, `${fieldName}.${key}`, cwd, save);
452
550
  }
453
551
  }
454
552
  }
455
553
  }
456
- async function validateSingleBinPath(binPath, fieldName, srcDir) {
457
- if (binPath.startsWith("dist/") || binPath.startsWith("./dist/")) {
554
+ async function validateSingleBinPath(binPath, fieldName, cwd, save) {
555
+ if (save) {
556
+ return;
557
+ }
558
+ if (binPath.startsWith("dist/src/") || binPath.startsWith("./dist/src/") || binPath.startsWith("dist/bin/") || binPath.startsWith("./dist/bin/")) {
559
+ const fullPath2 = Path3.join(cwd, binPath);
560
+ const distExists = await fileExists(fullPath2);
561
+ if (distExists) {
562
+ return;
563
+ }
458
564
  let srcPath;
565
+ let sourceDir;
459
566
  if (binPath.startsWith("./dist/src/")) {
460
567
  srcPath = binPath.replace("./dist/src/", "src/");
568
+ sourceDir = "src";
461
569
  } else if (binPath.startsWith("dist/src/")) {
462
570
  srcPath = binPath.replace("dist/src/", "src/");
463
- } else if (binPath.startsWith("./dist/")) {
464
- srcPath = binPath.replace("./dist/", "src/");
465
- } else if (binPath.startsWith("dist/")) {
466
- srcPath = binPath.replace("dist/", "src/");
571
+ sourceDir = "src";
572
+ } else if (binPath.startsWith("./dist/bin/")) {
573
+ srcPath = binPath.replace("./dist/bin/", "bin/");
574
+ sourceDir = "bin";
575
+ } else if (binPath.startsWith("dist/bin/")) {
576
+ srcPath = binPath.replace("dist/bin/", "bin/");
577
+ sourceDir = "bin";
467
578
  } else {
468
- srcPath = "";
579
+ srcPath = binPath;
580
+ sourceDir = "src";
469
581
  }
470
- const basePath = srcPath.replace(/\.(js|cjs)$/, "");
471
- const tsPath = Path3.join(srcDir, "..", basePath + ".ts");
472
- const jsPath = Path3.join(srcDir, "..", basePath + ".js");
582
+ const basePath = srcPath.replace(/\.(js|cjs|mjs)$/, "");
583
+ const tsPath = Path3.join(cwd, basePath + ".ts");
584
+ const jsPath = Path3.join(cwd, basePath + ".js");
473
585
  const srcExists = await fileExists(tsPath) || await fileExists(jsPath);
474
586
  if (!srcExists) {
475
- throw new Error(`${fieldName} field points to dist/ directory: "${binPath}"
476
-
477
- The bin field should reference source files that libuild will build and transform:
478
- CORRECT: "bin": {"tool": "src/cli.js"}
479
- INCORRECT: "bin": {"tool": "dist/cli.js"}
480
-
481
- Libuild workflow:
482
- 1. Point bin entries to src/ files
483
- 2. Run 'libuild build --save' to update package.json with dist/ paths
484
- 3. Set "private": true in your development package.json
485
-
486
- If you need to include pre-built executable files, use the files field instead:
487
- "files": ["scripts/my-tool.js"]`);
587
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" but neither the dist file nor corresponding src file exists.
588
+
589
+ Create "${srcPath}" and run 'libuild build --save' to update paths correctly.`);
590
+ return;
488
591
  }
489
- console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to dist/ directory: "${binPath}"
592
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" which doesn't exist yet.
490
593
 
491
- libuild is ZERO-CONFIG - there is no libuild.config.js file!
492
- Configuration comes from your package.json fields.
594
+ Run 'libuild build' to create the dist files, or use 'libuild build --save' to update the path to "${srcPath}".`);
595
+ return;
596
+ }
597
+ if (binPath.startsWith("dist/") || binPath.startsWith("./dist/")) {
598
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" in dist/ directory.
493
599
 
494
- The libuild workflow is to point bin entries to src/ files and use --save to update paths.
495
- Consider changing to: "${srcPath}" and running 'libuild build --save'`);
600
+ libuild expects bin entries to point to src/ files. Consider changing to the corresponding src/ path and using --save.`);
496
601
  return;
497
602
  }
498
- if (binPath.startsWith("src/") || binPath.startsWith("./src/")) {
603
+ const fullPath = Path3.join(cwd, binPath);
604
+ const pathExists = await fileExists(fullPath);
605
+ if (pathExists) {
499
606
  return;
500
607
  }
501
- if (!binPath.startsWith("src/") && !binPath.startsWith("./src/")) {
502
- console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" which is not in src/ directory.
503
- Consider moving executable to src/ or using the files field for pre-built scripts.
504
- Run 'libuild build --save' after fixing to update paths correctly.`);
608
+ if (binPath.startsWith("src/") || binPath.startsWith("./src/") || binPath.startsWith("bin/") || binPath.startsWith("./bin/")) {
609
+ let normalizedPath = binPath.startsWith("./") ? binPath.slice(2) : binPath;
610
+ if (normalizedPath.startsWith("src/")) {
611
+ const srcRelativePath = normalizedPath.replace("src/", "");
612
+ if (srcRelativePath.includes("/")) {
613
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" which is in a subdirectory of src/.`);
614
+ console.warn(` libuild only auto-detects top-level src/ files as entrypoints.`);
615
+ console.warn(` Consider moving the file to the top-level src/ directory or use the bin/ directory for executables.`);
616
+ }
617
+ }
618
+ const basePath = fullPath.replace(/\.(js|cjs|mjs)$/, "");
619
+ const tsPath = basePath + ".ts";
620
+ const jsPath = basePath + ".js";
621
+ const tsExists = await fileExists(tsPath);
622
+ const jsExists = await fileExists(jsPath);
623
+ if (tsExists || jsExists) {
624
+ return;
625
+ }
505
626
  }
627
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" which doesn't exist
628
+
629
+ libuild is ZERO-CONFIG - there is no libuild.config.js file!
630
+ Configuration comes from your package.json fields.
631
+
632
+ Use 'libuild build --save' to update package.json with correct dist/ paths automatically.`);
506
633
  }
507
634
  function transformBinPaths(value) {
508
635
  if (typeof value === "string") {
@@ -512,10 +639,19 @@ function transformBinPaths(value) {
512
639
  if (value.startsWith("dist/src/")) {
513
640
  return value.replace("dist/", "");
514
641
  }
642
+ if (value.startsWith("./dist/bin/")) {
643
+ return value.replace("./dist/", "");
644
+ }
645
+ if (value.startsWith("dist/bin/")) {
646
+ return value.replace("dist/", "");
647
+ }
515
648
  if (value.startsWith("./src/")) {
516
649
  return value.replace("./", "");
517
650
  }
518
- if (value.startsWith("src/") || value === "src") {
651
+ if (value.startsWith("./bin/")) {
652
+ return value.replace("./", "");
653
+ }
654
+ if (value.startsWith("src/") || value === "src" || value.startsWith("bin/") || value === "bin") {
519
655
  return value;
520
656
  }
521
657
  return value;
@@ -560,7 +696,103 @@ function fixExportsForDist(obj) {
560
696
  }
561
697
  return obj;
562
698
  }
563
- function cleanPackageJSON(pkg, mainEntry, options) {
699
+ async function setExecutablePermissions(filePath) {
700
+ try {
701
+ await FS3.chmod(filePath, 493);
702
+ } catch (error) {
703
+ console.warn(`\u26A0\uFE0F WARNING: Could not set executable permissions for ${filePath}: ${error.message}`);
704
+ }
705
+ }
706
+ async function makeFilesExecutable(pkg, cwd, allBinEntries) {
707
+ const filesToMakeExecutable = [];
708
+ if (pkg.bin) {
709
+ if (typeof pkg.bin === "string") {
710
+ filesToMakeExecutable.push(Path3.join(cwd, pkg.bin));
711
+ } else if (typeof pkg.bin === "object") {
712
+ for (const binPath of Object.values(pkg.bin)) {
713
+ if (typeof binPath === "string") {
714
+ filesToMakeExecutable.push(Path3.join(cwd, binPath));
715
+ }
716
+ }
717
+ }
718
+ }
719
+ for (const binEntryInfo of allBinEntries) {
720
+ const baseDir = binEntryInfo.source === "src" ? "dist/src/bin" : "dist/bin";
721
+ const jsPath = Path3.join(cwd, baseDir, `${binEntryInfo.name}.js`);
722
+ const cjsPath = Path3.join(cwd, baseDir, `${binEntryInfo.name}.cjs`);
723
+ if (!filesToMakeExecutable.includes(jsPath)) {
724
+ filesToMakeExecutable.push(jsPath);
725
+ }
726
+ if (!filesToMakeExecutable.includes(cjsPath)) {
727
+ filesToMakeExecutable.push(cjsPath);
728
+ }
729
+ }
730
+ for (const filePath of filesToMakeExecutable) {
731
+ if (await fileExists(filePath)) {
732
+ await setExecutablePermissions(filePath);
733
+ }
734
+ }
735
+ }
736
+ async function resolveWorkspaceDependencies(dependencies, cwd) {
737
+ if (!dependencies)
738
+ return void 0;
739
+ const resolved = {};
740
+ for (const [depName, depVersion] of Object.entries(dependencies)) {
741
+ if (depVersion.startsWith("workspace:")) {
742
+ const resolvedVersion = await resolveWorkspaceVersion(depName, depVersion, cwd);
743
+ resolved[depName] = resolvedVersion;
744
+ } else {
745
+ resolved[depName] = depVersion;
746
+ }
747
+ }
748
+ return resolved;
749
+ }
750
+ async function resolveWorkspaceVersion(packageName, workspaceSpec, cwd) {
751
+ try {
752
+ if (workspaceSpec === "workspace:*") {
753
+ const packageNameParts = packageName.replace("@", "").replace("/", "-");
754
+ const packageBaseName = packageName.split("/").pop() || packageName;
755
+ const possiblePaths = [
756
+ `../packages/${packageNameParts}/package.json`,
757
+ `../packages/${packageBaseName}/package.json`,
758
+ `../${packageNameParts}/package.json`,
759
+ `../${packageBaseName}/package.json`,
760
+ `./packages/${packageNameParts}/package.json`,
761
+ `./packages/${packageBaseName}/package.json`,
762
+ `./${packageNameParts}/package.json`,
763
+ `./${packageBaseName}/package.json`
764
+ ];
765
+ for (const pkgPath of possiblePaths) {
766
+ try {
767
+ const resolvedPath = Path3.resolve(cwd, pkgPath);
768
+ if (await fileExists(resolvedPath)) {
769
+ const pkgContent = await FS3.readFile(resolvedPath, "utf-8");
770
+ const pkgJson = JSON.parse(pkgContent);
771
+ if (pkgJson.name === packageName) {
772
+ console.info(` Resolved workspace:* dependency "${packageName}" to ^${pkgJson.version}`);
773
+ return `^${pkgJson.version}`;
774
+ }
775
+ }
776
+ } catch {
777
+ continue;
778
+ }
779
+ }
780
+ console.warn(`\u26A0\uFE0F WARNING: Could not resolve workspace dependency "${packageName}" - keeping as workspace:*`);
781
+ console.warn(` Searched in: ${possiblePaths.map((p) => Path3.resolve(cwd, p)).join(", ")}`);
782
+ return workspaceSpec;
783
+ }
784
+ const versionPart = workspaceSpec.replace("workspace:", "");
785
+ if (versionPart !== "*") {
786
+ console.info(` Resolved workspace:${versionPart} dependency "${packageName}" to ${versionPart}`);
787
+ return versionPart;
788
+ }
789
+ return workspaceSpec;
790
+ } catch (error) {
791
+ console.warn(`\u26A0\uFE0F WARNING: Error resolving workspace dependency "${packageName}": ${error.message}`);
792
+ return workspaceSpec;
793
+ }
794
+ }
795
+ async function cleanPackageJSON(pkg, mainEntry, options, cwd, distDir) {
564
796
  const cleaned = {
565
797
  name: pkg.name,
566
798
  version: pkg.version
@@ -611,6 +843,8 @@ function cleanPackageJSON(pkg, mainEntry, options) {
611
843
  cleaned[field] = transformBinPaths(pkg[field]);
612
844
  } else if (pathFields.includes(field)) {
613
845
  cleaned[field] = transformSrcToDist(pkg[field]);
846
+ } else if (field === "dependencies" || field === "devDependencies" || field === "peerDependencies" || field === "optionalDependencies") {
847
+ cleaned[field] = await resolveWorkspaceDependencies(pkg[field], cwd);
614
848
  } else {
615
849
  cleaned[field] = pkg[field];
616
850
  }
@@ -623,7 +857,13 @@ function cleanPackageJSON(pkg, mainEntry, options) {
623
857
  cleaned.main = `src/${mainEntry}.cjs`;
624
858
  }
625
859
  cleaned.module = `src/${mainEntry}.js`;
626
- cleaned.types = `src/${mainEntry}.d.ts`;
860
+ if (distDir) {
861
+ const dtsPath = Path3.join(distDir, "src", `${mainEntry}.d.ts`);
862
+ const exists = await fileExists(dtsPath);
863
+ if (exists) {
864
+ cleaned.types = `src/${mainEntry}.d.ts`;
865
+ }
866
+ }
627
867
  return cleaned;
628
868
  }
629
869
  async function fileExists(filePath) {
@@ -660,7 +900,7 @@ async function build2(cwd, save = false) {
660
900
  const pkgPath = Path3.join(cwd, "package.json");
661
901
  const pkg = JSON.parse(await FS3.readFile(pkgPath, "utf-8"));
662
902
  if (pkg.bin) {
663
- await validateBinPaths(pkg.bin, srcDir, "bin");
903
+ await validateBinPaths(pkg.bin, cwd, save, "bin");
664
904
  }
665
905
  if (!pkg.private) {
666
906
  console.warn("\u26A0\uFE0F WARNING: Root package.json is not private - this could lead to accidental publishing of development package.json");
@@ -675,9 +915,19 @@ async function build2(cwd, save = false) {
675
915
  console.warn(" Add 'dist/' to your .gitignore file");
676
916
  }
677
917
  }
678
- const entries = await findEntrypoints(srcDir);
679
- if (entries.length === 0) {
680
- throw new Error("No entry points found in src/");
918
+ const binDir = Path3.join(cwd, "bin");
919
+ const srcEntries = await findEntrypoints(srcDir);
920
+ const binEntries = await findBinEntrypoints(binDir);
921
+ const allBinEntries = binEntries.map((entry) => ({ name: entry, source: "top-level" }));
922
+ const entries = [
923
+ ...srcEntries,
924
+ ...allBinEntries.map((entry) => `bin/${entry.name}`)
925
+ ];
926
+ if (srcEntries.length === 0 && allBinEntries.length === 0) {
927
+ throw new Error("No entry points found in src/ or bin/");
928
+ }
929
+ if (allBinEntries.length > 0) {
930
+ console.info(` Found bin entries: ${allBinEntries.map((entry) => entry.name).join(", ")}`);
681
931
  }
682
932
  const options = {
683
933
  formats: {
@@ -688,7 +938,7 @@ async function build2(cwd, save = false) {
688
938
  umd: entries.includes("umd")
689
939
  }
690
940
  };
691
- const mainEntry = detectMainEntry(pkg, entries);
941
+ const mainEntry = detectMainEntry(pkg, srcEntries);
692
942
  console.info(" Found entries:", entries.join(", "));
693
943
  console.info(" Main entry:", mainEntry);
694
944
  if (options.formats.cjs) {
@@ -719,11 +969,29 @@ async function build2(cwd, save = false) {
719
969
  const entryPoints = [];
720
970
  const umdEntries = [];
721
971
  for (const entry of entries) {
722
- const entryPath = Path3.join(srcDir, `${entry}.ts`);
723
- const jsEntryPath = Path3.join(srcDir, `${entry}.js`);
972
+ let entryPath;
973
+ let jsEntryPath;
974
+ let sourceDir;
975
+ if (entry.startsWith("bin/")) {
976
+ const binEntryName = entry.replace("bin/", "");
977
+ const binEntryInfo = allBinEntries.find((binEntry) => binEntry.name === binEntryName);
978
+ if (binEntryInfo?.source === "src") {
979
+ entryPath = Path3.join(srcDir, "bin", `${binEntryName}.ts`);
980
+ jsEntryPath = Path3.join(srcDir, "bin", `${binEntryName}.js`);
981
+ sourceDir = "src/bin/";
982
+ } else {
983
+ entryPath = Path3.join(binDir, `${binEntryName}.ts`);
984
+ jsEntryPath = Path3.join(binDir, `${binEntryName}.js`);
985
+ sourceDir = "bin/";
986
+ }
987
+ } else {
988
+ entryPath = Path3.join(srcDir, `${entry}.ts`);
989
+ jsEntryPath = Path3.join(srcDir, `${entry}.js`);
990
+ sourceDir = "src/";
991
+ }
724
992
  const actualPath = await fileExists(entryPath) ? entryPath : jsEntryPath;
725
993
  if (!await fileExists(actualPath)) {
726
- throw new Error(`Entry point file not found: ${actualPath}. Expected ${entry}.ts or ${entry}.js in src/ directory.`);
994
+ throw new Error(`Entry point file not found: ${actualPath}. Expected ${entry.replace("bin/", "")}.ts or ${entry.replace("bin/", "")}.js in ${sourceDir} directory.`);
727
995
  }
728
996
  if (entry === "umd") {
729
997
  umdEntries.push(actualPath);
@@ -731,58 +999,107 @@ async function build2(cwd, save = false) {
731
999
  entryPoints.push(actualPath);
732
1000
  }
733
1001
  }
1002
+ const srcEntryPoints = entryPoints.filter((path) => path.includes(srcDir));
1003
+ const binEntryPoints = entryPoints.filter((path) => path.includes(binDir));
1004
+ const distBinDir = Path3.join(distDir, "bin");
1005
+ if (binEntryPoints.length > 0) {
1006
+ await FS3.mkdir(distBinDir, { recursive: true });
1007
+ }
734
1008
  if (entryPoints.length > 0) {
735
- const entryNames = entryPoints.map((path) => {
1009
+ console.info(` Building ${entryPoints.length} entries (ESM)...`);
1010
+ const srcEntryNames = srcEntryPoints.map((path) => {
736
1011
  const name = Path3.basename(path, Path3.extname(path));
737
1012
  return name;
738
1013
  });
739
- console.info(` Building ${entryPoints.length} entries (ESM)...`);
740
- await ESBuild.build({
741
- entryPoints,
742
- outdir: distSrcDir,
743
- format: "esm",
744
- outExtension: { ".js": ".js" },
745
- bundle: true,
746
- minify: false,
747
- sourcemap: false,
748
- external: externalDeps,
749
- platform: "node",
750
- target: "node18",
751
- packages: "external",
752
- supported: { "import-attributes": true },
753
- plugins: [
754
- externalEntrypointsPlugin({
755
- entryNames,
756
- outputExtension: ".js"
757
- }),
758
- dtsPlugin({
759
- outDir: distSrcDir,
760
- rootDir: srcDir,
761
- entryPoints
762
- })
763
- ]
1014
+ const binEntryNames = binEntryPoints.map((path) => {
1015
+ const name = Path3.basename(path, Path3.extname(path));
1016
+ return name;
764
1017
  });
765
- if (options.formats.cjs) {
766
- console.info(` Building ${entryPoints.length} entries (CJS)...`);
1018
+ const allESMEntryPoints = [...srcEntryPoints, ...binEntryPoints];
1019
+ const allEntryNames = [...srcEntryNames, ...binEntryNames];
1020
+ if (allESMEntryPoints.length > 0) {
767
1021
  await ESBuild.build({
768
- entryPoints,
769
- outdir: distSrcDir,
770
- format: "cjs",
771
- outExtension: { ".js": ".cjs" },
1022
+ entryPoints: allESMEntryPoints,
1023
+ outdir: distDir,
1024
+ outbase: cwd,
1025
+ // Preserve src/ and bin/ directory structure
1026
+ format: "esm",
1027
+ outExtension: { ".js": ".js" },
772
1028
  bundle: true,
773
1029
  minify: false,
774
1030
  sourcemap: false,
775
1031
  external: externalDeps,
776
1032
  platform: "node",
777
1033
  target: "node18",
1034
+ packages: "external",
778
1035
  supported: { "import-attributes": true },
779
1036
  plugins: [
780
1037
  externalEntrypointsPlugin({
781
- entryNames,
782
- outputExtension: ".cjs"
783
- })
1038
+ entryNames: allEntryNames,
1039
+ outputExtension: ".js"
1040
+ }),
1041
+ // Generate TypeScript declarations for src entries
1042
+ ...srcEntryPoints.length > 0 ? [dtsPlugin({
1043
+ outDir: distSrcDir,
1044
+ rootDir: srcDir,
1045
+ entryPoints: srcEntryPoints
1046
+ })] : [],
1047
+ // Generate TypeScript declarations for bin entries
1048
+ ...binEntryPoints.length > 0 ? [dtsPlugin({
1049
+ outDir: distBinDir,
1050
+ rootDir: binDir,
1051
+ entryPoints: binEntryPoints
1052
+ })] : []
784
1053
  ]
785
1054
  });
1055
+ if (binEntryPoints.length > 0) {
1056
+ const runtimeBanner = generateRuntimeBanner(pkg);
1057
+ for (const binEntryName of binEntryNames) {
1058
+ const outputPath = Path3.join(distBinDir, `${binEntryName}.js`);
1059
+ await processJavaScriptExecutable(outputPath, runtimeBanner);
1060
+ }
1061
+ }
1062
+ }
1063
+ if (options.formats.cjs) {
1064
+ console.info(` Building ${entryPoints.length} entries (CJS)...`);
1065
+ if (srcEntryPoints.length > 0) {
1066
+ try {
1067
+ await ESBuild.build({
1068
+ entryPoints: srcEntryPoints,
1069
+ outdir: distSrcDir,
1070
+ format: "cjs",
1071
+ outExtension: { ".js": ".cjs" },
1072
+ bundle: true,
1073
+ minify: false,
1074
+ sourcemap: false,
1075
+ external: externalDeps,
1076
+ platform: "node",
1077
+ target: "node18",
1078
+ supported: { "import-attributes": true },
1079
+ plugins: [
1080
+ externalEntrypointsPlugin({
1081
+ entryNames: srcEntryNames,
1082
+ outputExtension: ".cjs"
1083
+ })
1084
+ ]
1085
+ });
1086
+ } catch (error) {
1087
+ const errorMessage = error.message || "";
1088
+ const hasErrorsArray = error.errors && Array.isArray(error.errors);
1089
+ const isTLAError = errorMessage.includes('Top-level await is currently not supported with the "cjs" output format') || hasErrorsArray && error.errors.some((e) => e.text && e.text.includes('Top-level await is currently not supported with the "cjs" output format'));
1090
+ if (isTLAError) {
1091
+ console.info(`
1092
+ \u26A0\uFE0F Top-level await detected - CommonJS generation disabled`);
1093
+ console.info(` Top-level await is incompatible with CommonJS format`);
1094
+ console.info(` Building ESM-only (Node.js 14+ and modern bundlers supported)`);
1095
+ console.info(` To permanently disable CJS: remove "main" field from package.json
1096
+ `);
1097
+ options.formats.cjs = false;
1098
+ } else {
1099
+ throw error;
1100
+ }
1101
+ }
1102
+ }
786
1103
  }
787
1104
  }
788
1105
  for (const umdPath of umdEntries) {
@@ -805,8 +1122,8 @@ async function build2(cwd, save = false) {
805
1122
  }
806
1123
  const autoDiscoveredFiles = [];
807
1124
  console.info(" Generating package.json...");
808
- const cleanedPkg = cleanPackageJSON(pkg, mainEntry, options);
809
- const exportsResult = generateExports(entries, mainEntry, options, pkg.exports);
1125
+ const cleanedPkg = await cleanPackageJSON(pkg, mainEntry, options, cwd, distDir);
1126
+ const exportsResult = await generateExports(entries, mainEntry, options, pkg.exports, distDir, allBinEntries);
810
1127
  cleanedPkg.exports = fixExportsForDist(exportsResult.exports);
811
1128
  if (exportsResult.staleExports.length > 0) {
812
1129
  console.warn(`\u26A0\uFE0F WARNING: Found ${exportsResult.staleExports.length} stale export(s) pointing to missing src/ files:`);
@@ -907,7 +1224,10 @@ async function build2(cwd, save = false) {
907
1224
  rootPkg2.main = `./dist/src/${mainEntry}.cjs`;
908
1225
  }
909
1226
  rootPkg2.module = `./dist/src/${mainEntry}.js`;
910
- rootPkg2.types = `./dist/src/${mainEntry}.d.ts`;
1227
+ const dtsPath = Path3.join(distDir, "src", `${mainEntry}.d.ts`);
1228
+ if (await fileExists(dtsPath)) {
1229
+ rootPkg2.types = `./dist/src/${mainEntry}.d.ts`;
1230
+ }
911
1231
  if (rootPkg2.typings && typeof rootPkg2.typings === "string") {
912
1232
  rootPkg2.typings = rootPkg2.typings.startsWith("./dist/") ? rootPkg2.typings : "./" + Path3.join("dist", rootPkg2.typings);
913
1233
  }
@@ -938,6 +1258,27 @@ async function build2(cwd, save = false) {
938
1258
  }
939
1259
  }
940
1260
  rootPkg2.exports = rootExports;
1261
+ if (allBinEntries.length > 0) {
1262
+ const generatedBin = {};
1263
+ for (const binEntryInfo of allBinEntries) {
1264
+ const binPath = binEntryInfo.source === "src" ? `./dist/src/bin/${binEntryInfo.name}.js` : `./dist/bin/${binEntryInfo.name}.js`;
1265
+ const fullPath = Path3.join(cwd, binPath);
1266
+ if (await fileExists(fullPath)) {
1267
+ generatedBin[binEntryInfo.name] = binPath;
1268
+ }
1269
+ }
1270
+ if (Object.keys(generatedBin).length > 0) {
1271
+ if (rootPkg2.bin) {
1272
+ if (typeof rootPkg2.bin === "string") {
1273
+ const existingName = pkg.name?.split("/").pop() || "cli";
1274
+ rootPkg2.bin = { [existingName]: rootPkg2.bin };
1275
+ }
1276
+ rootPkg2.bin = { ...rootPkg2.bin, ...generatedBin };
1277
+ } else {
1278
+ rootPkg2.bin = generatedBin;
1279
+ }
1280
+ }
1281
+ }
941
1282
  if (rootPkg2.bin) {
942
1283
  if (typeof rootPkg2.bin === "string") {
943
1284
  const distPath = rootPkg2.bin.startsWith("./dist/") ? rootPkg2.bin : rootPkg2.bin.startsWith("dist/") ? "./" + rootPkg2.bin : "./" + Path3.join("dist", rootPkg2.bin);
@@ -1000,6 +1341,9 @@ async function build2(cwd, save = false) {
1000
1341
  if (save) {
1001
1342
  rootPkg = JSON.parse(await FS3.readFile(pkgPath, "utf-8"));
1002
1343
  }
1344
+ if (allBinEntries.length > 0 || pkg.bin) {
1345
+ await makeFilesExecutable(pkg, cwd, allBinEntries);
1346
+ }
1003
1347
  return { distPkg: fixedDistPkg, rootPkg };
1004
1348
  }
1005
1349
  async function publish(cwd, save = true, extraArgs = []) {