@foss.global/gitmanager 2.0.0 → 2.1.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.
@@ -0,0 +1,28 @@
1
+ import type { TGitManagerErrorCode } from './models.js';
2
+
3
+ const gitManagerErrorCodes: ReadonlySet<string> = new Set<TGitManagerErrorCode>([
4
+ 'invalidObjectId',
5
+ 'objectNotFound',
6
+ 'objectNotCommit',
7
+ 'ancestryBudgetExceeded',
8
+ 'archiveLimitExceeded',
9
+ 'archiveRejected',
10
+ ]);
11
+
12
+ export const isGitManagerErrorCode = (valueArg: unknown): valueArg is TGitManagerErrorCode =>
13
+ typeof valueArg === 'string' && gitManagerErrorCodes.has(valueArg);
14
+
15
+ /**
16
+ * A failure callers can branch on through its stable `code`. Other failures,
17
+ * such as an unknown repository or an engine that is not running, remain
18
+ * plain errors.
19
+ */
20
+ export class GitManagerError extends Error {
21
+ public readonly code: TGitManagerErrorCode;
22
+
23
+ constructor(codeArg: TGitManagerErrorCode, messageArg: string, optionsArg?: ErrorOptions) {
24
+ super(messageArg, optionsArg);
25
+ this.name = 'GitManagerError';
26
+ this.code = codeArg;
27
+ }
28
+ }
package/ts/index.ts CHANGED
@@ -2,3 +2,5 @@ export * from './models.js';
2
2
  export * from './rust-gitmanager-bridge.js';
3
3
  export * from './classes.gitmanager.js';
4
4
  export * from './classes.repositoryservice.js';
5
+ export * from './classes.gitmanagererror.js';
6
+ export { gitManagerArchivePathMetadataCeilingBytes } from './classes.archiveinspection.js';
package/ts/models.ts CHANGED
@@ -89,6 +89,71 @@ export interface IGitManagerRepositoryArchive
89
89
  entryCount: number;
90
90
  }
91
91
 
92
+ /**
93
+ * Asks whether `ancestorObjectId` is reachable from `descendantObjectId`
94
+ * (`git merge-base --is-ancestor`; a commit is its own ancestor). Both must be
95
+ * full lowercase object IDs of commits in the repository; revision
96
+ * expressions, abbreviations, and tag objects are refused.
97
+ */
98
+ export interface IGitManagerAncestryRequest extends IGitManagerRepositorySelector {
99
+ ancestorObjectId: string;
100
+ descendantObjectId: string;
101
+ /**
102
+ * Wall-clock work budget for the object lookup and the history walk,
103
+ * 1 to 60000 ms, default 10000. Exceeding it throws a `GitManagerError`
104
+ * with code `ancestryBudgetExceeded`.
105
+ */
106
+ timeoutMs?: number;
107
+ }
108
+
109
+ /**
110
+ * Limits for inspecting one `tar.gz` archive. Every limit is inclusive.
111
+ */
112
+ export interface IGitManagerArchiveInspectionRequest {
113
+ /** The archive bytes, or a stream of them; it is consumed and closed. */
114
+ archive: Uint8Array | AsyncIterable<Uint8Array>;
115
+ maxCompressedBytes: number;
116
+ maxExtractedBytes: number;
117
+ maxEntries: number;
118
+ /**
119
+ * Ceiling for the UTF-8 bytes of every effective entry path and symlink
120
+ * target, 1 to 134217728 (the fixed ci-spec ceiling, and the default).
121
+ */
122
+ maxPathMetadataBytes?: number;
123
+ }
124
+
125
+ export interface IGitManagerArchiveInspectionOptions {
126
+ signal?: AbortSignal;
127
+ }
128
+
129
+ /** Metadata recomputed from an accepted archive; nothing is taken on trust. */
130
+ export interface IGitManagerArchiveInspection {
131
+ sizeBytes: number;
132
+ sha256: string;
133
+ extractedSizeBytes: number;
134
+ entryCount: number;
135
+ pathMetadataBytes: number;
136
+ }
137
+
138
+ /**
139
+ * Stable codes of `GitManagerError`.
140
+ *
141
+ * - `invalidObjectId`: an object ID is not a full lowercase SHA-1 or SHA-256.
142
+ * - `objectNotFound`: the object does not exist in the repository.
143
+ * - `objectNotCommit`: the object exists but is not a commit.
144
+ * - `ancestryBudgetExceeded`: the ancestry check exceeded `timeoutMs`; the
145
+ * answer is unknown, never `false`.
146
+ * - `archiveLimitExceeded`: the archive exceeds a requested limit.
147
+ * - `archiveRejected`: the archive breaks a format, path, or link rule.
148
+ */
149
+ export type TGitManagerErrorCode =
150
+ | 'invalidObjectId'
151
+ | 'objectNotFound'
152
+ | 'objectNotCommit'
153
+ | 'ancestryBudgetExceeded'
154
+ | 'archiveLimitExceeded'
155
+ | 'archiveRejected';
156
+
92
157
  export interface IGitManagerRepositoryLfsPointer {
93
158
  oid: string;
94
159
  sizeBytes: number;
@@ -1,5 +1,7 @@
1
1
  import * as plugins from './plugins.js';
2
+ import { GitManagerError, isGitManagerErrorCode } from './classes.gitmanagererror.js';
2
3
  import type {
4
+ IGitManagerAncestryRequest,
3
5
  IGitManagerCommitDetail,
4
6
  IGitManagerCommitRequest,
5
7
  IGitManagerCommitSummary,
@@ -74,6 +76,7 @@ type TGitManagerCommands = {
74
76
  mergeRepository: { params: IGitManagerMergeRequest; result: IGitManagerMergeResult };
75
77
  listTree: { params: IGitManagerTreeRequest; result: IGitManagerTreeEntry[] };
76
78
  readFile: { params: IGitManagerReadFileRequest; result: IGitManagerFileContent };
79
+ isAncestor: { params: IGitManagerAncestryRequest; result: boolean };
77
80
  listCommits: { params: IGitManagerListCommitsRequest; result: IGitManagerCommitSummary[] };
78
81
  getCommit: { params: IGitManagerCommitRequest; result: IGitManagerCommitDetail };
79
82
  getDiff: { params: IGitManagerDiffRequest; result: IGitManagerDiff };
@@ -109,16 +112,37 @@ function buildLocalPaths(): string[] {
109
112
  return paths;
110
113
  }
111
114
 
115
+ /**
116
+ * Rethrows a Rust response that carries a stable GitManager error code as a
117
+ * `GitManagerError`; every other failure is rethrown unchanged.
118
+ */
119
+ const rethrowCodedError = (errorArg: unknown): never => {
120
+ if (
121
+ errorArg instanceof plugins.smartrust.RustBridgeRequestError
122
+ && isGitManagerErrorCode(errorArg.responseErrorCode)
123
+ ) {
124
+ throw new GitManagerError(errorArg.responseErrorCode, errorArg.message, {
125
+ cause: errorArg,
126
+ });
127
+ }
128
+ throw errorArg;
129
+ };
130
+
112
131
  export class RustGitManagerBridge extends plugins.EventEmitter {
113
132
  private bridge: plugins.smartrust.RustBridge<TGitManagerCommands>;
133
+ private binaryLocator: plugins.smartrust.RustBinaryLocator;
114
134
 
115
135
  constructor() {
116
136
  super();
117
- this.bridge = new plugins.smartrust.RustBridge<TGitManagerCommands>({
137
+ const binaryLocatorOptions: plugins.smartrust.IBinaryLocatorOptions = {
118
138
  binaryName: 'rustgitmanager',
119
139
  envVarName: 'GITMANAGER_RUST_BINARY',
120
140
  platformPackagePrefix: '@foss.global/gitmanager',
121
141
  localPaths: buildLocalPaths(),
142
+ };
143
+ this.binaryLocator = new plugins.smartrust.RustBinaryLocator(binaryLocatorOptions);
144
+ this.bridge = new plugins.smartrust.RustBridge<TGitManagerCommands>({
145
+ ...binaryLocatorOptions,
122
146
  requestTimeoutMs: 130000,
123
147
  readyTimeoutMs: 10000,
124
148
  });
@@ -135,6 +159,15 @@ export class RustGitManagerBridge extends plugins.EventEmitter {
135
159
  return this.bridge.spawn();
136
160
  }
137
161
 
162
+ /** Resolves the engine binary the bridge spawns, for its one-shot modes. */
163
+ public async findBinary(): Promise<string> {
164
+ const binaryPath = await this.binaryLocator.findBinary();
165
+ if (!binaryPath) {
166
+ throw new Error('Unable to locate the rustgitmanager binary');
167
+ }
168
+ return binaryPath;
169
+ }
170
+
138
171
  public kill(): void {
139
172
  this.bridge.kill();
140
173
  }
@@ -304,6 +337,20 @@ export class RustGitManagerBridge extends plugins.EventEmitter {
304
337
  return this.bridge.sendCommand('readFile', requestArg);
305
338
  }
306
339
 
340
+ public async isAncestor(requestArg: IGitManagerAncestryRequest): Promise<boolean> {
341
+ const result = await this.bridge.sendCommand('isAncestor', {
342
+ org: requestArg.org,
343
+ repo: requestArg.repo,
344
+ ancestorObjectId: requestArg.ancestorObjectId,
345
+ descendantObjectId: requestArg.descendantObjectId,
346
+ ...(requestArg.timeoutMs === undefined ? {} : { timeoutMs: requestArg.timeoutMs }),
347
+ }).catch(rethrowCodedError);
348
+ if (typeof result !== 'boolean') {
349
+ throw new Error('Rust GitManager returned an invalid ancestry result');
350
+ }
351
+ return result;
352
+ }
353
+
307
354
  public async listCommits(requestArg: IGitManagerListCommitsRequest): Promise<IGitManagerCommitSummary[]> {
308
355
  return this.bridge.sendCommand('listCommits', requestArg);
309
356
  }