@baton-dx/cli 0.4.2 → 0.4.4

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.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as __require, r as __toESM, t as __commonJSMin } from "./chunk-BbwQpWto.mjs";
3
- import { m as require_dist } from "./context-detection-DdbrKid3.mjs";
3
+ import { m as require_dist } from "./context-detection-Ddu0mj_K.mjs";
4
4
  import { i as AI_TOOL_PATHS, r as evaluateDetection } from "./ai-tool-detection-CMsBNa9e.mjs";
5
5
  import { d as esm_default, m as simpleGit } from "./esm-BagM-kVd.mjs";
6
6
  import { access, mkdir, readFile, readdir, rm, rmdir, stat, symlink, unlink, writeFile } from "node:fs/promises";
@@ -4815,22 +4815,55 @@ async function isCacheValid(cachePath) {
4815
4815
  }
4816
4816
  }
4817
4817
  /**
4818
+ * Checks if a cached repository is stale based on FETCH_HEAD or HEAD mtime.
4819
+ * Falls back to .git/HEAD if FETCH_HEAD does not exist.
4820
+ */
4821
+ async function isCacheStale(cachePath, maxAgeMs) {
4822
+ try {
4823
+ const fetchHeadStat = await stat(join(cachePath, ".git", "FETCH_HEAD"));
4824
+ return Date.now() - fetchHeadStat.mtimeMs > maxAgeMs;
4825
+ } catch {
4826
+ try {
4827
+ const headStat = await stat(join(cachePath, ".git", "HEAD"));
4828
+ return Date.now() - headStat.mtimeMs > maxAgeMs;
4829
+ } catch {
4830
+ return true;
4831
+ }
4832
+ }
4833
+ }
4834
+ /**
4818
4835
  * Clones a Git repository with shallow clone and optional sparse checkout
4819
4836
  */
4820
4837
  async function cloneGitSource(options) {
4821
- const { url, ref, subpath, useCache = true } = options;
4838
+ const { url, ref, subpath, useCache = true, maxCacheAgeMs } = options;
4822
4839
  const cachePath = getCachePath(url, ref);
4823
4840
  if (useCache && await isCacheValid(cachePath)) {
4824
4841
  const git = esm_default(cachePath);
4825
4842
  try {
4826
- try {
4843
+ if (maxCacheAgeMs !== void 0 && await isCacheStale(cachePath, maxCacheAgeMs)) try {
4844
+ await git.fetch(["--depth=1", "origin"]);
4845
+ await git.raw([
4846
+ "reset",
4847
+ "--hard",
4848
+ `origin/${ref || "HEAD"}`
4849
+ ]);
4850
+ } catch {
4851
+ try {
4852
+ await git.pull(["--depth=1"]);
4853
+ } catch {
4854
+ console.warn(`[baton] Network unavailable, using stale cache for ${url}`);
4855
+ }
4856
+ }
4857
+ else try {
4827
4858
  await git.pull(["--depth=1"]);
4828
4859
  } catch {}
4829
4860
  const sha = await git.revparse(["HEAD"]);
4830
4861
  return {
4831
4862
  localPath: subpath ? join(cachePath, subpath) : cachePath,
4832
4863
  fromCache: true,
4833
- sha: sha.trim()
4864
+ sha: sha.trim(),
4865
+ cachePath,
4866
+ sparseCheckout: !!subpath
4834
4867
  };
4835
4868
  } catch (error) {
4836
4869
  throw new GitSourceError(`Failed to read cached repository: ${error instanceof Error ? error.message : String(error)}`, error);
@@ -4852,11 +4885,20 @@ async function cloneGitSource(options) {
4852
4885
  const git = esm_default();
4853
4886
  try {
4854
4887
  const cloneOptions = ["--depth=1"];
4855
- if (ref && ref !== "main" && ref !== "master") cloneOptions.push("--branch", ref);
4888
+ const isSha = ref && /^[0-9a-f]{7,40}$/i.test(ref);
4889
+ if (ref && !isSha && ref !== "main" && ref !== "master") cloneOptions.push("--branch", ref);
4856
4890
  if (subpath) cloneOptions.push("--no-checkout");
4857
4891
  await git.clone(url, cachePath, cloneOptions);
4892
+ const repoGit = esm_default(cachePath);
4893
+ if (isSha) {
4894
+ await repoGit.fetch([
4895
+ "--depth=1",
4896
+ "origin",
4897
+ ref
4898
+ ]);
4899
+ await repoGit.checkout("FETCH_HEAD");
4900
+ }
4858
4901
  if (subpath) {
4859
- const repoGit = esm_default(cachePath);
4860
4902
  await repoGit.raw([
4861
4903
  "sparse-checkout",
4862
4904
  "init",
@@ -4867,18 +4909,31 @@ async function cloneGitSource(options) {
4867
4909
  "set",
4868
4910
  subpath
4869
4911
  ]);
4870
- await repoGit.checkout(ref || "HEAD");
4871
- } else if (ref && ref !== "main" && ref !== "master") await esm_default(cachePath).checkout(ref);
4872
- const sha = await esm_default(cachePath).revparse(["HEAD"]);
4912
+ if (!isSha) await repoGit.checkout(ref || "HEAD");
4913
+ } else if (ref && !isSha && ref !== "main" && ref !== "master") await repoGit.checkout(ref);
4914
+ const sha = await repoGit.revparse(["HEAD"]);
4873
4915
  return {
4874
4916
  localPath: subpath ? join(cachePath, subpath) : cachePath,
4875
4917
  fromCache: false,
4876
- sha: sha.trim()
4918
+ sha: sha.trim(),
4919
+ cachePath,
4920
+ sparseCheckout: !!subpath
4877
4921
  };
4878
4922
  } catch (error) {
4879
4923
  throw new GitSourceError(`Failed to clone Git repository from ${url}: ${error instanceof Error ? error.message : String(error)}`, error);
4880
4924
  }
4881
4925
  }
4926
+ /**
4927
+ * Expands sparse-checkout in a cached repository to include additional paths.
4928
+ * Uses 'git sparse-checkout add' to preserve existing checkout paths.
4929
+ */
4930
+ async function expandSparseCheckout(cachePath, additionalPaths) {
4931
+ await esm_default(cachePath).raw([
4932
+ "sparse-checkout",
4933
+ "add",
4934
+ ...additionalPaths
4935
+ ]);
4936
+ }
4882
4937
 
4883
4938
  //#endregion
4884
4939
  //#region ../core/src/sources/npm-resolver.ts
@@ -6487,22 +6542,31 @@ async function removePlacedFiles(filePaths, projectRoot) {
6487
6542
  //#region ../core/src/sources/load-profile-safe.ts
6488
6543
  /**
6489
6544
  * Loads and parses a profile manifest from a given path within a source repository.
6490
- * Returns null if the file doesn't exist or is invalid (non-throwing variant).
6545
+ * Returns null if the file doesn't exist (ENOENT). Logs a warning for schema
6546
+ * validation errors and other failures so they are visible to the user.
6491
6547
  *
6492
6548
  * @param sourceRoot - Absolute path to the source repository root
6493
6549
  * @param profilePath - Relative path to the profile (e.g., "profiles/frontend")
6494
6550
  * @returns Parsed manifest summary or null
6495
6551
  */
6496
6552
  async function loadProfileManifestSafe(sourceRoot, profilePath) {
6553
+ const manifestPath = join(sourceRoot, profilePath, "baton.profile.yaml");
6497
6554
  try {
6498
- const parsed = (0, import_dist.parse)(await readFile(join(sourceRoot, profilePath, "baton.profile.yaml"), "utf-8"));
6555
+ const parsed = (0, import_dist.parse)(await readFile(manifestPath, "utf-8"));
6499
6556
  const manifest = profileManifestSchema.parse(parsed);
6500
6557
  return {
6501
6558
  name: manifest.name,
6502
6559
  version: manifest.version,
6503
6560
  description: manifest.description
6504
6561
  };
6505
- } catch {
6562
+ } catch (error) {
6563
+ if (error.code === "ENOENT" || error instanceof Error && error.message.includes("ENOENT")) return null;
6564
+ if (error instanceof ZodError) {
6565
+ const issues = error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
6566
+ console.warn(`Warning: Invalid profile manifest at ${manifestPath}:\n${issues}`);
6567
+ return null;
6568
+ }
6569
+ console.warn(`Warning: Could not load profile manifest at ${manifestPath}: ${error instanceof Error ? error.message : error}`);
6506
6570
  return null;
6507
6571
  }
6508
6572
  }
@@ -6706,11 +6770,12 @@ const MAX_CHAIN_DEPTH = 10;
6706
6770
  * @param manifest - Root profile manifest
6707
6771
  * @param source - Source URL or path of root profile
6708
6772
  * @param baseDir - Base directory for resolving relative paths
6773
+ * @param cloneContext - Optional clone context for sparse-checkout expansion
6709
6774
  * @returns Array of resolved profiles in merge order (base first, overrides last)
6710
6775
  */
6711
- async function resolveProfileChain(manifest, source, baseDir) {
6776
+ async function resolveProfileChain(manifest, source, baseDir, cloneContext) {
6712
6777
  const chain = [];
6713
- await resolveChainRecursive(manifest, source, baseDir, chain, /* @__PURE__ */ new Set(), []);
6778
+ await resolveChainRecursive(manifest, source, baseDir, chain, /* @__PURE__ */ new Set(), [], void 0, cloneContext);
6714
6779
  return chain;
6715
6780
  }
6716
6781
  /**
@@ -6723,19 +6788,22 @@ async function resolveProfileChain(manifest, source, baseDir) {
6723
6788
  * @param visited - Set of visited sources (for circular detection)
6724
6789
  * @param path - Current path (for error messages)
6725
6790
  * @param localPath - Resolved local directory path for this profile
6791
+ * @param cloneContext - Optional clone context for sparse-checkout expansion
6726
6792
  */
6727
- async function resolveChainRecursive(manifest, source, baseDir, chain, visited, path, localPath) {
6793
+ async function resolveChainRecursive(manifest, source, baseDir, chain, visited, path, localPath, cloneContext) {
6728
6794
  if (path.length >= MAX_CHAIN_DEPTH) throw new Error(`Profile inheritance chain exceeds maximum depth of ${MAX_CHAIN_DEPTH}. Chain: ${path.join(" -> ")} -> ${manifest.name}`);
6729
6795
  if (visited.has(source)) throw new CircularInheritanceError(`Circular profile inheritance detected: ${[...path, manifest.name].join(" -> ")}`);
6730
6796
  const currentVisited = new Set(visited);
6731
6797
  currentVisited.add(source);
6732
6798
  path.push(manifest.name);
6733
6799
  if (manifest.extends && manifest.extends.length > 0) for (const extendSource of manifest.extends) try {
6734
- const { manifest: parentManifest, localPath: parentLocalPath } = await loadProfileFromSource(extendSource, baseDir);
6735
- await resolveChainRecursive(parentManifest, extendSource, baseDir, chain, currentVisited, [...path], parentLocalPath);
6800
+ const { manifest: parentManifest, localPath: parentLocalPath } = await loadProfileFromSource(extendSource, baseDir, cloneContext);
6801
+ await resolveChainRecursive(parentManifest, extendSource, baseDir, chain, currentVisited, [...path], parentLocalPath, cloneContext);
6736
6802
  } catch (error) {
6737
6803
  if (error instanceof CircularInheritanceError) throw error;
6738
6804
  if (error instanceof Error && error.message.includes("exceeds maximum depth")) throw error;
6805
+ const cause = error instanceof Error ? error.message : String(error);
6806
+ throw new Error(`Failed to resolve extends '${extendSource}' from profile '${manifest.name}': ${cause}`);
6739
6807
  }
6740
6808
  chain.push({
6741
6809
  manifest,
@@ -6748,25 +6816,55 @@ async function resolveChainRecursive(manifest, source, baseDir, chain, visited,
6748
6816
  /**
6749
6817
  * Load a profile from a source (Git URL or local path)
6750
6818
  *
6819
+ * When operating inside a sparse-checkout repo and the profile is not found,
6820
+ * expands the sparse-checkout to include the profile path and retries.
6821
+ *
6751
6822
  * @param source - Source URL or path
6752
6823
  * @param baseDir - Base directory for resolving relative paths
6824
+ * @param cloneContext - Optional clone context for sparse-checkout expansion
6753
6825
  * @returns Loaded profile manifest and resolved local directory path
6754
6826
  */
6755
- async function loadProfileFromSource(source, baseDir) {
6827
+ async function loadProfileFromSource(source, baseDir, cloneContext) {
6756
6828
  const parsed = parseSource(source);
6757
6829
  if (parsed.provider === "local") {
6758
6830
  const absolutePath = resolve(baseDir, parsed.path);
6759
- return {
6760
- manifest: await loadProfileManifest(resolve(absolutePath, "baton.profile.yaml")),
6761
- localPath: absolutePath
6762
- };
6831
+ const manifestPath = resolve(absolutePath, "baton.profile.yaml");
6832
+ try {
6833
+ return {
6834
+ manifest: await loadProfileManifest(manifestPath),
6835
+ localPath: absolutePath
6836
+ };
6837
+ } catch (error) {
6838
+ if (error instanceof FileNotFoundError && cloneContext?.sparseCheckout) {
6839
+ const relativePath = relative(cloneContext.cachePath, absolutePath);
6840
+ await expandSparseCheckout(cloneContext.cachePath, [relativePath]);
6841
+ return {
6842
+ manifest: await loadProfileManifest(manifestPath),
6843
+ localPath: absolutePath
6844
+ };
6845
+ }
6846
+ throw error;
6847
+ }
6763
6848
  }
6764
6849
  if (parsed.provider === "file") {
6765
6850
  const absolutePath = parsed.path.startsWith("/") ? parsed.path : resolve(baseDir, parsed.path);
6766
- return {
6767
- manifest: await loadProfileManifest(resolve(absolutePath, "baton.profile.yaml")),
6768
- localPath: absolutePath
6769
- };
6851
+ const manifestPath = resolve(absolutePath, "baton.profile.yaml");
6852
+ try {
6853
+ return {
6854
+ manifest: await loadProfileManifest(manifestPath),
6855
+ localPath: absolutePath
6856
+ };
6857
+ } catch (error) {
6858
+ if (error instanceof FileNotFoundError && cloneContext?.sparseCheckout) {
6859
+ const relativePath = relative(cloneContext.cachePath, absolutePath);
6860
+ await expandSparseCheckout(cloneContext.cachePath, [relativePath]);
6861
+ return {
6862
+ manifest: await loadProfileManifest(manifestPath),
6863
+ localPath: absolutePath
6864
+ };
6865
+ }
6866
+ throw error;
6867
+ }
6770
6868
  }
6771
6869
  if (parsed.provider === "npm") throw new Error("NPM sources are not yet implemented in inheritance chain");
6772
6870
  const subpath = parsed.provider !== "git" ? parsed.subpath : void 0;
@@ -14411,7 +14509,8 @@ const globalConfigSchema = objectType({
14411
14509
  ]).default("deep")
14412
14510
  }).optional(),
14413
14511
  ai: objectType({ tools: arrayType(stringType()).optional().default([]) }).optional(),
14414
- ide: objectType({ platforms: arrayType(stringType()).optional().default([]) }).optional()
14512
+ ide: objectType({ platforms: arrayType(stringType()).optional().default([]) }).optional(),
14513
+ sync: objectType({ cacheTtlHours: numberType().default(24) }).optional()
14415
14514
  });
14416
14515
 
14417
14516
  //#endregion
@@ -14788,5 +14887,5 @@ async function resolvePreferences(projectRoot) {
14788
14887
  }
14789
14888
 
14790
14889
  //#endregion
14791
- export { loadProjectManifest as $, placeFile as A, ensureBatonDirGitignored as B, getProfileWeight as C, resolveProfileSupport as D, mergeContentParts as E, readLock as F, idePlatformRegistry as G, updateGitignore as H, writeLock as I, getAllAIToolAdapters as J, isKnownIdePlatform as K, resolveVersion as L, findSourceManifest as M, removePlacedFiles as N, resolveProfileChain as O, generateLock as P, loadProfileManifest as Q, cloneGitSource as R, mergeSkillsWithWarnings as S, sortProfilesByWeight as T, getIdePlatformTargetDir as U, removeGitignoreManagedSection as V, getRegisteredIdePlatforms as W, parseSource as X, parseFrontmatter as Y, loadLockfile as Z, mergeMemoryWithWarnings as _, clearIdeCache as a, getAllAIToolKeys as at, mergeRulesWithWarnings as b, getDefaultGlobalSource as c, getGlobalSources as d, KEBAB_CASE_REGEX as et, removeGlobalSource as f, mergeMemory as g, require_lib as h, computeIntersection as i, getAIToolPath as it, discoverProfilesInSourceRepo as j, detectLegacyPaths as k, getGlobalAiTools as l, setGlobalIdePlatforms as m, readProjectPreferences as n, SourceParseError as nt, detectInstalledIdes as o, setGlobalAiTools as p, getAIToolAdaptersForKeys as q, writeProjectPreferences as r, getAIToolConfig as rt, addGlobalSource as s, resolvePreferences as t, FileNotFoundError as tt, getGlobalIdePlatforms as u, mergeAgentsWithWarnings as v, isLockedProfile as w, mergeSkills as x, mergeRules as y, collectComprehensivePatterns as z };
14792
- //# sourceMappingURL=src-BPYdPWlV.mjs.map
14890
+ export { loadLockfile as $, resolveProfileChain as A, cloneGitSource as B, mergeSkills as C, sortProfilesByWeight as D, isLockedProfile as E, removePlacedFiles as F, getIdePlatformTargetDir as G, ensureBatonDirGitignored as H, generateLock as I, isKnownIdePlatform as J, getRegisteredIdePlatforms as K, readLock as L, placeFile as M, discoverProfilesInSourceRepo as N, mergeContentParts as O, findSourceManifest as P, parseSource as Q, writeLock as R, mergeRulesWithWarnings as S, getProfileWeight as T, removeGitignoreManagedSection as U, collectComprehensivePatterns as V, updateGitignore as W, getAllAIToolAdapters as X, getAIToolAdaptersForKeys as Y, parseFrontmatter as Z, require_lib as _, clearIdeCache as a, getAIToolConfig as at, mergeAgentsWithWarnings as b, getDefaultGlobalSource as c, getGlobalSources as d, loadProfileManifest as et, loadGlobalConfig as f, setGlobalIdePlatforms as g, setGlobalAiTools as h, computeIntersection as i, SourceParseError as it, detectLegacyPaths as j, resolveProfileSupport as k, getGlobalAiTools as l, saveGlobalConfig as m, readProjectPreferences as n, KEBAB_CASE_REGEX as nt, detectInstalledIdes as o, getAIToolPath as ot, removeGlobalSource as p, idePlatformRegistry as q, writeProjectPreferences as r, FileNotFoundError as rt, addGlobalSource as s, getAllAIToolKeys as st, resolvePreferences as t, loadProjectManifest as tt, getGlobalIdePlatforms as u, mergeMemory as v, mergeSkillsWithWarnings as w, mergeRules as x, mergeMemoryWithWarnings as y, resolveVersion as z };
14891
+ //# sourceMappingURL=src-dY02psbw.mjs.map