@goodfellow-cms/gitlab 0.3.0 → 0.4.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.
- package/dist/index.d.ts +28 -1
- package/dist/index.js +180 -8
- package/dist/testing.d.ts +19 -0
- package/dist/testing.js +58 -16
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,20 @@ interface GitLabBackendOptions {
|
|
|
19
19
|
branch: string;
|
|
20
20
|
user: GitUser;
|
|
21
21
|
onSignOut: () => void;
|
|
22
|
+
/** Who commits name as their author, where that isn't the token's owner, as on a full-stack site's server. */
|
|
23
|
+
author?: {
|
|
24
|
+
name: string;
|
|
25
|
+
email: string;
|
|
26
|
+
};
|
|
27
|
+
/** The commit reads see until `revision()` is next called, as a full-stack site's server reads what a browser loaded. */
|
|
28
|
+
revision?: string;
|
|
29
|
+
/** The project holds only the site's content, with its code in a project of its own (`GitBackend.contentOnly`). */
|
|
30
|
+
contentOnly?: boolean;
|
|
31
|
+
/** The code project, for a project that holds only content, where the site's updates run. */
|
|
32
|
+
code?: {
|
|
33
|
+
project: string;
|
|
34
|
+
webUrl: string;
|
|
35
|
+
};
|
|
22
36
|
}
|
|
23
37
|
/**
|
|
24
38
|
* The site's files on GitLab. Reads come from the commit `revision()` last
|
|
@@ -29,8 +43,10 @@ export declare class GitLabBackend implements GitBackend {
|
|
|
29
43
|
readonly user: GitUser;
|
|
30
44
|
readonly pages: PagesDomains;
|
|
31
45
|
readonly editors: SiteEditors;
|
|
32
|
-
|
|
46
|
+
/** Absent where the project holds only content: the update job runs where the code is. */
|
|
47
|
+
readonly updates?: SiteUpdates;
|
|
33
48
|
readonly drafts: SiteDrafts;
|
|
49
|
+
readonly contentOnly: boolean;
|
|
34
50
|
private readonly options;
|
|
35
51
|
private readonly api;
|
|
36
52
|
private readonly project;
|
|
@@ -130,6 +146,17 @@ export interface GitLabOptions {
|
|
|
130
146
|
* one-click "Sign in with GitLab". Without it, people sign in with a token.
|
|
131
147
|
*/
|
|
132
148
|
clientId?: string;
|
|
149
|
+
/**
|
|
150
|
+
* The project holds only the site's content (`content/` and
|
|
151
|
+
* `public/media/`), and its code is in a project of its own. The admin
|
|
152
|
+
* panel then leaves adding blocks, which changes code, to a developer.
|
|
153
|
+
*/
|
|
154
|
+
contentOnly?: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* With `contentOnly`, the project the site's code is kept in, where its
|
|
157
|
+
* updates run: the Updates screen follows them, for people who can see it.
|
|
158
|
+
*/
|
|
159
|
+
code?: string;
|
|
133
160
|
/** For tests. */
|
|
134
161
|
fetch?: typeof fetch;
|
|
135
162
|
/** For tests. */
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConflictError, DomainError, DraftError, EDITABLE_FOLDERS, EditorsError, GitApiError, SetupError, SignInError, UpdatesError, branchDrafts, buildStepFor, credentialStorage, encodeBase64Bytes, findBuildCause, findUpdateResult } from "@goodfellow-cms/core";
|
|
1
|
+
import { ConflictError, DomainError, DraftError, EDITABLE_FOLDERS, EditorsError, GitApiError, REBUILD_JOB, SetupError, SignInError, UpdatesError, assertContentChanges, branchDrafts, buildStepFor, credentialStorage, encodeBase64Bytes, findBuildCause, findUpdateResult } from "@goodfellow-cms/core";
|
|
2
2
|
//#region src/api.ts
|
|
3
3
|
/** Calls the GitLab API. A 401 refreshes the token once; after that it means the sign-in has expired. */
|
|
4
4
|
async function gitlabRequest(api, path, init = {}) {
|
|
@@ -343,7 +343,7 @@ function notAllowed(error) {
|
|
|
343
343
|
* It publishes with the job's own token, which GitLab only allows once the
|
|
344
344
|
* project lets job tokens push.
|
|
345
345
|
*/
|
|
346
|
-
function gitlabUpdates(api, project, branch, readAt) {
|
|
346
|
+
function gitlabUpdates(api, project, branch, readAt, jobName = "pages") {
|
|
347
347
|
const base = `/projects/${encodeURIComponent(project)}`;
|
|
348
348
|
async function schedules() {
|
|
349
349
|
return gitlabJson(api, `${base}/pipeline_schedules`);
|
|
@@ -397,7 +397,7 @@ function gitlabUpdates(api, project, branch, readAt) {
|
|
|
397
397
|
state: "running",
|
|
398
398
|
...run
|
|
399
399
|
};
|
|
400
|
-
const job = (await gitlabJson(api, `${base}/pipelines/${pipeline.id}/jobs?per_page=100`)).find((candidate) => candidate.name ===
|
|
400
|
+
const job = (await gitlabJson(api, `${base}/pipelines/${pipeline.id}/jobs?per_page=100`)).find((candidate) => candidate.name === jobName);
|
|
401
401
|
if (!job) continue;
|
|
402
402
|
const response = await gitlabRequest(api, `${base}/jobs/${job.id}/trace`, { allow: [403, 404] });
|
|
403
403
|
const result = response.ok ? findUpdateResult(await response.text()) : void 0;
|
|
@@ -424,6 +424,64 @@ function gitlabUpdates(api, project, branch, readAt) {
|
|
|
424
424
|
}
|
|
425
425
|
};
|
|
426
426
|
}
|
|
427
|
+
function noAccess(error) {
|
|
428
|
+
return error instanceof GitApiError && (error.status === 403 || error.status === 404);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* The nightly update of a site whose content is kept in a project of its own,
|
|
432
|
+
* which runs in the code project's `update` job. The sign-in needs to see the
|
|
433
|
+
* code project too; where it can't, `setup()` says `no-access`.
|
|
434
|
+
*/
|
|
435
|
+
function gitlabCodeUpdates(options) {
|
|
436
|
+
const { api, project } = options;
|
|
437
|
+
let branch;
|
|
438
|
+
const liveBranch = () => {
|
|
439
|
+
branch ??= gitlabJson(api, `/projects/${encodeURIComponent(project)}`).then((found) => found.default_branch);
|
|
440
|
+
branch.catch(() => {
|
|
441
|
+
branch = void 0;
|
|
442
|
+
});
|
|
443
|
+
return branch;
|
|
444
|
+
};
|
|
445
|
+
/** The code project at its newest commit, read again each time, since updates change it. */
|
|
446
|
+
const files = async () => options.open(await liveBranch());
|
|
447
|
+
/** A file as it is now: its setup files are outside the folders backends list, so they're read at the newest commit. */
|
|
448
|
+
const readNow = async (path) => {
|
|
449
|
+
const backend = await files();
|
|
450
|
+
return backend.readAt(path, await backend.revision());
|
|
451
|
+
};
|
|
452
|
+
const updates = async () => gitlabUpdates(api, project, await liveBranch(), readNow, "update");
|
|
453
|
+
return {
|
|
454
|
+
code: {
|
|
455
|
+
repo: project,
|
|
456
|
+
webUrl: `${options.webUrl}/${project}`,
|
|
457
|
+
read: readNow,
|
|
458
|
+
history: async (path, history) => (await files()).history(path, history),
|
|
459
|
+
readAt: async (path, revision) => (await files()).readAt(path, revision)
|
|
460
|
+
},
|
|
461
|
+
async setup() {
|
|
462
|
+
try {
|
|
463
|
+
return await (await updates()).setup();
|
|
464
|
+
} catch (error) {
|
|
465
|
+
if (noAccess(error)) return "no-access";
|
|
466
|
+
throw error;
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
async enable() {
|
|
470
|
+
await (await updates()).enable?.();
|
|
471
|
+
},
|
|
472
|
+
async lastRun() {
|
|
473
|
+
try {
|
|
474
|
+
return await (await updates()).lastRun();
|
|
475
|
+
} catch (error) {
|
|
476
|
+
if (noAccess(error)) return void 0;
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
async start(target) {
|
|
481
|
+
await (await updates()).start(target);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
427
485
|
//#endregion
|
|
428
486
|
//#region src/backend.ts
|
|
429
487
|
const PAGE_SIZE = 100;
|
|
@@ -440,8 +498,10 @@ var GitLabBackend = class GitLabBackend {
|
|
|
440
498
|
user;
|
|
441
499
|
pages;
|
|
442
500
|
editors;
|
|
501
|
+
/** Absent where the project holds only content: the update job runs where the code is. */
|
|
443
502
|
updates;
|
|
444
503
|
drafts;
|
|
504
|
+
contentOnly;
|
|
445
505
|
options;
|
|
446
506
|
api;
|
|
447
507
|
project;
|
|
@@ -457,9 +517,26 @@ var GitLabBackend = class GitLabBackend {
|
|
|
457
517
|
this.branch = options.branch;
|
|
458
518
|
this.user = options.user;
|
|
459
519
|
this.onSignOut = options.onSignOut;
|
|
520
|
+
this.pinned = options.revision;
|
|
460
521
|
this.pages = gitlabPages(this.api, options.project);
|
|
461
522
|
this.editors = gitlabEditors(this.api, options.project, this.branch, this.user.login);
|
|
462
|
-
this.
|
|
523
|
+
this.contentOnly = options.contentOnly === true;
|
|
524
|
+
if (!this.contentOnly) this.updates = gitlabUpdates(this.api, options.project, this.branch, async (path) => this.readAt(path, this.pinned ?? await this.revision()));
|
|
525
|
+
else if (options.code) {
|
|
526
|
+
const code = options.code;
|
|
527
|
+
this.updates = gitlabCodeUpdates({
|
|
528
|
+
api: this.api,
|
|
529
|
+
project: code.project,
|
|
530
|
+
webUrl: code.webUrl,
|
|
531
|
+
open: (branch) => new GitLabBackend({
|
|
532
|
+
api: this.api,
|
|
533
|
+
project: code.project,
|
|
534
|
+
branch,
|
|
535
|
+
user: this.user,
|
|
536
|
+
onSignOut: () => {}
|
|
537
|
+
})
|
|
538
|
+
});
|
|
539
|
+
}
|
|
463
540
|
this.drafts = branchDrafts(this, this.draftHost());
|
|
464
541
|
}
|
|
465
542
|
/** Drafts on branches of the project, with merge requests for review. */
|
|
@@ -645,6 +722,7 @@ var GitLabBackend = class GitLabBackend {
|
|
|
645
722
|
return (await gitlabJson(this.api, this.repo(`/files/${encodeURIComponent(path)}?ref=${ref}`))).last_commit_id;
|
|
646
723
|
}
|
|
647
724
|
async write(changes, { message, expectedRevision }) {
|
|
725
|
+
if (this.contentOnly) assertContentChanges(changes);
|
|
648
726
|
if (await this.revision() !== expectedRevision) throw new ConflictError();
|
|
649
727
|
const existing = await this.tree(expectedRevision);
|
|
650
728
|
const toApply = (await Promise.all(changes.map(async (change) => {
|
|
@@ -676,7 +754,11 @@ var GitLabBackend = class GitLabBackend {
|
|
|
676
754
|
body: {
|
|
677
755
|
branch: this.branch,
|
|
678
756
|
commit_message: message,
|
|
679
|
-
actions: toApply
|
|
757
|
+
actions: toApply,
|
|
758
|
+
...this.options.author && {
|
|
759
|
+
author_name: this.options.author.name,
|
|
760
|
+
author_email: this.options.author.email
|
|
761
|
+
}
|
|
680
762
|
}
|
|
681
763
|
});
|
|
682
764
|
this.pinned = commit.id;
|
|
@@ -720,7 +802,9 @@ var GitLabBackend = class GitLabBackend {
|
|
|
720
802
|
*/
|
|
721
803
|
async deployStatus(revision) {
|
|
722
804
|
try {
|
|
723
|
-
const
|
|
805
|
+
const all = await gitlabJson(this.api, this.repo(`/commits/${revision}/statuses`));
|
|
806
|
+
const statuses = all.filter((status) => status.name !== REBUILD_JOB);
|
|
807
|
+
if (all.length > 0 && statuses.length === 0) return { state: "unknown" };
|
|
724
808
|
if (statuses.length === 0) return { state: (await gitlabJson(this.api, `/projects/${this.project}/pipelines?per_page=1`)).length > 0 ? "building" : "unknown" };
|
|
725
809
|
const failed = statuses.find((status) => status.status === "failed" || status.status === "canceled");
|
|
726
810
|
if (failed) {
|
|
@@ -863,6 +947,46 @@ function gitlabSetup(options = {}) {
|
|
|
863
947
|
async function isAvailable(owner, name) {
|
|
864
948
|
return (await gitlabRequest(api, `/projects/${encodeURIComponent(`${owner.path}/${name}`)}`, { allow: [404] })).status === 404;
|
|
865
949
|
}
|
|
950
|
+
/**
|
|
951
|
+
* Lets each project's pipelines reach the other, as their setup files say:
|
|
952
|
+
* a job token is refused by a project whose job token allowlist doesn't
|
|
953
|
+
* name its project, even a public one. Also lets the code project's job
|
|
954
|
+
* token publish updates, and runs pipelines each night where they need it.
|
|
955
|
+
* Returns whether it worked.
|
|
956
|
+
*/
|
|
957
|
+
async function connectProjects(link) {
|
|
958
|
+
const project = (path) => `/projects/${encodeURIComponent(path)}`;
|
|
959
|
+
try {
|
|
960
|
+
const ids = /* @__PURE__ */ new Map();
|
|
961
|
+
for (const which of ["code", "content"]) ids.set(which, (await gitlabJson(api, project(link[which]))).id);
|
|
962
|
+
for (const { reader, read } of link.links.reads) await gitlabRequest(api, `${project(link[read])}/job_token_scope/allowlist`, {
|
|
963
|
+
method: "POST",
|
|
964
|
+
body: { target_project_id: ids.get(reader) }
|
|
965
|
+
});
|
|
966
|
+
if (link.links.updates) await gitlabRequest(api, project(link[link.links.updates]), {
|
|
967
|
+
method: "PUT",
|
|
968
|
+
body: { ci_push_repository_for_job_token_allowed: true }
|
|
969
|
+
});
|
|
970
|
+
for (const which of link.links.nightly) await gitlabRequest(api, `${project(link[which])}/pipeline_schedules`, {
|
|
971
|
+
method: "POST",
|
|
972
|
+
body: {
|
|
973
|
+
description: which === link.links.updates ? "Rebuild the site and install Goodfellow's fixes each night" : "Rebuild the site each night",
|
|
974
|
+
ref: MAIN,
|
|
975
|
+
cron: NIGHTLY,
|
|
976
|
+
cron_timezone: "UTC",
|
|
977
|
+
active: true
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
return true;
|
|
981
|
+
} catch (error) {
|
|
982
|
+
if (error instanceof GitApiError && [
|
|
983
|
+
400,
|
|
984
|
+
403,
|
|
985
|
+
404
|
|
986
|
+
].includes(error.status)) return false;
|
|
987
|
+
throw error;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
866
990
|
return {
|
|
867
991
|
user: {
|
|
868
992
|
login: user.username,
|
|
@@ -900,6 +1024,11 @@ function gitlabSetup(options = {}) {
|
|
|
900
1024
|
if (error instanceof GitApiError && (error.status === 403 || error.status === 404)) throw new SetupError("not-allowed", `This sign-in can't create projects in ${site.owner.path}.`);
|
|
901
1025
|
throw error;
|
|
902
1026
|
}
|
|
1027
|
+
const warnings = [];
|
|
1028
|
+
if (site.link) {
|
|
1029
|
+
onStep?.("connect");
|
|
1030
|
+
if (!await connectProjects(site.link)) warnings.push("connect-unavailable");
|
|
1031
|
+
}
|
|
903
1032
|
onStep?.("files");
|
|
904
1033
|
await gitlabRequest(api, `/projects/${project.id}/repository/commits`, {
|
|
905
1034
|
method: "POST",
|
|
@@ -923,7 +1052,7 @@ function gitlabSetup(options = {}) {
|
|
|
923
1052
|
webUrl: project.web_url,
|
|
924
1053
|
buildsUrl: `${project.web_url}/-/pipelines`,
|
|
925
1054
|
pagesUrl: site.pages ? `${project.web_url}/pages` : void 0,
|
|
926
|
-
warnings
|
|
1055
|
+
warnings
|
|
927
1056
|
};
|
|
928
1057
|
}
|
|
929
1058
|
};
|
|
@@ -1056,6 +1185,11 @@ function gitlab(options) {
|
|
|
1056
1185
|
api,
|
|
1057
1186
|
project: options.project,
|
|
1058
1187
|
branch: options.branch ?? project.default_branch,
|
|
1188
|
+
...options.contentOnly && { contentOnly: true },
|
|
1189
|
+
...options.contentOnly && options.code && { code: {
|
|
1190
|
+
project: options.code,
|
|
1191
|
+
webUrl
|
|
1192
|
+
} },
|
|
1059
1193
|
user: {
|
|
1060
1194
|
login: user.username,
|
|
1061
1195
|
name: user.name,
|
|
@@ -1092,6 +1226,43 @@ function gitlab(options) {
|
|
|
1092
1226
|
code_verifier: redirect.verifier
|
|
1093
1227
|
}), redirect.verifier), redirect.remember);
|
|
1094
1228
|
}
|
|
1229
|
+
/** The project through a token of the site's own, such as a project access token, on a full-stack site's server. */
|
|
1230
|
+
function server({ token }) {
|
|
1231
|
+
const api = {
|
|
1232
|
+
apiUrl,
|
|
1233
|
+
token: async () => typeof token === "string" ? token : token(),
|
|
1234
|
+
canRefresh: false,
|
|
1235
|
+
fetch: fetcher
|
|
1236
|
+
};
|
|
1237
|
+
let branch;
|
|
1238
|
+
const liveBranch = () => {
|
|
1239
|
+
branch ??= options.branch ? Promise.resolve(options.branch) : gitlabJson(api, `/projects/${encodeURIComponent(options.project)}`).then((project) => project.default_branch);
|
|
1240
|
+
branch.catch(() => {
|
|
1241
|
+
branch = void 0;
|
|
1242
|
+
});
|
|
1243
|
+
return branch;
|
|
1244
|
+
};
|
|
1245
|
+
const backend = async (revision, author) => new GitLabBackend({
|
|
1246
|
+
api,
|
|
1247
|
+
project: options.project,
|
|
1248
|
+
branch: await liveBranch(),
|
|
1249
|
+
...options.contentOnly && { contentOnly: true },
|
|
1250
|
+
user: {
|
|
1251
|
+
login: author?.name ?? options.project,
|
|
1252
|
+
...author && { name: author.name }
|
|
1253
|
+
},
|
|
1254
|
+
onSignOut: () => {},
|
|
1255
|
+
...revision && { revision },
|
|
1256
|
+
...author && { author }
|
|
1257
|
+
});
|
|
1258
|
+
return {
|
|
1259
|
+
...options.contentOnly && { contentOnly: true },
|
|
1260
|
+
async revision() {
|
|
1261
|
+
return (await backend()).revision();
|
|
1262
|
+
},
|
|
1263
|
+
at: backend
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1095
1266
|
return {
|
|
1096
1267
|
name: "GitLab",
|
|
1097
1268
|
canRedirect: Boolean(options.clientId),
|
|
@@ -1145,7 +1316,8 @@ function gitlab(options) {
|
|
|
1145
1316
|
code_challenge_method: "S256"
|
|
1146
1317
|
});
|
|
1147
1318
|
location().assign(`${webUrl}/oauth/authorize?${query}`);
|
|
1148
|
-
}
|
|
1319
|
+
},
|
|
1320
|
+
server
|
|
1149
1321
|
};
|
|
1150
1322
|
}
|
|
1151
1323
|
//#endregion
|
package/dist/testing.d.ts
CHANGED
|
@@ -21,6 +21,10 @@ export interface FakeGitLabOptions {
|
|
|
21
21
|
};
|
|
22
22
|
/** Simulates a GitLab Pages pipeline that succeeds after being checked this many times. Off by default. */
|
|
23
23
|
deployAfterChecks?: number;
|
|
24
|
+
/** The job each published commit's pipeline runs: `pages` by default. */
|
|
25
|
+
deployJob?: string;
|
|
26
|
+
/** The job the update runs in, which `addUpdateRun()` logs: `pages` by default, as in the starters, or a code project's `update`. */
|
|
27
|
+
updateJob?: string;
|
|
24
28
|
/** Groups the users belong to, which new projects can be created in. */
|
|
25
29
|
groups?: string[];
|
|
26
30
|
/** Publishes the site with GitLab Pages. Off by default. */
|
|
@@ -63,6 +67,16 @@ export interface FakeCreatedProject {
|
|
|
63
67
|
description?: string;
|
|
64
68
|
pagesAccessLevel?: string;
|
|
65
69
|
repo: FakeRepo;
|
|
70
|
+
/** The projects whose job tokens may reach this one (its job token allowlist), by id. */
|
|
71
|
+
jobTokenAllowlist: number[];
|
|
72
|
+
/** Whether its pipelines' job tokens may push to it. */
|
|
73
|
+
jobTokenPush: boolean;
|
|
74
|
+
schedules: Array<{
|
|
75
|
+
description: string;
|
|
76
|
+
ref: string;
|
|
77
|
+
cron: string;
|
|
78
|
+
active: boolean;
|
|
79
|
+
}>;
|
|
66
80
|
}
|
|
67
81
|
export declare function fakeGitLab(options: FakeGitLabOptions): {
|
|
68
82
|
/** How many GraphQL requests read files, as `readFiles()` makes. */
|
|
@@ -123,6 +137,11 @@ export declare function fakeGitLab(options: FakeGitLabOptions): {
|
|
|
123
137
|
created: Map<string, FakeCreatedProject>;
|
|
124
138
|
/** The site's GitLab Pages, if it has them, which tests can change. */
|
|
125
139
|
pages: FakeGitLabPages | undefined;
|
|
140
|
+
/** Who commits that named their author did name, by commit. */
|
|
141
|
+
commitAuthors: Map<string, {
|
|
142
|
+
name: string;
|
|
143
|
+
email: string;
|
|
144
|
+
}>;
|
|
126
145
|
requests: Request[];
|
|
127
146
|
commit: (changes: FileChange[], message: string, author?: string, branch?: string) => string;
|
|
128
147
|
authorize: (authorizeUrl: string) => string;
|
package/dist/testing.js
CHANGED
|
@@ -20,6 +20,8 @@ async function sha256Base64Url(text) {
|
|
|
20
20
|
function fakeGitLab(options) {
|
|
21
21
|
const webUrl = options.url ?? "https://gitlab.com";
|
|
22
22
|
const repo = new FakeRepo(options.files);
|
|
23
|
+
/** Who each commit that named its author did name, by commit. */
|
|
24
|
+
const commitAuthors = /* @__PURE__ */ new Map();
|
|
23
25
|
const tokens = new Map(Object.entries(options.tokens ?? { "test-token": {
|
|
24
26
|
username: "editor",
|
|
25
27
|
name: "Test Editor"
|
|
@@ -297,7 +299,10 @@ function fakeGitLab(options) {
|
|
|
297
299
|
visibility: body.visibility,
|
|
298
300
|
description: body.description,
|
|
299
301
|
pagesAccessLevel: body.pages_access_level,
|
|
300
|
-
repo: new FakeRepo({})
|
|
302
|
+
repo: new FakeRepo({}),
|
|
303
|
+
jobTokenAllowlist: [],
|
|
304
|
+
jobTokenPush: false,
|
|
305
|
+
schedules: []
|
|
301
306
|
};
|
|
302
307
|
created.set(fullPath, project);
|
|
303
308
|
return json({
|
|
@@ -311,11 +316,40 @@ function fakeGitLab(options) {
|
|
|
311
316
|
const project = created.get(key) ?? [...created.values()].find((candidate) => String(candidate.id) === key);
|
|
312
317
|
if (!match || !project) return void 0;
|
|
313
318
|
const rest = match[2] ?? "";
|
|
314
|
-
if (rest === "") return json({
|
|
319
|
+
if (rest === "" && request.method === "GET") return json({
|
|
315
320
|
id: project.id,
|
|
316
321
|
path_with_namespace: project.path,
|
|
317
|
-
default_branch: "main"
|
|
322
|
+
default_branch: "main",
|
|
323
|
+
ci_push_repository_for_job_token_allowed: project.jobTokenPush
|
|
318
324
|
});
|
|
325
|
+
if (rest === "" && request.method === "PUT") {
|
|
326
|
+
const body = await request.json();
|
|
327
|
+
if (body.ci_push_repository_for_job_token_allowed !== void 0) project.jobTokenPush = body.ci_push_repository_for_job_token_allowed;
|
|
328
|
+
return json({
|
|
329
|
+
id: project.id,
|
|
330
|
+
ci_push_repository_for_job_token_allowed: project.jobTokenPush
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
if (rest === "/job_token_scope/allowlist" && request.method === "POST") {
|
|
334
|
+
const body = await request.json();
|
|
335
|
+
if (![...created.values()].some((candidate) => candidate.id === body.target_project_id)) return json({ message: "404 Project Not Found" }, 404);
|
|
336
|
+
if (!project.jobTokenAllowlist.includes(body.target_project_id)) project.jobTokenAllowlist.push(body.target_project_id);
|
|
337
|
+
return json({
|
|
338
|
+
source_project_id: project.id,
|
|
339
|
+
target_project_id: body.target_project_id
|
|
340
|
+
}, 201);
|
|
341
|
+
}
|
|
342
|
+
if (rest === "/pipeline_schedules" && request.method === "POST") {
|
|
343
|
+
const body = await request.json();
|
|
344
|
+
project.schedules.push({
|
|
345
|
+
...body,
|
|
346
|
+
active: body.active !== false
|
|
347
|
+
});
|
|
348
|
+
return json({
|
|
349
|
+
id: project.schedules.length,
|
|
350
|
+
...body
|
|
351
|
+
}, 201);
|
|
352
|
+
}
|
|
319
353
|
if (rest === "/repository/commits" && request.method === "POST") {
|
|
320
354
|
const body = await request.json();
|
|
321
355
|
return json({
|
|
@@ -549,17 +583,22 @@ function fakeGitLab(options) {
|
|
|
549
583
|
if ((action.action === "update" || action.action === "delete") && !files.has(action.file_path)) return json({ message: "A file with this name doesn't exist" }, 400);
|
|
550
584
|
if (action.last_commit_id && repo.lastCommitFor(action.file_path, body.branch) !== action.last_commit_id) return json({ message: "You are attempting to update a file that has changed since you started editing it." }, 400);
|
|
551
585
|
}
|
|
586
|
+
const sha = commit(body.actions.map((action) => action.action === "delete" ? {
|
|
587
|
+
path: action.file_path,
|
|
588
|
+
delete: true
|
|
589
|
+
} : action.encoding === "base64" ? {
|
|
590
|
+
path: action.file_path,
|
|
591
|
+
bytes: decodeBase64(action.content ?? "")
|
|
592
|
+
} : {
|
|
593
|
+
path: action.file_path,
|
|
594
|
+
content: action.content ?? ""
|
|
595
|
+
}), body.commit_message, body.author_name ?? user.name ?? user.username, body.branch);
|
|
596
|
+
if (body.author_name && body.author_email) commitAuthors.set(sha, {
|
|
597
|
+
name: body.author_name,
|
|
598
|
+
email: body.author_email
|
|
599
|
+
});
|
|
552
600
|
return json({
|
|
553
|
-
id:
|
|
554
|
-
path: action.file_path,
|
|
555
|
-
delete: true
|
|
556
|
-
} : action.encoding === "base64" ? {
|
|
557
|
-
path: action.file_path,
|
|
558
|
-
bytes: decodeBase64(action.content ?? "")
|
|
559
|
-
} : {
|
|
560
|
-
path: action.file_path,
|
|
561
|
-
content: action.content ?? ""
|
|
562
|
-
}), body.commit_message, user.name ?? user.username, body.branch),
|
|
601
|
+
id: sha,
|
|
563
602
|
message: body.commit_message
|
|
564
603
|
}, 201);
|
|
565
604
|
}
|
|
@@ -596,9 +635,10 @@ function fakeGitLab(options) {
|
|
|
596
635
|
}]);
|
|
597
636
|
}
|
|
598
637
|
pipeline.checks += 1;
|
|
638
|
+
const done = pipeline.checks > (options.deployAfterChecks ?? 0);
|
|
599
639
|
return json([{
|
|
600
|
-
name: "pages",
|
|
601
|
-
status:
|
|
640
|
+
name: options.deployJob ?? "pages",
|
|
641
|
+
status: done ? "success" : "running",
|
|
602
642
|
target_url: `${webUrl}/${options.project}/-/jobs/1`
|
|
603
643
|
}]);
|
|
604
644
|
}
|
|
@@ -647,7 +687,7 @@ function fakeGitLab(options) {
|
|
|
647
687
|
const run = updateRuns.find((candidate) => candidate.id === Number(updateJobs[1]));
|
|
648
688
|
return run ? json([{
|
|
649
689
|
id: run.id,
|
|
650
|
-
name: "pages",
|
|
690
|
+
name: options.updateJob ?? "pages",
|
|
651
691
|
status: run.status
|
|
652
692
|
}]) : json({ message: "404 Not Found" }, 404);
|
|
653
693
|
}
|
|
@@ -707,6 +747,8 @@ function fakeGitLab(options) {
|
|
|
707
747
|
created,
|
|
708
748
|
/** The site's GitLab Pages, if it has them, which tests can change. */
|
|
709
749
|
pages,
|
|
750
|
+
/** Who commits that named their author did name, by commit. */
|
|
751
|
+
commitAuthors,
|
|
710
752
|
requests,
|
|
711
753
|
commit,
|
|
712
754
|
authorize,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodfellow-cms/gitlab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Edit a Goodfellow site stored on GitLab, straight from the browser.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"goodfellow",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dist"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@goodfellow-cms/core": "0.
|
|
38
|
+
"@goodfellow-cms/core": "0.4.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@puckeditor/core": "0.23.0"
|