decap-cms-backend-gitea 3.0.4
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 +22 -0
- package/LICENSE +22 -0
- package/dist/decap-cms-backend-gitea.js +2 -0
- package/dist/decap-cms-backend-gitea.js.map +1 -0
- package/dist/esm/API.js +355 -0
- package/dist/esm/AuthenticationPage.js +97 -0
- package/dist/esm/implementation.js +397 -0
- package/dist/esm/index.js +27 -0
- package/dist/esm/types.js +5 -0
- package/package.json +37 -0
- package/src/API.ts +463 -0
- package/src/AuthenticationPage.js +70 -0
- package/src/__tests__/API.spec.js +388 -0
- package/src/__tests__/implementation.spec.js +284 -0
- package/src/implementation.tsx +450 -0
- package/src/index.ts +3 -0
- package/src/types.ts +260 -0
- package/webpack.config.js +3 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { stripIndent } from 'common-tags';
|
|
2
|
+
import trimStart from 'lodash/trimStart';
|
|
3
|
+
import semaphore from 'semaphore';
|
|
4
|
+
import {
|
|
5
|
+
asyncLock,
|
|
6
|
+
basename,
|
|
7
|
+
blobToFileObj,
|
|
8
|
+
Cursor,
|
|
9
|
+
CURSOR_COMPATIBILITY_SYMBOL,
|
|
10
|
+
entriesByFiles,
|
|
11
|
+
entriesByFolder,
|
|
12
|
+
filterByExtension,
|
|
13
|
+
getBlobSHA,
|
|
14
|
+
getMediaAsBlob,
|
|
15
|
+
getMediaDisplayURL,
|
|
16
|
+
runWithLock,
|
|
17
|
+
unsentRequest,
|
|
18
|
+
} from 'decap-cms-lib-util';
|
|
19
|
+
|
|
20
|
+
import API, { API_NAME } from './API';
|
|
21
|
+
import AuthenticationPage from './AuthenticationPage';
|
|
22
|
+
|
|
23
|
+
import type {
|
|
24
|
+
AssetProxy,
|
|
25
|
+
AsyncLock,
|
|
26
|
+
Config,
|
|
27
|
+
Credentials,
|
|
28
|
+
DisplayURL,
|
|
29
|
+
Entry,
|
|
30
|
+
Implementation,
|
|
31
|
+
ImplementationFile,
|
|
32
|
+
PersistOptions,
|
|
33
|
+
User,
|
|
34
|
+
} from 'decap-cms-lib-util';
|
|
35
|
+
import type { Semaphore } from 'semaphore';
|
|
36
|
+
import type { GiteaUser } from './types';
|
|
37
|
+
|
|
38
|
+
const MAX_CONCURRENT_DOWNLOADS = 10;
|
|
39
|
+
|
|
40
|
+
type ApiFile = { id: string; type: string; name: string; path: string; size: number };
|
|
41
|
+
|
|
42
|
+
const { fetchWithTimeout: fetch } = unsentRequest;
|
|
43
|
+
|
|
44
|
+
export default class Gitea implements Implementation {
|
|
45
|
+
lock: AsyncLock;
|
|
46
|
+
api: API | null;
|
|
47
|
+
options: {
|
|
48
|
+
proxied: boolean;
|
|
49
|
+
API: API | null;
|
|
50
|
+
useWorkflow?: boolean;
|
|
51
|
+
};
|
|
52
|
+
originRepo: string;
|
|
53
|
+
repo?: string;
|
|
54
|
+
branch: string;
|
|
55
|
+
apiRoot: string;
|
|
56
|
+
mediaFolder?: string;
|
|
57
|
+
token: string | null;
|
|
58
|
+
_currentUserPromise?: Promise<GiteaUser>;
|
|
59
|
+
_userIsOriginMaintainerPromises?: {
|
|
60
|
+
[key: string]: Promise<boolean>;
|
|
61
|
+
};
|
|
62
|
+
_mediaDisplayURLSem?: Semaphore;
|
|
63
|
+
|
|
64
|
+
constructor(config: Config, options = {}) {
|
|
65
|
+
this.options = {
|
|
66
|
+
proxied: false,
|
|
67
|
+
API: null,
|
|
68
|
+
useWorkflow: false,
|
|
69
|
+
...options,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
!this.options.proxied &&
|
|
74
|
+
(config.backend.repo === null || config.backend.repo === undefined)
|
|
75
|
+
) {
|
|
76
|
+
throw new Error('The Gitea backend needs a "repo" in the backend configuration.');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (this.options.useWorkflow) {
|
|
80
|
+
throw new Error('The Gitea backend does not support editorial workflow.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.api = this.options.API || null;
|
|
84
|
+
this.repo = this.originRepo = config.backend.repo || '';
|
|
85
|
+
this.branch = config.backend.branch?.trim() || 'master';
|
|
86
|
+
this.apiRoot = config.backend.api_root || 'https://try.gitea.io/api/v1';
|
|
87
|
+
this.token = '';
|
|
88
|
+
this.mediaFolder = config.media_folder;
|
|
89
|
+
this.lock = asyncLock();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
isGitBackend() {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async status() {
|
|
97
|
+
const auth =
|
|
98
|
+
(await this.api
|
|
99
|
+
?.user()
|
|
100
|
+
.then(user => !!user)
|
|
101
|
+
.catch(e => {
|
|
102
|
+
console.warn('[StaticCMS] Failed getting Gitea user', e);
|
|
103
|
+
return false;
|
|
104
|
+
})) || false;
|
|
105
|
+
|
|
106
|
+
return { auth: { status: auth }, api: { status: true, statusPage: '' } };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
authComponent() {
|
|
110
|
+
return AuthenticationPage;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
restoreUser(user: User) {
|
|
114
|
+
return this.authenticate(user);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async currentUser({ token }: { token: string }) {
|
|
118
|
+
if (!this._currentUserPromise) {
|
|
119
|
+
this._currentUserPromise = fetch(`${this.apiRoot}/user`, {
|
|
120
|
+
headers: {
|
|
121
|
+
Authorization: `token ${token}`,
|
|
122
|
+
},
|
|
123
|
+
}).then(res => res.json());
|
|
124
|
+
}
|
|
125
|
+
return this._currentUserPromise;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async userIsOriginMaintainer({
|
|
129
|
+
username: usernameArg,
|
|
130
|
+
token,
|
|
131
|
+
}: {
|
|
132
|
+
username?: string;
|
|
133
|
+
token: string;
|
|
134
|
+
}) {
|
|
135
|
+
const username = usernameArg || (await this.currentUser({ token })).login;
|
|
136
|
+
this._userIsOriginMaintainerPromises = this._userIsOriginMaintainerPromises || {};
|
|
137
|
+
if (!this._userIsOriginMaintainerPromises[username]) {
|
|
138
|
+
this._userIsOriginMaintainerPromises[username] = fetch(
|
|
139
|
+
`${this.apiRoot}/repos/${this.originRepo}/collaborators/${username}/permission`,
|
|
140
|
+
{
|
|
141
|
+
headers: {
|
|
142
|
+
Authorization: `token ${token}`,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
)
|
|
146
|
+
.then(res => res.json())
|
|
147
|
+
.then(({ permission }) => permission === 'admin' || permission === 'write');
|
|
148
|
+
}
|
|
149
|
+
return this._userIsOriginMaintainerPromises[username];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async authenticate(state: Credentials) {
|
|
153
|
+
this.token = state.token as string;
|
|
154
|
+
const apiCtor = API;
|
|
155
|
+
this.api = new apiCtor({
|
|
156
|
+
token: this.token,
|
|
157
|
+
branch: this.branch,
|
|
158
|
+
repo: this.repo,
|
|
159
|
+
originRepo: this.originRepo,
|
|
160
|
+
apiRoot: this.apiRoot,
|
|
161
|
+
});
|
|
162
|
+
const user = await this.api!.user();
|
|
163
|
+
const isCollab = await this.api!.hasWriteAccess().catch(error => {
|
|
164
|
+
error.message = stripIndent`
|
|
165
|
+
Repo "${this.repo}" not found.
|
|
166
|
+
|
|
167
|
+
Please ensure the repo information is spelled correctly.
|
|
168
|
+
|
|
169
|
+
If the repo is private, make sure you're logged into a Gitea account with access.
|
|
170
|
+
|
|
171
|
+
If your repo is under an organization, ensure the organization has granted access to Static
|
|
172
|
+
CMS.
|
|
173
|
+
`;
|
|
174
|
+
throw error;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Unauthorized user
|
|
178
|
+
if (!isCollab) {
|
|
179
|
+
throw new Error('Your Gitea user account does not have access to this repo.');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Authorized user
|
|
183
|
+
return {
|
|
184
|
+
name: user.full_name,
|
|
185
|
+
login: user.login,
|
|
186
|
+
avatar_url: user.avatar_url,
|
|
187
|
+
token: state.token as string,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
logout() {
|
|
192
|
+
this.token = null;
|
|
193
|
+
if (this.api && this.api.reset && typeof this.api.reset === 'function') {
|
|
194
|
+
return this.api.reset();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
getToken() {
|
|
199
|
+
return Promise.resolve(this.token);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getCursorAndFiles = (files: ApiFile[], page: number) => {
|
|
203
|
+
const pageSize = 20;
|
|
204
|
+
const count = files.length;
|
|
205
|
+
const pageCount = Math.ceil(files.length / pageSize);
|
|
206
|
+
|
|
207
|
+
const actions = [] as string[];
|
|
208
|
+
if (page > 1) {
|
|
209
|
+
actions.push('prev');
|
|
210
|
+
actions.push('first');
|
|
211
|
+
}
|
|
212
|
+
if (page < pageCount) {
|
|
213
|
+
actions.push('next');
|
|
214
|
+
actions.push('last');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const cursor = Cursor.create({
|
|
218
|
+
actions,
|
|
219
|
+
meta: { page, count, pageSize, pageCount },
|
|
220
|
+
data: { files },
|
|
221
|
+
});
|
|
222
|
+
const pageFiles = files.slice((page - 1) * pageSize, page * pageSize);
|
|
223
|
+
return { cursor, files: pageFiles };
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
async entriesByFolder(folder: string, extension: string, depth: number) {
|
|
227
|
+
const repoURL = this.api!.originRepoURL;
|
|
228
|
+
|
|
229
|
+
let cursor: Cursor;
|
|
230
|
+
|
|
231
|
+
const listFiles = () =>
|
|
232
|
+
this.api!.listFiles(folder, {
|
|
233
|
+
repoURL,
|
|
234
|
+
depth,
|
|
235
|
+
}).then(files => {
|
|
236
|
+
const filtered = files.filter(file => filterByExtension(file, extension));
|
|
237
|
+
const result = this.getCursorAndFiles(filtered, 1);
|
|
238
|
+
cursor = result.cursor;
|
|
239
|
+
return result.files;
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const readFile = (path: string, id: string | null | undefined) =>
|
|
243
|
+
this.api!.readFile(path, id, { repoURL }) as Promise<string>;
|
|
244
|
+
|
|
245
|
+
const files = await entriesByFolder(
|
|
246
|
+
listFiles,
|
|
247
|
+
readFile,
|
|
248
|
+
this.api!.readFileMetadata.bind(this.api),
|
|
249
|
+
API_NAME,
|
|
250
|
+
);
|
|
251
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
252
|
+
// @ts-ignore
|
|
253
|
+
files[CURSOR_COMPATIBILITY_SYMBOL] = cursor;
|
|
254
|
+
return files;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async allEntriesByFolder(folder: string, extension: string, depth: number) {
|
|
258
|
+
const repoURL = this.api!.originRepoURL;
|
|
259
|
+
|
|
260
|
+
const listFiles = () =>
|
|
261
|
+
this.api!.listFiles(folder, {
|
|
262
|
+
repoURL,
|
|
263
|
+
depth,
|
|
264
|
+
}).then(files => files.filter(file => filterByExtension(file, extension)));
|
|
265
|
+
|
|
266
|
+
const readFile = (path: string, id: string | null | undefined) => {
|
|
267
|
+
return this.api!.readFile(path, id, { repoURL }) as Promise<string>;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const files = await entriesByFolder(
|
|
271
|
+
listFiles,
|
|
272
|
+
readFile,
|
|
273
|
+
this.api!.readFileMetadata.bind(this.api),
|
|
274
|
+
API_NAME,
|
|
275
|
+
);
|
|
276
|
+
return files;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
entriesByFiles(files: ImplementationFile[]) {
|
|
280
|
+
const repoURL = this.api!.repoURL;
|
|
281
|
+
|
|
282
|
+
const readFile = (path: string, id: string | null | undefined) =>
|
|
283
|
+
this.api!.readFile(path, id, { repoURL }).catch(() => '') as Promise<string>;
|
|
284
|
+
|
|
285
|
+
return entriesByFiles(files, readFile, this.api!.readFileMetadata.bind(this.api), API_NAME);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Fetches a single entry.
|
|
289
|
+
getEntry(path: string) {
|
|
290
|
+
const repoURL = this.api!.originRepoURL;
|
|
291
|
+
return this.api!.readFile(path, null, { repoURL })
|
|
292
|
+
.then(data => ({
|
|
293
|
+
file: { path, id: null },
|
|
294
|
+
data: data as string,
|
|
295
|
+
}))
|
|
296
|
+
.catch(() => ({ file: { path, id: null }, data: '' }));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async getMedia(mediaFolder = this.mediaFolder, folderSupport?: boolean) {
|
|
300
|
+
if (!mediaFolder) {
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
return this.api!.listFiles(mediaFolder, undefined, folderSupport).then(files =>
|
|
304
|
+
files.map(({ id, name, size, path, type }) => {
|
|
305
|
+
return { id, name, size, displayURL: { id, path }, path, isDirectory: type === 'tree' };
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async getMediaFile(path: string) {
|
|
311
|
+
const blob = await getMediaAsBlob(path, null, this.api!.readFile.bind(this.api!));
|
|
312
|
+
|
|
313
|
+
const name = basename(path);
|
|
314
|
+
const fileObj = blobToFileObj(name, blob);
|
|
315
|
+
const url = URL.createObjectURL(fileObj);
|
|
316
|
+
const id = await getBlobSHA(blob);
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
id,
|
|
320
|
+
displayURL: url,
|
|
321
|
+
path,
|
|
322
|
+
name,
|
|
323
|
+
size: fileObj.size,
|
|
324
|
+
file: fileObj,
|
|
325
|
+
url,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
getMediaDisplayURL(displayURL: DisplayURL) {
|
|
330
|
+
this._mediaDisplayURLSem = this._mediaDisplayURLSem || semaphore(MAX_CONCURRENT_DOWNLOADS);
|
|
331
|
+
return getMediaDisplayURL(
|
|
332
|
+
displayURL,
|
|
333
|
+
this.api!.readFile.bind(this.api!),
|
|
334
|
+
this._mediaDisplayURLSem,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
persistEntry(entry: Entry, options: PersistOptions) {
|
|
339
|
+
// persistEntry is a transactional operation
|
|
340
|
+
return runWithLock(
|
|
341
|
+
this.lock,
|
|
342
|
+
() => this.api!.persistFiles(entry.dataFiles, entry.assets, options),
|
|
343
|
+
'Failed to acquire persist entry lock',
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async persistMedia(mediaFile: AssetProxy, options: PersistOptions) {
|
|
348
|
+
try {
|
|
349
|
+
await this.api!.persistFiles([], [mediaFile], options);
|
|
350
|
+
const { sha, path, fileObj } = mediaFile as AssetProxy & { sha: string };
|
|
351
|
+
const displayURL = URL.createObjectURL(fileObj as Blob);
|
|
352
|
+
return {
|
|
353
|
+
id: sha,
|
|
354
|
+
name: fileObj!.name,
|
|
355
|
+
size: fileObj!.size,
|
|
356
|
+
displayURL,
|
|
357
|
+
path: trimStart(path, '/'),
|
|
358
|
+
};
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.error(error);
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
deleteFiles(paths: string[], commitMessage: string) {
|
|
366
|
+
return this.api!.deleteFiles(paths, commitMessage);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async traverseCursor(cursor: Cursor, action: string) {
|
|
370
|
+
const meta = cursor.meta!;
|
|
371
|
+
const files = cursor.data!.get('files')!.toJS() as ApiFile[];
|
|
372
|
+
|
|
373
|
+
let result: { cursor: Cursor; files: ApiFile[] };
|
|
374
|
+
switch (action) {
|
|
375
|
+
case 'first': {
|
|
376
|
+
result = this.getCursorAndFiles(files, 1);
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
case 'last': {
|
|
380
|
+
result = this.getCursorAndFiles(files, meta.get('pageCount'));
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
case 'next': {
|
|
384
|
+
result = this.getCursorAndFiles(files, meta.get('page') + 1);
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
case 'prev': {
|
|
388
|
+
result = this.getCursorAndFiles(files, meta.get('page') - 1);
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
default: {
|
|
392
|
+
result = this.getCursorAndFiles(files, 1);
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const readFile = (path: string, id: string | null | undefined) =>
|
|
398
|
+
this.api!.readFile(path, id, { repoURL: this.api!.originRepoURL }).catch(
|
|
399
|
+
() => '',
|
|
400
|
+
) as Promise<string>;
|
|
401
|
+
|
|
402
|
+
const entries = await entriesByFiles(
|
|
403
|
+
result.files,
|
|
404
|
+
readFile,
|
|
405
|
+
this.api!.readFileMetadata.bind(this.api),
|
|
406
|
+
API_NAME,
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
entries,
|
|
411
|
+
cursor: result.cursor,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async unpublishedEntries() {
|
|
416
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
417
|
+
return {} as any;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async unpublishedEntry() {
|
|
421
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
422
|
+
return {} as any;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async unpublishedEntryDataFile() {
|
|
426
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
427
|
+
return {} as any;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async unpublishedEntryMediaFile() {
|
|
431
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
432
|
+
return {} as any;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async updateUnpublishedEntryStatus() {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async publishUnpublishedEntry() {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
async deleteUnpublishedEntry() {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async getDeployPreview() {
|
|
447
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
448
|
+
return {} as any;
|
|
449
|
+
}
|
|
450
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
export type GiteaUser = {
|
|
2
|
+
active: boolean;
|
|
3
|
+
avatar_url: string;
|
|
4
|
+
created: string;
|
|
5
|
+
description: string;
|
|
6
|
+
email: string;
|
|
7
|
+
followers_count: number;
|
|
8
|
+
following_count: number;
|
|
9
|
+
full_name: string;
|
|
10
|
+
id: number;
|
|
11
|
+
is_admin: boolean;
|
|
12
|
+
language: string;
|
|
13
|
+
last_login: string;
|
|
14
|
+
location: string;
|
|
15
|
+
login: string;
|
|
16
|
+
login_name?: string;
|
|
17
|
+
prohibit_login: boolean;
|
|
18
|
+
restricted: boolean;
|
|
19
|
+
starred_repos_count: number;
|
|
20
|
+
visibility: string;
|
|
21
|
+
website: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type GiteaTeam = {
|
|
25
|
+
can_create_org_repo: boolean;
|
|
26
|
+
description: string;
|
|
27
|
+
id: number;
|
|
28
|
+
includes_all_repositories: boolean;
|
|
29
|
+
name: string;
|
|
30
|
+
organization: GiteaOrganization;
|
|
31
|
+
permission: string;
|
|
32
|
+
units: Array<string>;
|
|
33
|
+
units_map: Map<string, string>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type GiteaOrganization = {
|
|
37
|
+
avatar_url: string;
|
|
38
|
+
description: string;
|
|
39
|
+
full_name: string;
|
|
40
|
+
id: number;
|
|
41
|
+
location: string;
|
|
42
|
+
name: string;
|
|
43
|
+
repo_admin_change_team_access: boolean;
|
|
44
|
+
username: string;
|
|
45
|
+
visibility: string;
|
|
46
|
+
website: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type CommitUser = {
|
|
50
|
+
date: string;
|
|
51
|
+
email: string;
|
|
52
|
+
name: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type CommitMeta = {
|
|
56
|
+
created: string;
|
|
57
|
+
sha: string;
|
|
58
|
+
url: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type PayloadUser = {
|
|
62
|
+
email: string;
|
|
63
|
+
name: string;
|
|
64
|
+
username: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type PayloadCommitVerification = {
|
|
68
|
+
payload: string;
|
|
69
|
+
reason: string;
|
|
70
|
+
signature: string;
|
|
71
|
+
signer: PayloadUser;
|
|
72
|
+
verified: boolean;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
type ReposListCommitsResponseItemCommit = {
|
|
76
|
+
author: CommitUser;
|
|
77
|
+
committer: CommitUser;
|
|
78
|
+
message: string;
|
|
79
|
+
tree: CommitMeta;
|
|
80
|
+
url: string;
|
|
81
|
+
verification: PayloadCommitVerification;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
type GiteaRepositoryPermissions = {
|
|
85
|
+
admin: boolean;
|
|
86
|
+
pull: boolean;
|
|
87
|
+
push: boolean;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
type GiteaRepositoryExternalTracker = {
|
|
91
|
+
external_tracker_format: string;
|
|
92
|
+
external_tracker_regexp_pattern: string;
|
|
93
|
+
external_tracker_style: string;
|
|
94
|
+
external_tracker_url: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type GiteaRepositoryExternalWiki = {
|
|
98
|
+
external_wiki_url: string;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
type GiteaRepositoryInternalTracker = {
|
|
102
|
+
allow_only_contributors_to_track_time: boolean;
|
|
103
|
+
enable_issue_dependencies: boolean;
|
|
104
|
+
enable_time_tracker: boolean;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
type GiteaRepositoryRepoTransfer = {
|
|
108
|
+
description: string;
|
|
109
|
+
doer: GiteaUser;
|
|
110
|
+
recipient: GiteaUser;
|
|
111
|
+
teams: Array<GiteaTeam>;
|
|
112
|
+
enable_issue_dependencies: boolean;
|
|
113
|
+
enable_time_tracker: boolean;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type GiteaRepository = {
|
|
117
|
+
allow_merge_commits: boolean;
|
|
118
|
+
allow_rebase: boolean;
|
|
119
|
+
allow_rebase_explicit: boolean;
|
|
120
|
+
allow_rebase_update: boolean;
|
|
121
|
+
allow_squash_merge: boolean;
|
|
122
|
+
archived: boolean;
|
|
123
|
+
avatar_url: string;
|
|
124
|
+
clone_url: string;
|
|
125
|
+
created_at: string;
|
|
126
|
+
default_branch: string;
|
|
127
|
+
default_delete_branch_after_merge: boolean;
|
|
128
|
+
default_merge_style: boolean;
|
|
129
|
+
description: string;
|
|
130
|
+
empty: boolean;
|
|
131
|
+
external_tracker: GiteaRepositoryExternalTracker;
|
|
132
|
+
external_wiki: GiteaRepositoryExternalWiki;
|
|
133
|
+
fork: boolean;
|
|
134
|
+
forks_count: number;
|
|
135
|
+
full_name: string;
|
|
136
|
+
has_issues: boolean;
|
|
137
|
+
has_projects: boolean;
|
|
138
|
+
has_pull_requests: boolean;
|
|
139
|
+
has_wiki: boolean;
|
|
140
|
+
html_url: string;
|
|
141
|
+
id: number;
|
|
142
|
+
ignore_whitespace_conflicts: boolean;
|
|
143
|
+
internal: boolean;
|
|
144
|
+
internal_tracker: GiteaRepositoryInternalTracker;
|
|
145
|
+
language: string;
|
|
146
|
+
languages_url: string;
|
|
147
|
+
mirror: boolean;
|
|
148
|
+
mirror_interval: string;
|
|
149
|
+
mirror_updated: string;
|
|
150
|
+
name: string;
|
|
151
|
+
open_issues_count: number;
|
|
152
|
+
open_pr_counter: number;
|
|
153
|
+
original_url: string;
|
|
154
|
+
owner: GiteaUser;
|
|
155
|
+
parent: null;
|
|
156
|
+
permissions: GiteaRepositoryPermissions;
|
|
157
|
+
private: boolean;
|
|
158
|
+
release_counter: number;
|
|
159
|
+
repo_transfer: GiteaRepositoryRepoTransfer;
|
|
160
|
+
size: number;
|
|
161
|
+
ssh_url: string;
|
|
162
|
+
stars_count: number;
|
|
163
|
+
template: boolean;
|
|
164
|
+
updated_at: string;
|
|
165
|
+
watchers_count: number;
|
|
166
|
+
website: string;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
type ReposListCommitsResponseItemCommitAffectedFiles = {
|
|
170
|
+
filename: string;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
type ReposListCommitsResponseItemCommitStats = {
|
|
174
|
+
additions: number;
|
|
175
|
+
deletions: number;
|
|
176
|
+
total: number;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
type ReposListCommitsResponseItem = {
|
|
180
|
+
author: GiteaUser;
|
|
181
|
+
commit: ReposListCommitsResponseItemCommit;
|
|
182
|
+
committer: GiteaUser;
|
|
183
|
+
created: string;
|
|
184
|
+
files: Array<ReposListCommitsResponseItemCommitAffectedFiles>;
|
|
185
|
+
html_url: string;
|
|
186
|
+
parents: Array<CommitMeta>;
|
|
187
|
+
sha: string;
|
|
188
|
+
stats: ReposListCommitsResponseItemCommitStats;
|
|
189
|
+
url: string;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export type ReposListCommitsResponse = Array<ReposListCommitsResponseItem>;
|
|
193
|
+
|
|
194
|
+
export type GitGetBlobResponse = {
|
|
195
|
+
content: string;
|
|
196
|
+
encoding: string;
|
|
197
|
+
sha: string;
|
|
198
|
+
size: number;
|
|
199
|
+
url: string;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
type GitGetTreeResponseTreeItem = {
|
|
203
|
+
mode: string;
|
|
204
|
+
path: string;
|
|
205
|
+
sha: string;
|
|
206
|
+
size?: number;
|
|
207
|
+
type: string;
|
|
208
|
+
url: string;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export type GitGetTreeResponse = {
|
|
212
|
+
page: number;
|
|
213
|
+
sha: string;
|
|
214
|
+
total_count: number;
|
|
215
|
+
tree: Array<GitGetTreeResponseTreeItem>;
|
|
216
|
+
truncated: boolean;
|
|
217
|
+
url: string;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
type FileLinksResponse = {
|
|
221
|
+
git: string;
|
|
222
|
+
html: string;
|
|
223
|
+
self: string;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
type ContentsResponse = {
|
|
227
|
+
_links: FileLinksResponse;
|
|
228
|
+
content?: string | null;
|
|
229
|
+
download_url: string;
|
|
230
|
+
encoding?: string | null;
|
|
231
|
+
git_url: string;
|
|
232
|
+
html_url: string;
|
|
233
|
+
last_commit_sha: string;
|
|
234
|
+
name: string;
|
|
235
|
+
path: string;
|
|
236
|
+
sha: string;
|
|
237
|
+
size: number;
|
|
238
|
+
submodule_git_url?: string | null;
|
|
239
|
+
target?: string | null;
|
|
240
|
+
type: string;
|
|
241
|
+
url: string;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
type FileCommitResponse = {
|
|
245
|
+
author: CommitUser;
|
|
246
|
+
committer: CommitUser;
|
|
247
|
+
created: string;
|
|
248
|
+
html_url: string;
|
|
249
|
+
message: string;
|
|
250
|
+
parents: Array<CommitMeta>;
|
|
251
|
+
sha: string;
|
|
252
|
+
tree: CommitMeta;
|
|
253
|
+
url: string;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export type FilesResponse = {
|
|
257
|
+
commit: FileCommitResponse;
|
|
258
|
+
content: Array<ContentsResponse>;
|
|
259
|
+
verification: PayloadCommitVerification;
|
|
260
|
+
};
|