@cvr/repo 1.1.1 → 1.2.0

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/bin/repo CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cvr/repo",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cevr/repo.git"
@@ -19,12 +19,6 @@ const forceOption = Options.boolean("force").pipe(
19
19
  Options.withDescription("Force re-clone (removes existing and clones fresh)"),
20
20
  );
21
21
 
22
- const updateOption = Options.boolean("update").pipe(
23
- Options.withAlias("u"),
24
- Options.withDefault(false),
25
- Options.withDescription("Update existing repo (git pull)"),
26
- );
27
-
28
22
  const fullHistoryOption = Options.boolean("full").pipe(
29
23
  Options.withDefault(false),
30
24
  Options.withDescription("Clone full git history (default: shallow clone with depth 100)"),
@@ -32,8 +26,8 @@ const fullHistoryOption = Options.boolean("full").pipe(
32
26
 
33
27
  export const fetch = Command.make(
34
28
  "fetch",
35
- { spec: specArg, force: forceOption, update: updateOption, full: fullHistoryOption },
36
- ({ spec, force, update, full }) =>
29
+ { spec: specArg, force: forceOption, full: fullHistoryOption },
30
+ ({ spec, force, full }) =>
37
31
  Effect.gen(function* () {
38
32
  const registry = yield* RegistryService;
39
33
  const cache = yield* CacheService;
@@ -56,52 +50,40 @@ export const fetch = Command.make(
56
50
  yield* Console.log(`Force re-fetching ${specStr}...`);
57
51
  yield* cache.remove(existing.path);
58
52
  yield* metadata.remove(parsedSpec);
59
- } else if (update) {
60
- // Explicit update requested
61
- if (isGit) {
62
- yield* Console.log(`Updating ${specStr}...`);
63
- yield* git
64
- .update(existing.path)
65
- .pipe(
66
- Effect.catchAll((e) =>
67
- Console.log(`Update failed, repo may be up to date: ${e._tag}`),
68
- ),
69
- );
70
-
71
- // Recalculate size after update
72
- const sizeBytes = yield* cache.getSize(existing.path);
73
- const currentRef = yield* git
74
- .getCurrentRef(existing.path)
75
- .pipe(Effect.orElseSucceed(() => "unknown"));
76
-
77
- yield* metadata.add({
78
- spec: parsedSpec,
79
- fetchedAt: existing.fetchedAt,
80
- lastAccessedAt: new Date().toISOString(),
81
- sizeBytes,
82
- path: existing.path,
83
- });
84
-
85
- yield* Console.log(`Updated: ${existing.path}`);
86
- yield* Console.log(`Current ref: ${currentRef}`);
87
- yield* Console.log(`Size: ${formatBytes(sizeBytes)}`);
88
- return;
89
- } else {
90
- // Not a git repo, can't update
91
- yield* metadata.updateAccessTime(parsedSpec);
92
- yield* Console.log(`Already cached at: ${existing.path}`);
93
- yield* Console.log(`Size: ${formatBytes(existing.sizeBytes)}`);
94
- yield* Console.log(`(Not a git repo - use --force to re-fetch)`);
95
- return;
96
- }
53
+ } else if (isGit) {
54
+ // Git repo: always pull latest
55
+ yield* Console.log(`Updating ${specStr}...`);
56
+ yield* git
57
+ .update(existing.path)
58
+ .pipe(
59
+ Effect.catchAll((e) =>
60
+ Console.log(`Update failed, repo may be up to date: ${e._tag}`),
61
+ ),
62
+ );
63
+
64
+ // Recalculate size after update
65
+ const sizeBytes = yield* cache.getSize(existing.path);
66
+ const currentRef = yield* git
67
+ .getCurrentRef(existing.path)
68
+ .pipe(Effect.orElseSucceed(() => "unknown"));
69
+
70
+ yield* metadata.add({
71
+ spec: parsedSpec,
72
+ fetchedAt: existing.fetchedAt,
73
+ lastAccessedAt: new Date().toISOString(),
74
+ sizeBytes,
75
+ path: existing.path,
76
+ });
77
+
78
+ yield* Console.log(`Updated: ${existing.path}`);
79
+ yield* Console.log(`Current ref: ${currentRef}`);
80
+ yield* Console.log(`Size: ${formatBytes(sizeBytes)}`);
81
+ return;
97
82
  } else {
98
- // Already cached, just update access time
83
+ // Not a git repo, can't update
99
84
  yield* metadata.updateAccessTime(parsedSpec);
100
85
  yield* Console.log(`Already cached at: ${existing.path}`);
101
86
  yield* Console.log(`Size: ${formatBytes(existing.sizeBytes)}`);
102
- if (isGit) {
103
- yield* Console.log(`Use --update to pull latest changes`);
104
- }
105
87
  yield* Console.log(`Use --force to re-fetch from scratch`);
106
88
  return;
107
89
  }
@@ -3,6 +3,7 @@ import { Console, Effect } from "effect";
3
3
  import { NotFoundError, specToString } from "../types.js";
4
4
  import { MetadataService } from "../services/metadata.js";
5
5
  import { RegistryService } from "../services/registry.js";
6
+ import { GitService } from "../services/git.js";
6
7
 
7
8
  const specArg = Args.text({ name: "spec" }).pipe(
8
9
  Args.withDescription("Package spec to get path for"),
@@ -18,6 +19,7 @@ export const path = Command.make("path", { spec: specArg, quiet: quietOption },
18
19
  Effect.gen(function* () {
19
20
  const registry = yield* RegistryService;
20
21
  const metadata = yield* MetadataService;
22
+ const git = yield* GitService;
21
23
 
22
24
  const parsedSpec = yield* registry.parseSpec(spec);
23
25
  const existing = yield* metadata.find(parsedSpec);
@@ -31,5 +33,15 @@ export const path = Command.make("path", { spec: specArg, quiet: quietOption },
31
33
  }
32
34
 
33
35
  yield* Console.log(existing.path);
36
+ yield* metadata.updateAccessTime(parsedSpec);
37
+
38
+ // Background refresh for git repos — fire-and-forget so we don't block the caller
39
+ const isGit = yield* git.isGitRepo(existing.path);
40
+ if (isGit) {
41
+ yield* git.fetchRefs(existing.path).pipe(
42
+ Effect.catchAll(() => Effect.void),
43
+ Effect.fork,
44
+ );
45
+ }
34
46
  }).pipe(Effect.catchAll(() => Effect.void)),
35
47
  );
@@ -11,6 +11,7 @@ export class GitService extends Context.Tag("@cvr/repo/services/git/GitService")
11
11
  options?: { depth?: number; ref?: string },
12
12
  ) => Effect.Effect<void, GitError>;
13
13
  readonly update: (path: string) => Effect.Effect<void, GitError>;
14
+ readonly fetchRefs: (path: string) => Effect.Effect<void, GitError>;
14
15
  readonly isGitRepo: (path: string) => Effect.Effect<boolean>;
15
16
  readonly getDefaultBranch: (url: string) => Effect.Effect<string, GitError>;
16
17
  readonly getCurrentRef: (path: string) => Effect.Effect<string, GitError>;
@@ -85,6 +86,27 @@ export class GitService extends Context.Tag("@cvr/repo/services/git/GitService")
85
86
  }
86
87
  }),
87
88
 
89
+ fetchRefs: (path) =>
90
+ Effect.gen(function* () {
91
+ const fetchProc = Bun.spawn(["git", "-C", path, "fetch", "--all", "--prune"], {
92
+ stdout: "pipe",
93
+ stderr: "pipe",
94
+ });
95
+
96
+ const fetchExit = yield* Effect.tryPromise({
97
+ try: () => fetchProc.exited,
98
+ catch: (cause) => new GitError({ operation: "fetch", repo: path, cause }),
99
+ });
100
+
101
+ if (fetchExit !== 0) {
102
+ return yield* new GitError({
103
+ operation: "fetch",
104
+ repo: path,
105
+ cause: new Error(`git fetch failed with exit code ${fetchExit}`),
106
+ });
107
+ }
108
+ }),
109
+
88
110
  update: (path) =>
89
111
  Effect.gen(function* () {
90
112
  // Fetch all updates
@@ -70,6 +70,8 @@ export function createMockGitService(options: CreateMockGitServiceOptions = {}):
70
70
  }));
71
71
  }),
72
72
 
73
+ fetchRefs: (path) => record("fetchRefs", { path }),
74
+
73
75
  isGitRepo: (path) =>
74
76
  Effect.gen(function* () {
75
77
  const s = yield* Ref.get(stateRef);