Merge e1555f7017 into 3bc2ad30a3
This commit is contained in:
commit
cf195ec33e
|
|
@ -19,6 +19,10 @@ function quoteCustomRef(ref) {
|
|||
class CustomModuleManager {
|
||||
/** @type {Map<string, Object>} Shared across all instances: module code -> ResolvedModule */
|
||||
static _resolutionCache = new Map();
|
||||
/** @type {Set<string>} Repo roots refreshed in the current process (dedupe quick-update fetches). */
|
||||
static _refreshedRepoPaths = new Set();
|
||||
/** @type {Map<string, Promise<void>>} In-flight refresh operations keyed by repo path. */
|
||||
static _refreshInFlight = new Map();
|
||||
|
||||
// ─── Source Parsing ───────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -466,6 +470,32 @@ class CustomModuleManager {
|
|||
} catch {
|
||||
// swallow — a non-git repo (local path) wouldn't reach here anyway
|
||||
}
|
||||
// Best-effort: capture the remote default branch name so channel marker
|
||||
// metadata for "next" reflects the actual tracked ref (not always "main").
|
||||
let defaultRef = 'main';
|
||||
if (!effectiveVersion) {
|
||||
try {
|
||||
const symbolic = execSync('git symbolic-ref --short refs/remotes/origin/HEAD', {
|
||||
cwd: repoCacheDir,
|
||||
stdio: 'pipe',
|
||||
})
|
||||
.toString()
|
||||
.trim();
|
||||
if (symbolic.startsWith('origin/')) {
|
||||
defaultRef = symbolic.slice('origin/'.length) || defaultRef;
|
||||
}
|
||||
} catch {
|
||||
// Fallback to previous marker value when symbolic ref is unavailable.
|
||||
try {
|
||||
const existingMarker = await fs.readJson(path.join(repoCacheDir, '.bmad-channel.json'));
|
||||
if (existingMarker?.channel === 'next' && typeof existingMarker.version === 'string' && existingMarker.version.trim()) {
|
||||
defaultRef = existingMarker.version.trim();
|
||||
}
|
||||
} catch {
|
||||
// Keep default fallback.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write source metadata for later URL reconstruction
|
||||
const metadataPath = path.join(repoCacheDir, '.bmad-source.json');
|
||||
|
|
@ -478,6 +508,15 @@ class CustomModuleManager {
|
|||
sha: resolvedSha,
|
||||
clonedAt: new Date().toISOString(),
|
||||
});
|
||||
// Keep a channel marker in custom cache too so update paths that rely on
|
||||
// channel metadata (same as official-module cache) can treat this clone as
|
||||
// refreshable. URL + no explicit ref => next, explicit ref => pinned.
|
||||
await fs.writeJson(path.join(repoCacheDir, '.bmad-channel.json'), {
|
||||
channel: effectiveVersion ? 'pinned' : 'next',
|
||||
version: effectiveVersion || defaultRef,
|
||||
sha: resolvedSha,
|
||||
writtenAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Install dependencies if package.json exists (skip during browsing/analysis)
|
||||
const packageJsonPath = path.join(repoCacheDir, 'package.json');
|
||||
|
|
@ -642,6 +681,13 @@ class CustomModuleManager {
|
|||
const repoRoots = await this._findCacheRepoRoots(cacheDir);
|
||||
|
||||
for (const { repoPath, metadata } of repoRoots) {
|
||||
// Quick-update path: refresh URL-backed cached repos before reading
|
||||
// files from them so re-deploy uses latest commits for `next` and
|
||||
// the pinned ref for `pinned`.
|
||||
if (options.bmadDir && metadata?.rawInput) {
|
||||
await this._refreshRepoCacheOnce(repoPath, metadata);
|
||||
}
|
||||
|
||||
// Check marketplace.json for matching module code
|
||||
const marketplacePath = path.join(repoPath, '.claude-plugin', 'marketplace.json');
|
||||
if (!(await fs.pathExists(marketplacePath))) continue;
|
||||
|
|
@ -692,6 +738,40 @@ class CustomModuleManager {
|
|||
return this._findLocalSourceFromManifest(moduleCode, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh one cached repo at most once per process with in-flight dedupe.
|
||||
* Prevents concurrent quick-update callers from racing the same cache path.
|
||||
* @param {string} repoPath - Absolute cache repo path
|
||||
* @param {Object} metadata - Parsed .bmad-source.json metadata
|
||||
*/
|
||||
async _refreshRepoCacheOnce(repoPath, metadata) {
|
||||
if (CustomModuleManager._refreshedRepoPaths.has(repoPath)) return;
|
||||
|
||||
const existing = CustomModuleManager._refreshInFlight.get(repoPath);
|
||||
if (existing) {
|
||||
await existing;
|
||||
return;
|
||||
}
|
||||
|
||||
const refreshPromise = (async () => {
|
||||
try {
|
||||
await this.cloneRepo(metadata.rawInput, {
|
||||
silent: true,
|
||||
pinOverride: metadata.version || undefined,
|
||||
});
|
||||
CustomModuleManager._refreshedRepoPaths.add(repoPath);
|
||||
} catch {
|
||||
// Keep existing cache on refresh failure; caller may still resolve
|
||||
// module source from the previous clone.
|
||||
} finally {
|
||||
CustomModuleManager._refreshInFlight.delete(repoPath);
|
||||
}
|
||||
})();
|
||||
|
||||
CustomModuleManager._refreshInFlight.set(repoPath, refreshPromise);
|
||||
await refreshPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the installation manifest for a localPath entry for this module.
|
||||
* Used as fallback when the module was installed from a local source (no cache entry).
|
||||
|
|
|
|||
Loading…
Reference in New Issue