@foss.global/codefeed 2.1.0 → 3.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,85 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { ILfsRequestOptions, TLfsDownload } from './classes.lfsbatchclient.js';
3
+ export interface IGiteaSourceTransferOptions {
4
+ /** Instance root, including an optional deployment subpath. HTTPS is required. */
5
+ baseUrl: string;
6
+ /** The reader's login, the user name of its Git and LFS credentials. */
7
+ login: string;
8
+ /**
9
+ * The reader's token, retained only in this reader's memory. It is sent only
10
+ * to `baseUrl`, and to Git only through `GIT_CONFIG_*` entries in the child
11
+ * process environment; it never appears in results, reasons or errors.
12
+ */
13
+ token: string;
14
+ /** PEM certificates that replace the default trust store for this reader's connections, Git transfers included. */
15
+ caCertificates?: string[];
16
+ /** Wall-clock limit of one mirror clone; defaults to 30 minutes, at most six hours. */
17
+ gitTimeoutMs?: number;
18
+ /** Deadline of one REST or LFS batch request, and the idle limit of an LFS download; defaults to 60 s, at most 300 s. */
19
+ requestTimeoutMs?: number;
20
+ /** Response-size budget of one REST or LFS batch answer; defaults to 16 MiB, at most 64 MiB. */
21
+ maxResponseBytes?: number;
22
+ /** Wall-clock limit of one LFS object download; defaults to 30 minutes, at most six hours. */
23
+ lfsTransferTimeoutMs?: number;
24
+ }
25
+ /** A source repository, by its owner's and its own name. */
26
+ export interface IGiteaSourceRepository {
27
+ owner: string;
28
+ name: string;
29
+ }
30
+ export interface IGiteaMirrorImportOptions {
31
+ /** The repository the mirror becomes in the supplied GitManager. */
32
+ target: plugins.fossInterfaces.data.IGitManagerRepositorySelector;
33
+ /**
34
+ * Aborts the import with `request-aborted`. An abort while the clone runs
35
+ * cancels it through the GitManager transfer's own signal: the call settles
36
+ * once Git has exited, leaves no repository at `target`, and the GitManager
37
+ * keeps serving its other work.
38
+ */
39
+ signal?: AbortSignal;
40
+ }
41
+ export interface IGiteaMirrorImport {
42
+ repository: plugins.TGitManagerRepositoryInfo;
43
+ /** The repository size Gitea reported, in KiB, which sized the scratch reservation. */
44
+ reportedSizeKiB: string;
45
+ }
46
+ /**
47
+ * Reads a Gitea repository's Git data and LFS objects for its one-time
48
+ * migration, with the reader's login and token: a mirror clone into a
49
+ * GitManager the caller supplies, and verified LFS object downloads. It never
50
+ * writes to Gitea: Git is read only through `git-upload-pack`, LFS only
51
+ * through batch `download` requests and their download actions.
52
+ */
53
+ export declare class GiteaSourceTransfer {
54
+ #private;
55
+ constructor(optionsArg: IGiteaSourceTransferOptions);
56
+ /** Validated, normalized instance root; contains no credentials or API suffix. */
57
+ get baseUrl(): string;
58
+ /**
59
+ * Clones every ref of the repository into `options.target` of `gitManager`,
60
+ * a started GitManager the caller roots in its own scratch space. The size
61
+ * Gitea reports for the repository is read first; the clone starts only once
62
+ * its scratch reservation fits the free space of the file system holding
63
+ * the GitManager's data directory, in the process-wide `GitScratchBudget`,
64
+ * and the reservation is released once the clone has ended. While it runs
65
+ * the import holds `target`: writes to it through the GitManager fail there
66
+ * with `repositoryBusy`. Rejects with a fixed `ForgeInventoryError` code:
67
+ * `invalid-repository`, `invalid-target`, `gitmanager-not-running`, a REST
68
+ * code such as `http-404`, `git-workdir-failed`, `git-workdir-too-small`,
69
+ * `git-mirror-failed` (also when GitManager refuses `target`, such as with
70
+ * `repositoryBusy` while another transfer holds it) or `request-aborted`
71
+ * (an abort, or a GitManager stop that cancelled the clone). GitManager
72
+ * error text is never passed on.
73
+ */
74
+ importMirror(repositoryArg: IGiteaSourceRepository, gitManagerArg: plugins.TGitManager, optionsArg: IGiteaMirrorImportOptions): Promise<IGiteaMirrorImport>;
75
+ /**
76
+ * Downloads one LFS object of the repository: a batch `download` request
77
+ * with the reader's Basic credentials, then a GET of the action's href with
78
+ * the action's headers only. Resolves with `{ status: 'missing' }` when
79
+ * Gitea answers error 404, otherwise with a stream of the object verified
80
+ * against `oid` and `size` as described for `LfsBatchClient.download`.
81
+ * Rejects with a fixed code, such as `lfs-object-error-422` for a size Gitea
82
+ * disagrees with.
83
+ */
84
+ downloadLfsObject(repositoryArg: IGiteaSourceRepository, oidArg: string, sizeArg: number, optionsArg?: ILfsRequestOptions): Promise<TLfsDownload>;
85
+ }
@@ -0,0 +1,164 @@
1
+ import * as plugins from './plugins.js';
2
+ import * as transport from './forgeinventory.transport.js';
3
+ import * as validation from './forgeinventory.validation.js';
4
+ import { ForgeRequestClient } from './forgeinventory.client.js';
5
+ import { LfsBatchClient } from './classes.lfsbatchclient.js';
6
+ import { giteaCloneUrl, reserveMirrorScratch } from './giteasourceinventory.git.js';
7
+ /**
8
+ * Reads a Gitea repository's Git data and LFS objects for its one-time
9
+ * migration, with the reader's login and token: a mirror clone into a
10
+ * GitManager the caller supplies, and verified LFS object downloads. It never
11
+ * writes to Gitea: Git is read only through `git-upload-pack`, LFS only
12
+ * through batch `download` requests and their download actions.
13
+ */
14
+ export class GiteaSourceTransfer {
15
+ #options;
16
+ #client;
17
+ constructor(optionsArg) {
18
+ const baseUrl = validation.httpUrl(optionsArg.baseUrl);
19
+ const unsafe = (valueArg) => typeof valueArg !== 'string' || !valueArg || /[\s\u0000-\u001f\u007f]/.test(valueArg);
20
+ if (!baseUrl || !baseUrl.startsWith('https:') || unsafe(optionsArg.token) || unsafe(optionsArg.login) || optionsArg.login.includes(':')) {
21
+ throw new validation.ForgeInventoryError('invalid-options');
22
+ }
23
+ const limits = {
24
+ gitTimeoutMs: optionsArg.gitTimeoutMs ?? 30 * 60 * 1000,
25
+ requestTimeoutMs: optionsArg.requestTimeoutMs ?? 60_000,
26
+ maxResponseBytes: optionsArg.maxResponseBytes ?? 16 * 1024 * 1024,
27
+ lfsTransferTimeoutMs: optionsArg.lfsTransferTimeoutMs ?? 30 * 60 * 1000,
28
+ };
29
+ for (const [name, maximum] of [
30
+ ['gitTimeoutMs', 6 * 60 * 60 * 1000], ['requestTimeoutMs', 300_000], ['maxResponseBytes', 64 * 1024 * 1024], ['lfsTransferTimeoutMs', 6 * 60 * 60 * 1000],
31
+ ]) {
32
+ const value = limits[name];
33
+ if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
34
+ throw new validation.ForgeInventoryError('invalid-limits');
35
+ }
36
+ const caCertificates = optionsArg.caCertificates === undefined ? undefined : validation.caCertificates(optionsArg.caCertificates);
37
+ this.#options = { baseUrl: baseUrl.replace(/\/+$/, ''), login: optionsArg.login, token: optionsArg.token, caCertificates, ...limits };
38
+ this.#client = new ForgeRequestClient({
39
+ transport: caCertificates === undefined ? transport.globalFetchTransport : transport.createHttpsTransport(caCertificates),
40
+ headers: { authorization: `token ${optionsArg.token}` },
41
+ requestTimeoutMs: limits.requestTimeoutMs,
42
+ maxResponseBytes: limits.maxResponseBytes,
43
+ });
44
+ }
45
+ /** Validated, normalized instance root; contains no credentials or API suffix. */
46
+ get baseUrl() { return this.#options.baseUrl; }
47
+ /**
48
+ * Clones every ref of the repository into `options.target` of `gitManager`,
49
+ * a started GitManager the caller roots in its own scratch space. The size
50
+ * Gitea reports for the repository is read first; the clone starts only once
51
+ * its scratch reservation fits the free space of the file system holding
52
+ * the GitManager's data directory, in the process-wide `GitScratchBudget`,
53
+ * and the reservation is released once the clone has ended. While it runs
54
+ * the import holds `target`: writes to it through the GitManager fail there
55
+ * with `repositoryBusy`. Rejects with a fixed `ForgeInventoryError` code:
56
+ * `invalid-repository`, `invalid-target`, `gitmanager-not-running`, a REST
57
+ * code such as `http-404`, `git-workdir-failed`, `git-workdir-too-small`,
58
+ * `git-mirror-failed` (also when GitManager refuses `target`, such as with
59
+ * `repositoryBusy` while another transfer holds it) or `request-aborted`
60
+ * (an abort, or a GitManager stop that cancelled the clone). GitManager
61
+ * error text is never passed on.
62
+ */
63
+ async importMirror(repositoryArg, gitManagerArg, optionsArg) {
64
+ const { owner, name } = this.#repository(repositoryArg);
65
+ const target = optionsArg?.target;
66
+ if (typeof target?.org !== 'string' || typeof target?.repo !== 'string')
67
+ throw new validation.ForgeInventoryError('invalid-target');
68
+ const { signal } = optionsArg;
69
+ if (signal?.aborted)
70
+ throw new validation.ForgeInventoryError('request-aborted');
71
+ if (!gitManagerArg.running)
72
+ throw new validation.ForgeInventoryError('gitmanager-not-running');
73
+ let reportedSizeKiB;
74
+ try {
75
+ const repository = validation.record((await this.#client.request(new URL(`${this.#options.baseUrl}/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`), { signal })).body);
76
+ reportedSizeKiB = BigInt(validation.decimal(repository.size));
77
+ }
78
+ catch (error) {
79
+ if (signal?.aborted)
80
+ throw new validation.ForgeInventoryError('request-aborted');
81
+ throw error;
82
+ }
83
+ let dataDir;
84
+ try {
85
+ dataDir = (await gitManagerArg.getStatus()).dataDir;
86
+ }
87
+ catch {
88
+ throw new validation.ForgeInventoryError('git-workdir-failed');
89
+ }
90
+ const release = await reserveMirrorScratch(dataDir, reportedSizeKiB, signal);
91
+ try {
92
+ const imported = await gitManagerArg.importMirror({
93
+ org: target.org,
94
+ repo: target.repo,
95
+ remoteUrl: giteaCloneUrl(this.#options.baseUrl, owner, name),
96
+ credential: { kind: 'basic', username: this.#options.login, password: this.#options.token },
97
+ caCertificates: this.#options.caCertificates,
98
+ timeoutMs: this.#options.gitTimeoutMs,
99
+ }, { signal });
100
+ return { repository: imported, reportedSizeKiB: reportedSizeKiB.toString() };
101
+ }
102
+ catch (error) {
103
+ const { GitManagerError } = await plugins.getGitManager();
104
+ const aborted = signal?.aborted || (error instanceof GitManagerError && error.code === 'transferAborted');
105
+ // GitManager errors are redacted, but still remote text.
106
+ throw new validation.ForgeInventoryError(aborted ? 'request-aborted' : 'git-mirror-failed');
107
+ }
108
+ finally {
109
+ // The import settles only after Git has exited, so the scratch is free again.
110
+ release();
111
+ }
112
+ }
113
+ /**
114
+ * Downloads one LFS object of the repository: a batch `download` request
115
+ * with the reader's Basic credentials, then a GET of the action's href with
116
+ * the action's headers only. Resolves with `{ status: 'missing' }` when
117
+ * Gitea answers error 404, otherwise with a stream of the object verified
118
+ * against `oid` and `size` as described for `LfsBatchClient.download`.
119
+ * Rejects with a fixed code, such as `lfs-object-error-422` for a size Gitea
120
+ * disagrees with.
121
+ */
122
+ async downloadLfsObject(repositoryArg, oidArg, sizeArg, optionsArg = {}) {
123
+ const { owner, name } = this.#repository(repositoryArg);
124
+ const client = new LfsBatchClient({
125
+ endpoint: `${giteaCloneUrl(this.#options.baseUrl, owner, name)}/info/lfs`,
126
+ // Gitea's LFS routes take the token as a Basic password, not as a `token` authorization.
127
+ credential: { kind: 'basic', username: this.#options.login, password: this.#options.token },
128
+ caCertificates: this.#options.caCertificates,
129
+ requestTimeoutMs: this.#options.requestTimeoutMs,
130
+ transferTimeoutMs: this.#options.lfsTransferTimeoutMs,
131
+ maxResponseBytes: this.#options.maxResponseBytes,
132
+ batchSize: 1,
133
+ });
134
+ try {
135
+ const download = await client.download({ oid: oidArg, size: sizeArg }, optionsArg);
136
+ if (download.status === 'available') {
137
+ download.stream.once('close', () => client.close());
138
+ }
139
+ else {
140
+ client.close();
141
+ }
142
+ return download;
143
+ }
144
+ catch (error) {
145
+ client.close();
146
+ throw error;
147
+ }
148
+ }
149
+ #repository(repositoryArg) {
150
+ const single = (valueArg) => {
151
+ const segments = validation.segments(valueArg);
152
+ if (segments.length !== 1)
153
+ throw new validation.ForgeInventoryError('invalid-repository');
154
+ return segments[0];
155
+ };
156
+ try {
157
+ return { owner: single(repositoryArg?.owner), name: single(repositoryArg?.name) };
158
+ }
159
+ catch {
160
+ throw new validation.ForgeInventoryError('invalid-repository');
161
+ }
162
+ }
163
+ }
164
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5naXRlYXNvdXJjZXRyYW5zZmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5naXRlYXNvdXJjZXRyYW5zZmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sS0FBSyxTQUFTLE1BQU0sK0JBQStCLENBQUM7QUFDM0QsT0FBTyxLQUFLLFVBQVUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUM3RCxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUNoRSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFFN0QsT0FBTyxFQUFFLGFBQWEsRUFBRSxvQkFBb0IsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBaURwRjs7Ozs7O0dBTUc7QUFDSCxNQUFNLE9BQU8sbUJBQW1CO0lBQ3JCLFFBQVEsQ0FHZjtJQUNPLE9BQU8sQ0FBcUI7SUFFckMsWUFBWSxVQUF1QztRQUNqRCxNQUFNLE9BQU8sR0FBRyxVQUFVLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUN2RCxNQUFNLE1BQU0sR0FBRyxDQUFDLFFBQWlCLEVBQUUsRUFBRSxDQUFDLE9BQU8sUUFBUSxLQUFLLFFBQVEsSUFBSSxDQUFDLFFBQVEsSUFBSSx5QkFBeUIsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDNUgsSUFBSSxDQUFDLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLElBQUksTUFBTSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxJQUFJLFVBQVUsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDeEksTUFBTSxJQUFJLFVBQVUsQ0FBQyxtQkFBbUIsQ0FBQyxp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
@@ -0,0 +1,126 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { IForgeRequestMetrics } from './forgeinventory.client.js';
3
+ /**
4
+ * The credential of an LFS endpoint. It is sent only as the `Authorization`
5
+ * header of batch requests to the configured endpoint, never to action hrefs
6
+ * and never in a URL, error or result.
7
+ */
8
+ export type TLfsBatchCredential =
9
+ /** HTTP Basic, such as a forge login with an access token as the password. */
10
+ {
11
+ kind: 'basic';
12
+ username: string;
13
+ password: string;
14
+ }
15
+ /** A complete `Authorization` header value, such as `Bearer <token>`. */
16
+ | {
17
+ kind: 'authorization';
18
+ value: string;
19
+ };
20
+ export interface ILfsBatchClientOptions {
21
+ /**
22
+ * The repository's LFS endpoint, normally `<repository>.git/info/lfs`:
23
+ * `https://`, or `http://` only to an IP literal in 127.0.0.0/8 or `[::1]`.
24
+ * No user information, query or fragment.
25
+ */
26
+ endpoint: string;
27
+ credential: TLfsBatchCredential;
28
+ /** PEM certificates that replace the default trust store for this client's HTTPS connections. */
29
+ caCertificates?: string[];
30
+ /** Deadline of a batch or verify request, and of the response headers and every idle gap of a transfer; defaults to 60 s, at most 300 s. */
31
+ requestTimeoutMs?: number;
32
+ /** Wall-clock limit of one object's upload or download; defaults to 30 minutes, at most six hours. */
33
+ transferTimeoutMs?: number;
34
+ /** Response-size budget of a batch answer; defaults to 4 MiB, at most 64 MiB. */
35
+ maxResponseBytes?: number;
36
+ /** Objects per batch request; defaults to 100, at most 100. */
37
+ batchSize?: number;
38
+ }
39
+ /** One LFS object: its SHA-256 as 64 lowercase hex digits and its size in bytes. */
40
+ export interface ILfsObjectId {
41
+ oid: string;
42
+ size: number;
43
+ }
44
+ export interface ILfsUploadObject extends ILfsObjectId {
45
+ /**
46
+ * Opens the object's bytes, called only when the server asks for them. The
47
+ * source must end when `signalArg` aborts; a Node.js `Readable` is also
48
+ * destroyed when the upload ends early.
49
+ */
50
+ open: (signalArg: AbortSignal) => AsyncIterable<Uint8Array> | Promise<AsyncIterable<Uint8Array>>;
51
+ }
52
+ export interface ILfsUploadResult extends ILfsObjectId {
53
+ /** `uploaded` when this call sent the object, `present` when the server already held it. */
54
+ outcome: 'uploaded' | 'present';
55
+ }
56
+ export interface ILfsPresence extends ILfsObjectId {
57
+ status: 'held' | 'missing';
58
+ }
59
+ /** A download: a stream of the verified object, or `missing` when the server answered error 404. */
60
+ export type TLfsDownload = (ILfsObjectId & {
61
+ status: 'available';
62
+ stream: InstanceType<typeof plugins.stream.Readable>;
63
+ }) | (ILfsObjectId & {
64
+ status: 'missing';
65
+ });
66
+ export interface ILfsRequestOptions {
67
+ signal?: AbortSignal;
68
+ }
69
+ /**
70
+ * The client's URL policy, for the endpoint and for every action href: an
71
+ * `https:` URL, or an `http:` URL whose host is an IP literal in 127.0.0.0/8
72
+ * or `[::1]`, without user information or fragment. Host names over HTTP,
73
+ * `localhost` included, are refused, because a name can resolve elsewhere.
74
+ */
75
+ export declare const isAllowedLfsUrl: (urlArg: URL) => boolean;
76
+ /**
77
+ * A provider-neutral Git LFS batch API client with the `basic` transfer
78
+ * adapter, bound to one LFS endpoint and its credential: uploads objects the
79
+ * server lacks, checks which objects it holds and downloads verified objects.
80
+ * Every request is bounded by deadlines, size budgets and an optional
81
+ * `AbortSignal`; redirects are never followed; errors are fixed
82
+ * `ForgeInventoryError` codes that carry no URL, header, credential or
83
+ * response text.
84
+ */
85
+ export declare class LfsBatchClient {
86
+ #private;
87
+ constructor(optionsArg: ILfsBatchClientOptions);
88
+ /** Batch requests sent and their response bytes; object transfers are not counted. */
89
+ get metrics(): IForgeRequestMetrics;
90
+ /**
91
+ * Uploads every object the server lacks: one batch `upload` request per
92
+ * `batchSize` objects, then for each object with an `upload` action a PUT of
93
+ * exactly `size` bytes, verified against `oid` while they stream, and the
94
+ * `verify` action when the server sent one. An object without an `upload`
95
+ * action is already present. Objects are sent one at a time after their
96
+ * batch answer; an object whose upload or verify action would expire within
97
+ * the expiry margin (30 s, or `requestTimeoutMs` when that is shorter) is
98
+ * asked for again alone, once, and fails with `lfs-action-expired` when the
99
+ * fresh answer is again too short-lived. Rejects on the first object that
100
+ * fails.
101
+ */
102
+ upload(objectsArg: ILfsUploadObject[], optionsArg?: ILfsRequestOptions): Promise<ILfsUploadResult[]>;
103
+ /**
104
+ * Asks the server through batch `download` requests which objects it holds,
105
+ * without downloading them: a download action means `held`, error 404
106
+ * `missing`. Any other object error rejects with `lfs-object-error-<code>`.
107
+ * The actions are not used, so their expiry is validated but not enforced.
108
+ */
109
+ checkPresence(objectsArg: ILfsObjectId[], optionsArg?: ILfsRequestOptions): Promise<ILfsPresence[]>;
110
+ /**
111
+ * Downloads one object: a batch `download` request, then a GET of the
112
+ * action's href with the action's headers; a download action that would
113
+ * expire within the expiry margin is asked for again once, as for `upload`.
114
+ * Resolves with `missing` when the
115
+ * server answered error 404, otherwise with a stream of the object that
116
+ * never holds it whole. The stream errors with `lfs-size-overrun`,
117
+ * `lfs-short-body` or `lfs-digest-mismatch` before its last chunk when the
118
+ * bytes are not the object, with `request-aborted` when `signal` aborts and
119
+ * with `lfs-transfer-timed-out` when the transfer exceeds
120
+ * `transferTimeoutMs` or stalls for `requestTimeoutMs`. Destroying the
121
+ * stream ends the download.
122
+ */
123
+ download(objectArg: ILfsObjectId, optionsArg?: ILfsRequestOptions): Promise<TLfsDownload>;
124
+ /** Destroys this client's pooled connections. Idle ones close on their own after a few seconds. */
125
+ close(): void;
126
+ }