@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.
- package/changelog.md +18 -0
- package/dist_rust/rustgitmanager_linux_amd64 +0 -0
- package/dist_rust/rustgitmanager_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.gitmanager.d.ts +23 -2
- package/dist_ts/classes.gitmanager.js +265 -8
- package/dist_ts/models.d.ts +18 -0
- package/dist_ts/plugins.d.ts +3 -1
- package/dist_ts/plugins.js +4 -2
- package/dist_ts/rust-gitmanager-bridge.d.ts +4 -3
- package/dist_ts/rust-gitmanager-bridge.js +48 -5
- package/package.json +5 -5
- package/readme.md +20 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.gitmanager.ts +318 -6
- package/ts/models.ts +30 -0
- package/ts/plugins.ts +3 -1
- package/ts/rust-gitmanager-bridge.ts +69 -4
|
@@ -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
|
-
|
|
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,
|
|
183
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVzdC1naXRtYW5hZ2VyLWJyaWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3J1c3QtZ2l0bWFuYWdlci1icmlkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFtRnhDLFNBQVMsY0FBYztJQUNyQixNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDakYsT0FBTyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDN0MsQ0FBQztBQUVELFNBQVMsdUJBQXVCO0lBQzlCLE1BQU0sT0FBTyxHQUEyQixFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxDQUFDO0lBQ3pFLE1BQU0sS0FBSyxHQUEyQixFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxDQUFDO0lBQzFFLE1BQU0sRUFBRSxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDbkMsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNuQyxPQUFPLEVBQUUsSUFBSSxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsRUFBRSxJQUFJLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7QUFDN0MsQ0FBQztBQUVELFNBQVMsZUFBZTtJQUN0QixNQUFNLFdBQVcsR0FBRyxjQUFjLEVBQUUsQ0FBQztJQUNyQyxNQUFNLE1BQU0sR0FBRyx1QkFBdUIsRUFBRSxDQUFDO0lBQ3pDLE1BQU0sS0FBSyxHQUFhLEVBQUUsQ0FBQztJQUUzQixJQUFJLE1BQU0sRUFBRSxDQUFDO1FBQ1gsS0FBSyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsV0FBVyxFQUFFLGtCQUFrQixNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDdEYsQ0FBQztJQUNELEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLFdBQVcsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDLENBQUM7SUFDMUUsS0FBSyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDO0lBQy9GLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLGdCQUFnQixDQUFDLENBQUMsQ0FBQztJQUU3RixPQUFPLEtBQUssQ0FBQztBQUNmLENBQUM7QUFFRCxNQUFNLE9BQU8sb0JBQXFCLFNBQVEsT0FBTyxDQUFDLFlBQVk7SUFHNUQ7UUFDRSxLQUFLLEVBQUUsQ0FBQztRQUNSLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxPQUFPLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBc0I7WUFDbEUsVUFBVSxFQUFFLGdCQUFnQjtZQUM1QixVQUFVLEVBQUUsd0JBQXdCO1lBQ3BDLHFCQUFxQixFQUFFLHlCQUF5QjtZQUNoRCxVQUFVLEVBQUUsZUFBZSxFQUFFO1lBQzdCLGdCQUFnQixFQUFFLE1BQU07WUFDeEIsY0FBYyxFQUFFLEtBQUs7U0FDdEIsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxFQUFFLENBQUMsSUFBbUIsRUFBRSxNQUFxQixFQUFFLEVBQUU7WUFDcEUsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ2xDLENBQUMsQ0FBQyxDQUFDO1FBQ0gsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsSUFBWSxFQUFFLEVBQUU7WUFDeEMsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDNUIsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRU0sS0FBSyxDQUFDLEtBQUs7UUFDaEIsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQzdCLENBQUM7SUFFTSxJQUFJO1FBQ1QsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNyQixDQUFDO0lBRU0sS0FBSyxDQUFDLFNBQVM7UUFDcEIsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsRUFBRSxDQUFDO0lBQ2hDLENBQUM7SUFFRCxJQUFXLE9BQU87UUFDaEIsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQztJQUM3QixDQUFDO0lBRU0sS0FBSyxDQUFDLGVBQWUsQ0FBQyxTQUFnQztRQUMzRCxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLE9BQU8sRUFBRSxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO0lBQ2hFLENBQUM7SUFFTSxLQUFLLENBQUMsY0FBYztRQUN6QixNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsQ0FBQztJQUM1QyxDQUFDO0lBRU0sS0FBSyxDQUFDLFNBQVMsQ0FDcEIsVUFBd0Q7UUFFeEQsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxXQUFXLEVBQUUsRUFBRSxFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQzlELENBQUM7SUFFTSxLQUFLLENBQUMsNEJBQTRCLENBQUMsU0FBK0M7UUFDdkYsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyw4QkFBOEIsRUFBRSxTQUFTLENBQUMsQ0FBQztJQUMzRSxDQUFDO0lBRU0sS0FBSyxDQUFDLGdDQUFnQyxDQUMzQyxhQUF1RDtRQUV2RCxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLGtDQUFrQyxFQUFFLGFBQWEsQ0FBQyxDQUFDO0lBQ25GLENBQUM7SUFFTSxLQUFLLENBQUMsbUNBQW1DLENBQzlDLGdCQUE2RDtRQUU3RCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUM1QixxQ0FBcUMsRUFDckMsZ0JBQWdCLENBQ2pCLENBQUM7SUFDSixDQUFDO0lBRU0sS0FBSyxDQUFDLGtCQUFrQixDQUFDLFVBQWdEO1FBQzlFLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsb0JBQW9CLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDbkUsQ0FBQztJQUVNLEtBQUssQ0FBQyxZQUFZLENBQUMsVUFBMEM7UUFDbEUsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxjQUFjLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDN0QsQ0FBQztJQUVNLEtBQUssQ0FBQyxhQUFhLENBQUMsVUFBeUM7UUFDbEUsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxlQUFlLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDOUQsQ0FBQztJQUVNLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxVQUF5QztRQUNyRSxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLGtCQUFrQixFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQ2pFLENBQUM7SUFFTSxLQUFLLENBQUMsc0JBQXNCLENBQ2pDLFVBQXlDO1FBRXpDLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsd0JBQXdCLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDdkUsQ0FBQztJQUVNLEtBQUssQ0FBQyx1QkFBdUIsQ0FDbEMsVUFBK0MsRUFDL0MsYUFBa0QsRUFBRTtRQUVwRCxJQUFJLFVBQVUsQ0FBQyxNQUFNLEVBQUUsT0FBTyxFQUFFLENBQUM7WUFDL0IsTUFBTSxVQUFVLENBQUMsTUFBTSxDQUFDLE1BQU0sSUFBSSxJQUFJLEtBQUssQ0FBQywwQ0FBMEMsQ0FBQyxDQUFDO1FBQzFGLENBQUM7UUFDRCxNQUFNLFdBQVcsR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQ2hELE1BQU0sY0FBYyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLHlCQUF5QixFQUFFO1lBQ3hFLFdBQVc7WUFDWCxPQUFPLEVBQUUsVUFBVTtTQUNwQixFQUFFO1lBQ0QsU0FBUyxFQUFFLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSTtTQUMxQixDQUFDLENBQUM7UUFDSCxJQUFJLGFBRVMsQ0FBQztRQUNkLE1BQU0sYUFBYSxHQUFHLEdBQVMsRUFBRTtZQUMvQixhQUFhLEtBQUssSUFBSSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMseUJBQXlCLEVBQUU7Z0JBQ25FLFdBQVc7YUFDWixFQUFFO2dCQUNELFNBQVMsRUFBRSxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUk7YUFDMUIsQ0FBQyxDQUFDO1FBQ0wsQ0FBQyxDQUFDO1FBQ0YsVUFBVSxDQUFDLE1BQU0sRUFBRSxnQkFBZ0IsQ0FBQyxPQUFPLEVBQUUsYUFBYSxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7UUFDNUUsSUFBSSxVQUFVLENBQUMsTUFBTSxFQUFFLE9BQU8sRUFBRSxDQUFDO1lBQy9CLGFBQWEsRUFBRSxDQUFDO1FBQ2xCLENBQUM7UUFFRCxJQUFJLE1BQW9DLENBQUM7UUFDekMsSUFBSSxDQUFDO1lBQ0gsTUFBTSxHQUFHLE1BQU0sY0FBYyxDQUFDO1lBQzlCLElBQUksYUFBYSxFQUFFLENBQUM7Z0JBQ2xCLE1BQU0sYUFBYSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUM3QyxDQUFDO1FBQ0gsQ0FBQztRQUFDLE9BQU8sWUFBWSxFQUFFLENBQUM7WUFDdEIsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO2dCQUNuQixNQUFNLFlBQVksQ0FBQztZQUNyQixDQUFDO1lBQ0QsTUFBTSxZQUFZLEdBQUcsTUFBTSxhQUFhLENBQUM7WUFDekMsSUFBSSxZQUFZLENBQUMsTUFBTSxLQUFLLFdBQVcsRUFBRSxDQUFDO2dCQUN4QyxNQUFNLFVBQVUsQ0FBQyxNQUFNLEVBQUUsTUFBTTt1QkFDMUIsSUFBSSxLQUFLLENBQUMsMENBQTBDLENBQUMsQ0FBQztZQUM3RCxDQUFDO1lBQ0QsTUFBTSxZQUFZLENBQUM7UUFDckIsQ0FBQztnQkFBUyxDQUFDO1lBQ1QsVUFBVSxDQUFDLE1BQU0sRUFBRSxtQkFBbUIsQ0FBQyxPQUFPLEVBQUUsYUFBYSxDQUFDLENBQUM7UUFDakUsQ0FBQztRQUNELEtBQUssTUFBTSxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsSUFBSTtZQUMzQixDQUFDLFdBQVcsRUFBRSxNQUFNLENBQUMsU0FBUyxDQUFDO1lBQy9CLENBQUMsb0JBQW9CLEVBQUUsTUFBTSxDQUFDLGtCQUFrQixDQUFDO1lBQ2pELENBQUMsWUFBWSxFQUFFLE1BQU0sQ0FBQyxVQUFVLENBQUM7U0FDekIsRUFBRSxDQUFDO1lBQ1gsSUFBSSxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLElBQUksS0FBSyxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUM5QyxNQUFNLElBQUksS0FBSyxDQUFDLHVDQUF1QyxLQUFLLEVBQUUsQ0FBQyxDQUFDO1lBQ2xFLENBQUM7UUFDSCxDQUFDO1FBQ0QsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVNLEtBQUssQ0FBQyx3QkFBd0IsQ0FDbkMsVUFBK0M7UUFFL0MsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQywwQkFBMEIsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUN6RSxDQUFDO0lBRU0sS0FBSyxDQUFDLHVCQUF1QixDQUNsQyxVQUEyQztRQUUzQyxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLHlCQUF5QixFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQ3hFLENBQUM7SUFFTSxLQUFLLENBQUMsZ0JBQWdCO1FBQzNCLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsa0JBQWtCLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVNLEtBQUssQ0FBQyxRQUFRLENBQUMsVUFBaUM7UUFDckQsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxVQUFVLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVNLEtBQUssQ0FBQyx5QkFBeUIsQ0FDcEMsVUFBeUM7UUFFekMsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQywyQkFBMkIsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUMxRSxDQUFDO0lBRU0sS0FBSyxDQUFDLGVBQWUsQ0FDMUIsVUFBbUM7UUFFbkMsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxpQkFBaUIsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUNoRSxDQUFDO0lBRU0sS0FBSyxDQUFDLFFBQVEsQ0FBQyxVQUFrQztRQUN0RCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLFVBQVUsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUN6RCxDQUFDO0lBRU0sS0FBSyxDQUFDLFFBQVEsQ0FBQyxVQUFzQztRQUMxRCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLFVBQVUsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUN6RCxDQUFDO0lBRU0sS0FBSyxDQUFDLFdBQVcsQ0FBQyxVQUF5QztRQUNoRSxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLGFBQWEsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUM1RCxDQUFDO0lBRU0sS0FBSyxDQUFDLFNBQVMsQ0FBQyxVQUFvQztRQUN6RCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLFdBQVcsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUMxRCxDQUFDO0lBRU0sS0FBSyxDQUFDLE9BQU8sQ0FBQyxVQUFrQztRQUNyRCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLFNBQVMsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUN4RCxDQUFDO0lBRU0sS0FBSyxDQUFDLFFBQVEsQ0FBQyxVQUFzQztRQUMxRCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLFVBQVUsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUN6RCxDQUFDO0lBRU0sS0FBSyxDQUFDLE1BQU0sQ0FBQyxVQUFpQztRQUNuRCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLFFBQVEsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUN2RCxDQUFDO0NBQ0YifQ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foss.global/gitmanager",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
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.
|
|
22
|
-
"@git.zone/tstest": "^
|
|
23
|
-
"@types/node": "^26.
|
|
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()`:
|
|
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
|
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/classes.gitmanager.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|