opencode-repos 0.3.1 → 0.3.3

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.
Files changed (3) hide show
  1. package/index.ts +17 -7
  2. package/package.json +27 -27
  3. package/src/manifest.ts +21 -19
package/index.ts CHANGED
@@ -333,7 +333,8 @@ async function ensureRepoAvailable(
333
333
  cacheDir: string,
334
334
  useHttps: boolean,
335
335
  autoSyncOnExplore: boolean,
336
- autoSyncIntervalHours: number
336
+ autoSyncIntervalHours: number,
337
+ explicitBranch?: string
337
338
  ): Promise<{
338
339
  repoPath: string
339
340
  branch: string
@@ -350,7 +351,8 @@ async function ensureRepoAvailable(
350
351
  let actualBranch = branch
351
352
  try {
352
353
  await withManifestLock(async () => {
353
- const cloneResult = await cloneRepo(url, repoPath, { branch })
354
+ const cloneOptions = explicitBranch ? { branch: explicitBranch } : {}
355
+ const cloneResult = await cloneRepo(url, repoPath, cloneOptions)
354
356
  actualBranch = cloneResult.branch
355
357
 
356
358
  const now = new Date().toISOString()
@@ -957,7 +959,8 @@ When user mentions another project or asks about external code:
957
959
  },
958
960
  async execute(args) {
959
961
  const spec = parseRepoSpec(args.repo)
960
- const branch = spec.branch || defaultBranch
962
+ const explicitBranch = spec.branch
963
+ const branch = explicitBranch || defaultBranch
961
964
  const repoKey = `${spec.owner}/${spec.repo}`
962
965
 
963
966
  const result = await withManifestLock(async () => {
@@ -992,7 +995,8 @@ When user mentions another project or asks about external code:
992
995
 
993
996
  let actualBranch = branch
994
997
  try {
995
- const cloneResult = await cloneRepo(url, destPath, { branch })
998
+ const cloneOptions = explicitBranch ? { branch: explicitBranch } : {}
999
+ const cloneResult = await cloneRepo(url, destPath, cloneOptions)
996
1000
  actualBranch = cloneResult.branch
997
1001
  } catch (error) {
998
1002
  const message =
@@ -1842,6 +1846,7 @@ Provide either \`query\` or a non-empty \`repos\` list.`
1842
1846
  const targets: Array<{
1843
1847
  repoKey: string
1844
1848
  branch: string
1849
+ explicitBranch?: string
1845
1850
  source?: RepoSource
1846
1851
  remote?: string
1847
1852
  path?: string
@@ -1862,6 +1867,7 @@ Provide either \`query\` or a non-empty \`repos\` list.`
1862
1867
  targets.push({
1863
1868
  repoKey: `${spec.owner}/${spec.repo}`,
1864
1869
  branch: spec.branch || defaultBranch,
1870
+ explicitBranch: spec.branch ?? undefined,
1865
1871
  })
1866
1872
  debugInfo.selectedTargets.push({
1867
1873
  repoKey: `${spec.owner}/${spec.repo}`,
@@ -1930,7 +1936,8 @@ Failed to parse \`${repo}\`: ${message}`
1930
1936
  cacheDir,
1931
1937
  useHttps,
1932
1938
  autoSyncOnExplore,
1933
- autoSyncIntervalHours
1939
+ autoSyncIntervalHours,
1940
+ target.explicitBranch
1934
1941
  )
1935
1942
 
1936
1943
  await requestExternalDirectoryAccess(
@@ -2067,6 +2074,7 @@ Be more specific or pass an explicit list with \`repos\`.
2067
2074
  targets.push({
2068
2075
  repoKey: selected.key,
2069
2076
  branch,
2077
+ explicitBranch: branchOverride ?? undefined,
2070
2078
  source: selected.source,
2071
2079
  remote: selected.remote,
2072
2080
  path: selected.path,
@@ -2099,7 +2107,8 @@ Be more specific or pass an explicit list with \`repos\`.
2099
2107
  cacheDir,
2100
2108
  useHttps,
2101
2109
  autoSyncOnExplore,
2102
- autoSyncIntervalHours
2110
+ autoSyncIntervalHours,
2111
+ target.explicitBranch
2103
2112
  )
2104
2113
 
2105
2114
  await requestExternalDirectoryAccess(
@@ -2181,7 +2190,8 @@ Be more specific or pass an explicit list with \`repos\`.
2181
2190
  cacheDir,
2182
2191
  useHttps,
2183
2192
  autoSyncOnExplore,
2184
- autoSyncIntervalHours
2193
+ autoSyncIntervalHours,
2194
+ spec.branch ?? undefined
2185
2195
  )
2186
2196
  repoPath = resolved.repoPath
2187
2197
  await requestExternalDirectoryAccess(ctx as PermissionContext, repoPath)
package/package.json CHANGED
@@ -1,29 +1,29 @@
1
1
  {
2
- "name": "opencode-repos",
3
- "version": "0.3.1",
4
- "description": "Repository cache, registry, and cross-codebase intelligence for OpenCode agents",
5
- "main": "index.ts",
6
- "type": "module",
7
- "keywords": [
8
- "opencode",
9
- "opencode-plugin",
10
- "repos",
11
- "repository",
12
- "cache",
13
- "codebase"
14
- ],
15
- "author": "Liam Vinberg",
16
- "license": "MIT",
17
- "repository": {
18
- "type": "git",
19
- "url": "https://github.com/liamjv1/opencode-repos"
20
- },
21
- "peerDependencies": {
22
- "@opencode-ai/plugin": "*"
23
- },
24
- "devDependencies": {
25
- "@opencode-ai/plugin": "^1.1.25",
26
- "@types/bun": "^1.2.0",
27
- "typescript": "^5.0.0"
28
- }
2
+ "name": "opencode-repos",
3
+ "version": "0.3.3",
4
+ "description": "Repository cache, registry, and cross-codebase intelligence for OpenCode agents",
5
+ "main": "index.ts",
6
+ "type": "module",
7
+ "keywords": [
8
+ "opencode",
9
+ "opencode-plugin",
10
+ "repos",
11
+ "repository",
12
+ "cache",
13
+ "codebase"
14
+ ],
15
+ "author": "Liam Vinberg",
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/liamjv1/opencode-repos"
20
+ },
21
+ "peerDependencies": {
22
+ "@opencode-ai/plugin": "*"
23
+ },
24
+ "devDependencies": {
25
+ "@opencode-ai/plugin": "^1.1.25",
26
+ "@types/bun": "^1.2.0",
27
+ "typescript": "^5.0.0"
28
+ }
29
29
  }
package/src/manifest.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { homedir } from "node:os"
2
2
  import { join } from "node:path"
3
- import { mkdir, rename, unlink, stat } from "node:fs/promises"
3
+ import { mkdir, rename, unlink, stat, open } from "node:fs/promises"
4
4
 
5
5
  export interface RepoEntry {
6
6
  type: "cached" | "local"
@@ -27,7 +27,7 @@ let cacheDir = join(homedir(), ".cache", "opencode-repos")
27
27
  let manifestPath = join(cacheDir, "manifest.json")
28
28
  let manifestTmpPath = join(cacheDir, "manifest.json.tmp")
29
29
  let lockPath = join(cacheDir, "manifest.lock")
30
- const LOCK_STALE_MS = 5 * 60 * 1000
30
+ const LOCK_STALE_MS = 30_000
31
31
 
32
32
  function createEmptyManifest(): Manifest {
33
33
  return {
@@ -72,31 +72,33 @@ async function isLockStale(): Promise<boolean> {
72
72
  }
73
73
 
74
74
  async function acquireLock(): Promise<void> {
75
- const maxAttempts = 50
76
- const retryDelayMs = 100
75
+ const maxAttempts = 150
76
+ const retryDelayMs = 200
77
+
78
+ await mkdir(cacheDir, { recursive: true })
77
79
 
78
80
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
79
- const lockFile = Bun.file(lockPath)
80
- const exists = await lockFile.exists()
81
-
82
- if (exists) {
83
- if (await isLockStale()) {
84
- await unlink(lockPath).catch(() => {})
85
- } else {
86
- await Bun.sleep(retryDelayMs)
87
- continue
81
+ try {
82
+ const fd = await open(lockPath, "wx")
83
+ await fd.write(String(process.pid))
84
+ await fd.close()
85
+ return
86
+ } catch (error) {
87
+ const code = (error as NodeJS.ErrnoException).code
88
+ if (code !== "EEXIST") {
89
+ throw error
88
90
  }
89
91
  }
90
92
 
91
- try {
92
- await mkdir(cacheDir, { recursive: true })
93
- await Bun.write(lockPath, String(Date.now()))
94
- return
95
- } catch {
96
- await Bun.sleep(retryDelayMs)
93
+ if (await isLockStale()) {
94
+ await unlink(lockPath).catch(() => {})
95
+ continue
97
96
  }
97
+
98
+ await Bun.sleep(retryDelayMs)
98
99
  }
99
100
 
101
+ await unlink(lockPath).catch(() => {})
100
102
  throw new Error("Failed to acquire manifest lock after maximum attempts")
101
103
  }
102
104