init claude-code
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Plugin Version Calculation Module
|
||||
*
|
||||
* Handles version calculation for plugins from various sources.
|
||||
* Versions are used for versioned cache paths and update detection.
|
||||
*
|
||||
* Version sources (in order of preference):
|
||||
* 1. Explicit version from plugin.json
|
||||
* 2. Git commit SHA (for git/github sources)
|
||||
* 3. Fallback timestamp for local sources
|
||||
*/
|
||||
|
||||
import { createHash } from 'crypto'
|
||||
import { logForDebugging } from '../debug.js'
|
||||
import { getHeadForDir } from '../git/gitFilesystem.js'
|
||||
import type { PluginManifest, PluginSource } from './schemas.js'
|
||||
|
||||
/**
|
||||
* Calculate the version for a plugin based on its source.
|
||||
*
|
||||
* Version sources (in order of priority):
|
||||
* 1. plugin.json version field (highest priority)
|
||||
* 2. Provided version (typically from marketplace entry)
|
||||
* 3. Git commit SHA from install path
|
||||
* 4. 'unknown' as last resort
|
||||
*
|
||||
* @param pluginId - Plugin identifier (e.g., "plugin@marketplace")
|
||||
* @param source - Plugin source configuration (used for git-subdir path hashing)
|
||||
* @param manifest - Optional plugin manifest with version field
|
||||
* @param installPath - Optional path to installed plugin (for git SHA extraction)
|
||||
* @param providedVersion - Optional version from marketplace entry or caller
|
||||
* @param gitCommitSha - Optional pre-resolved git SHA (for sources like
|
||||
* git-subdir where the clone is discarded and the install path has no .git)
|
||||
* @returns Version string (semver, short SHA, or 'unknown')
|
||||
*/
|
||||
export async function calculatePluginVersion(
|
||||
pluginId: string,
|
||||
source: PluginSource,
|
||||
manifest?: PluginManifest,
|
||||
installPath?: string,
|
||||
providedVersion?: string,
|
||||
gitCommitSha?: string,
|
||||
): Promise<string> {
|
||||
// 1. Use explicit version from plugin.json if available
|
||||
if (manifest?.version) {
|
||||
logForDebugging(
|
||||
`Using manifest version for ${pluginId}: ${manifest.version}`,
|
||||
)
|
||||
return manifest.version
|
||||
}
|
||||
|
||||
// 2. Use provided version (typically from marketplace entry)
|
||||
if (providedVersion) {
|
||||
logForDebugging(
|
||||
`Using provided version for ${pluginId}: ${providedVersion}`,
|
||||
)
|
||||
return providedVersion
|
||||
}
|
||||
|
||||
// 3. Use pre-resolved git SHA if caller captured it before discarding the clone
|
||||
if (gitCommitSha) {
|
||||
const shortSha = gitCommitSha.substring(0, 12)
|
||||
if (typeof source === 'object' && source.source === 'git-subdir') {
|
||||
// Encode the subdir path in the version so cache keys differ when
|
||||
// marketplace.json's `path` changes but the monorepo SHA doesn't.
|
||||
// Without this, two plugins at different subdirs of the same commit
|
||||
// collide at cache/<m>/<p>/<sha>/ and serve each other's trees.
|
||||
//
|
||||
// Normalization MUST match the squashfs cron byte-for-byte:
|
||||
// 1. backslash → forward slash
|
||||
// 2. strip one leading `./`
|
||||
// 3. strip all trailing `/`
|
||||
// 4. UTF-8 sha256, first 8 hex chars
|
||||
// See api/…/plugins_official_squashfs/job.py _validate_subdir().
|
||||
const normPath = source.path
|
||||
.replace(/\\/g, '/')
|
||||
.replace(/^\.\//, '')
|
||||
.replace(/\/+$/, '')
|
||||
const pathHash = createHash('sha256')
|
||||
.update(normPath)
|
||||
.digest('hex')
|
||||
.substring(0, 8)
|
||||
const v = `${shortSha}-${pathHash}`
|
||||
logForDebugging(
|
||||
`Using git-subdir SHA+path version for ${pluginId}: ${v} (path=${normPath})`,
|
||||
)
|
||||
return v
|
||||
}
|
||||
logForDebugging(`Using pre-resolved git SHA for ${pluginId}: ${shortSha}`)
|
||||
return shortSha
|
||||
}
|
||||
|
||||
// 4. Try to get git SHA from install path
|
||||
if (installPath) {
|
||||
const sha = await getGitCommitSha(installPath)
|
||||
if (sha) {
|
||||
const shortSha = sha.substring(0, 12)
|
||||
logForDebugging(`Using git SHA for ${pluginId}: ${shortSha}`)
|
||||
return shortSha
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Return 'unknown' as last resort
|
||||
logForDebugging(`No version found for ${pluginId}, using 'unknown'`)
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the git commit SHA for a directory.
|
||||
*
|
||||
* @param dirPath - Path to directory (should be a git repository)
|
||||
* @returns Full commit SHA or null if not a git repo
|
||||
*/
|
||||
export function getGitCommitSha(dirPath: string): Promise<string | null> {
|
||||
return getHeadForDir(dirPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract version from a versioned cache path.
|
||||
*
|
||||
* Given a path like `~/.claude/plugins/cache/marketplace/plugin/1.0.0`,
|
||||
* extracts and returns `1.0.0`.
|
||||
*
|
||||
* @param installPath - Full path to plugin installation
|
||||
* @returns Version string from path, or null if not a versioned path
|
||||
*/
|
||||
export function getVersionFromPath(installPath: string): string | null {
|
||||
// Versioned paths have format: .../plugins/cache/marketplace/plugin/version/
|
||||
const parts = installPath.split('/').filter(Boolean)
|
||||
|
||||
// Find 'cache' index to determine depth
|
||||
const cacheIndex = parts.findIndex(
|
||||
(part, i) => part === 'cache' && parts[i - 1] === 'plugins',
|
||||
)
|
||||
|
||||
if (cacheIndex === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Versioned path has 3 components after 'cache': marketplace/plugin/version
|
||||
const componentsAfterCache = parts.slice(cacheIndex + 1)
|
||||
if (componentsAfterCache.length >= 3) {
|
||||
return componentsAfterCache[2] || null
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path is a versioned plugin path.
|
||||
*
|
||||
* @param path - Path to check
|
||||
* @returns True if path follows versioned structure
|
||||
*/
|
||||
export function isVersionedPath(path: string): boolean {
|
||||
return getVersionFromPath(path) !== null
|
||||
}
|
||||
Reference in New Issue
Block a user