@knowttl/gitea-axi 0.0.0-stage → 0.1.1
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/README.md +414 -2
- package/axi-credential.schema.json +28 -0
- package/dist/action-values.js +37 -0
- package/dist/api.js +195 -0
- package/dist/attachments.js +171 -0
- package/dist/catalogue.js +65 -0
- package/dist/ci.js +24 -0
- package/dist/cli.js +11 -0
- package/dist/credentials.js +196 -0
- package/dist/error.js +9 -0
- package/dist/git.js +89 -0
- package/dist/local-workflows.js +222 -0
- package/dist/main.js +376 -0
- package/dist/private-file.js +51 -0
- package/dist/pulls.js +299 -0
- package/dist/reads.js +236 -0
- package/dist/registry.js +189 -0
- package/dist/releases.js +145 -0
- package/dist/repo-workflows.js +147 -0
- package/dist/repository.js +37 -0
- package/dist/search.js +111 -0
- package/dist/stacks.js +486 -0
- package/dist/transport.js +120 -0
- package/dist/vendor.generated.js +5 -0
- package/dist/writes.js +547 -0
- package/generated/SKILL.md +141 -0
- package/generated/capabilities.json +964 -0
- package/generated/help.txt +5387 -0
- package/generated/host-inventory.json +7641 -0
- package/generated/routes.json +5096 -0
- package/package.json +46 -4
- package/schemas/catalogue.json +2672 -0
- package/schemas/responses.json +63 -0
- package/schemas/vendor.json +7130 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { limits } from './registry.js';
|
|
2
|
+
import { CliError } from './error.js';
|
|
3
|
+
import { canonicalPath } from './api.js';
|
|
4
|
+
import { transferBytes } from './attachments.js';
|
|
5
|
+
function transportCause(error) {
|
|
6
|
+
if (!(error instanceof Error))
|
|
7
|
+
return 'connection error';
|
|
8
|
+
if (error.name === 'TimeoutError' || error.name === 'AbortError')
|
|
9
|
+
return 'ETIMEDOUT';
|
|
10
|
+
const cause = error.cause ?? error;
|
|
11
|
+
const code = cause && typeof cause === 'object' && 'code' in cause ? cause.code : undefined;
|
|
12
|
+
if (typeof code === 'string') {
|
|
13
|
+
if (['ECONNREFUSED', 'ECONNRESET', 'ENOTFOUND', 'EAI_AGAIN', 'ETIMEDOUT', 'ENETUNREACH', 'EHOSTUNREACH'].includes(code))
|
|
14
|
+
return code;
|
|
15
|
+
if (code === 'UND_ERR_CONNECT_TIMEOUT')
|
|
16
|
+
return 'ETIMEDOUT';
|
|
17
|
+
if (/^(?:ERR_TLS_|ERR_SSL_|CERT_|DEPTH_ZERO_|SELF_SIGNED_|UNABLE_TO_)/.test(code))
|
|
18
|
+
return 'TLS error';
|
|
19
|
+
}
|
|
20
|
+
return 'connection error';
|
|
21
|
+
}
|
|
22
|
+
export class Client {
|
|
23
|
+
origin;
|
|
24
|
+
token;
|
|
25
|
+
deadline;
|
|
26
|
+
constructor(origin, token, timeoutMs = limits.timeoutMs) {
|
|
27
|
+
this.origin = origin;
|
|
28
|
+
this.token = token;
|
|
29
|
+
this.deadline = Date.now() + timeoutMs;
|
|
30
|
+
}
|
|
31
|
+
async request(path, text = false, mutation) {
|
|
32
|
+
canonicalPath(path);
|
|
33
|
+
const url = new URL(path, this.origin);
|
|
34
|
+
if (url.origin !== this.origin || !path.startsWith('/') || path.startsWith('//'))
|
|
35
|
+
throw new CliError('UNSAFE_PATH', 'Request must stay within the selected origin');
|
|
36
|
+
try {
|
|
37
|
+
const form = mutation?.upload ? new FormData() : undefined;
|
|
38
|
+
if (form && mutation?.upload)
|
|
39
|
+
form.set('attachment', new Blob([Buffer.from(mutation.upload.base64, 'base64')], { type: mutation.upload.type }), mutation.upload.name);
|
|
40
|
+
const response = await fetch(url, {
|
|
41
|
+
method: mutation?.method ?? 'GET',
|
|
42
|
+
...(form ? { body: form } : mutation?.payload === undefined ? {} : { body: JSON.stringify(mutation.payload) }),
|
|
43
|
+
headers: { accept: text ? 'text/plain' : 'application/json', ...(mutation && !form ? { 'content-type': 'application/json' } : {}), ...(this.token ? { authorization: 'token ' + this.token } : {}) },
|
|
44
|
+
redirect: 'error',
|
|
45
|
+
signal: AbortSignal.timeout(Math.max(1, Math.min(limits.timeoutMs, this.deadline - Date.now()))),
|
|
46
|
+
});
|
|
47
|
+
if (mutation?.sensitive) {
|
|
48
|
+
await response.body?.cancel();
|
|
49
|
+
if (!response.ok)
|
|
50
|
+
throw new CliError('HTTP_ERROR', 'Protected secret operation returned HTTP ' + response.status);
|
|
51
|
+
return { data: null, headers: response.headers };
|
|
52
|
+
}
|
|
53
|
+
const chunks = [];
|
|
54
|
+
let bytes = 0;
|
|
55
|
+
for await (const chunk of response.body ?? []) {
|
|
56
|
+
bytes += chunk.byteLength;
|
|
57
|
+
if (bytes > limits.responseBytes) {
|
|
58
|
+
throw new CliError('RESPONSE_LIMIT', 'Gitea response exceeded the byte limit');
|
|
59
|
+
}
|
|
60
|
+
chunks.push(chunk);
|
|
61
|
+
}
|
|
62
|
+
const body = Buffer.concat(chunks).toString('utf8');
|
|
63
|
+
if (response.ok && response.status !== 204 && !text && !(mutation && body === '') && !response.headers.get('content-type')?.includes('application/json'))
|
|
64
|
+
throw new CliError('INVALID_RESPONSE', 'Gitea did not return JSON');
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
let reason = '';
|
|
67
|
+
if (response.headers.get('content-type')?.includes('application/json')) {
|
|
68
|
+
try {
|
|
69
|
+
const error = JSON.parse(body);
|
|
70
|
+
if (error && typeof error === 'object' && 'message' in error && typeof error.message === 'string')
|
|
71
|
+
reason = ': ' + error.message.slice(0, 1000);
|
|
72
|
+
}
|
|
73
|
+
catch { /* HTTP status remains authoritative for malformed errors. */ }
|
|
74
|
+
}
|
|
75
|
+
const retry = response.headers.get('retry-after');
|
|
76
|
+
const code = response.status === 401 ? 'AUTH_REQUIRED' : response.status === 403 ? 'PERMISSION_DENIED' :
|
|
77
|
+
response.status === 404 ? 'NOT_FOUND_OR_HIDDEN' : response.status === 429 ? 'RATE_LIMITED' : 'HTTP_ERROR';
|
|
78
|
+
throw new CliError(code, `${path.split('?')[0]} returned HTTP ${response.status}${reason}${retry ? '; retry-after ' + retry : ''}`);
|
|
79
|
+
}
|
|
80
|
+
return { data: response.status === 204 || mutation && body === '' ? null : text ? body : JSON.parse(body), headers: response.headers };
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error instanceof CliError)
|
|
84
|
+
throw error;
|
|
85
|
+
if (error instanceof SyntaxError)
|
|
86
|
+
throw new CliError('INVALID_RESPONSE', 'Gitea returned malformed JSON');
|
|
87
|
+
throw new CliError('TRANSPORT_ERROR', 'Gitea request failed (' + transportCause(error) + '); check the selected origin and connection');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async get(path) { return (await this.request(path)).data; }
|
|
91
|
+
async attachment(uuid, expectedType) {
|
|
92
|
+
if (!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(uuid))
|
|
93
|
+
throw new CliError('UNSAFE_PATH', 'Invalid attachment UUID');
|
|
94
|
+
try {
|
|
95
|
+
const response = await fetch(new URL('/attachments/' + uuid, this.origin), { headers: { ...(this.token ? { authorization: 'token ' + this.token } : {}) }, redirect: 'error', signal: AbortSignal.timeout(Math.max(1, this.deadline - Date.now())) });
|
|
96
|
+
const type = response.headers.get('content-type')?.split(';')[0]?.trim();
|
|
97
|
+
if (!response.ok || !type || expectedType && type !== expectedType) {
|
|
98
|
+
await response.body?.cancel();
|
|
99
|
+
throw new CliError('INVALID_RESPONSE', 'Binary attachment returned an unexpected status or content type');
|
|
100
|
+
}
|
|
101
|
+
let size = 0;
|
|
102
|
+
const chunks = [];
|
|
103
|
+
for await (const chunk of response.body ?? []) {
|
|
104
|
+
size += chunk.byteLength;
|
|
105
|
+
if (size > transferBytes)
|
|
106
|
+
throw new CliError('TRANSFER_LIMIT', 'Binary attachment exceeds 10000000 bytes');
|
|
107
|
+
chunks.push(chunk);
|
|
108
|
+
}
|
|
109
|
+
return { bytes: Buffer.concat(chunks), type };
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error instanceof CliError)
|
|
113
|
+
throw error;
|
|
114
|
+
throw new CliError('TRANSPORT_ERROR', 'Binary attachment transfer failed (' + transportCause(error) + '); redirects are refused');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export async function get(origin, path, token) {
|
|
119
|
+
return new Client(origin, token).get(path);
|
|
120
|
+
}
|