@foss.global/forgefixtures 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.smartconfig.json +49 -0
  2. package/changelog.md +17 -0
  3. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  4. package/dist_ts/00_commitinfo_data.js +9 -0
  5. package/dist_ts/classes.certificateauthority.d.ts +22 -0
  6. package/dist_ts/classes.certificateauthority.js +93 -0
  7. package/dist_ts/classes.containerlifecycle.d.ts +88 -0
  8. package/dist_ts/classes.containerlifecycle.js +383 -0
  9. package/dist_ts/classes.giteafixture.d.ts +41 -0
  10. package/dist_ts/classes.giteafixture.js +182 -0
  11. package/dist_ts/classes.giteaseed.d.ts +13 -0
  12. package/dist_ts/classes.giteaseed.js +432 -0
  13. package/dist_ts/classes.gitlabfixture.d.ts +49 -0
  14. package/dist_ts/classes.gitlabfixture.js +237 -0
  15. package/dist_ts/classes.gitlabseed.d.ts +13 -0
  16. package/dist_ts/classes.gitlabseed.js +466 -0
  17. package/dist_ts/classes.httpclient.d.ts +53 -0
  18. package/dist_ts/classes.httpclient.js +116 -0
  19. package/dist_ts/classes.reaper.d.ts +25 -0
  20. package/dist_ts/classes.reaper.js +102 -0
  21. package/dist_ts/classes.tlsterminator.d.ts +21 -0
  22. package/dist_ts/classes.tlsterminator.js +131 -0
  23. package/dist_ts/constants.d.ts +22 -0
  24. package/dist_ts/constants.js +30 -0
  25. package/dist_ts/giteaseed.default.d.ts +10 -0
  26. package/dist_ts/giteaseed.default.js +87 -0
  27. package/dist_ts/gitlabseed.default.d.ts +12 -0
  28. package/dist_ts/gitlabseed.default.js +84 -0
  29. package/dist_ts/index.d.ts +17 -0
  30. package/dist_ts/index.js +17 -0
  31. package/dist_ts/interfaces.d.ts +92 -0
  32. package/dist_ts/interfaces.giteaseed.d.ts +182 -0
  33. package/dist_ts/interfaces.giteaseed.js +2 -0
  34. package/dist_ts/interfaces.gitlabseed.d.ts +183 -0
  35. package/dist_ts/interfaces.gitlabseed.js +2 -0
  36. package/dist_ts/interfaces.js +2 -0
  37. package/dist_ts/ownership.d.ts +29 -0
  38. package/dist_ts/ownership.js +140 -0
  39. package/dist_ts/plugins.d.ts +13 -0
  40. package/dist_ts/plugins.js +18 -0
  41. package/dist_ts/responses.d.ts +7 -0
  42. package/dist_ts/responses.js +30 -0
  43. package/license.md +21 -0
  44. package/package.json +65 -0
  45. package/readme.md +206 -0
  46. package/ts/00_commitinfo_data.ts +8 -0
  47. package/ts/classes.certificateauthority.ts +117 -0
  48. package/ts/classes.containerlifecycle.ts +432 -0
  49. package/ts/classes.giteafixture.ts +205 -0
  50. package/ts/classes.giteaseed.ts +486 -0
  51. package/ts/classes.gitlabfixture.ts +258 -0
  52. package/ts/classes.gitlabseed.ts +502 -0
  53. package/ts/classes.httpclient.ts +156 -0
  54. package/ts/classes.reaper.ts +126 -0
  55. package/ts/classes.tlsterminator.ts +136 -0
  56. package/ts/constants.ts +35 -0
  57. package/ts/giteaseed.default.ts +88 -0
  58. package/ts/gitlabseed.default.ts +86 -0
  59. package/ts/index.ts +17 -0
  60. package/ts/interfaces.giteaseed.ts +130 -0
  61. package/ts/interfaces.gitlabseed.ts +135 -0
  62. package/ts/interfaces.ts +94 -0
  63. package/ts/ownership.ts +160 -0
  64. package/ts/plugins.ts +23 -0
  65. package/ts/responses.ts +33 -0
@@ -0,0 +1,116 @@
1
+ import * as plugins from './plugins.js';
2
+ export class ForgeFixtureHttpError extends Error {
3
+ status;
4
+ method;
5
+ path;
6
+ constructor(methodArg, pathArg, statusArg, bodyExcerptArg) {
7
+ super(`${methodArg} ${pathArg} answered ${statusArg}: ${bodyExcerptArg}`);
8
+ this.name = 'ForgeFixtureHttpError';
9
+ this.status = statusArg;
10
+ this.method = methodArg;
11
+ this.path = pathArg;
12
+ }
13
+ }
14
+ /**
15
+ * HTTPS client scoped to one fixture: it trusts only the fixture CA, talks only
16
+ * to the fixture origin, never follows redirects, and bounds time and body size.
17
+ * It changes no process-global TLS state.
18
+ */
19
+ export class ForgeFixtureHttpClient {
20
+ #baseUrl;
21
+ #agent;
22
+ #requestTimeoutMs;
23
+ #maxResponseBytes;
24
+ constructor(optionsArg) {
25
+ const baseUrl = new URL(optionsArg.baseUrl);
26
+ if (baseUrl.protocol !== 'https:' || baseUrl.username || baseUrl.password || baseUrl.search || baseUrl.hash) {
27
+ throw new TypeError('The fixture base URL must be a credential-free HTTPS URL.');
28
+ }
29
+ this.#baseUrl = new URL(baseUrl.href.endsWith('/') ? baseUrl.href : `${baseUrl.href}/`);
30
+ this.#agent = new plugins.https.Agent({ ca: [optionsArg.caCertificatePem], keepAlive: true, maxSockets: 8 });
31
+ this.#requestTimeoutMs = optionsArg.requestTimeoutMs ?? 60_000;
32
+ this.#maxResponseBytes = optionsArg.maxResponseBytes ?? 16 * 1024 * 1024;
33
+ }
34
+ get baseUrl() {
35
+ return this.#baseUrl.href.replace(/\/$/, '');
36
+ }
37
+ /** Resolves a request target and refuses anything outside the fixture origin. */
38
+ resolve(urlArg, queryArg) {
39
+ const url = /^https?:\/\//.test(urlArg)
40
+ ? new URL(urlArg)
41
+ : new URL(urlArg.replace(/^\//, ''), this.#baseUrl);
42
+ if (url.origin !== this.#baseUrl.origin || url.username || url.password) {
43
+ throw new Error(`Refusing a request outside the fixture origin: ${url.origin}`);
44
+ }
45
+ for (const [key, value] of Object.entries(queryArg ?? {}))
46
+ url.searchParams.set(key, value);
47
+ return url;
48
+ }
49
+ async request(requestArg) {
50
+ if (requestArg.json !== undefined && requestArg.body !== undefined) {
51
+ throw new TypeError('A fixture request cannot carry both json and body.');
52
+ }
53
+ const url = this.resolve(requestArg.url, requestArg.query);
54
+ const payload = requestArg.json !== undefined
55
+ ? Buffer.from(JSON.stringify(requestArg.json))
56
+ : requestArg.body === undefined ? undefined : Buffer.from(requestArg.body);
57
+ const headers = { accept: 'application/json' };
58
+ for (const [name, value] of Object.entries(requestArg.headers ?? {}))
59
+ headers[name.toLowerCase()] = value;
60
+ if (requestArg.json !== undefined)
61
+ headers['content-type'] = 'application/json';
62
+ // A caller-requested transfer coding (for example Git LFS upload actions) replaces Content-Length.
63
+ if (payload !== undefined && headers['transfer-encoding'] === undefined) {
64
+ headers['content-length'] = String(payload.byteLength);
65
+ }
66
+ const timeoutMs = requestArg.timeoutMs ?? this.#requestTimeoutMs;
67
+ const maxResponseBytes = requestArg.maxResponseBytes ?? this.#maxResponseBytes;
68
+ const response = await new Promise((resolveArg, rejectArg) => {
69
+ const request = plugins.https.request(url, {
70
+ method: requestArg.method,
71
+ headers,
72
+ agent: this.#agent,
73
+ signal: AbortSignal.timeout(timeoutMs),
74
+ }, (responseArg) => {
75
+ const chunks = [];
76
+ let size = 0;
77
+ responseArg.on('data', (chunkArg) => {
78
+ size += chunkArg.byteLength;
79
+ if (size > maxResponseBytes) {
80
+ responseArg.destroy(new Error(`${requestArg.method} ${url.pathname} exceeded ${maxResponseBytes} response bytes.`));
81
+ return;
82
+ }
83
+ chunks.push(chunkArg);
84
+ });
85
+ responseArg.once('error', rejectArg);
86
+ responseArg.once('end', () => resolveArg({
87
+ status: responseArg.statusCode ?? 0,
88
+ headers: responseArg.headers,
89
+ body: Buffer.concat(chunks),
90
+ }));
91
+ });
92
+ request.once('error', rejectArg);
93
+ request.end(payload);
94
+ });
95
+ if (!requestArg.expectedStatus.includes(response.status)) {
96
+ throw new ForgeFixtureHttpError(requestArg.method, url.pathname, response.status, response.body.subarray(0, 512).toString('utf8'));
97
+ }
98
+ return response;
99
+ }
100
+ /** Performs a request and parses its JSON body. An empty body yields `null`. */
101
+ async requestJson(requestArg) {
102
+ const response = await this.request(requestArg);
103
+ if (response.body.byteLength === 0)
104
+ return null;
105
+ const contentType = response.headers['content-type']?.split(';')[0]?.trim().toLowerCase();
106
+ if (contentType !== 'application/json' && contentType !== 'application/vnd.git-lfs+json') {
107
+ throw new Error(`${requestArg.method} ${requestArg.url} answered ${contentType ?? 'no content type'} instead of JSON.`);
108
+ }
109
+ return JSON.parse(response.body.toString('utf8'));
110
+ }
111
+ /** Destroys pooled connections. The client is unusable afterwards. */
112
+ close() {
113
+ this.#agent.destroy();
114
+ }
115
+ }
116
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5odHRwY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5odHRwY2xpZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBbUN4QyxNQUFNLE9BQU8scUJBQXNCLFNBQVEsS0FBSztJQUM5QixNQUFNLENBQVM7SUFDZixNQUFNLENBQVM7SUFDZixJQUFJLENBQVM7SUFFN0IsWUFBWSxTQUFpQixFQUFFLE9BQWUsRUFBRSxTQUFpQixFQUFFLGNBQXNCO1FBQ3ZGLEtBQUssQ0FBQyxHQUFHLFNBQVMsSUFBSSxPQUFPLGFBQWEsU0FBUyxLQUFLLGNBQWMsRUFBRSxDQUFDLENBQUM7UUFDMUUsSUFBSSxDQUFDLElBQUksR0FBRyx1QkFBdUIsQ0FBQztRQUNwQyxJQUFJLENBQUMsTUFBTSxHQUFHLFNBQVMsQ0FBQztRQUN4QixJQUFJLENBQUMsTUFBTSxHQUFHLFNBQVMsQ0FBQztRQUN4QixJQUFJLENBQUMsSUFBSSxHQUFHLE9BQU8sQ0FBQztJQUN0QixDQUFDO0NBQ0Y7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxPQUFPLHNCQUFzQjtJQUN4QixRQUFRLENBQU07SUFDZCxNQUFNLENBQXNCO0lBQzVCLGlCQUFpQixDQUFTO0lBQzFCLGlCQUFpQixDQUFTO0lBRW5DLFlBQVksVUFBMEM7UUFDcEQsTUFBTSxPQUFPLEdBQUcsSUFBSSxHQUFHLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQzVDLElBQUksT0FBTyxDQUFDLFFBQVEsS0FBSyxRQUFRLElBQUksT0FBTyxDQUFDLFFBQVEsSUFBSSxPQUFPLENBQUMsUUFBUSxJQUFJLE9BQU8sQ0FBQyxNQUFNLElBQUksT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO1lBQzVHLE1BQU0sSUFBSSxTQUFTLENBQUMsMkRBQTJELENBQUMsQ0FBQztRQUNuRixDQUFDO1FBQ0QsSUFBSSxDQUFDLFFB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
@@ -0,0 +1,25 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { IForgeFixtureReapReport } from './interfaces.js';
3
+ export interface IForgeFixtureReaperOptions {
4
+ /** Docker Engine socket; omit for `@apiclient.xyz/docker` resolution. */
5
+ dockerSocketPath?: string;
6
+ }
7
+ export interface IForgeFixtureInventory {
8
+ containers: plugins.docker.DockerContainer[];
9
+ networks: plugins.docker.DockerNetwork[];
10
+ volumes: plugins.docker.DockerVolume[];
11
+ }
12
+ export declare const createForgeFixtureDockerHost: (socketPathArg?: string) => plugins.docker.DockerHost;
13
+ /** Lists every Docker resource carrying this package's managed label on one daemon. */
14
+ export declare const listForgeFixtureResources: (dockerHostArg: plugins.docker.DockerHost) => Promise<IForgeFixtureInventory>;
15
+ /**
16
+ * Removes the Docker resources of lifecycles whose owner process exited,
17
+ * whose host rebooted, or whose declared lifetime expired. Resources of live
18
+ * owners and resources with uninterpretable labels are left untouched.
19
+ */
20
+ export declare class ForgeFixtureReaper {
21
+ #private;
22
+ constructor(optionsArg?: IForgeFixtureReaperOptions);
23
+ reap(nowArg?: Date): Promise<IForgeFixtureReapReport>;
24
+ }
25
+ export declare const reapStaleForgeFixtures: (dockerHostArg: plugins.docker.DockerHost, nowArg?: Date) => Promise<IForgeFixtureReapReport>;
@@ -0,0 +1,102 @@
1
+ import * as plugins from './plugins.js';
2
+ import { forgeFixtureLabels } from './constants.js';
3
+ import { classifyForgeFixtureOwner, parseForgeFixtureOwnerLabels, readCurrentProcessIdentity, } from './ownership.js';
4
+ export const createForgeFixtureDockerHost = (socketPathArg) => new plugins.docker.DockerHost({
5
+ ...(socketPathArg === undefined ? {} : { socketPath: socketPathArg }),
6
+ enableImageStore: false,
7
+ });
8
+ const managedFilter = () => ({ label: [`${forgeFixtureLabels.managed}=true`] });
9
+ /** Lists every Docker resource carrying this package's managed label on one daemon. */
10
+ export const listForgeFixtureResources = async (dockerHostArg) => {
11
+ const [containers, networks, volumes] = await Promise.all([
12
+ dockerHostArg.listContainers({ all: true, filters: managedFilter() }),
13
+ dockerHostArg.listNetworks({ filters: managedFilter() }),
14
+ dockerHostArg.listVolumes({ filters: managedFilter() }),
15
+ ]);
16
+ return { containers, networks, volumes };
17
+ };
18
+ /**
19
+ * Removes the Docker resources of lifecycles whose owner process exited,
20
+ * whose host rebooted, or whose declared lifetime expired. Resources of live
21
+ * owners and resources with uninterpretable labels are left untouched.
22
+ */
23
+ export class ForgeFixtureReaper {
24
+ #options;
25
+ constructor(optionsArg = {}) {
26
+ this.#options = { ...optionsArg };
27
+ }
28
+ async reap(nowArg = new Date()) {
29
+ const dockerHost = createForgeFixtureDockerHost(this.#options.dockerSocketPath);
30
+ try {
31
+ return await reapStaleForgeFixtures(dockerHost, nowArg);
32
+ }
33
+ finally {
34
+ await dockerHost.stop();
35
+ }
36
+ }
37
+ }
38
+ export const reapStaleForgeFixtures = async (dockerHostArg, nowArg = new Date()) => {
39
+ const current = await readCurrentProcessIdentity();
40
+ const inventory = await listForgeFixtureResources(dockerHostArg);
41
+ const report = {
42
+ removedContainers: [], removedNetworks: [], removedVolumes: [], reapedLifecycles: [], unrecognizedResources: [],
43
+ };
44
+ const verdicts = new Map();
45
+ const verdictFor = async (ownerArg) => {
46
+ let verdict = verdicts.get(ownerArg.lifecycleId);
47
+ if (verdict === undefined) {
48
+ verdict = await classifyForgeFixtureOwner(ownerArg, current, nowArg);
49
+ verdicts.set(ownerArg.lifecycleId, verdict);
50
+ if (verdict !== 'alive')
51
+ report.reapedLifecycles.push({ lifecycleId: ownerArg.lifecycleId, reason: verdict });
52
+ }
53
+ return verdict;
54
+ };
55
+ const stale = async (typeArg, idArg, labelsArg) => {
56
+ const owner = parseForgeFixtureOwnerLabels(labelsArg);
57
+ if (!owner) {
58
+ report.unrecognizedResources.push({ type: typeArg, id: idArg });
59
+ return false;
60
+ }
61
+ return (await verdictFor(owner)) !== 'alive';
62
+ };
63
+ const failures = [];
64
+ // Dependency order: containers release their network endpoints and volumes first.
65
+ for (const container of inventory.containers) {
66
+ if (!await stale('container', container.Id, container.Labels))
67
+ continue;
68
+ try {
69
+ await container.remove({ force: true, removeAnonymousVolumes: true });
70
+ report.removedContainers.push(container.Id);
71
+ }
72
+ catch (error) {
73
+ failures.push(error);
74
+ }
75
+ }
76
+ for (const network of inventory.networks) {
77
+ if (!await stale('network', network.Id, network.Labels))
78
+ continue;
79
+ try {
80
+ await network.remove();
81
+ report.removedNetworks.push(network.Id);
82
+ }
83
+ catch (error) {
84
+ failures.push(error);
85
+ }
86
+ }
87
+ for (const volume of inventory.volumes) {
88
+ if (!await stale('volume', volume.Name, volume.Labels))
89
+ continue;
90
+ try {
91
+ await volume.remove({ force: true });
92
+ report.removedVolumes.push(volume.Name);
93
+ }
94
+ catch (error) {
95
+ failures.push(error);
96
+ }
97
+ }
98
+ if (failures.length > 0)
99
+ throw new AggregateError(failures, 'Forge fixture reaping left stale resources behind.');
100
+ return report;
101
+ };
102
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZWFwZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLnJlYXBlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUNwRCxPQUFPLEVBQ0wseUJBQXlCLEVBQ3pCLDRCQUE0QixFQUM1QiwwQkFBMEIsR0FDM0IsTUFBTSxnQkFBZ0IsQ0FBQztBQWN4QixNQUFNLENBQUMsTUFBTSw0QkFBNEIsR0FBRyxDQUFDLGFBQXNCLEVBQTZCLEVBQUUsQ0FDaEcsSUFBSSxPQUFPLENBQUMsTUFBTSxDQUFDLFVBQVUsQ0FBQztJQUM1QixHQUFHLENBQUMsYUFBYSxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLFVBQVUsRUFBRSxhQUFhLEVBQUUsQ0FBQztJQUNyRSxnQkFBZ0IsRUFBRSxLQUFLO0NBQ3hCLENBQUMsQ0FBQztBQUVMLE1BQU0sYUFBYSxHQUFHLEdBQTZCLEVBQUUsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUMsR0FBRyxrQkFBa0IsQ0FBQyxPQUFPLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQztBQUUxRyx1RkFBdUY7QUFDdkYsTUFBTSxDQUFDLE1BQU0seUJBQXlCLEdBQUcsS0FBSyxFQUM1QyxhQUF3QyxFQUNQLEVBQUU7SUFDbkMsTUFBTSxDQUFDLFVBQVUsRUFBRSxRQUFRLEVBQUUsT0FBTyxDQUFDLEdBQUcsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDO1FBQ3hELGFBQWEsQ0FBQyxjQUFjLENBQUMsRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxhQUFhLEVBQUUsRUFBRSxDQUFDO1FBQ3JFLGFBQWEsQ0FBQyxZQUFZLENBQUMsRUFBRSxPQUFPLEVBQUUsYUFBYSxFQUFFLEVBQUUsQ0FBQztRQUN4RCxhQUFhLENBQUMsV0FBVyxDQUFDLEVBQUUsT0FBTyxFQUFFLGFBQWEsRUFBRSxFQUFFLENBQUM7S0FDeEQsQ0FBQyxDQUFDO0lBQ0gsT0FBTyxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLENBQUM7QUFDM0MsQ0FBQyxDQUFDO0FBRUY7Ozs7R0FJRztBQUNILE1BQU0sT0FBTyxrQkFBa0I7SUFDcEIsUUFBUSxDQUE2QjtJQUU5QyxZQUFZLGFBQXlDLEVBQUU7UUFDckQsSUFBSSxDQUFDLFFBQVEsR0FBRyxFQUFFLEdBQUcsVUFBVSxFQUFFLENBQUM7SUFDcEMsQ0FBQztJQUVNLEtBQUssQ0FBQyxJQUFJLENBQUMsU0FBZSxJQUFJLElBQUksRUFBRTtRQUN6QyxNQUFNLFVBQVUsR0FBRyw0QkFBNEIsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLGdCQUFnQixDQUFDLENBQUM7UUFDaEYsSUFBSSxDQUFDO1lBQ0gsT0FBTyxNQUFNLHNCQUFzQixDQUFDLFVBQVUsRUFBRSxNQUFNLENBQUMsQ0FBQztRQUMxRCxDQUFDO2dCQUFTLENBQUM7WUFDVCxNQUFNLFVBQVUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUMxQixDQUFDO0lBQ0gsQ0FBQztDQUNGO0FBRUQsTUFBTSxDQUFDLE1BQU0sc0JBQXNCLEdBQUcsS0FBSyxFQUN6QyxhQUF3QyxFQUN4QyxTQUFlLElBQUksSUFBSSxFQUFFLEVBQ1MsRUFBRTtJQUNwQyxNQUFNLE9BQU8sR0FBRyxNQUFNLDBCQUEwQixFQUFFLENBQUM7SUFDbkQsTUFBTSxTQUFTLEdBQUcsTUFBTSx5QkFBeUIsQ0FBQyxhQUFhLENBQUMsQ0FBQztJQUNqRSxNQUFNLE1BQU0sR0FBNEI7UUFDdEMsaUJBQWlCLEVBQUUsRUFBRSxFQUFFLGVBQWUsRUFBRSxFQUFFLEVBQUUsY0FBYyxFQUFFLEVBQUUsRUFBRSxnQkFBZ0IsRUFBRSxFQUFFLEVBQUUscUJBQXFCLEVBQUUsRUFBRTtLQUNoSCxDQUFDO0lBQ0YsTUFBTSxRQUFRLEdBQUcsSUFBSSxHQUFHLEVBQTZDLENBQUM7SUFDdEUsTUFBTSxVQUFVLEdBQUcsS0FBSyxFQUFFLFFBQTRCLEVBQThDLEVBQUU7UUFDcEcsSUFBSSxPQUFPLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDakQsSUFBSSxPQUFPLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDMUIsT0FBTyxHQUFHLE1BQU0seUJBQXlCLENBQUMsUUFBUSxFQUFFLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUNyRSxRQUFRLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxXQUFXLEVBQUUsT0FBTyxDQUFDLENBQUM7WUFDNUMsSUFBSSxPQUFPLEtBQUssT0FBTztnQkFBRSxNQUFNLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEVBQUUsV0FBVyxFQUFFLFFBQVEsQ0FBQyxXQUFXLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUM7UUFDaEgsQ0FBQztRQUNELE9BQU8sT0FBTyxDQUFDO0lBQ2pCLENBQUMsQ0FBQztJQUNGLE1BQU0sS0FBSyxHQUFHLEtBQUssRUFDakIsT0FBMkMsRUFDM0MsS0FBYSxFQUNiLFNBQTZDLEVBQzNCLEVBQUU7UUFDcEIsTUFBTSxLQUFLLEdBQUcsNEJBQTRCLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDdEQsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ1gsTUFBTSxDQUFDLHFCQUFxQixDQUFDLElBQUksQ0FBQyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsRUFBRSxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUM7WUFDaEUsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO1FBQ0QsT0FBTyxDQUFDLE1BQU0sVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLEtBQUssT0FBTyxDQUFDO0lBQy9DLENBQUMsQ0FBQztJQUVGLE1BQU0sUUFBUSxHQUFjLEVBQUUsQ0FBQztJQUMvQixrRkFBa0Y7SUFDbEYsS0FBSyxNQUFNLFNBQVMsSUFBSSxTQUFTLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDN0MsSUFBSSxDQUFDLE1BQU0sS0FBSyxDQUFDLFdBQVcsRUFBRSxTQUFTLENBQUMsRUFBRSxFQUFFLFNBQVMsQ0FBQyxNQUFNLENBQUM7WUFBRSxTQUFTO1FBQ3hFLElBQUksQ0FBQztZQUNILE1BQU0sU0FBUyxDQUFDLE1BQU0sQ0FBQyxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsc0JBQXNCLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUN0RSxNQUFNLENBQUMsaUJBQWlCLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUM5QyxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLFFBQVEsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDdkIsQ0FBQztJQUNILENBQUM7SUFDRCxLQUFLLE1BQU0sT0FBTyxJQUFJLFNBQVMsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUN6QyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxFQUFFLEVBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQztZQUFFLFNBQVM7UUFDbEUsSUFBSSxDQUFDO1lBQ0gsTUFBTSxPQUFPLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDdkIsTUFBTSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQzFDLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsUUFBUSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN2QixDQUFDO0lBQ0gsQ0FBQztJQUNELEtBQUssTUFBTSxNQUFNLElBQUksU0FBUyxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3ZDLElBQUksQ0FBQyxNQUFNLEtBQUssQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDO1lBQUUsU0FBUztRQUNqRSxJQUFJLENBQUM7WUFDSCxNQUFNLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUNyQyxNQUFNLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDMUMsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixRQUFRLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3ZCLENBQUM7SUFDSCxDQUFDO0lBQ0QsSUFBSSxRQUFRLENBQUMsTUFBTSxHQUFHLENBQUM7UUFBRSxNQUFNLElBQUksY0FBYyxDQUFDLFFBQVEsRUFBRSxvREFBb0QsQ0FBQyxDQUFDO0lBQ2xILE9BQU8sTUFBTSxDQUFDO0FBQ2hCLENBQUMsQ0FBQyJ9
@@ -0,0 +1,21 @@
1
+ import type { IForgeFixtureServerCertificate } from './classes.certificateauthority.js';
2
+ /**
3
+ * Loopback TLS-terminating reverse proxy in front of one loopback HTTP
4
+ * upstream, configured the way a production proxy is: the client's Host header
5
+ * is preserved and `X-Forwarded-Proto: https` tells the forge the public scheme.
6
+ * It listens before the forge starts, so the forge can be configured with its
7
+ * exact external HTTPS origin.
8
+ */
9
+ export declare class ForgeFixtureTlsTerminator {
10
+ #private;
11
+ constructor(certificateArg: IForgeFixtureServerCertificate);
12
+ /** Last error the listening server emitted, if any. */
13
+ get lastServerError(): Error | undefined;
14
+ /** Bound loopback port, available after `listen()`. */
15
+ get port(): number;
16
+ listen(): Promise<number>;
17
+ /** Requests received before an upstream is set are answered with 503. */
18
+ setUpstreamPort(portArg: number): void;
19
+ /** Stops listening and destroys every open client and upstream connection. Idempotent. */
20
+ close(): Promise<void>;
21
+ }
@@ -0,0 +1,131 @@
1
+ import * as plugins from './plugins.js';
2
+ /** RFC 9110 hop-by-hop fields; they describe one connection and are never forwarded. */
3
+ const hopByHopHeaders = new Set([
4
+ 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'proxy-connection',
5
+ 'te', 'trailer', 'transfer-encoding', 'upgrade',
6
+ ]);
7
+ const forwardableHeaders = (headersArg) => {
8
+ const connectionTokens = new Set(String(headersArg.connection ?? '').split(',').map((tokenArg) => tokenArg.trim().toLowerCase()).filter(Boolean));
9
+ const headers = {};
10
+ for (const [name, value] of Object.entries(headersArg)) {
11
+ if (value === undefined || hopByHopHeaders.has(name) || connectionTokens.has(name))
12
+ continue;
13
+ headers[name] = value;
14
+ }
15
+ return headers;
16
+ };
17
+ /**
18
+ * Loopback TLS-terminating reverse proxy in front of one loopback HTTP
19
+ * upstream, configured the way a production proxy is: the client's Host header
20
+ * is preserved and `X-Forwarded-Proto: https` tells the forge the public scheme.
21
+ * It listens before the forge starts, so the forge can be configured with its
22
+ * exact external HTTPS origin.
23
+ */
24
+ export class ForgeFixtureTlsTerminator {
25
+ #server;
26
+ #agent = new plugins.http.Agent({ keepAlive: true, maxSockets: 32 });
27
+ #upstreamPort;
28
+ #port;
29
+ #closed = false;
30
+ #lastServerError;
31
+ constructor(certificateArg) {
32
+ this.#server = plugins.https.createServer({
33
+ cert: certificateArg.certificatePem,
34
+ key: certificateArg.privateKeyPem,
35
+ minVersion: 'TLSv1.2',
36
+ }, (requestArg, responseArg) => this.#forward(requestArg, responseArg));
37
+ // Handshake failures concern only that client; the listener stays up.
38
+ this.#server.on('tlsClientError', (_errorArg, socketArg) => socketArg.destroy());
39
+ // A server error must never become an unhandled 'error' event that crashes
40
+ // the test process; it is recorded for diagnostics instead.
41
+ this.#server.on('error', (errorArg) => {
42
+ this.#lastServerError = errorArg;
43
+ });
44
+ }
45
+ /** Last error the listening server emitted, if any. */
46
+ get lastServerError() {
47
+ return this.#lastServerError;
48
+ }
49
+ /** Bound loopback port, available after `listen()`. */
50
+ get port() {
51
+ if (this.#port === undefined)
52
+ throw new Error('The TLS terminator is not listening.');
53
+ return this.#port;
54
+ }
55
+ async listen() {
56
+ if (this.#closed)
57
+ throw new Error('The TLS terminator is closed.');
58
+ if (this.#port !== undefined)
59
+ return this.#port;
60
+ await new Promise((resolveArg, rejectArg) => {
61
+ const onError = (errorArg) => rejectArg(errorArg);
62
+ this.#server.once('error', onError);
63
+ this.#server.listen({ host: '127.0.0.1', port: 0, exclusive: true }, () => {
64
+ this.#server.off('error', onError);
65
+ resolveArg();
66
+ });
67
+ });
68
+ const address = this.#server.address();
69
+ if (address === null || typeof address === 'string')
70
+ throw new Error('The TLS terminator has no TCP address.');
71
+ this.#port = address.port;
72
+ return address.port;
73
+ }
74
+ /** Requests received before an upstream is set are answered with 503. */
75
+ setUpstreamPort(portArg) {
76
+ if (!Number.isSafeInteger(portArg) || portArg < 1 || portArg > 65535) {
77
+ throw new TypeError('The upstream port must be an integer from 1 through 65535.');
78
+ }
79
+ this.#upstreamPort = portArg;
80
+ }
81
+ /** Stops listening and destroys every open client and upstream connection. Idempotent. */
82
+ async close() {
83
+ this.#closed = true;
84
+ this.#agent.destroy();
85
+ if (!this.#server.listening)
86
+ return;
87
+ const closed = new Promise((resolveArg, rejectArg) => {
88
+ this.#server.close((errorArg) => (errorArg ? rejectArg(errorArg) : resolveArg()));
89
+ });
90
+ this.#server.closeAllConnections();
91
+ await closed;
92
+ }
93
+ #forward(requestArg, responseArg) {
94
+ const upstreamPort = this.#upstreamPort;
95
+ if (this.#closed || upstreamPort === undefined) {
96
+ responseArg.writeHead(503, { 'content-type': 'text/plain', connection: 'close' });
97
+ responseArg.end('The forge fixture is not ready.\n');
98
+ return;
99
+ }
100
+ const headers = forwardableHeaders(requestArg.headers);
101
+ headers['x-forwarded-proto'] = 'https';
102
+ headers['x-forwarded-for'] = requestArg.socket.remoteAddress ?? '127.0.0.1';
103
+ const upstreamRequest = plugins.http.request({
104
+ host: '127.0.0.1',
105
+ port: upstreamPort,
106
+ method: requestArg.method,
107
+ path: requestArg.url,
108
+ headers,
109
+ agent: this.#agent,
110
+ }, (upstreamResponse) => {
111
+ responseArg.writeHead(upstreamResponse.statusCode ?? 502, forwardableHeaders(upstreamResponse.headers));
112
+ upstreamResponse.pipe(responseArg);
113
+ upstreamResponse.once('error', () => responseArg.destroy());
114
+ });
115
+ upstreamRequest.once('error', () => {
116
+ if (responseArg.headersSent) {
117
+ responseArg.destroy();
118
+ return;
119
+ }
120
+ responseArg.writeHead(502, { 'content-type': 'text/plain', connection: 'close' });
121
+ responseArg.end('The forge fixture upstream is unavailable.\n');
122
+ });
123
+ requestArg.once('error', () => upstreamRequest.destroy());
124
+ responseArg.once('close', () => {
125
+ if (!responseArg.writableFinished)
126
+ upstreamRequest.destroy();
127
+ });
128
+ requestArg.pipe(upstreamRequest);
129
+ }
130
+ }
131
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy50bHN0ZXJtaW5hdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy50bHN0ZXJtaW5hdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBR3hDLHdGQUF3RjtBQUN4RixNQUFNLGVBQWUsR0FBRyxJQUFJLEdBQUcsQ0FBQztJQUM5QixZQUFZLEVBQUUsWUFBWSxFQUFFLG9CQUFvQixFQUFFLHFCQUFxQixFQUFFLGtCQUFrQjtJQUMzRixJQUFJLEVBQUUsU0FBUyxFQUFFLG1CQUFtQixFQUFFLFNBQVM7Q0FDaEQsQ0FBQyxDQUFDO0FBRUgsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLFVBQTRDLEVBQW9DLEVBQUU7SUFDNUcsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLEdBQUcsQ0FDOUIsTUFBTSxDQUFDLFVBQVUsQ0FBQyxVQUFVLElBQUksRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLFFBQVEsRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUNoSCxDQUFDO0lBQ0YsTUFBTSxPQUFPLEdBQXFDLEVBQUUsQ0FBQztJQUNyRCxLQUFLLE1BQU0sQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1FBQ3ZELElBQUksS0FBSyxLQUFLLFNBQVMsSUFBSSxlQUFlLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUM7WUFBRSxTQUFTO1FBQzdGLE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBRyxLQUFLLENBQUM7SUFDeEIsQ0FBQztJQUNELE9BQU8sT0FBTyxDQUFDO0FBQ2pCLENB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
@@ -0,0 +1,22 @@
1
+ import type { IForgeFixtureImage } from './interfaces.js';
2
+ /** Gitea release that code.foss.global serves (`/api/v1/version`). Rootless variant, uid 1000. */
3
+ export declare const giteaFixtureImage: IForgeFixtureImage;
4
+ /** Self-managed GitLab Community Edition. The omnibus image runs as root, so it runs on a rootless daemon. */
5
+ export declare const gitlabFixtureImage: IForgeFixtureImage;
6
+ /** Prefix of every Docker label this package writes. */
7
+ export declare const forgeFixtureLabelPrefix = "global.foss.forgefixtures";
8
+ export declare const forgeFixtureLabels: Readonly<{
9
+ managed: "global.foss.forgefixtures.managed";
10
+ lifecycleId: "global.foss.forgefixtures.lifecycle";
11
+ kind: "global.foss.forgefixtures.kind";
12
+ machineId: "global.foss.forgefixtures.owner.machineid";
13
+ hostname: "global.foss.forgefixtures.owner.hostname";
14
+ bootId: "global.foss.forgefixtures.owner.bootid";
15
+ pidNamespace: "global.foss.forgefixtures.owner.pidns";
16
+ pid: "global.foss.forgefixtures.owner.pid";
17
+ processStartTicks: "global.foss.forgefixtures.owner.starttime";
18
+ createdAt: "global.foss.forgefixtures.createdat";
19
+ expiresAt: "global.foss.forgefixtures.expiresat";
20
+ }>;
21
+ export declare const defaultMaxLifetimeMs: number;
22
+ export declare const defaultPullTimeoutMs: number;
@@ -0,0 +1,30 @@
1
+ /** Gitea release that code.foss.global serves (`/api/v1/version`). Rootless variant, uid 1000. */
2
+ export const giteaFixtureImage = Object.freeze({
3
+ version: '1.27.3',
4
+ tag: 'gitea/gitea:1.27.3-rootless',
5
+ repoDigest: 'gitea/gitea@sha256:1c17ecaead42eb3b5391553d8708103a4beb0e86edf5b9ebc1eb269c318845f2',
6
+ });
7
+ /** Self-managed GitLab Community Edition. The omnibus image runs as root, so it runs on a rootless daemon. */
8
+ export const gitlabFixtureImage = Object.freeze({
9
+ version: '19.4.1',
10
+ tag: 'gitlab/gitlab-ce:19.4.1-ce.0',
11
+ repoDigest: 'gitlab/gitlab-ce@sha256:9b33b45b9f42d176bada85ee5ecb81ddab7e506c435f44cd582206e284b2809c',
12
+ });
13
+ /** Prefix of every Docker label this package writes. */
14
+ export const forgeFixtureLabelPrefix = 'global.foss.forgefixtures';
15
+ export const forgeFixtureLabels = Object.freeze({
16
+ managed: `${forgeFixtureLabelPrefix}.managed`,
17
+ lifecycleId: `${forgeFixtureLabelPrefix}.lifecycle`,
18
+ kind: `${forgeFixtureLabelPrefix}.kind`,
19
+ machineId: `${forgeFixtureLabelPrefix}.owner.machineid`,
20
+ hostname: `${forgeFixtureLabelPrefix}.owner.hostname`,
21
+ bootId: `${forgeFixtureLabelPrefix}.owner.bootid`,
22
+ pidNamespace: `${forgeFixtureLabelPrefix}.owner.pidns`,
23
+ pid: `${forgeFixtureLabelPrefix}.owner.pid`,
24
+ processStartTicks: `${forgeFixtureLabelPrefix}.owner.starttime`,
25
+ createdAt: `${forgeFixtureLabelPrefix}.createdat`,
26
+ expiresAt: `${forgeFixtureLabelPrefix}.expiresat`,
27
+ });
28
+ export const defaultMaxLifetimeMs = 2 * 60 * 60 * 1000;
29
+ export const defaultPullTimeoutMs = 15 * 60 * 1000;
30
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uc3RhbnRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY29uc3RhbnRzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLGtHQUFrRztBQUNsRyxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBdUIsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUNqRSxPQUFPLEVBQUUsUUFBUTtJQUNqQixHQUFHLEVBQUUsNkJBQTZCO0lBQ2xDLFVBQVUsRUFBRSxxRkFBcUY7Q0FDbEcsQ0FBQyxDQUFDO0FBRUgsOEdBQThHO0FBQzlHLE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUF1QixNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ2xFLE9BQU8sRUFBRSxRQUFRO0lBQ2pCLEdBQUcsRUFBRSw4QkFBOEI7SUFDbkMsVUFBVSxFQUFFLDBGQUEwRjtDQUN2RyxDQUFDLENBQUM7QUFFSCx3REFBd0Q7QUFDeEQsTUFBTSxDQUFDLE1BQU0sdUJBQXVCLEdBQUcsMkJBQTJCLENBQUM7QUFFbkUsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUM5QyxPQUFPLEVBQUUsR0FBRyx1QkFBdUIsVUFBVTtJQUM3QyxXQUFXLEVBQUUsR0FBRyx1QkFBdUIsWUFBWTtJQUNuRCxJQUFJLEVBQUUsR0FBRyx1QkFBdUIsT0FBTztJQUN2QyxTQUFTLEVBQUUsR0FBRyx1QkFBdUIsa0JBQWtCO0lBQ3ZELFFBQVEsRUFBRSxHQUFHLHVCQUF1QixpQkFBaUI7SUFDckQsTUFBTSxFQUFFLEdBQUcsdUJBQXVCLGVBQWU7SUFDakQsWUFBWSxFQUFFLEdBQUcsdUJBQXVCLGNBQWM7SUFDdEQsR0FBRyxFQUFFLEdBQUcsdUJBQXVCLFlBQVk7SUFDM0MsaUJBQWlCLEVBQUUsR0FBRyx1QkFBdUIsa0JBQWtCO0lBQy9ELFNBQVMsRUFBRSxHQUFHLHVCQUF1QixZQUFZO0lBQ2pELFNBQVMsRUFBRSxHQUFHLHVCQUF1QixZQUFZO0NBQ2xELENBQUMsQ0FBQztBQUVILE1BQU0sQ0FBQyxNQUFNLG9CQUFvQixHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztBQUN2RCxNQUFNLENBQUMsTUFBTSxvQkFBb0IsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyJ9
@@ -0,0 +1,10 @@
1
+ import type { IGiteaSeedSpec } from './interfaces.giteaseed.js';
2
+ /**
3
+ * A compact seed covering the Gitea behaviours inventory readers must handle:
4
+ * public, limited and private owners, private repositories, a public
5
+ * repository of a private organization (which Gitea reports as `internal`),
6
+ * issue and pull request numbers from one sequence, closed items, labels,
7
+ * milestones, assignees, comments, tags, releases, an LFS object and a
8
+ * deleted author whose content Gitea reattributes to its ghost user.
9
+ */
10
+ export declare const createDefaultGiteaSeedSpec: () => IGiteaSeedSpec;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * A compact seed covering the Gitea behaviours inventory readers must handle:
3
+ * public, limited and private owners, private repositories, a public
4
+ * repository of a private organization (which Gitea reports as `internal`),
5
+ * issue and pull request numbers from one sequence, closed items, labels,
6
+ * milestones, assignees, comments, tags, releases, an LFS object and a
7
+ * deleted author whose content Gitea reattributes to its ghost user.
8
+ */
9
+ export const createDefaultGiteaSeedSpec = () => ({
10
+ users: [
11
+ { username: 'alice', fullName: 'Alice Example' },
12
+ { username: 'bob', fullName: 'Bob Example' },
13
+ { username: 'carol', visibility: 'private' },
14
+ { username: 'dora' },
15
+ ],
16
+ organizations: [
17
+ { name: 'team-with-hyphens', visibility: 'public' },
18
+ { name: 'limited-org', visibility: 'limited' },
19
+ { name: 'private-org', visibility: 'private' },
20
+ ],
21
+ repositories: [
22
+ {
23
+ owner: 'team-with-hyphens',
24
+ name: 'public-repo',
25
+ private: false,
26
+ description: 'Public organization repository',
27
+ collaborators: [{ username: 'bob', permission: 'write' }],
28
+ files: [{ path: 'src/index.ts', content: 'export const seeded = true;\n' }],
29
+ branches: [{ name: 'feature-a', files: [{ path: 'src/feature.ts', content: 'export const feature = 1;\n' }] }],
30
+ labels: [
31
+ { name: 'bug', color: '#d73a4a' },
32
+ { name: 'enhancement', color: '#a2eeef' },
33
+ ],
34
+ milestones: [
35
+ { title: 'v1', state: 'open' },
36
+ { title: 'v0', state: 'closed' },
37
+ ],
38
+ tags: [{ name: 'v1.0.0', target: 'main', message: 'First release' }],
39
+ issuesAndPullRequests: [
40
+ {
41
+ kind: 'issue', key: 'open-labelled', title: 'Open labelled issue', body: 'Body with #2 reference', author: 'alice',
42
+ state: 'open', labels: ['bug'], milestone: 'v1', assignees: ['bob'],
43
+ comments: [{ author: 'bob', body: 'Confirmed.' }],
44
+ },
45
+ { kind: 'pullRequest', key: 'feature-pr', title: 'Add feature', author: 'bob', head: 'feature-a', base: 'main', state: 'open', labels: ['enhancement'] },
46
+ {
47
+ kind: 'issue', key: 'closed-by-deleted-user', title: 'Closed issue from a deleted user', author: 'dora', state: 'closed',
48
+ milestone: 'v0', comments: [{ author: 'dora', body: 'Comment from a user who will be deleted.' }],
49
+ },
50
+ ],
51
+ releases: [{ tagName: 'v1.0.0', target: 'main', name: 'v1.0.0', body: 'Release notes' }],
52
+ lfsObjects: [{ path: 'assets/blob.bin', content: 'forgefixtures LFS payload\n' }],
53
+ },
54
+ {
55
+ owner: 'limited-org',
56
+ name: 'limited-repo',
57
+ private: false,
58
+ issuesAndPullRequests: [{ kind: 'issue', key: 'limited-issue', title: 'Issue in a limited organization', author: 'alice', state: 'open' }],
59
+ },
60
+ {
61
+ owner: 'private-org',
62
+ name: 'internal-repo',
63
+ private: false,
64
+ collaborators: [{ username: 'alice', permission: 'read' }],
65
+ issuesAndPullRequests: [{ kind: 'issue', key: 'internal-issue', title: 'Issue in a private organization', author: 'alice', state: 'open' }],
66
+ },
67
+ {
68
+ owner: 'private-org',
69
+ name: 'secret-repo',
70
+ private: true,
71
+ collaborators: [{ username: 'alice', permission: 'read' }],
72
+ issuesAndPullRequests: [{ kind: 'issue', key: 'private-issue', title: 'Issue in a private repository', author: 'alice', state: 'open' }],
73
+ },
74
+ {
75
+ owner: 'alice',
76
+ name: 'personal-repo',
77
+ private: false,
78
+ branches: [{ name: 'topic', files: [{ path: 'notes.md', content: '# Notes\n' }] }],
79
+ issuesAndPullRequests: [
80
+ { kind: 'pullRequest', key: 'closed-pr', title: 'Closed proposal', author: 'alice', head: 'topic', base: 'main', state: 'closed' },
81
+ { kind: 'issue', key: 'after-pr', title: 'Issue numbered after a pull request', author: 'carol', state: 'closed' },
82
+ ],
83
+ },
84
+ ],
85
+ deleteUsers: ['dora'],
86
+ });
87
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2l0ZWFzZWVkLmRlZmF1bHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9naXRlYXNlZWQuZGVmYXVsdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQTs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sMEJBQTBCLEdBQUcsR0FBbUIsRUFBRSxDQUFDLENBQUM7SUFDL0QsS0FBSyxFQUFFO1FBQ0wsRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxlQUFlLEVBQUU7UUFDaEQsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxhQUFhLEVBQUU7UUFDNUMsRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUU7UUFDNUMsRUFBRSxRQUFRLEVBQUUsTUFBTSxFQUFFO0tBQ3JCO0lBQ0QsYUFBYSxFQUFFO1FBQ2IsRUFBRSxJQUFJLEVBQUUsbUJBQW1CLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRTtRQUNuRCxFQUFFLElBQUksRUFBRSxhQUFhLEVBQUUsVUFBVSxFQUFFLFNBQVMsRUFBRTtRQUM5QyxFQUFFLElBQUksRUFBRSxhQUFhLEVBQUUsVUFBVSxFQUFFLFNBQVMsRUFBRTtLQUMvQztJQUNELFlBQVksRUFBRTtRQUNaO1lBQ0UsS0FBSyxFQUFFLG1CQUFtQjtZQUMxQixJQUFJLEVBQUUsYUFBYTtZQUNuQixPQUFPLEVBQUUsS0FBSztZQUNkLFdBQVcsRUFBRSxnQ0FBZ0M7WUFDN0MsYUFBYSxFQUFFLENBQUMsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxPQUFPLEVBQUUsQ0FBQztZQUN6RCxLQUFLLEVBQUUsQ0FBQyxFQUFFLElBQUksRUFBRSxjQUFjLEVBQUUsT0FBTyxFQUFFLCtCQUErQixFQUFFLENBQUM7WUFDM0UsUUFBUSxFQUFFLENBQUMsRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxDQUFDLEVBQUUsSUFBSSxFQUFFLGdC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
@@ -0,0 +1,12 @@
1
+ import type { IGitlabSeedSpec } from './interfaces.gitlabseed.js';
2
+ /** Projects created in `bulk-group` so project listings span more than one default page (20). */
3
+ export declare const defaultGitlabBulkProjectCount = 22;
4
+ /**
5
+ * A compact seed covering the GitLab behaviours inventory readers must handle:
6
+ * nested groups with public, internal and private visibility, a personal
7
+ * project, confidential issues and incidents, separate issue and merge request
8
+ * IID sequences, closed items, labels, milestones, assignees, notes, tags,
9
+ * releases, a deleted author whose content GitLab moves to its ghost user, and
10
+ * more projects than one page of a project listing.
11
+ */
12
+ export declare const createDefaultGitlabSeedSpec: () => IGitlabSeedSpec;