@b9g/libuild 0.1.9 → 0.1.11

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/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.1.11] - 2025-11-02
6
+
7
+ ### Fixed
8
+ - **CRITICAL**: Workspace dependencies (workspace:*) are now properly resolved to actual version numbers during build
9
+ - Validation warnings for valid libuild output paths (dist/src/ files that libuild creates)
10
+
11
+ ## [0.1.10] - 2025-10-29
12
+
13
+ ### Added
14
+ - Automatic cleanup of invalid bin/exports paths when using --save flag
15
+ - Clear messaging that libuild is zero-config with NO libuild.config.js file
16
+
17
+ ### Fixed
18
+ - --save now validates and removes bin/exports entries pointing to non-existent files
19
+ - Package.json fields are regenerated based on actual built files during --save
20
+ - Validation logic is now context-aware of --save flag to prevent warnings about configuration that libuild itself creates
21
+ - CLI argument parsing to prevent npm flags from being incorrectly treated as directory arguments
22
+
5
23
  ## [0.1.9] - 2025-10-29
6
24
 
7
25
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/libuild",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Zero-config library builds",
5
5
  "keywords": [
6
6
  "build",
package/src/cli.cjs CHANGED
@@ -56,6 +56,12 @@ Options:
56
56
  --save Update root package.json to point to dist files
57
57
  --no-save Skip package.json updates (for publish command)
58
58
 
59
+ IMPORTANT:
60
+ \u2022 libuild is zero-config - there is NO libuild.config.js file
61
+ \u2022 Configuration comes from your package.json (main, module, exports, etc.)
62
+ \u2022 Use --save to regenerate package.json fields based on built files
63
+ \u2022 Invalid bin/exports paths are automatically cleaned up with --save
64
+
59
65
  For publish command, all additional flags are forwarded to npm publish.
60
66
 
61
67
  Examples:
package/src/cli.js CHANGED
@@ -34,6 +34,12 @@ Options:
34
34
  --save Update root package.json to point to dist files
35
35
  --no-save Skip package.json updates (for publish command)
36
36
 
37
+ IMPORTANT:
38
+ \u2022 libuild is zero-config - there is NO libuild.config.js file
39
+ \u2022 Configuration comes from your package.json (main, module, exports, etc.)
40
+ \u2022 Use --save to regenerate package.json fields based on built files
41
+ \u2022 Invalid bin/exports paths are automatically cleaned up with --save
42
+
37
43
  For publish command, all additional flags are forwarded to npm publish.
38
44
 
39
45
  Examples:
package/src/libuild.cjs CHANGED
@@ -215463,63 +215463,70 @@ function transformSrcToDist(value) {
215463
215463
  }
215464
215464
  return value;
215465
215465
  }
215466
- async function validateBinPaths(value, srcDir, fieldName = "bin") {
215466
+ async function validateBinPaths(value, cwd, save, fieldName = "bin") {
215467
215467
  if (typeof value === "string") {
215468
- await validateSingleBinPath(value, fieldName, srcDir);
215468
+ await validateSingleBinPath(value, fieldName, cwd, save);
215469
215469
  } else if (typeof value === "object" && value !== null) {
215470
215470
  for (const [key, val] of Object.entries(value)) {
215471
215471
  if (typeof val === "string") {
215472
- await validateSingleBinPath(val, `${fieldName}.${key}`, srcDir);
215472
+ await validateSingleBinPath(val, `${fieldName}.${key}`, cwd, save);
215473
215473
  }
215474
215474
  }
215475
215475
  }
215476
215476
  }
215477
- async function validateSingleBinPath(binPath, fieldName, srcDir) {
215478
- if (binPath.startsWith("dist/") || binPath.startsWith("./dist/")) {
215479
- let srcPath;
215480
- if (binPath.startsWith("./dist/src/")) {
215481
- srcPath = binPath.replace("./dist/src/", "src/");
215482
- } else if (binPath.startsWith("dist/src/")) {
215483
- srcPath = binPath.replace("dist/src/", "src/");
215484
- } else if (binPath.startsWith("./dist/")) {
215485
- srcPath = binPath.replace("./dist/", "src/");
215486
- } else if (binPath.startsWith("dist/")) {
215487
- srcPath = binPath.replace("dist/", "src/");
215488
- } else {
215489
- srcPath = "";
215477
+ async function validateSingleBinPath(binPath, fieldName, cwd, save) {
215478
+ if (save) {
215479
+ return;
215480
+ }
215481
+ if (binPath.startsWith("dist/src/") || binPath.startsWith("./dist/src/")) {
215482
+ const fullPath2 = Path3.join(cwd, binPath);
215483
+ const distExists = await fileExists(fullPath2);
215484
+ if (distExists) {
215485
+ return;
215490
215486
  }
215491
- const basePath = srcPath.replace(/\.(js|cjs)$/, "");
215492
- const tsPath = Path3.join(srcDir, "..", basePath + ".ts");
215493
- const jsPath = Path3.join(srcDir, "..", basePath + ".js");
215487
+ const srcPath = binPath.startsWith("./dist/src/") ? binPath.replace("./dist/src/", "src/") : binPath.replace("dist/src/", "src/");
215488
+ const basePath = srcPath.replace(/\.(js|cjs|mjs)$/, "");
215489
+ const tsPath = Path3.join(cwd, basePath + ".ts");
215490
+ const jsPath = Path3.join(cwd, basePath + ".js");
215494
215491
  const srcExists = await fileExists(tsPath) || await fileExists(jsPath);
215495
215492
  if (!srcExists) {
215496
- throw new Error(`${fieldName} field points to dist/ directory: "${binPath}"
215497
-
215498
- The bin field should reference source files that libuild will build and transform:
215499
- CORRECT: "bin": {"tool": "src/cli.js"}
215500
- INCORRECT: "bin": {"tool": "dist/cli.js"}
215501
-
215502
- Libuild workflow:
215503
- 1. Point bin entries to src/ files
215504
- 2. Run 'libuild build --save' to update package.json with dist/ paths
215505
- 3. Set "private": true in your development package.json
215506
-
215507
- If you need to include pre-built executable files, use the files field instead:
215508
- "files": ["scripts/my-tool.js"]`);
215493
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" but neither the dist file nor corresponding src file exists.
215494
+
215495
+ Create "${srcPath}" and run 'libuild build --save' to update paths correctly.`);
215496
+ return;
215509
215497
  }
215510
- console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to dist/ directory: "${binPath}"
215511
- The libuild workflow is to point bin entries to src/ files and use --save to update paths.
215512
- Consider changing to: "${srcPath}" and running 'libuild build --save'`);
215498
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" which doesn't exist yet.
215499
+
215500
+ Run 'libuild build' to create the dist files, or use 'libuild build --save' to update the path to "${srcPath}".`);
215513
215501
  return;
215514
215502
  }
215515
- if (binPath.startsWith("src/") || binPath.startsWith("./src/")) {
215503
+ if (binPath.startsWith("dist/") || binPath.startsWith("./dist/")) {
215504
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" in dist/ directory.
215505
+
215506
+ libuild expects bin entries to point to src/ files. Consider changing to the corresponding src/ path and using --save.`);
215516
215507
  return;
215517
215508
  }
215518
- if (!binPath.startsWith("src/") && !binPath.startsWith("./src/")) {
215519
- console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" which is not in src/ directory.
215520
- Consider moving executable to src/ or using the files field for pre-built scripts.
215521
- Run 'libuild build --save' after fixing to update paths correctly.`);
215509
+ const fullPath = Path3.join(cwd, binPath);
215510
+ const pathExists = await fileExists(fullPath);
215511
+ if (pathExists) {
215512
+ return;
215522
215513
  }
215514
+ if (binPath.startsWith("src/") || binPath.startsWith("./src/")) {
215515
+ const basePath = fullPath.replace(/\.(js|cjs|mjs)$/, "");
215516
+ const tsPath = basePath + ".ts";
215517
+ const jsPath = basePath + ".js";
215518
+ const tsExists = await fileExists(tsPath);
215519
+ const jsExists = await fileExists(jsPath);
215520
+ if (tsExists || jsExists) {
215521
+ return;
215522
+ }
215523
+ }
215524
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" which doesn't exist
215525
+
215526
+ libuild is ZERO-CONFIG - there is no libuild.config.js file!
215527
+ Configuration comes from your package.json fields.
215528
+
215529
+ Use 'libuild build --save' to update package.json with correct dist/ paths automatically.`);
215523
215530
  }
215524
215531
  function transformBinPaths(value) {
215525
215532
  if (typeof value === "string") {
@@ -215577,7 +215584,66 @@ function fixExportsForDist(obj) {
215577
215584
  }
215578
215585
  return obj;
215579
215586
  }
215580
- function cleanPackageJSON(pkg, mainEntry, options) {
215587
+ async function resolveWorkspaceDependencies(dependencies, cwd) {
215588
+ if (!dependencies)
215589
+ return void 0;
215590
+ const resolved = {};
215591
+ for (const [depName, depVersion] of Object.entries(dependencies)) {
215592
+ if (depVersion.startsWith("workspace:")) {
215593
+ const resolvedVersion = await resolveWorkspaceVersion(depName, depVersion, cwd);
215594
+ resolved[depName] = resolvedVersion;
215595
+ } else {
215596
+ resolved[depName] = depVersion;
215597
+ }
215598
+ }
215599
+ return resolved;
215600
+ }
215601
+ async function resolveWorkspaceVersion(packageName, workspaceSpec, cwd) {
215602
+ try {
215603
+ if (workspaceSpec === "workspace:*") {
215604
+ const packageNameParts = packageName.replace("@", "").replace("/", "-");
215605
+ const packageBaseName = packageName.split("/").pop() || packageName;
215606
+ const possiblePaths = [
215607
+ `../packages/${packageNameParts}/package.json`,
215608
+ `../packages/${packageBaseName}/package.json`,
215609
+ `../${packageNameParts}/package.json`,
215610
+ `../${packageBaseName}/package.json`,
215611
+ `./packages/${packageNameParts}/package.json`,
215612
+ `./packages/${packageBaseName}/package.json`,
215613
+ `./${packageNameParts}/package.json`,
215614
+ `./${packageBaseName}/package.json`
215615
+ ];
215616
+ for (const pkgPath of possiblePaths) {
215617
+ try {
215618
+ const resolvedPath = Path3.resolve(cwd, pkgPath);
215619
+ if (await fileExists(resolvedPath)) {
215620
+ const pkgContent = await FS3.readFile(resolvedPath, "utf-8");
215621
+ const pkgJson = JSON.parse(pkgContent);
215622
+ if (pkgJson.name === packageName) {
215623
+ console.info(` Resolved workspace:* dependency "${packageName}" to ^${pkgJson.version}`);
215624
+ return `^${pkgJson.version}`;
215625
+ }
215626
+ }
215627
+ } catch {
215628
+ continue;
215629
+ }
215630
+ }
215631
+ console.warn(`\u26A0\uFE0F WARNING: Could not resolve workspace dependency "${packageName}" - keeping as workspace:*`);
215632
+ console.warn(` Searched in: ${possiblePaths.map((p) => Path3.resolve(cwd, p)).join(", ")}`);
215633
+ return workspaceSpec;
215634
+ }
215635
+ const versionPart = workspaceSpec.replace("workspace:", "");
215636
+ if (versionPart !== "*") {
215637
+ console.info(` Resolved workspace:${versionPart} dependency "${packageName}" to ${versionPart}`);
215638
+ return versionPart;
215639
+ }
215640
+ return workspaceSpec;
215641
+ } catch (error) {
215642
+ console.warn(`\u26A0\uFE0F WARNING: Error resolving workspace dependency "${packageName}": ${error.message}`);
215643
+ return workspaceSpec;
215644
+ }
215645
+ }
215646
+ async function cleanPackageJSON(pkg, mainEntry, options, cwd) {
215581
215647
  const cleaned = {
215582
215648
  name: pkg.name,
215583
215649
  version: pkg.version
@@ -215628,6 +215694,8 @@ function cleanPackageJSON(pkg, mainEntry, options) {
215628
215694
  cleaned[field] = transformBinPaths(pkg[field]);
215629
215695
  } else if (pathFields.includes(field)) {
215630
215696
  cleaned[field] = transformSrcToDist(pkg[field]);
215697
+ } else if (field === "dependencies" || field === "devDependencies" || field === "peerDependencies" || field === "optionalDependencies") {
215698
+ cleaned[field] = await resolveWorkspaceDependencies(pkg[field], cwd);
215631
215699
  } else {
215632
215700
  cleaned[field] = pkg[field];
215633
215701
  }
@@ -215667,6 +215735,7 @@ function validatePath(inputPath, basePath) {
215667
215735
  }
215668
215736
  async function build2(cwd, save = false) {
215669
215737
  console.info("Building with libuild...");
215738
+ console.info(" Zero-config library build tool - configuration from package.json");
215670
215739
  const srcDir = Path3.join(cwd, "src");
215671
215740
  const distDir = Path3.join(cwd, "dist");
215672
215741
  const distSrcDir = Path3.join(distDir, "src");
@@ -215676,7 +215745,7 @@ async function build2(cwd, save = false) {
215676
215745
  const pkgPath = Path3.join(cwd, "package.json");
215677
215746
  const pkg = JSON.parse(await FS3.readFile(pkgPath, "utf-8"));
215678
215747
  if (pkg.bin) {
215679
- await validateBinPaths(pkg.bin, srcDir, "bin");
215748
+ await validateBinPaths(pkg.bin, cwd, save, "bin");
215680
215749
  }
215681
215750
  if (!pkg.private) {
215682
215751
  console.warn("\u26A0\uFE0F WARNING: Root package.json is not private - this could lead to accidental publishing of development package.json");
@@ -215821,7 +215890,7 @@ async function build2(cwd, save = false) {
215821
215890
  }
215822
215891
  const autoDiscoveredFiles = [];
215823
215892
  console.info(" Generating package.json...");
215824
- const cleanedPkg = cleanPackageJSON(pkg, mainEntry, options);
215893
+ const cleanedPkg = await cleanPackageJSON(pkg, mainEntry, options, cwd);
215825
215894
  const exportsResult = generateExports(entries, mainEntry, options, pkg.exports);
215826
215895
  cleanedPkg.exports = fixExportsForDist(exportsResult.exports);
215827
215896
  if (exportsResult.staleExports.length > 0) {
@@ -215832,7 +215901,8 @@ async function build2(cwd, save = false) {
215832
215901
  if (save) {
215833
215902
  console.info(" Removing stale exports from root package.json (--save mode)");
215834
215903
  } else {
215835
- console.warn(" Use --save to remove these from root package.json");
215904
+ console.warn(" libuild is ZERO-CONFIG - no libuild.config.js file needed!");
215905
+ console.warn(" Use 'libuild build --save' to clean up package.json automatically");
215836
215906
  }
215837
215907
  }
215838
215908
  if (cleanedPkg.files && Array.isArray(cleanedPkg.files)) {
@@ -215929,36 +215999,55 @@ async function build2(cwd, save = false) {
215929
215999
  const rootExports = {};
215930
216000
  for (const [key, value] of Object.entries(cleanedPkg.exports)) {
215931
216001
  if (typeof value === "string") {
215932
- rootExports[key] = value.startsWith("./dist/") ? value : `./dist${value.startsWith(".") ? value.slice(1) : value}`;
216002
+ const distPath = value.startsWith("./dist/") ? value : `./dist${value.startsWith(".") ? value.slice(1) : value}`;
216003
+ const fullPath = Path3.join(cwd, distPath);
216004
+ if (await fileExists(fullPath)) {
216005
+ rootExports[key] = distPath;
216006
+ }
215933
216007
  } else if (typeof value === "object" && value !== null) {
215934
- rootExports[key] = {};
216008
+ const cleanedValue = {};
216009
+ let hasValidPaths = false;
215935
216010
  for (const [subKey, subValue] of Object.entries(value)) {
215936
216011
  if (typeof subValue === "string") {
215937
- rootExports[key][subKey] = subValue.startsWith("./dist/") ? subValue : `./dist${subValue.startsWith(".") ? subValue.slice(1) : subValue}`;
216012
+ const distPath = subValue.startsWith("./dist/") ? subValue : `./dist${subValue.startsWith(".") ? subValue.slice(1) : subValue}`;
216013
+ const fullPath = Path3.join(cwd, distPath);
216014
+ if (await fileExists(fullPath)) {
216015
+ cleanedValue[subKey] = distPath;
216016
+ hasValidPaths = true;
216017
+ }
215938
216018
  }
215939
216019
  }
216020
+ if (hasValidPaths) {
216021
+ rootExports[key] = cleanedValue;
216022
+ }
215940
216023
  }
215941
216024
  }
215942
216025
  rootPkg2.exports = rootExports;
215943
216026
  if (rootPkg2.bin) {
215944
216027
  if (typeof rootPkg2.bin === "string") {
215945
- if (!rootPkg2.bin.startsWith("./dist/")) {
215946
- if (rootPkg2.bin.startsWith("dist/")) {
215947
- rootPkg2.bin = "./" + rootPkg2.bin;
215948
- } else {
215949
- rootPkg2.bin = "./" + Path3.join("dist", rootPkg2.bin);
215950
- }
216028
+ const distPath = rootPkg2.bin.startsWith("./dist/") ? rootPkg2.bin : rootPkg2.bin.startsWith("dist/") ? "./" + rootPkg2.bin : "./" + Path3.join("dist", rootPkg2.bin);
216029
+ const fullPath = Path3.join(cwd, distPath);
216030
+ if (await fileExists(fullPath)) {
216031
+ rootPkg2.bin = distPath;
216032
+ } else {
216033
+ delete rootPkg2.bin;
215951
216034
  }
215952
216035
  } else {
216036
+ const cleanedBin = {};
215953
216037
  for (const [name, binPath] of Object.entries(rootPkg2.bin)) {
215954
- if (typeof binPath === "string" && !binPath.startsWith("./dist/")) {
215955
- if (binPath.startsWith("dist/")) {
215956
- rootPkg2.bin[name] = "./" + binPath;
215957
- } else {
215958
- rootPkg2.bin[name] = "./" + Path3.join("dist", binPath);
216038
+ if (typeof binPath === "string") {
216039
+ const distPath = binPath.startsWith("./dist/") ? binPath : binPath.startsWith("dist/") ? "./" + binPath : "./" + Path3.join("dist", binPath);
216040
+ const fullPath = Path3.join(cwd, distPath);
216041
+ if (await fileExists(fullPath)) {
216042
+ cleanedBin[name] = distPath;
215959
216043
  }
215960
216044
  }
215961
216045
  }
216046
+ if (Object.keys(cleanedBin).length > 0) {
216047
+ rootPkg2.bin = cleanedBin;
216048
+ } else {
216049
+ delete rootPkg2.bin;
216050
+ }
215962
216051
  }
215963
216052
  }
215964
216053
  if (pkg.files !== void 0) {
package/src/libuild.js CHANGED
@@ -442,63 +442,70 @@ function transformSrcToDist(value) {
442
442
  }
443
443
  return value;
444
444
  }
445
- async function validateBinPaths(value, srcDir, fieldName = "bin") {
445
+ async function validateBinPaths(value, cwd, save, fieldName = "bin") {
446
446
  if (typeof value === "string") {
447
- await validateSingleBinPath(value, fieldName, srcDir);
447
+ await validateSingleBinPath(value, fieldName, cwd, save);
448
448
  } else if (typeof value === "object" && value !== null) {
449
449
  for (const [key, val] of Object.entries(value)) {
450
450
  if (typeof val === "string") {
451
- await validateSingleBinPath(val, `${fieldName}.${key}`, srcDir);
451
+ await validateSingleBinPath(val, `${fieldName}.${key}`, cwd, save);
452
452
  }
453
453
  }
454
454
  }
455
455
  }
456
- async function validateSingleBinPath(binPath, fieldName, srcDir) {
457
- if (binPath.startsWith("dist/") || binPath.startsWith("./dist/")) {
458
- let srcPath;
459
- if (binPath.startsWith("./dist/src/")) {
460
- srcPath = binPath.replace("./dist/src/", "src/");
461
- } else if (binPath.startsWith("dist/src/")) {
462
- 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/");
467
- } else {
468
- srcPath = "";
456
+ async function validateSingleBinPath(binPath, fieldName, cwd, save) {
457
+ if (save) {
458
+ return;
459
+ }
460
+ if (binPath.startsWith("dist/src/") || binPath.startsWith("./dist/src/")) {
461
+ const fullPath2 = Path3.join(cwd, binPath);
462
+ const distExists = await fileExists(fullPath2);
463
+ if (distExists) {
464
+ return;
469
465
  }
470
- const basePath = srcPath.replace(/\.(js|cjs)$/, "");
471
- const tsPath = Path3.join(srcDir, "..", basePath + ".ts");
472
- const jsPath = Path3.join(srcDir, "..", basePath + ".js");
466
+ const srcPath = binPath.startsWith("./dist/src/") ? binPath.replace("./dist/src/", "src/") : binPath.replace("dist/src/", "src/");
467
+ const basePath = srcPath.replace(/\.(js|cjs|mjs)$/, "");
468
+ const tsPath = Path3.join(cwd, basePath + ".ts");
469
+ const jsPath = Path3.join(cwd, basePath + ".js");
473
470
  const srcExists = await fileExists(tsPath) || await fileExists(jsPath);
474
471
  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"]`);
472
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" but neither the dist file nor corresponding src file exists.
473
+
474
+ Create "${srcPath}" and run 'libuild build --save' to update paths correctly.`);
475
+ return;
488
476
  }
489
- console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to dist/ directory: "${binPath}"
490
- The libuild workflow is to point bin entries to src/ files and use --save to update paths.
491
- Consider changing to: "${srcPath}" and running 'libuild build --save'`);
477
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" which doesn't exist yet.
478
+
479
+ Run 'libuild build' to create the dist files, or use 'libuild build --save' to update the path to "${srcPath}".`);
492
480
  return;
493
481
  }
494
- if (binPath.startsWith("src/") || binPath.startsWith("./src/")) {
482
+ if (binPath.startsWith("dist/") || binPath.startsWith("./dist/")) {
483
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" in dist/ directory.
484
+
485
+ libuild expects bin entries to point to src/ files. Consider changing to the corresponding src/ path and using --save.`);
495
486
  return;
496
487
  }
497
- if (!binPath.startsWith("src/") && !binPath.startsWith("./src/")) {
498
- console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" which is not in src/ directory.
499
- Consider moving executable to src/ or using the files field for pre-built scripts.
500
- Run 'libuild build --save' after fixing to update paths correctly.`);
488
+ const fullPath = Path3.join(cwd, binPath);
489
+ const pathExists = await fileExists(fullPath);
490
+ if (pathExists) {
491
+ return;
501
492
  }
493
+ if (binPath.startsWith("src/") || binPath.startsWith("./src/")) {
494
+ const basePath = fullPath.replace(/\.(js|cjs|mjs)$/, "");
495
+ const tsPath = basePath + ".ts";
496
+ const jsPath = basePath + ".js";
497
+ const tsExists = await fileExists(tsPath);
498
+ const jsExists = await fileExists(jsPath);
499
+ if (tsExists || jsExists) {
500
+ return;
501
+ }
502
+ }
503
+ console.warn(`\u26A0\uFE0F WARNING: ${fieldName} field points to "${binPath}" which doesn't exist
504
+
505
+ libuild is ZERO-CONFIG - there is no libuild.config.js file!
506
+ Configuration comes from your package.json fields.
507
+
508
+ Use 'libuild build --save' to update package.json with correct dist/ paths automatically.`);
502
509
  }
503
510
  function transformBinPaths(value) {
504
511
  if (typeof value === "string") {
@@ -556,7 +563,66 @@ function fixExportsForDist(obj) {
556
563
  }
557
564
  return obj;
558
565
  }
559
- function cleanPackageJSON(pkg, mainEntry, options) {
566
+ async function resolveWorkspaceDependencies(dependencies, cwd) {
567
+ if (!dependencies)
568
+ return void 0;
569
+ const resolved = {};
570
+ for (const [depName, depVersion] of Object.entries(dependencies)) {
571
+ if (depVersion.startsWith("workspace:")) {
572
+ const resolvedVersion = await resolveWorkspaceVersion(depName, depVersion, cwd);
573
+ resolved[depName] = resolvedVersion;
574
+ } else {
575
+ resolved[depName] = depVersion;
576
+ }
577
+ }
578
+ return resolved;
579
+ }
580
+ async function resolveWorkspaceVersion(packageName, workspaceSpec, cwd) {
581
+ try {
582
+ if (workspaceSpec === "workspace:*") {
583
+ const packageNameParts = packageName.replace("@", "").replace("/", "-");
584
+ const packageBaseName = packageName.split("/").pop() || packageName;
585
+ const possiblePaths = [
586
+ `../packages/${packageNameParts}/package.json`,
587
+ `../packages/${packageBaseName}/package.json`,
588
+ `../${packageNameParts}/package.json`,
589
+ `../${packageBaseName}/package.json`,
590
+ `./packages/${packageNameParts}/package.json`,
591
+ `./packages/${packageBaseName}/package.json`,
592
+ `./${packageNameParts}/package.json`,
593
+ `./${packageBaseName}/package.json`
594
+ ];
595
+ for (const pkgPath of possiblePaths) {
596
+ try {
597
+ const resolvedPath = Path3.resolve(cwd, pkgPath);
598
+ if (await fileExists(resolvedPath)) {
599
+ const pkgContent = await FS3.readFile(resolvedPath, "utf-8");
600
+ const pkgJson = JSON.parse(pkgContent);
601
+ if (pkgJson.name === packageName) {
602
+ console.info(` Resolved workspace:* dependency "${packageName}" to ^${pkgJson.version}`);
603
+ return `^${pkgJson.version}`;
604
+ }
605
+ }
606
+ } catch {
607
+ continue;
608
+ }
609
+ }
610
+ console.warn(`\u26A0\uFE0F WARNING: Could not resolve workspace dependency "${packageName}" - keeping as workspace:*`);
611
+ console.warn(` Searched in: ${possiblePaths.map((p) => Path3.resolve(cwd, p)).join(", ")}`);
612
+ return workspaceSpec;
613
+ }
614
+ const versionPart = workspaceSpec.replace("workspace:", "");
615
+ if (versionPart !== "*") {
616
+ console.info(` Resolved workspace:${versionPart} dependency "${packageName}" to ${versionPart}`);
617
+ return versionPart;
618
+ }
619
+ return workspaceSpec;
620
+ } catch (error) {
621
+ console.warn(`\u26A0\uFE0F WARNING: Error resolving workspace dependency "${packageName}": ${error.message}`);
622
+ return workspaceSpec;
623
+ }
624
+ }
625
+ async function cleanPackageJSON(pkg, mainEntry, options, cwd) {
560
626
  const cleaned = {
561
627
  name: pkg.name,
562
628
  version: pkg.version
@@ -607,6 +673,8 @@ function cleanPackageJSON(pkg, mainEntry, options) {
607
673
  cleaned[field] = transformBinPaths(pkg[field]);
608
674
  } else if (pathFields.includes(field)) {
609
675
  cleaned[field] = transformSrcToDist(pkg[field]);
676
+ } else if (field === "dependencies" || field === "devDependencies" || field === "peerDependencies" || field === "optionalDependencies") {
677
+ cleaned[field] = await resolveWorkspaceDependencies(pkg[field], cwd);
610
678
  } else {
611
679
  cleaned[field] = pkg[field];
612
680
  }
@@ -646,6 +714,7 @@ function validatePath(inputPath, basePath) {
646
714
  }
647
715
  async function build2(cwd, save = false) {
648
716
  console.info("Building with libuild...");
717
+ console.info(" Zero-config library build tool - configuration from package.json");
649
718
  const srcDir = Path3.join(cwd, "src");
650
719
  const distDir = Path3.join(cwd, "dist");
651
720
  const distSrcDir = Path3.join(distDir, "src");
@@ -655,7 +724,7 @@ async function build2(cwd, save = false) {
655
724
  const pkgPath = Path3.join(cwd, "package.json");
656
725
  const pkg = JSON.parse(await FS3.readFile(pkgPath, "utf-8"));
657
726
  if (pkg.bin) {
658
- await validateBinPaths(pkg.bin, srcDir, "bin");
727
+ await validateBinPaths(pkg.bin, cwd, save, "bin");
659
728
  }
660
729
  if (!pkg.private) {
661
730
  console.warn("\u26A0\uFE0F WARNING: Root package.json is not private - this could lead to accidental publishing of development package.json");
@@ -800,7 +869,7 @@ async function build2(cwd, save = false) {
800
869
  }
801
870
  const autoDiscoveredFiles = [];
802
871
  console.info(" Generating package.json...");
803
- const cleanedPkg = cleanPackageJSON(pkg, mainEntry, options);
872
+ const cleanedPkg = await cleanPackageJSON(pkg, mainEntry, options, cwd);
804
873
  const exportsResult = generateExports(entries, mainEntry, options, pkg.exports);
805
874
  cleanedPkg.exports = fixExportsForDist(exportsResult.exports);
806
875
  if (exportsResult.staleExports.length > 0) {
@@ -811,7 +880,8 @@ async function build2(cwd, save = false) {
811
880
  if (save) {
812
881
  console.info(" Removing stale exports from root package.json (--save mode)");
813
882
  } else {
814
- console.warn(" Use --save to remove these from root package.json");
883
+ console.warn(" libuild is ZERO-CONFIG - no libuild.config.js file needed!");
884
+ console.warn(" Use 'libuild build --save' to clean up package.json automatically");
815
885
  }
816
886
  }
817
887
  if (cleanedPkg.files && Array.isArray(cleanedPkg.files)) {
@@ -908,36 +978,55 @@ async function build2(cwd, save = false) {
908
978
  const rootExports = {};
909
979
  for (const [key, value] of Object.entries(cleanedPkg.exports)) {
910
980
  if (typeof value === "string") {
911
- rootExports[key] = value.startsWith("./dist/") ? value : `./dist${value.startsWith(".") ? value.slice(1) : value}`;
981
+ const distPath = value.startsWith("./dist/") ? value : `./dist${value.startsWith(".") ? value.slice(1) : value}`;
982
+ const fullPath = Path3.join(cwd, distPath);
983
+ if (await fileExists(fullPath)) {
984
+ rootExports[key] = distPath;
985
+ }
912
986
  } else if (typeof value === "object" && value !== null) {
913
- rootExports[key] = {};
987
+ const cleanedValue = {};
988
+ let hasValidPaths = false;
914
989
  for (const [subKey, subValue] of Object.entries(value)) {
915
990
  if (typeof subValue === "string") {
916
- rootExports[key][subKey] = subValue.startsWith("./dist/") ? subValue : `./dist${subValue.startsWith(".") ? subValue.slice(1) : subValue}`;
991
+ const distPath = subValue.startsWith("./dist/") ? subValue : `./dist${subValue.startsWith(".") ? subValue.slice(1) : subValue}`;
992
+ const fullPath = Path3.join(cwd, distPath);
993
+ if (await fileExists(fullPath)) {
994
+ cleanedValue[subKey] = distPath;
995
+ hasValidPaths = true;
996
+ }
917
997
  }
918
998
  }
999
+ if (hasValidPaths) {
1000
+ rootExports[key] = cleanedValue;
1001
+ }
919
1002
  }
920
1003
  }
921
1004
  rootPkg2.exports = rootExports;
922
1005
  if (rootPkg2.bin) {
923
1006
  if (typeof rootPkg2.bin === "string") {
924
- if (!rootPkg2.bin.startsWith("./dist/")) {
925
- if (rootPkg2.bin.startsWith("dist/")) {
926
- rootPkg2.bin = "./" + rootPkg2.bin;
927
- } else {
928
- rootPkg2.bin = "./" + Path3.join("dist", rootPkg2.bin);
929
- }
1007
+ const distPath = rootPkg2.bin.startsWith("./dist/") ? rootPkg2.bin : rootPkg2.bin.startsWith("dist/") ? "./" + rootPkg2.bin : "./" + Path3.join("dist", rootPkg2.bin);
1008
+ const fullPath = Path3.join(cwd, distPath);
1009
+ if (await fileExists(fullPath)) {
1010
+ rootPkg2.bin = distPath;
1011
+ } else {
1012
+ delete rootPkg2.bin;
930
1013
  }
931
1014
  } else {
1015
+ const cleanedBin = {};
932
1016
  for (const [name, binPath] of Object.entries(rootPkg2.bin)) {
933
- if (typeof binPath === "string" && !binPath.startsWith("./dist/")) {
934
- if (binPath.startsWith("dist/")) {
935
- rootPkg2.bin[name] = "./" + binPath;
936
- } else {
937
- rootPkg2.bin[name] = "./" + Path3.join("dist", binPath);
1017
+ if (typeof binPath === "string") {
1018
+ const distPath = binPath.startsWith("./dist/") ? binPath : binPath.startsWith("dist/") ? "./" + binPath : "./" + Path3.join("dist", binPath);
1019
+ const fullPath = Path3.join(cwd, distPath);
1020
+ if (await fileExists(fullPath)) {
1021
+ cleanedBin[name] = distPath;
938
1022
  }
939
1023
  }
940
1024
  }
1025
+ if (Object.keys(cleanedBin).length > 0) {
1026
+ rootPkg2.bin = cleanedBin;
1027
+ } else {
1028
+ delete rootPkg2.bin;
1029
+ }
941
1030
  }
942
1031
  }
943
1032
  if (pkg.files !== void 0) {