@b9g/libuild 0.1.11 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/libuild",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Zero-config library builds",
5
5
  "keywords": [
6
6
  "build",
package/src/cli.cjs CHANGED
@@ -83,7 +83,42 @@ async function main() {
83
83
  process.exit(0);
84
84
  }
85
85
  const command = positionals[0] || "build";
86
- const targetDir = positionals[1];
86
+ let targetDir;
87
+ for (let i = 1; i < positionals.length; i++) {
88
+ const arg = positionals[i];
89
+ const flagValueFlags = ["--tag", "--access", "--registry", "--otp", "--workspace"];
90
+ const isValueForFlag = flagValueFlags.some((flag) => {
91
+ const flagIndex = process.argv.indexOf(flag);
92
+ const argIndex = process.argv.indexOf(arg);
93
+ return flagIndex !== -1 && argIndex === flagIndex + 1;
94
+ });
95
+ if (isValueForFlag) {
96
+ continue;
97
+ }
98
+ if (!arg.startsWith("--")) {
99
+ try {
100
+ const isSystemPath = Path.isAbsolute(arg) && (arg.startsWith("/bin/") || arg.startsWith("/usr/") || arg.startsWith("/etc/") || arg.startsWith("/var/") || arg.startsWith("/opt/") || arg.startsWith("/tmp/") || arg.startsWith("/System/") || arg.startsWith("/Applications/"));
101
+ if (isSystemPath) {
102
+ continue;
103
+ }
104
+ const resolvedPath = Path.resolve(arg);
105
+ if (arg.includes("/") || arg.includes("\\") || arg === "." || arg === ".." || arg.startsWith("./") || arg.startsWith("../")) {
106
+ targetDir = arg;
107
+ break;
108
+ }
109
+ const fs = await import("fs/promises");
110
+ try {
111
+ const stat = await fs.stat(resolvedPath);
112
+ if (stat.isDirectory() && !Path.isAbsolute(arg)) {
113
+ targetDir = arg;
114
+ break;
115
+ }
116
+ } catch {
117
+ }
118
+ } catch {
119
+ }
120
+ }
121
+ }
87
122
  const cwd = targetDir ? Path.resolve(targetDir) : process.cwd();
88
123
  const shouldSave = values.save || command === "publish" && !values["no-save"];
89
124
  const allowedNpmFlags = [
@@ -98,7 +133,8 @@ async function main() {
98
133
  "--include-workspace-root"
99
134
  ];
100
135
  const rawExtraArgs = process.argv.slice(2).filter(
101
- (arg) => !["build", "publish"].includes(arg) && !["--save", "--no-save", "--help", "-h", "--version", "-v"].includes(arg)
136
+ (arg) => !["build", "publish"].includes(arg) && !["--save", "--no-save", "--help", "-h", "--version", "-v"].includes(arg) && arg !== targetDir
137
+ // Don't filter out the target directory
102
138
  );
103
139
  const extraArgs = [];
104
140
  for (let i = 0; i < rawExtraArgs.length; i++) {
package/src/cli.js CHANGED
@@ -61,7 +61,42 @@ async function main() {
61
61
  process.exit(0);
62
62
  }
63
63
  const command = positionals[0] || "build";
64
- const targetDir = positionals[1];
64
+ let targetDir;
65
+ for (let i = 1; i < positionals.length; i++) {
66
+ const arg = positionals[i];
67
+ const flagValueFlags = ["--tag", "--access", "--registry", "--otp", "--workspace"];
68
+ const isValueForFlag = flagValueFlags.some((flag) => {
69
+ const flagIndex = process.argv.indexOf(flag);
70
+ const argIndex = process.argv.indexOf(arg);
71
+ return flagIndex !== -1 && argIndex === flagIndex + 1;
72
+ });
73
+ if (isValueForFlag) {
74
+ continue;
75
+ }
76
+ if (!arg.startsWith("--")) {
77
+ try {
78
+ const isSystemPath = Path.isAbsolute(arg) && (arg.startsWith("/bin/") || arg.startsWith("/usr/") || arg.startsWith("/etc/") || arg.startsWith("/var/") || arg.startsWith("/opt/") || arg.startsWith("/tmp/") || arg.startsWith("/System/") || arg.startsWith("/Applications/"));
79
+ if (isSystemPath) {
80
+ continue;
81
+ }
82
+ const resolvedPath = Path.resolve(arg);
83
+ if (arg.includes("/") || arg.includes("\\") || arg === "." || arg === ".." || arg.startsWith("./") || arg.startsWith("../")) {
84
+ targetDir = arg;
85
+ break;
86
+ }
87
+ const fs = await import("fs/promises");
88
+ try {
89
+ const stat = await fs.stat(resolvedPath);
90
+ if (stat.isDirectory() && !Path.isAbsolute(arg)) {
91
+ targetDir = arg;
92
+ break;
93
+ }
94
+ } catch {
95
+ }
96
+ } catch {
97
+ }
98
+ }
99
+ }
65
100
  const cwd = targetDir ? Path.resolve(targetDir) : process.cwd();
66
101
  const shouldSave = values.save || command === "publish" && !values["no-save"];
67
102
  const allowedNpmFlags = [
@@ -76,7 +111,8 @@ async function main() {
76
111
  "--include-workspace-root"
77
112
  ];
78
113
  const rawExtraArgs = process.argv.slice(2).filter(
79
- (arg) => !["build", "publish"].includes(arg) && !["--save", "--no-save", "--help", "-h", "--version", "-v"].includes(arg)
114
+ (arg) => !["build", "publish"].includes(arg) && !["--save", "--no-save", "--help", "-h", "--version", "-v"].includes(arg) && arg !== targetDir
115
+ // Don't filter out the target directory
80
116
  );
81
117
  const extraArgs = [];
82
118
  for (let i = 0; i < rawExtraArgs.length; i++) {
package/src/libuild.cjs CHANGED
@@ -215088,6 +215088,21 @@ function externalEntrypointsPlugin(options) {
215088
215088
  };
215089
215089
  }
215090
215090
  });
215091
+ build3.onResolve({ filter: /^\.\.\/(?:src|bin)\// }, (args) => {
215092
+ const withoutExt = args.path.replace(/\.(ts|js)$/, "");
215093
+ const match = withoutExt.match(/^\.\.\/(?:src|bin)\/(.+)$/);
215094
+ if (match) {
215095
+ const entryName = match[1];
215096
+ if (externalEntries.includes(entryName)) {
215097
+ const dir = withoutExt.match(/^(\.\.\/(?:src|bin))\//)?.[1];
215098
+ return {
215099
+ path: `${dir}/${entryName}${outputExtension}`,
215100
+ external: true
215101
+ };
215102
+ }
215103
+ }
215104
+ return void 0;
215105
+ });
215091
215106
  }
215092
215107
  };
215093
215108
  }
@@ -215113,27 +215128,34 @@ function dtsPlugin(options) {
215113
215128
  let TS;
215114
215129
  try {
215115
215130
  TS = await Promise.resolve().then(() => __toESM(require_typescript(), 1));
215116
- } catch {
215131
+ } catch (error) {
215117
215132
  return;
215118
215133
  }
215119
215134
  const tsFiles = entryFiles.filter((file) => {
215120
215135
  if (!file.endsWith(".ts"))
215121
215136
  return false;
215122
215137
  return options.entryPoints.some((entryPoint) => {
215123
- const normalizedEntry = Path2.resolve(entryPoint);
215124
- const normalizedFile = Path2.resolve(file);
215125
- return normalizedEntry === normalizedFile;
215138
+ try {
215139
+ const fs = require("fs");
215140
+ const normalizedEntry = fs.realpathSync(Path2.resolve(entryPoint));
215141
+ const normalizedFile = fs.realpathSync(Path2.resolve(file));
215142
+ return normalizedEntry === normalizedFile;
215143
+ } catch {
215144
+ const normalizedEntry = Path2.resolve(entryPoint);
215145
+ const normalizedFile = Path2.resolve(file);
215146
+ return normalizedEntry === normalizedFile;
215147
+ }
215126
215148
  });
215127
215149
  });
215128
215150
  if (tsFiles.length === 0) {
215129
215151
  return;
215130
215152
  }
215131
215153
  try {
215154
+ await FS2.mkdir(options.outDir, { recursive: true });
215132
215155
  const compilerOptions = {
215133
215156
  declaration: true,
215134
215157
  emitDeclarationOnly: true,
215135
215158
  outDir: options.outDir,
215136
- rootDir: options.rootDir,
215137
215159
  skipLibCheck: true,
215138
215160
  esModuleInterop: true,
215139
215161
  target: TS.ScriptTarget.ES2020,
@@ -215208,6 +215230,38 @@ ${content}`;
215208
215230
  }
215209
215231
 
215210
215232
  // src/libuild.ts
215233
+ function generateRuntimeBanner(pkg) {
215234
+ const prefersBun = pkg.engines?.bun !== void 0;
215235
+ if (prefersBun) {
215236
+ return '//bin/true; exec "$({ [ "${npm_config_user_agent#bun/}" != "$npm_config_user_agent" ] || true; } && command -v bun || command -v node)" "$0" "$@"';
215237
+ } else {
215238
+ return '//bin/true; exec "$([ "${npm_config_user_agent#bun/}" != "$npm_config_user_agent" ] && command -v bun || command -v node)" "$0" "$@"';
215239
+ }
215240
+ }
215241
+ async function processJavaScriptExecutable(filePath, runtimeBanner) {
215242
+ try {
215243
+ const contents = await FS3.readFile(filePath, "utf8");
215244
+ const lines = contents.split("\n");
215245
+ let modified = false;
215246
+ if (lines[0]?.startsWith("#!")) {
215247
+ const existingShebang = lines[0];
215248
+ if (existingShebang.includes("python") || existingShebang.includes("bash") || !existingShebang.includes("node") && !existingShebang.includes("bun") && !existingShebang.includes("sh")) {
215249
+ console.warn(`\u26A0\uFE0F WARNING: ${filePath} has non-JavaScript shebang but is a JS file. Adding dual runtime support.`);
215250
+ }
215251
+ lines[0] = "#!/usr/bin/env sh";
215252
+ lines.splice(1, 0, runtimeBanner);
215253
+ modified = true;
215254
+ } else {
215255
+ lines.unshift("#!/usr/bin/env sh", runtimeBanner);
215256
+ modified = true;
215257
+ }
215258
+ if (modified) {
215259
+ await FS3.writeFile(filePath, lines.join("\n"));
215260
+ }
215261
+ } catch (error) {
215262
+ console.warn(`\u26A0\uFE0F WARNING: Could not process executable ${filePath}: ${error.message}`);
215263
+ }
215264
+ }
215211
215265
  function isValidEntrypoint(filename) {
215212
215266
  if (!filename.endsWith(".ts") && !filename.endsWith(".js"))
215213
215267
  return false;
@@ -215234,6 +215288,22 @@ async function findEntrypoints(srcDir) {
215234
215288
  return isValidEntrypoint(dirent.name);
215235
215289
  }).map((dirent) => Path3.basename(dirent.name, Path3.extname(dirent.name))).sort();
215236
215290
  }
215291
+ async function findBinEntrypoints(binDir) {
215292
+ try {
215293
+ const files = await FS3.readdir(binDir, { withFileTypes: true });
215294
+ return files.filter((dirent) => {
215295
+ if (dirent.isDirectory()) {
215296
+ return false;
215297
+ }
215298
+ return isValidEntrypoint(dirent.name);
215299
+ }).map((dirent) => Path3.basename(dirent.name, Path3.extname(dirent.name))).sort();
215300
+ } catch (error) {
215301
+ if (error.code === "ENOENT") {
215302
+ return [];
215303
+ }
215304
+ throw error;
215305
+ }
215306
+ }
215237
215307
  function detectMainEntry(pkg, entries) {
215238
215308
  function extractEntryFromPath(path) {
215239
215309
  const match = path.match(/\.\/src\/([^.]+)/);
@@ -215331,18 +215401,38 @@ function checkIfExportIsStale(exportKey, exportValue, entries) {
215331
215401
  }
215332
215402
  return entryName ? !entries.includes(entryName) : false;
215333
215403
  }
215334
- function generateExports(entries, mainEntry, options, existingExports = {}) {
215404
+ async function generateExports(entries, mainEntry, options, existingExports = {}, distDir, allBinEntries = []) {
215335
215405
  const exports2 = {};
215336
215406
  const staleExports = [];
215337
- function createExportEntry(entry) {
215338
- const exportEntry = {
215339
- types: `./src/${entry}.d.ts`,
215340
- import: `./src/${entry}.js`
215341
- };
215342
- if (options.formats.cjs) {
215343
- exportEntry.require = `./src/${entry}.cjs`;
215407
+ async function createExportEntry(entry) {
215408
+ if (entry.startsWith("bin/")) {
215409
+ const binEntry = entry.replace("bin/", "");
215410
+ const exportEntry = {
215411
+ import: `./bin/${binEntry}.js`
215412
+ };
215413
+ if (distDir) {
215414
+ const dtsPath = Path3.join(distDir, "bin", `${binEntry}.d.ts`);
215415
+ if (await fileExists(dtsPath)) {
215416
+ exportEntry.types = `./bin/${binEntry}.d.ts`;
215417
+ }
215418
+ }
215419
+ return exportEntry;
215420
+ } else {
215421
+ const exportEntry = {
215422
+ import: `./src/${entry}.js`
215423
+ };
215424
+ if (distDir) {
215425
+ const dtsPath = Path3.join(distDir, "src", `${entry}.d.ts`);
215426
+ const exists = await fileExists(dtsPath);
215427
+ if (exists) {
215428
+ exportEntry.types = `./src/${entry}.d.ts`;
215429
+ }
215430
+ }
215431
+ if (options.formats.cjs) {
215432
+ exportEntry.require = `./src/${entry}.cjs`;
215433
+ }
215434
+ return exportEntry;
215344
215435
  }
215345
- return exportEntry;
215346
215436
  }
215347
215437
  function expandExistingExport(existing, entryFromPath) {
215348
215438
  if (typeof existing === "string") {
@@ -215416,7 +215506,7 @@ function generateExports(entries, mainEntry, options, existingExports = {}) {
215416
215506
  }
215417
215507
  }
215418
215508
  if (!exports2["."]) {
215419
- exports2["."] = createExportEntry(mainEntry);
215509
+ exports2["."] = await createExportEntry(mainEntry);
215420
215510
  } else {
215421
215511
  exports2["."] = expandExistingExport(exports2["."], mainEntry);
215422
215512
  }
@@ -215425,7 +215515,7 @@ function generateExports(entries, mainEntry, options, existingExports = {}) {
215425
215515
  continue;
215426
215516
  const key = `./${entry}`;
215427
215517
  if (!exports2[key]) {
215428
- exports2[key] = createExportEntry(entry);
215518
+ exports2[key] = await createExportEntry(entry);
215429
215519
  } else {
215430
215520
  exports2[key] = expandExistingExport(exports2[key], entry);
215431
215521
  }
@@ -215478,13 +215568,30 @@ async function validateSingleBinPath(binPath, fieldName, cwd, save) {
215478
215568
  if (save) {
215479
215569
  return;
215480
215570
  }
215481
- if (binPath.startsWith("dist/src/") || binPath.startsWith("./dist/src/")) {
215571
+ if (binPath.startsWith("dist/src/") || binPath.startsWith("./dist/src/") || binPath.startsWith("dist/bin/") || binPath.startsWith("./dist/bin/")) {
215482
215572
  const fullPath2 = Path3.join(cwd, binPath);
215483
215573
  const distExists = await fileExists(fullPath2);
215484
215574
  if (distExists) {
215485
215575
  return;
215486
215576
  }
215487
- const srcPath = binPath.startsWith("./dist/src/") ? binPath.replace("./dist/src/", "src/") : binPath.replace("dist/src/", "src/");
215577
+ let srcPath;
215578
+ let sourceDir;
215579
+ if (binPath.startsWith("./dist/src/")) {
215580
+ srcPath = binPath.replace("./dist/src/", "src/");
215581
+ sourceDir = "src";
215582
+ } else if (binPath.startsWith("dist/src/")) {
215583
+ srcPath = binPath.replace("dist/src/", "src/");
215584
+ sourceDir = "src";
215585
+ } else if (binPath.startsWith("./dist/bin/")) {
215586
+ srcPath = binPath.replace("./dist/bin/", "bin/");
215587
+ sourceDir = "bin";
215588
+ } else if (binPath.startsWith("dist/bin/")) {
215589
+ srcPath = binPath.replace("dist/bin/", "bin/");
215590
+ sourceDir = "bin";
215591
+ } else {
215592
+ srcPath = binPath;
215593
+ sourceDir = "src";
215594
+ }
215488
215595
  const basePath = srcPath.replace(/\.(js|cjs|mjs)$/, "");
215489
215596
  const tsPath = Path3.join(cwd, basePath + ".ts");
215490
215597
  const jsPath = Path3.join(cwd, basePath + ".js");
@@ -215511,7 +215618,16 @@ async function validateSingleBinPath(binPath, fieldName, cwd, save) {
215511
215618
  if (pathExists) {
215512
215619
  return;
215513
215620
  }
215514
- if (binPath.startsWith("src/") || binPath.startsWith("./src/")) {
215621
+ if (binPath.startsWith("src/") || binPath.startsWith("./src/") || binPath.startsWith("bin/") || binPath.startsWith("./bin/")) {
215622
+ let normalizedPath = binPath.startsWith("./") ? binPath.slice(2) : binPath;
215623
+ if (normalizedPath.startsWith("src/")) {
215624
+ const srcRelativePath = normalizedPath.replace("src/", "");
215625
+ if (srcRelativePath.includes("/")) {
215626
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" which is in a subdirectory of src/.`);
215627
+ console.warn(` libuild only auto-detects top-level src/ files as entrypoints.`);
215628
+ console.warn(` Consider moving the file to the top-level src/ directory or use the bin/ directory for executables.`);
215629
+ }
215630
+ }
215515
215631
  const basePath = fullPath.replace(/\.(js|cjs|mjs)$/, "");
215516
215632
  const tsPath = basePath + ".ts";
215517
215633
  const jsPath = basePath + ".js";
@@ -215536,10 +215652,19 @@ function transformBinPaths(value) {
215536
215652
  if (value.startsWith("dist/src/")) {
215537
215653
  return value.replace("dist/", "");
215538
215654
  }
215655
+ if (value.startsWith("./dist/bin/")) {
215656
+ return value.replace("./dist/", "");
215657
+ }
215658
+ if (value.startsWith("dist/bin/")) {
215659
+ return value.replace("dist/", "");
215660
+ }
215539
215661
  if (value.startsWith("./src/")) {
215540
215662
  return value.replace("./", "");
215541
215663
  }
215542
- if (value.startsWith("src/") || value === "src") {
215664
+ if (value.startsWith("./bin/")) {
215665
+ return value.replace("./", "");
215666
+ }
215667
+ if (value.startsWith("src/") || value === "src" || value.startsWith("bin/") || value === "bin") {
215543
215668
  return value;
215544
215669
  }
215545
215670
  return value;
@@ -215584,6 +215709,43 @@ function fixExportsForDist(obj) {
215584
215709
  }
215585
215710
  return obj;
215586
215711
  }
215712
+ async function setExecutablePermissions(filePath) {
215713
+ try {
215714
+ await FS3.chmod(filePath, 493);
215715
+ } catch (error) {
215716
+ console.warn(`\u26A0\uFE0F WARNING: Could not set executable permissions for ${filePath}: ${error.message}`);
215717
+ }
215718
+ }
215719
+ async function makeFilesExecutable(pkg, cwd, allBinEntries) {
215720
+ const filesToMakeExecutable = [];
215721
+ if (pkg.bin) {
215722
+ if (typeof pkg.bin === "string") {
215723
+ filesToMakeExecutable.push(Path3.join(cwd, pkg.bin));
215724
+ } else if (typeof pkg.bin === "object") {
215725
+ for (const binPath of Object.values(pkg.bin)) {
215726
+ if (typeof binPath === "string") {
215727
+ filesToMakeExecutable.push(Path3.join(cwd, binPath));
215728
+ }
215729
+ }
215730
+ }
215731
+ }
215732
+ for (const binEntryInfo of allBinEntries) {
215733
+ const baseDir = binEntryInfo.source === "src" ? "dist/src/bin" : "dist/bin";
215734
+ const jsPath = Path3.join(cwd, baseDir, `${binEntryInfo.name}.js`);
215735
+ const cjsPath = Path3.join(cwd, baseDir, `${binEntryInfo.name}.cjs`);
215736
+ if (!filesToMakeExecutable.includes(jsPath)) {
215737
+ filesToMakeExecutable.push(jsPath);
215738
+ }
215739
+ if (!filesToMakeExecutable.includes(cjsPath)) {
215740
+ filesToMakeExecutable.push(cjsPath);
215741
+ }
215742
+ }
215743
+ for (const filePath of filesToMakeExecutable) {
215744
+ if (await fileExists(filePath)) {
215745
+ await setExecutablePermissions(filePath);
215746
+ }
215747
+ }
215748
+ }
215587
215749
  async function resolveWorkspaceDependencies(dependencies, cwd) {
215588
215750
  if (!dependencies)
215589
215751
  return void 0;
@@ -215643,7 +215805,7 @@ async function resolveWorkspaceVersion(packageName, workspaceSpec, cwd) {
215643
215805
  return workspaceSpec;
215644
215806
  }
215645
215807
  }
215646
- async function cleanPackageJSON(pkg, mainEntry, options, cwd) {
215808
+ async function cleanPackageJSON(pkg, mainEntry, options, cwd, distDir) {
215647
215809
  const cleaned = {
215648
215810
  name: pkg.name,
215649
215811
  version: pkg.version
@@ -215708,7 +215870,13 @@ async function cleanPackageJSON(pkg, mainEntry, options, cwd) {
215708
215870
  cleaned.main = `src/${mainEntry}.cjs`;
215709
215871
  }
215710
215872
  cleaned.module = `src/${mainEntry}.js`;
215711
- cleaned.types = `src/${mainEntry}.d.ts`;
215873
+ if (distDir) {
215874
+ const dtsPath = Path3.join(distDir, "src", `${mainEntry}.d.ts`);
215875
+ const exists = await fileExists(dtsPath);
215876
+ if (exists) {
215877
+ cleaned.types = `src/${mainEntry}.d.ts`;
215878
+ }
215879
+ }
215712
215880
  return cleaned;
215713
215881
  }
215714
215882
  async function fileExists(filePath) {
@@ -215760,9 +215928,19 @@ async function build2(cwd, save = false) {
215760
215928
  console.warn(" Add 'dist/' to your .gitignore file");
215761
215929
  }
215762
215930
  }
215763
- const entries = await findEntrypoints(srcDir);
215764
- if (entries.length === 0) {
215765
- throw new Error("No entry points found in src/");
215931
+ const binDir = Path3.join(cwd, "bin");
215932
+ const srcEntries = await findEntrypoints(srcDir);
215933
+ const binEntries = await findBinEntrypoints(binDir);
215934
+ const allBinEntries = binEntries.map((entry) => ({ name: entry, source: "top-level" }));
215935
+ const entries = [
215936
+ ...srcEntries,
215937
+ ...allBinEntries.map((entry) => `bin/${entry.name}`)
215938
+ ];
215939
+ if (srcEntries.length === 0 && allBinEntries.length === 0) {
215940
+ throw new Error("No entry points found in src/ or bin/");
215941
+ }
215942
+ if (allBinEntries.length > 0) {
215943
+ console.info(` Found bin entries: ${allBinEntries.map((entry) => entry.name).join(", ")}`);
215766
215944
  }
215767
215945
  const options = {
215768
215946
  formats: {
@@ -215773,7 +215951,7 @@ async function build2(cwd, save = false) {
215773
215951
  umd: entries.includes("umd")
215774
215952
  }
215775
215953
  };
215776
- const mainEntry = detectMainEntry(pkg, entries);
215954
+ const mainEntry = detectMainEntry(pkg, srcEntries);
215777
215955
  console.info(" Found entries:", entries.join(", "));
215778
215956
  console.info(" Main entry:", mainEntry);
215779
215957
  if (options.formats.cjs) {
@@ -215804,11 +215982,29 @@ async function build2(cwd, save = false) {
215804
215982
  const entryPoints = [];
215805
215983
  const umdEntries = [];
215806
215984
  for (const entry of entries) {
215807
- const entryPath = Path3.join(srcDir, `${entry}.ts`);
215808
- const jsEntryPath = Path3.join(srcDir, `${entry}.js`);
215985
+ let entryPath;
215986
+ let jsEntryPath;
215987
+ let sourceDir;
215988
+ if (entry.startsWith("bin/")) {
215989
+ const binEntryName = entry.replace("bin/", "");
215990
+ const binEntryInfo = allBinEntries.find((binEntry) => binEntry.name === binEntryName);
215991
+ if (binEntryInfo?.source === "src") {
215992
+ entryPath = Path3.join(srcDir, "bin", `${binEntryName}.ts`);
215993
+ jsEntryPath = Path3.join(srcDir, "bin", `${binEntryName}.js`);
215994
+ sourceDir = "src/bin/";
215995
+ } else {
215996
+ entryPath = Path3.join(binDir, `${binEntryName}.ts`);
215997
+ jsEntryPath = Path3.join(binDir, `${binEntryName}.js`);
215998
+ sourceDir = "bin/";
215999
+ }
216000
+ } else {
216001
+ entryPath = Path3.join(srcDir, `${entry}.ts`);
216002
+ jsEntryPath = Path3.join(srcDir, `${entry}.js`);
216003
+ sourceDir = "src/";
216004
+ }
215809
216005
  const actualPath = await fileExists(entryPath) ? entryPath : jsEntryPath;
215810
216006
  if (!await fileExists(actualPath)) {
215811
- throw new Error(`Entry point file not found: ${actualPath}. Expected ${entry}.ts or ${entry}.js in src/ directory.`);
216007
+ throw new Error(`Entry point file not found: ${actualPath}. Expected ${entry.replace("bin/", "")}.ts or ${entry.replace("bin/", "")}.js in ${sourceDir} directory.`);
215812
216008
  }
215813
216009
  if (entry === "umd") {
215814
216010
  umdEntries.push(actualPath);
@@ -215816,58 +216012,107 @@ async function build2(cwd, save = false) {
215816
216012
  entryPoints.push(actualPath);
215817
216013
  }
215818
216014
  }
216015
+ const srcEntryPoints = entryPoints.filter((path) => path.includes(srcDir));
216016
+ const binEntryPoints = entryPoints.filter((path) => path.includes(binDir));
216017
+ const distBinDir = Path3.join(distDir, "bin");
216018
+ if (binEntryPoints.length > 0) {
216019
+ await FS3.mkdir(distBinDir, { recursive: true });
216020
+ }
215819
216021
  if (entryPoints.length > 0) {
215820
- const entryNames = entryPoints.map((path) => {
216022
+ console.info(` Building ${entryPoints.length} entries (ESM)...`);
216023
+ const srcEntryNames = srcEntryPoints.map((path) => {
215821
216024
  const name = Path3.basename(path, Path3.extname(path));
215822
216025
  return name;
215823
216026
  });
215824
- console.info(` Building ${entryPoints.length} entries (ESM)...`);
215825
- await ESBuild.build({
215826
- entryPoints,
215827
- outdir: distSrcDir,
215828
- format: "esm",
215829
- outExtension: { ".js": ".js" },
215830
- bundle: true,
215831
- minify: false,
215832
- sourcemap: false,
215833
- external: externalDeps,
215834
- platform: "node",
215835
- target: "node18",
215836
- packages: "external",
215837
- supported: { "import-attributes": true },
215838
- plugins: [
215839
- externalEntrypointsPlugin({
215840
- entryNames,
215841
- outputExtension: ".js"
215842
- }),
215843
- dtsPlugin({
215844
- outDir: distSrcDir,
215845
- rootDir: srcDir,
215846
- entryPoints
215847
- })
215848
- ]
216027
+ const binEntryNames = binEntryPoints.map((path) => {
216028
+ const name = Path3.basename(path, Path3.extname(path));
216029
+ return name;
215849
216030
  });
215850
- if (options.formats.cjs) {
215851
- console.info(` Building ${entryPoints.length} entries (CJS)...`);
216031
+ const allESMEntryPoints = [...srcEntryPoints, ...binEntryPoints];
216032
+ const allEntryNames = [...srcEntryNames, ...binEntryNames];
216033
+ if (allESMEntryPoints.length > 0) {
215852
216034
  await ESBuild.build({
215853
- entryPoints,
215854
- outdir: distSrcDir,
215855
- format: "cjs",
215856
- outExtension: { ".js": ".cjs" },
216035
+ entryPoints: allESMEntryPoints,
216036
+ outdir: distDir,
216037
+ outbase: cwd,
216038
+ // Preserve src/ and bin/ directory structure
216039
+ format: "esm",
216040
+ outExtension: { ".js": ".js" },
215857
216041
  bundle: true,
215858
216042
  minify: false,
215859
216043
  sourcemap: false,
215860
216044
  external: externalDeps,
215861
216045
  platform: "node",
215862
216046
  target: "node18",
216047
+ packages: "external",
215863
216048
  supported: { "import-attributes": true },
215864
216049
  plugins: [
215865
216050
  externalEntrypointsPlugin({
215866
- entryNames,
215867
- outputExtension: ".cjs"
215868
- })
216051
+ entryNames: allEntryNames,
216052
+ outputExtension: ".js"
216053
+ }),
216054
+ // Generate TypeScript declarations for src entries
216055
+ ...srcEntryPoints.length > 0 ? [dtsPlugin({
216056
+ outDir: distSrcDir,
216057
+ rootDir: srcDir,
216058
+ entryPoints: srcEntryPoints
216059
+ })] : [],
216060
+ // Generate TypeScript declarations for bin entries
216061
+ ...binEntryPoints.length > 0 ? [dtsPlugin({
216062
+ outDir: distBinDir,
216063
+ rootDir: binDir,
216064
+ entryPoints: binEntryPoints
216065
+ })] : []
215869
216066
  ]
215870
216067
  });
216068
+ if (binEntryPoints.length > 0) {
216069
+ const runtimeBanner = generateRuntimeBanner(pkg);
216070
+ for (const binEntryName of binEntryNames) {
216071
+ const outputPath = Path3.join(distBinDir, `${binEntryName}.js`);
216072
+ await processJavaScriptExecutable(outputPath, runtimeBanner);
216073
+ }
216074
+ }
216075
+ }
216076
+ if (options.formats.cjs) {
216077
+ console.info(` Building ${entryPoints.length} entries (CJS)...`);
216078
+ if (srcEntryPoints.length > 0) {
216079
+ try {
216080
+ await ESBuild.build({
216081
+ entryPoints: srcEntryPoints,
216082
+ outdir: distSrcDir,
216083
+ format: "cjs",
216084
+ outExtension: { ".js": ".cjs" },
216085
+ bundle: true,
216086
+ minify: false,
216087
+ sourcemap: false,
216088
+ external: externalDeps,
216089
+ platform: "node",
216090
+ target: "node18",
216091
+ supported: { "import-attributes": true },
216092
+ plugins: [
216093
+ externalEntrypointsPlugin({
216094
+ entryNames: srcEntryNames,
216095
+ outputExtension: ".cjs"
216096
+ })
216097
+ ]
216098
+ });
216099
+ } catch (error) {
216100
+ const errorMessage = error.message || "";
216101
+ const hasErrorsArray = error.errors && Array.isArray(error.errors);
216102
+ 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'));
216103
+ if (isTLAError) {
216104
+ console.info(`
216105
+ \u26A0\uFE0F Top-level await detected - CommonJS generation disabled`);
216106
+ console.info(` Top-level await is incompatible with CommonJS format`);
216107
+ console.info(` Building ESM-only (Node.js 14+ and modern bundlers supported)`);
216108
+ console.info(` To permanently disable CJS: remove "main" field from package.json
216109
+ `);
216110
+ options.formats.cjs = false;
216111
+ } else {
216112
+ throw error;
216113
+ }
216114
+ }
216115
+ }
215871
216116
  }
215872
216117
  }
215873
216118
  for (const umdPath of umdEntries) {
@@ -215890,8 +216135,8 @@ async function build2(cwd, save = false) {
215890
216135
  }
215891
216136
  const autoDiscoveredFiles = [];
215892
216137
  console.info(" Generating package.json...");
215893
- const cleanedPkg = await cleanPackageJSON(pkg, mainEntry, options, cwd);
215894
- const exportsResult = generateExports(entries, mainEntry, options, pkg.exports);
216138
+ const cleanedPkg = await cleanPackageJSON(pkg, mainEntry, options, cwd, distDir);
216139
+ const exportsResult = await generateExports(entries, mainEntry, options, pkg.exports, distDir, allBinEntries);
215895
216140
  cleanedPkg.exports = fixExportsForDist(exportsResult.exports);
215896
216141
  if (exportsResult.staleExports.length > 0) {
215897
216142
  console.warn(`\u26A0\uFE0F WARNING: Found ${exportsResult.staleExports.length} stale export(s) pointing to missing src/ files:`);
@@ -215992,7 +216237,10 @@ async function build2(cwd, save = false) {
215992
216237
  rootPkg2.main = `./dist/src/${mainEntry}.cjs`;
215993
216238
  }
215994
216239
  rootPkg2.module = `./dist/src/${mainEntry}.js`;
215995
- rootPkg2.types = `./dist/src/${mainEntry}.d.ts`;
216240
+ const dtsPath = Path3.join(distDir, "src", `${mainEntry}.d.ts`);
216241
+ if (await fileExists(dtsPath)) {
216242
+ rootPkg2.types = `./dist/src/${mainEntry}.d.ts`;
216243
+ }
215996
216244
  if (rootPkg2.typings && typeof rootPkg2.typings === "string") {
215997
216245
  rootPkg2.typings = rootPkg2.typings.startsWith("./dist/") ? rootPkg2.typings : "./" + Path3.join("dist", rootPkg2.typings);
215998
216246
  }
@@ -216023,6 +216271,27 @@ async function build2(cwd, save = false) {
216023
216271
  }
216024
216272
  }
216025
216273
  rootPkg2.exports = rootExports;
216274
+ if (allBinEntries.length > 0) {
216275
+ const generatedBin = {};
216276
+ for (const binEntryInfo of allBinEntries) {
216277
+ const binPath = binEntryInfo.source === "src" ? `./dist/src/bin/${binEntryInfo.name}.js` : `./dist/bin/${binEntryInfo.name}.js`;
216278
+ const fullPath = Path3.join(cwd, binPath);
216279
+ if (await fileExists(fullPath)) {
216280
+ generatedBin[binEntryInfo.name] = binPath;
216281
+ }
216282
+ }
216283
+ if (Object.keys(generatedBin).length > 0) {
216284
+ if (rootPkg2.bin) {
216285
+ if (typeof rootPkg2.bin === "string") {
216286
+ const existingName = pkg.name?.split("/").pop() || "cli";
216287
+ rootPkg2.bin = { [existingName]: rootPkg2.bin };
216288
+ }
216289
+ rootPkg2.bin = { ...rootPkg2.bin, ...generatedBin };
216290
+ } else {
216291
+ rootPkg2.bin = generatedBin;
216292
+ }
216293
+ }
216294
+ }
216026
216295
  if (rootPkg2.bin) {
216027
216296
  if (typeof rootPkg2.bin === "string") {
216028
216297
  const distPath = rootPkg2.bin.startsWith("./dist/") ? rootPkg2.bin : rootPkg2.bin.startsWith("dist/") ? "./" + rootPkg2.bin : "./" + Path3.join("dist", rootPkg2.bin);
@@ -216085,6 +216354,9 @@ async function build2(cwd, save = false) {
216085
216354
  if (save) {
216086
216355
  rootPkg = JSON.parse(await FS3.readFile(pkgPath, "utf-8"));
216087
216356
  }
216357
+ if (allBinEntries.length > 0 || pkg.bin) {
216358
+ await makeFilesExecutable(pkg, cwd, allBinEntries);
216359
+ }
216088
216360
  return { distPkg: fixedDistPkg, rootPkg };
216089
216361
  }
216090
216362
  async function publish(cwd, save = true, extraArgs = []) {
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
  }
@@ -457,13 +555,30 @@ async function validateSingleBinPath(binPath, fieldName, cwd, save) {
457
555
  if (save) {
458
556
  return;
459
557
  }
460
- if (binPath.startsWith("dist/src/") || binPath.startsWith("./dist/src/")) {
558
+ if (binPath.startsWith("dist/src/") || binPath.startsWith("./dist/src/") || binPath.startsWith("dist/bin/") || binPath.startsWith("./dist/bin/")) {
461
559
  const fullPath2 = Path3.join(cwd, binPath);
462
560
  const distExists = await fileExists(fullPath2);
463
561
  if (distExists) {
464
562
  return;
465
563
  }
466
- const srcPath = binPath.startsWith("./dist/src/") ? binPath.replace("./dist/src/", "src/") : binPath.replace("dist/src/", "src/");
564
+ let srcPath;
565
+ let sourceDir;
566
+ if (binPath.startsWith("./dist/src/")) {
567
+ srcPath = binPath.replace("./dist/src/", "src/");
568
+ sourceDir = "src";
569
+ } else if (binPath.startsWith("dist/src/")) {
570
+ srcPath = binPath.replace("dist/src/", "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";
578
+ } else {
579
+ srcPath = binPath;
580
+ sourceDir = "src";
581
+ }
467
582
  const basePath = srcPath.replace(/\.(js|cjs|mjs)$/, "");
468
583
  const tsPath = Path3.join(cwd, basePath + ".ts");
469
584
  const jsPath = Path3.join(cwd, basePath + ".js");
@@ -490,7 +605,16 @@ async function validateSingleBinPath(binPath, fieldName, cwd, save) {
490
605
  if (pathExists) {
491
606
  return;
492
607
  }
493
- if (binPath.startsWith("src/") || binPath.startsWith("./src/")) {
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
+ }
494
618
  const basePath = fullPath.replace(/\.(js|cjs|mjs)$/, "");
495
619
  const tsPath = basePath + ".ts";
496
620
  const jsPath = basePath + ".js";
@@ -515,10 +639,19 @@ function transformBinPaths(value) {
515
639
  if (value.startsWith("dist/src/")) {
516
640
  return value.replace("dist/", "");
517
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
+ }
518
648
  if (value.startsWith("./src/")) {
519
649
  return value.replace("./", "");
520
650
  }
521
- 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") {
522
655
  return value;
523
656
  }
524
657
  return value;
@@ -563,6 +696,43 @@ function fixExportsForDist(obj) {
563
696
  }
564
697
  return obj;
565
698
  }
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
+ }
566
736
  async function resolveWorkspaceDependencies(dependencies, cwd) {
567
737
  if (!dependencies)
568
738
  return void 0;
@@ -622,7 +792,7 @@ async function resolveWorkspaceVersion(packageName, workspaceSpec, cwd) {
622
792
  return workspaceSpec;
623
793
  }
624
794
  }
625
- async function cleanPackageJSON(pkg, mainEntry, options, cwd) {
795
+ async function cleanPackageJSON(pkg, mainEntry, options, cwd, distDir) {
626
796
  const cleaned = {
627
797
  name: pkg.name,
628
798
  version: pkg.version
@@ -687,7 +857,13 @@ async function cleanPackageJSON(pkg, mainEntry, options, cwd) {
687
857
  cleaned.main = `src/${mainEntry}.cjs`;
688
858
  }
689
859
  cleaned.module = `src/${mainEntry}.js`;
690
- 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
+ }
691
867
  return cleaned;
692
868
  }
693
869
  async function fileExists(filePath) {
@@ -739,9 +915,19 @@ async function build2(cwd, save = false) {
739
915
  console.warn(" Add 'dist/' to your .gitignore file");
740
916
  }
741
917
  }
742
- const entries = await findEntrypoints(srcDir);
743
- if (entries.length === 0) {
744
- 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(", ")}`);
745
931
  }
746
932
  const options = {
747
933
  formats: {
@@ -752,7 +938,7 @@ async function build2(cwd, save = false) {
752
938
  umd: entries.includes("umd")
753
939
  }
754
940
  };
755
- const mainEntry = detectMainEntry(pkg, entries);
941
+ const mainEntry = detectMainEntry(pkg, srcEntries);
756
942
  console.info(" Found entries:", entries.join(", "));
757
943
  console.info(" Main entry:", mainEntry);
758
944
  if (options.formats.cjs) {
@@ -783,11 +969,29 @@ async function build2(cwd, save = false) {
783
969
  const entryPoints = [];
784
970
  const umdEntries = [];
785
971
  for (const entry of entries) {
786
- const entryPath = Path3.join(srcDir, `${entry}.ts`);
787
- 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
+ }
788
992
  const actualPath = await fileExists(entryPath) ? entryPath : jsEntryPath;
789
993
  if (!await fileExists(actualPath)) {
790
- 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.`);
791
995
  }
792
996
  if (entry === "umd") {
793
997
  umdEntries.push(actualPath);
@@ -795,58 +999,107 @@ async function build2(cwd, save = false) {
795
999
  entryPoints.push(actualPath);
796
1000
  }
797
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
+ }
798
1008
  if (entryPoints.length > 0) {
799
- const entryNames = entryPoints.map((path) => {
1009
+ console.info(` Building ${entryPoints.length} entries (ESM)...`);
1010
+ const srcEntryNames = srcEntryPoints.map((path) => {
800
1011
  const name = Path3.basename(path, Path3.extname(path));
801
1012
  return name;
802
1013
  });
803
- console.info(` Building ${entryPoints.length} entries (ESM)...`);
804
- await ESBuild.build({
805
- entryPoints,
806
- outdir: distSrcDir,
807
- format: "esm",
808
- outExtension: { ".js": ".js" },
809
- bundle: true,
810
- minify: false,
811
- sourcemap: false,
812
- external: externalDeps,
813
- platform: "node",
814
- target: "node18",
815
- packages: "external",
816
- supported: { "import-attributes": true },
817
- plugins: [
818
- externalEntrypointsPlugin({
819
- entryNames,
820
- outputExtension: ".js"
821
- }),
822
- dtsPlugin({
823
- outDir: distSrcDir,
824
- rootDir: srcDir,
825
- entryPoints
826
- })
827
- ]
1014
+ const binEntryNames = binEntryPoints.map((path) => {
1015
+ const name = Path3.basename(path, Path3.extname(path));
1016
+ return name;
828
1017
  });
829
- if (options.formats.cjs) {
830
- console.info(` Building ${entryPoints.length} entries (CJS)...`);
1018
+ const allESMEntryPoints = [...srcEntryPoints, ...binEntryPoints];
1019
+ const allEntryNames = [...srcEntryNames, ...binEntryNames];
1020
+ if (allESMEntryPoints.length > 0) {
831
1021
  await ESBuild.build({
832
- entryPoints,
833
- outdir: distSrcDir,
834
- format: "cjs",
835
- 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" },
836
1028
  bundle: true,
837
1029
  minify: false,
838
1030
  sourcemap: false,
839
1031
  external: externalDeps,
840
1032
  platform: "node",
841
1033
  target: "node18",
1034
+ packages: "external",
842
1035
  supported: { "import-attributes": true },
843
1036
  plugins: [
844
1037
  externalEntrypointsPlugin({
845
- entryNames,
846
- outputExtension: ".cjs"
847
- })
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
+ })] : []
848
1053
  ]
849
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
+ }
850
1103
  }
851
1104
  }
852
1105
  for (const umdPath of umdEntries) {
@@ -869,8 +1122,8 @@ async function build2(cwd, save = false) {
869
1122
  }
870
1123
  const autoDiscoveredFiles = [];
871
1124
  console.info(" Generating package.json...");
872
- const cleanedPkg = await cleanPackageJSON(pkg, mainEntry, options, cwd);
873
- 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);
874
1127
  cleanedPkg.exports = fixExportsForDist(exportsResult.exports);
875
1128
  if (exportsResult.staleExports.length > 0) {
876
1129
  console.warn(`\u26A0\uFE0F WARNING: Found ${exportsResult.staleExports.length} stale export(s) pointing to missing src/ files:`);
@@ -971,7 +1224,10 @@ async function build2(cwd, save = false) {
971
1224
  rootPkg2.main = `./dist/src/${mainEntry}.cjs`;
972
1225
  }
973
1226
  rootPkg2.module = `./dist/src/${mainEntry}.js`;
974
- 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
+ }
975
1231
  if (rootPkg2.typings && typeof rootPkg2.typings === "string") {
976
1232
  rootPkg2.typings = rootPkg2.typings.startsWith("./dist/") ? rootPkg2.typings : "./" + Path3.join("dist", rootPkg2.typings);
977
1233
  }
@@ -1002,6 +1258,27 @@ async function build2(cwd, save = false) {
1002
1258
  }
1003
1259
  }
1004
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
+ }
1005
1282
  if (rootPkg2.bin) {
1006
1283
  if (typeof rootPkg2.bin === "string") {
1007
1284
  const distPath = rootPkg2.bin.startsWith("./dist/") ? rootPkg2.bin : rootPkg2.bin.startsWith("dist/") ? "./" + rootPkg2.bin : "./" + Path3.join("dist", rootPkg2.bin);
@@ -1064,6 +1341,9 @@ async function build2(cwd, save = false) {
1064
1341
  if (save) {
1065
1342
  rootPkg = JSON.parse(await FS3.readFile(pkgPath, "utf-8"));
1066
1343
  }
1344
+ if (allBinEntries.length > 0 || pkg.bin) {
1345
+ await makeFilesExecutable(pkg, cwd, allBinEntries);
1346
+ }
1067
1347
  return { distPkg: fixedDistPkg, rootPkg };
1068
1348
  }
1069
1349
  async function publish(cwd, save = true, extraArgs = []) {