@foss.global/gitmanager 1.5.0 → 1.7.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.
@@ -46,6 +46,9 @@ export class RustGitManagerBridge extends plugins.EventEmitter {
46
46
  kill() {
47
47
  this.bridge.kill();
48
48
  }
49
+ async terminate() {
50
+ await this.bridge.terminate();
51
+ }
49
52
  get running() {
50
53
  return this.bridge.running;
51
54
  }
@@ -55,8 +58,8 @@ export class RustGitManagerBridge extends plugins.EventEmitter {
55
58
  async stopGitManager() {
56
59
  await this.bridge.sendCommand('stop', {});
57
60
  }
58
- async getStatus() {
59
- return this.bridge.sendCommand('getStatus', {});
61
+ async getStatus(optionsArg) {
62
+ return this.bridge.sendCommand('getStatus', {}, optionsArg);
60
63
  }
61
64
  async setSmartHttpRepositoryAccess(accessArg) {
62
65
  await this.bridge.sendCommand('setSmartHttpRepositoryAccess', accessArg);
@@ -82,10 +85,50 @@ export class RustGitManagerBridge extends plugins.EventEmitter {
82
85
  async createRepositoryBundle(requestArg) {
83
86
  return this.bridge.sendCommand('createRepositoryBundle', requestArg);
84
87
  }
85
- async createRepositoryArchive(requestArg) {
86
- const result = await this.bridge.sendCommand('createRepositoryArchive', requestArg, {
88
+ async createRepositoryArchive(requestArg, optionsArg = {}) {
89
+ if (optionsArg.signal?.aborted) {
90
+ throw optionsArg.signal.reason ?? new Error('Repository archive creation was aborted.');
91
+ }
92
+ const operationId = plugins.crypto.randomUUID();
93
+ const archivePromise = this.bridge.sendCommand('createRepositoryArchive', {
94
+ operationId,
95
+ request: requestArg,
96
+ }, {
87
97
  timeoutMs: 65 * 60 * 1000,
88
98
  });
99
+ let cancelPromise;
100
+ const cancelArchive = () => {
101
+ cancelPromise ??= this.bridge.sendCommand('cancelRepositoryArchive', {
102
+ operationId,
103
+ }, {
104
+ timeoutMs: 65 * 60 * 1000,
105
+ });
106
+ };
107
+ optionsArg.signal?.addEventListener('abort', cancelArchive, { once: true });
108
+ if (optionsArg.signal?.aborted) {
109
+ cancelArchive();
110
+ }
111
+ let result;
112
+ try {
113
+ result = await archivePromise;
114
+ if (cancelPromise) {
115
+ await cancelPromise.catch(() => undefined);
116
+ }
117
+ }
118
+ catch (archiveError) {
119
+ if (!cancelPromise) {
120
+ throw archiveError;
121
+ }
122
+ const cancellation = await cancelPromise;
123
+ if (cancellation.status === 'cancelled') {
124
+ throw optionsArg.signal?.reason
125
+ ?? new Error('Repository archive creation was aborted.');
126
+ }
127
+ throw archiveError;
128
+ }
129
+ finally {
130
+ optionsArg.signal?.removeEventListener('abort', cancelArchive);
131
+ }
89
132
  for (const [field, value] of [
90
133
  ['sizeBytes', result.sizeBytes],
91
134
  ['extractedSizeBytes', result.extractedSizeBytes],
@@ -137,4 +180,4 @@ export class RustGitManagerBridge extends plugins.EventEmitter {
137
180
  return this.bridge.sendCommand('getTag', requestArg);
138
181
  }
139
182
  }
140
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVzdC1naXRtYW5hZ2VyLWJyaWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3J1c3QtZ2l0bWFuYWdlci1icmlkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFrRXhDLFNBQVMsY0FBYztJQUNyQixNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDakYsT0FBTyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDN0MsQ0FBQztBQUVELFNBQVMsdUJBQXVCO0lBQzlCLE1BQU0sT0FBTyxHQUEyQixFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxDQUFDO0lBQ3pFLE1BQU0sS0FBSyxHQUEyQixFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxDQUFDO0lBQzFFLE1BQU0sRUFBRSxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDbkMsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNuQyxPQUFPLEVBQUUsSUFBSSxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsRUFBRSxJQUFJLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7QUFDN0MsQ0FBQztBQUVELFNBQVMsZUFBZTtJQUN0QixNQUFNLFdBQVcsR0FBRyxjQUFjLEVBQUUsQ0FBQztJQUNyQyxNQUFNLE1BQU0sR0FBRyx1QkFBdUIsRUFBRSxDQUFDO0lBQ3pDLE1BQU0sS0FBSyxHQUFhLEVBQUUsQ0FBQztJQUUzQixJQUFJLE1BQU0sRUFBRSxD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
183
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVzdC1naXRtYW5hZ2VyLWJyaWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3J1c3QtZ2l0bWFuYWdlci1icmlkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFtRnhDLFNBQVMsY0FBYztJQUNyQixNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDakYsT0FBTyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDN0MsQ0FBQztBQUVELFNBQVMsdUJBQXVCO0lBQzlCLE1BQU0sT0FBTyxHQUEyQixFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxDQUFDO0lBQ3pFLE1BQU0sS0FBSyxHQUEyQixFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxDQUFDO0lBQzFFLE1BQU0sRUFBRSxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDbkMsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNuQyxPQUFPLEVBQUUsSUFBSSxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsRUFBRSxJQUFJLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7QUFDN0MsQ0FBQztBQUVELFNBQVMsZUFBZTtJQUN0QixNQUFNLFdBQVcsR0FBRyxjQUFjLEVBQUUsQ0FBQztJQUNyQyxNQUFNLE1BQU0sR0FBRyx1QkFBdUIsRUFBRSxDQUFDO0lBQ3pDLE1BQU0sS0FBSyxHQUFhLEVBQUUsQ0FBQztJQUUzQixJQUFJLE1BQU0sRUFBRSxD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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foss.global/gitmanager",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "private": false,
5
5
  "description": "Headless Git manager for the foss.global Git platform, with a TypeScript API and Rust engine.",
6
6
  "exports": {
@@ -11,16 +11,16 @@
11
11
  "license": "MIT",
12
12
  "dependencies": {
13
13
  "@foss.global/interfaces": "^0.11.0",
14
- "@push.rocks/smartrust": "^1.5.0"
14
+ "@push.rocks/smartrust": "^1.7.0"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@foss.global/ci-spec": "0.2.0",
18
18
  "@git.zone/tsbuild": "^4.4.2",
19
19
  "@git.zone/tsdoc": "^2.1.1",
20
20
  "@git.zone/tsrun": "^2.0.6",
21
- "@git.zone/tsrust": "^1.7.0",
22
- "@git.zone/tstest": "^3.6.7",
23
- "@types/node": "^26.1.1"
21
+ "@git.zone/tsrust": "^1.8.0",
22
+ "@git.zone/tstest": "^4.0.0",
23
+ "@types/node": "^26.2.0"
24
24
  },
25
25
  "files": [
26
26
  "ts/**/*",
package/readme.md CHANGED
@@ -76,6 +76,8 @@ await gitManager.restoreRepositoryBundle({
76
76
 
77
77
  Exact-commit source archives default to a 128 MiB compressed limit, a 64 GiB extracted regular-file limit, and 1,000,000 filesystem entries. Configure these with `maxArchiveBytes`, `maxArchiveExtractedBytes`, and `maxArchiveEntries`; their hard ceilings are 16 GiB, 64 GiB, and 1,000,000 respectively. CI source consumers apply their negotiated compressed limit, which is at most 8 GiB in runner protocol v2. Before publication, GitManager validates the exact generated gzip/tar stream against the returned `extractedSizeBytes` and `entryCount`, including paths, entry types, links, PAX metadata, checksums, padding, and archive terminators.
78
78
 
79
+ Pass `{ signal }` as the second argument to `createRepositoryArchive()` to cancel archive creation. GitManager keeps operation IDs private, terminates and reaps active Git or inspection subprocesses, removes partial staging files, and only then rejects with the caller's abort reason. If final archive publication wins the cancellation race, the method returns the completed archive. `stop()` is single-flight and uses the same cleanup barrier before confirmed bridge termination.
80
+
79
81
  The transfer directory is disposable staging, not persistence. GitManager holds exclusive ownership of it while running so startup can safely remove interrupted archive temporary files. The caller owns upload/download completion and removal of finished transfer files. Durable repository bytes belong in managed object storage.
80
82
 
81
83
  ## Git Smart HTTP
@@ -211,12 +213,26 @@ const result = await service.completed;
211
213
 
212
214
  The returned service counts request, response, and stderr bytes, runs with an isolated Git environment, has its own process group and deadline, and is terminated when `GitManager.stop()` runs. Receive-pack uses the same private managed pre-receive hook and exact policy snapshot as Smart HTTP. The caller must authenticate the SSH key, authorize the repository operation, cap sessions, serialize writes with HTTP pushes and policy changes, inspect refs, persist the repository, record downstream events, and withhold the final receive-pack status until all durable work succeeds.
213
215
 
216
+ ## Bounded readiness
217
+
218
+ `checkReadiness()` performs one bounded, read-only probe. Lifecycle states return failure results instead of throwing; only an invalid `timeoutMs` throws a `TypeError`. The default timeout is 2000 ms, the maximum is 30000 ms, and an `AbortSignal` cancels the local wait.
219
+
220
+ ```ts
221
+ const readiness = await gitManager.checkReadiness({ timeoutMs: 2000 });
222
+ if (!readiness.ready) {
223
+ console.error(`GitManager unavailable: ${readiness.reason}`);
224
+ }
225
+ ```
226
+
227
+ When Git Smart HTTP is running on a loopback bind, the probe targets the engine's own `GET /healthz` route out-of-band, so readiness stays available while the serial management channel is busy with a long-running command such as a mirror import. Repository archive creation also keeps management status and cancellation responsive while preserving serial admission for every other stateful command. Without a loopback Smart HTTP route, including a service bound to a non-loopback address with `allowUnsafeNonLoopback`, one shared bounded `getStatus` round trip per instance is used; that fallback queues behind other in-flight management commands and a Smart HTTP probe failure never falls through to it. Failure reasons are `not_running`, `starting`, `stopping`, `aborted`, `timeout`, and `unavailable`; the result matches the readiness contracts of `@push.rocks/smartdata` and `@push.rocks/smartbucket`.
228
+
214
229
  ## Public API
215
230
 
216
231
  - `GitManager`: TypeScript facade for the Rust engine lifecycle.
217
232
  - `GitManager.start()`: spawns the Rust binary through `smartrust` and starts the engine.
218
- - `GitManager.stop()`: asks the engine to stop and always kills the bridge process in cleanup.
233
+ - `GitManager.stop()`: shares one in-flight stop, cancels and cleans active archives, then confirms bridge process termination.
219
234
  - `GitManager.getStatus()`: returns `{ running, dataDir, uptimeMs, smartHttp? }`.
235
+ - `GitManager.checkReadiness()`: performs a bounded, cancellable read-only readiness probe and returns `{ ready, status, durationMs }` plus a failure `reason` when unavailable.
220
236
  - `GitManager.setSmartHttpRepositoryAccess()`: atomically replaces the running Smart HTTP allow-all/allowlist state.
221
237
  - `GitManager.setSmartHttpRepositoryVisibility()`: applies one revision-ordered repository visibility update.
222
238
  - `GitManager.setSmartHttpRepositoryProtectedRefs()`: applies a complete revision-fenced set of exact protected-head rules and returns its effective revision and digest.
@@ -225,7 +241,7 @@ The returned service counts request, response, and stderr bytes, runs with an is
225
241
  - `GitManager.refreshMirror()`: atomically fetches and verifies the configured mirror source.
226
242
  - `GitManager.verifyRepository()`: runs a strict full integrity check.
227
243
  - `GitManager.createRepositoryBundle()`: writes a bounded full Git bundle into `transferDir` and returns its temporary path and size.
228
- - `GitManager.createRepositoryArchive()`: writes a bounded deterministic exact-commit `tar.gz` archive into `transferDir` and returns its digest, compressed size, extracted regular-file size, entry count, and temporary path.
244
+ - `GitManager.createRepositoryArchive(request, { signal? })`: writes a bounded deterministic exact-commit `tar.gz` archive into `transferDir` and returns its digest, compressed size, extracted regular-file size, entry count, and temporary path; cancellation settles after Rust process and staging cleanup.
229
245
  - `GitManager.restoreRepositoryBundle()`: validates and atomically restores a bounded bundle from `transferDir`.
230
246
  - `GitManager.openRepositoryService()`: opens a bounded upload-pack or protected receive-pack stream for an authenticated SSH-style ingress.
231
247
  - `GitManager.listRepositories()`: lists local repositories managed below the configured data directory.
@@ -239,7 +255,7 @@ The returned service counts request, response, and stderr bytes, runs with an is
239
255
  - `GitManager.getTag()`: reads one tag by exact name.
240
256
  - `GitManager.running`: local facade running state.
241
257
  - `RustGitManagerBridge`: lower-level typed bridge around `@push.rocks/smartrust`.
242
- - `IGitManagerOptions`, `IGitManagerStatus`, `IRustGitManagerConfig`, `IGitManagerProtectedRefRule`, `IGitManagerSmartHttpRepositoryProtectedRefs`, `TGitManagerProtectedRefsApplyStatus`, and `IGitManagerProtectedRefsApplyResult`: exported option, status, and protected-ref contracts.
258
+ - `IGitManagerOptions`, `IGitManagerStatus`, `IGitManagerReadinessOptions`, `TGitManagerReadinessResult`, `TGitManagerReadinessFailureReason`, `IRustGitManagerConfig`, `IGitManagerProtectedRefRule`, `IGitManagerSmartHttpRepositoryProtectedRefs`, `TGitManagerProtectedRefsApplyStatus`, and `IGitManagerProtectedRefsApplyResult`: exported option, status, readiness, and protected-ref contracts.
243
259
 
244
260
  Repository/read DTO contracts are re-exported from the published `@foss.global/interfaces` package.
245
261
 
@@ -266,7 +282,7 @@ pnpm run test:rust
266
282
  pnpm run build
267
283
  ```
268
284
 
269
- `pnpm test` runs the `pretest` hook first, which builds the Rust binaries with `tsrust`, and then runs lifecycle, local mirror/read API, raw upload-pack/receive-pack preparation, clone/fetch denial, trusted receive-pack denial, explicit policy initialization, protected create/delete/non-fast-forward enforcement, inherited self-test hardening, restart replay, and real push tests through `smartrust`.
285
+ `pnpm test` runs the `pretest` hook first, which builds the Rust binaries with `tsrust`, and then runs lifecycle, bounded readiness, local mirror/read API, raw upload-pack/receive-pack preparation, clone/fetch denial, trusted receive-pack denial, explicit policy initialization, protected create/delete/non-fast-forward enforcement, inherited self-test hardening, restart replay, and real push tests through `smartrust`.
270
286
 
271
287
  ## License and Legal Information
272
288
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@foss.global/gitmanager',
6
- version: '1.5.0',
6
+ version: '1.7.0',
7
7
  description: 'Headless Git manager for the foss.global Git platform, with a TypeScript API and Rust engine.'
8
8
  }
@@ -19,6 +19,7 @@ import type {
19
19
  IGitManagerSmartHttpRepositoryProtectedRefs,
20
20
  IGitManagerRepositoryBundle,
21
21
  IGitManagerRepositoryArchive,
22
+ IGitManagerRepositoryArchiveOptions,
22
23
  IGitManagerRepositoryArchiveRequest,
23
24
  IGitManagerRepositoryLfsPointer,
24
25
  IGitManagerRepositoryServiceRequest,
@@ -33,18 +34,32 @@ import type {
33
34
  IGitManagerTreeEntry,
34
35
  IGitManagerTreeRequest,
35
36
  IGitManagerOptions,
37
+ IGitManagerReadinessOptions,
36
38
  IRustGitManagerConfig,
39
+ TGitManagerReadinessFailureReason,
40
+ TGitManagerReadinessResult,
37
41
  } from './models.js';
38
42
  import { RustGitManagerBridge } from './rust-gitmanager-bridge.js';
39
43
  import { GitManagerRepositoryService } from './classes.repositoryservice.js';
40
44
 
45
+ const readinessDefaultTimeoutMs = 2_000;
46
+ const readinessMaximumTimeoutMs = 30_000;
47
+
48
+ type TGitManagerReadinessAbortSource = 'caller' | 'deadline';
49
+ type TGitManagerReadinessProbeOutcome = 'ready' | 'aborted' | 'unavailable';
50
+
41
51
  export class GitManager extends plugins.EventEmitter {
42
52
  private bridge = new RustGitManagerBridge();
43
53
  private started = false;
44
54
  private startPromise: Promise<void> | null = null;
45
55
  private stopRequested = false;
56
+ private stopPromise: Promise<void> | null = null;
46
57
  private config: IRustGitManagerConfig;
47
58
  private readonly repositoryServices = new Set<GitManagerRepositoryService>();
59
+ private lifecycleEpoch = 0;
60
+ private smartHttpProbeUrl: string | null = null;
61
+ private readonly activeReadinessControllers = new Set<AbortController>();
62
+ private managementStatusProbePromise: Promise<TGitManagerReadinessProbeOutcome> | null = null;
48
63
 
49
64
  constructor(optionsArg: IGitManagerOptions = {}) {
50
65
  super();
@@ -98,6 +113,9 @@ export class GitManager extends plugins.EventEmitter {
98
113
  };
99
114
  this.bridge.on('exit', (code: number | null, signal: string | null) => {
100
115
  this.started = false;
116
+ this.lifecycleEpoch++;
117
+ this.smartHttpProbeUrl = null;
118
+ this.abortActiveReadinessProbes();
101
119
  this.emit('exit', code, signal);
102
120
  });
103
121
  this.bridge.on('stderr', (line: string) => {
@@ -112,8 +130,12 @@ export class GitManager extends plugins.EventEmitter {
112
130
  if (this.startPromise) {
113
131
  throw new Error('GitManager is already starting');
114
132
  }
133
+ if (this.stopPromise) {
134
+ throw new Error('GitManager is stopping');
135
+ }
115
136
 
116
137
  this.stopRequested = false;
138
+ this.lifecycleEpoch++;
117
139
  this.startPromise = this.startInternal();
118
140
  try {
119
141
  await this.startPromise;
@@ -122,13 +144,31 @@ export class GitManager extends plugins.EventEmitter {
122
144
  }
123
145
  }
124
146
 
125
- public async stop(): Promise<void> {
147
+ public stop(): Promise<void> {
148
+ if (this.stopPromise) {
149
+ return this.stopPromise;
150
+ }
151
+ const stopPromise = this.stopInternal();
152
+ this.stopPromise = stopPromise;
153
+ void stopPromise.finally(() => {
154
+ if (this.stopPromise === stopPromise) {
155
+ this.stopPromise = null;
156
+ }
157
+ }).catch(() => undefined);
158
+ return stopPromise;
159
+ }
160
+
161
+ private async stopInternal(): Promise<void> {
126
162
  this.stopRequested = true;
163
+ this.lifecycleEpoch++;
164
+ this.smartHttpProbeUrl = null;
165
+ this.abortActiveReadinessProbes();
127
166
  if (this.startPromise) {
128
167
  await this.startPromise.catch(() => undefined);
129
168
  }
130
169
  if (!this.bridge.running) {
131
170
  await this.closeRepositoryServices();
171
+ await this.bridge.terminate();
132
172
  this.started = false;
133
173
  return;
134
174
  }
@@ -136,7 +176,7 @@ export class GitManager extends plugins.EventEmitter {
136
176
  await this.closeRepositoryServices();
137
177
  await this.bridge.stopGitManager();
138
178
  } finally {
139
- this.bridge.kill();
179
+ await this.bridge.terminate();
140
180
  this.started = false;
141
181
  }
142
182
  }
@@ -152,6 +192,94 @@ export class GitManager extends plugins.EventEmitter {
152
192
  return this.bridge.getStatus();
153
193
  }
154
194
 
195
+ /**
196
+ * Performs a bounded, read-only probe to verify engine readiness. When Git
197
+ * Smart HTTP is configured, the probe uses its independent /healthz route so
198
+ * readiness stays available while the serial management loop is busy with a
199
+ * long-running command; otherwise a single-flight bounded getStatus round
200
+ * trip is used, which queues behind in-flight management commands.
201
+ */
202
+ public async checkReadiness(
203
+ optionsArg: IGitManagerReadinessOptions = {},
204
+ ): Promise<TGitManagerReadinessResult> {
205
+ const startedAt = performance.now();
206
+ const timeoutMs = optionsArg.timeoutMs ?? readinessDefaultTimeoutMs;
207
+ if (
208
+ !Number.isSafeInteger(timeoutMs) ||
209
+ timeoutMs <= 0 ||
210
+ timeoutMs > readinessMaximumTimeoutMs
211
+ ) {
212
+ throw new TypeError(
213
+ `Readiness timeoutMs must be a positive safe integer no greater than ${readinessMaximumTimeoutMs}.`,
214
+ );
215
+ }
216
+
217
+ const callerSignal = optionsArg.signal;
218
+ if (callerSignal?.aborted) {
219
+ return this.createReadinessFailure('aborted', startedAt);
220
+ }
221
+ const lifecycleReason = this.getReadinessLifecycleReason();
222
+ if (lifecycleReason) {
223
+ return this.createReadinessFailure(lifecycleReason, startedAt);
224
+ }
225
+
226
+ const lifecycleEpoch = this.lifecycleEpoch;
227
+ const readinessController = new AbortController();
228
+ let abortSource: TGitManagerReadinessAbortSource | undefined;
229
+ const abortForCaller = (): void => {
230
+ if (!abortSource) {
231
+ abortSource = 'caller';
232
+ }
233
+ readinessController.abort();
234
+ };
235
+ callerSignal?.addEventListener('abort', abortForCaller, { once: true });
236
+ const deadlineTimer = setTimeout(() => {
237
+ if (!abortSource) {
238
+ abortSource = 'deadline';
239
+ }
240
+ readinessController.abort();
241
+ }, timeoutMs);
242
+ this.activeReadinessControllers.add(readinessController);
243
+
244
+ let probeOutcome: TGitManagerReadinessProbeOutcome;
245
+ try {
246
+ probeOutcome = this.smartHttpProbeUrl
247
+ ? await this.probeSmartHttpHealth(
248
+ this.smartHttpProbeUrl,
249
+ readinessController.signal,
250
+ )
251
+ : await this.probeManagementStatus(readinessController.signal);
252
+ } finally {
253
+ clearTimeout(deadlineTimer);
254
+ callerSignal?.removeEventListener('abort', abortForCaller);
255
+ this.activeReadinessControllers.delete(readinessController);
256
+ }
257
+
258
+ if (this.lifecycleEpoch !== lifecycleEpoch) {
259
+ return this.createReadinessFailure(
260
+ this.getReadinessLifecycleReason() ?? 'unavailable',
261
+ startedAt,
262
+ );
263
+ }
264
+ if (probeOutcome === 'aborted') {
265
+ if (abortSource === 'caller') {
266
+ return this.createReadinessFailure('aborted', startedAt);
267
+ }
268
+ if (abortSource === 'deadline') {
269
+ return this.createReadinessFailure('timeout', startedAt);
270
+ }
271
+ return this.createReadinessFailure('unavailable', startedAt);
272
+ }
273
+ if (probeOutcome === 'unavailable') {
274
+ return this.createReadinessFailure('unavailable', startedAt);
275
+ }
276
+ return {
277
+ ready: true,
278
+ status: 'ready',
279
+ durationMs: performance.now() - startedAt,
280
+ };
281
+ }
282
+
155
283
  public async setSmartHttpRepositoryAccess(
156
284
  accessArg: IGitManagerSmartHttpRepositoryAccess,
157
285
  ): Promise<void> {
@@ -227,21 +355,30 @@ export class GitManager extends plugins.EventEmitter {
227
355
 
228
356
  public async createRepositoryArchive(
229
357
  requestArg: IGitManagerRepositoryArchiveRequest,
358
+ optionsArg: IGitManagerRepositoryArchiveOptions = {},
230
359
  ): Promise<IGitManagerRepositoryArchive> {
231
360
  this.assertRunning();
232
- return this.bridge.createRepositoryArchive(requestArg);
361
+ return this.bridge.createRepositoryArchive(requestArg, optionsArg);
233
362
  }
234
363
 
235
364
  public async openRepositoryService(
236
365
  requestArg: IGitManagerRepositoryServiceRequest,
237
366
  ): Promise<GitManagerRepositoryService> {
238
367
  this.assertRunning();
368
+ const serviceEpoch = this.lifecycleEpoch;
239
369
  const descriptor = await this.bridge.prepareRepositoryService({
240
370
  org: requestArg.org,
241
371
  repo: requestArg.repo,
242
372
  service: requestArg.service,
243
373
  ...(requestArg.protocol ? { protocol: requestArg.protocol } : {}),
244
374
  });
375
+ if (
376
+ this.lifecycleEpoch !== serviceEpoch
377
+ || this.stopRequested
378
+ || !this.running
379
+ ) {
380
+ throw new Error('GitManager stopped while preparing repository service');
381
+ }
245
382
  const service = new GitManagerRepositoryService(
246
383
  descriptor,
247
384
  requestArg,
@@ -326,7 +463,7 @@ export class GitManager extends plugins.EventEmitter {
326
463
  throw new Error('Failed to spawn rustgitmanager binary');
327
464
  }
328
465
  if (this.stopRequested) {
329
- this.bridge.kill();
466
+ await this.bridge.terminate();
330
467
  throw new Error('GitManager start was cancelled');
331
468
  }
332
469
 
@@ -336,20 +473,195 @@ export class GitManager extends plugins.EventEmitter {
336
473
  try {
337
474
  await this.bridge.stopGitManager();
338
475
  } finally {
339
- this.bridge.kill();
476
+ await this.bridge.terminate();
340
477
  this.started = false;
341
478
  }
342
479
  throw new Error('GitManager start was cancelled');
343
480
  }
344
481
  this.started = true;
482
+ await this.captureSmartHttpProbeUrl();
345
483
  } catch (error) {
346
- this.bridge.kill();
484
+ await this.bridge.terminate();
347
485
  this.started = false;
348
486
  throw error;
349
487
  }
350
488
  }
351
489
 
490
+ private async captureSmartHttpProbeUrl(): Promise<void> {
491
+ if (!this.config.smartHttp) {
492
+ return;
493
+ }
494
+ try {
495
+ const status = await this.bridge.getStatus({ timeoutMs: 5_000 });
496
+ this.smartHttpProbeUrl = this.resolveSmartHttpProbeUrl(status);
497
+ } catch {
498
+ this.smartHttpProbeUrl = null;
499
+ }
500
+ }
501
+
502
+ private resolveSmartHttpProbeUrl(statusArg: IGitManagerStatus): string | null {
503
+ const smartHttpStatus = statusArg.smartHttp;
504
+ if (!smartHttpStatus?.running || !smartHttpStatus.baseUrl) {
505
+ return null;
506
+ }
507
+ let baseUrl: URL;
508
+ try {
509
+ baseUrl = new URL(smartHttpStatus.baseUrl);
510
+ } catch {
511
+ return null;
512
+ }
513
+ if (baseUrl.hostname === '0.0.0.0') {
514
+ baseUrl.hostname = '127.0.0.1';
515
+ } else if (baseUrl.hostname === '::' || baseUrl.hostname === '[::]') {
516
+ baseUrl.hostname = '[::1]';
517
+ }
518
+ const hostname = baseUrl.hostname;
519
+ const isLoopback =
520
+ hostname === 'localhost' ||
521
+ hostname === '::1' ||
522
+ hostname === '[::1]' ||
523
+ hostname.startsWith('127.');
524
+ if (!isLoopback) {
525
+ return null;
526
+ }
527
+ return baseUrl.origin;
528
+ }
529
+
530
+ private probeSmartHttpHealth(
531
+ probeUrlArg: string,
532
+ signalArg: AbortSignal,
533
+ ): Promise<TGitManagerReadinessProbeOutcome> {
534
+ return new Promise((resolve) => {
535
+ let settled = false;
536
+ let healthRequest: plugins.http.ClientRequest | undefined;
537
+ const settle = (outcomeArg: TGitManagerReadinessProbeOutcome): void => {
538
+ if (settled) {
539
+ return;
540
+ }
541
+ settled = true;
542
+ signalArg.removeEventListener('abort', abortProbe);
543
+ resolve(outcomeArg);
544
+ };
545
+ const abortProbe = (): void => {
546
+ healthRequest?.destroy();
547
+ settle('aborted');
548
+ };
549
+ signalArg.addEventListener('abort', abortProbe, { once: true });
550
+ if (signalArg.aborted) {
551
+ abortProbe();
552
+ return;
553
+ }
554
+ try {
555
+ healthRequest = plugins.http.request(
556
+ new URL('/healthz', probeUrlArg),
557
+ { method: 'GET', agent: false },
558
+ (response) => {
559
+ const outcome: TGitManagerReadinessProbeOutcome =
560
+ response.statusCode === 200 ? 'ready' : 'unavailable';
561
+ // The body is never needed; destroying immediately keeps a peer
562
+ // that stalls mid-body from retaining the socket indefinitely.
563
+ healthRequest?.destroy();
564
+ settle(outcome);
565
+ },
566
+ );
567
+ healthRequest.on('error', () => settle('unavailable'));
568
+ healthRequest.end();
569
+ } catch {
570
+ settle('unavailable');
571
+ }
572
+ });
573
+ }
574
+
575
+ private probeManagementStatus(
576
+ signalArg: AbortSignal,
577
+ ): Promise<TGitManagerReadinessProbeOutcome> {
578
+ if (!this.managementStatusProbePromise) {
579
+ const probeEpoch = this.lifecycleEpoch;
580
+ const managementProbe = this.bridge
581
+ .getStatus({ timeoutMs: readinessMaximumTimeoutMs })
582
+ .then(
583
+ (status): TGitManagerReadinessProbeOutcome => {
584
+ if (
585
+ this.lifecycleEpoch === probeEpoch &&
586
+ this.config.smartHttp &&
587
+ !this.smartHttpProbeUrl
588
+ ) {
589
+ this.smartHttpProbeUrl = this.resolveSmartHttpProbeUrl(status);
590
+ }
591
+ return status.running ? 'ready' : 'unavailable';
592
+ },
593
+ (): TGitManagerReadinessProbeOutcome => 'unavailable',
594
+ );
595
+ this.managementStatusProbePromise = managementProbe;
596
+ void managementProbe.finally(() => {
597
+ if (this.managementStatusProbePromise === managementProbe) {
598
+ this.managementStatusProbePromise = null;
599
+ }
600
+ });
601
+ }
602
+ const sharedProbe = this.managementStatusProbePromise;
603
+ return new Promise((resolve) => {
604
+ let settled = false;
605
+ const settle = (outcomeArg: TGitManagerReadinessProbeOutcome): void => {
606
+ if (settled) {
607
+ return;
608
+ }
609
+ settled = true;
610
+ signalArg.removeEventListener('abort', abortWait);
611
+ resolve(outcomeArg);
612
+ };
613
+ const abortWait = (): void => {
614
+ settle('aborted');
615
+ };
616
+ signalArg.addEventListener('abort', abortWait, { once: true });
617
+ if (signalArg.aborted) {
618
+ abortWait();
619
+ return;
620
+ }
621
+ void sharedProbe.then(settle);
622
+ });
623
+ }
624
+
625
+ private getReadinessLifecycleReason():
626
+ | Extract<
627
+ TGitManagerReadinessFailureReason,
628
+ 'not_running' | 'starting' | 'stopping'
629
+ >
630
+ | undefined {
631
+ if (this.startPromise) {
632
+ return 'starting';
633
+ }
634
+ if (!this.running) {
635
+ return 'not_running';
636
+ }
637
+ if (this.stopRequested) {
638
+ return 'stopping';
639
+ }
640
+ return undefined;
641
+ }
642
+
643
+ private createReadinessFailure(
644
+ reasonArg: TGitManagerReadinessFailureReason,
645
+ startedAtArg: number,
646
+ ): TGitManagerReadinessResult {
647
+ return {
648
+ ready: false,
649
+ status: 'unavailable',
650
+ reason: reasonArg,
651
+ durationMs: performance.now() - startedAtArg,
652
+ };
653
+ }
654
+
655
+ private abortActiveReadinessProbes(): void {
656
+ for (const readinessController of [...this.activeReadinessControllers]) {
657
+ readinessController.abort();
658
+ }
659
+ }
660
+
352
661
  private assertRunning(): void {
662
+ if (this.stopRequested) {
663
+ throw new Error('GitManager is stopping');
664
+ }
353
665
  if (!this.running) {
354
666
  throw new Error('GitManager is not running');
355
667
  }