@goodfellow-cms/gitlab 0.5.0 → 0.6.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/README.md +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +73 -4
- package/dist/testing.d.ts +6 -0
- package/dist/testing.js +30 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ import { gitlab } from "@goodfellow-cms/gitlab";
|
|
|
8
8
|
export default defineConfig({ blocks, backend: gitlab({ project: "your-name/your-site" }) });
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
[Goodfellow](https://github.com/goodfellow-cms/goodfellow-cms) is a website builder for people who aren't developers, with no server or database: pages are files in your own GitHub or
|
|
11
|
+
[Goodfellow](https://github.com/goodfellow-cms/goodfellow-cms) is a website builder for people who aren't developers, with no server or database: pages are files in your own GitHub, GitLab or Bitbucket repository, edited in a visual editor built on [Puck](https://puckeditor.com). To start a new site, run `npm create goodfellow@latest my-site`. See the [documentation](https://goodfellow-cms.github.io/goodfellow-cms/) for everything else.
|
|
12
12
|
|
|
13
13
|
More in the docs: [Hosts and business sites](https://goodfellow-cms.github.io/goodfellow-cms/docs/hosts).
|
|
14
14
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConnectDomainOptions, DeployStatus, DnsRecord, FileChange, FileVersion, GitBackend, GitHost, GitUser, HistoryOptions, PagesDomains, RepositoryVisibility, RevisionFiles, ScheduledBuilds, SetupHost, SiteDrafts, SiteEditors, SiteUpdates, StorageLike, WriteOptions } from "@goodfellow-cms/core";
|
|
1
|
+
import { ConnectDomainOptions, DeployStatus, DnsRecord, FileChange, FileVersion, GitBackend, GitHost, GitUser, HistoryOptions, PagesDomains, PluginTasks, RepositoryVisibility, RevisionFiles, ScheduledBuilds, SetupHost, SiteDrafts, SiteEditors, SiteUpdates, StorageLike, WriteOptions } from "@goodfellow-cms/core";
|
|
2
2
|
//#region src/api.d.ts
|
|
3
3
|
interface ApiOptions {
|
|
4
4
|
/** The API root, such as `https://gitlab.com/api/v4`. */
|
|
@@ -12,6 +12,15 @@ interface ApiOptions {
|
|
|
12
12
|
fetch: typeof fetch;
|
|
13
13
|
}
|
|
14
14
|
//#endregion
|
|
15
|
+
//#region src/tasks.d.ts
|
|
16
|
+
interface GitLabTasksOptions {
|
|
17
|
+
/** How often to look for the answer, and how long to wait for it. */
|
|
18
|
+
pollMs?: number;
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
/** Waits between looks; tests pass one that doesn't. */
|
|
21
|
+
wait?: (ms: number) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
15
24
|
//#region src/backend.d.ts
|
|
16
25
|
interface GitLabBackendOptions {
|
|
17
26
|
api: ApiOptions;
|
|
@@ -26,6 +35,8 @@ interface GitLabBackendOptions {
|
|
|
26
35
|
};
|
|
27
36
|
/** The commit reads see until `revision()` is next called, as a full-stack site's server reads what a browser loaded. */
|
|
28
37
|
revision?: string;
|
|
38
|
+
/** How plugins' tasks wait for their answers; tests pass short waits. */
|
|
39
|
+
tasks?: GitLabTasksOptions;
|
|
29
40
|
/** The project holds only the site's content, with its code in a project of its own (`GitBackend.contentOnly`). */
|
|
30
41
|
contentOnly?: boolean;
|
|
31
42
|
/** The code project, for a project that holds only content, where the site's updates run. */
|
|
@@ -47,6 +58,7 @@ export declare class GitLabBackend implements GitBackend {
|
|
|
47
58
|
readonly updates?: SiteUpdates;
|
|
48
59
|
readonly drafts: SiteDrafts;
|
|
49
60
|
readonly scheduledBuilds: ScheduledBuilds;
|
|
61
|
+
readonly tasks: PluginTasks;
|
|
50
62
|
readonly contentOnly: boolean;
|
|
51
63
|
private readonly options;
|
|
52
64
|
private readonly api;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConflictError, DomainError, DraftError, EDITABLE_FOLDERS, EditorsError, GitApiError, NIGHTLY_VARIABLE, REBUILD_JOB, ScheduledBuildsError, SetupError, SignInError, UpdatesError, assertContentChanges, branchDrafts, buildStepFor, credentialStorage, encodeBase64Bytes, findBuildCause, findUpdateResult, hostSource } from "@goodfellow-cms/core";
|
|
1
|
+
import { ConflictError, DomainError, DraftError, EDITABLE_FOLDERS, EditorsError, GitApiError, LOCKFILE_JOB, NIGHTLY_VARIABLE, PluginTaskError, REBUILD_JOB, ScheduledBuildsError, SetupError, SignInError, TASK_JOB, TASK_POLL_MS, TASK_RESULT_FILE, TASK_TIMEOUT_MS, UpdatesError, assertContentChanges, branchDrafts, buildStepFor, credentialStorage, encodeBase64Bytes, findBuildCause, findUpdateResult, hostSource, taskRequestProblem } 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 = {}) {
|
|
@@ -411,6 +411,72 @@ function gitlabScheduledBuilds(api, project, branch, readCi, now = () => /* @__P
|
|
|
411
411
|
};
|
|
412
412
|
}
|
|
413
413
|
//#endregion
|
|
414
|
+
//#region src/tasks.ts
|
|
415
|
+
/**
|
|
416
|
+
* Plugins' tasks on GitLab, through the starters' `plugin-task` job: a
|
|
417
|
+
* pipeline started with the request's variables (`GOODFELLOW_TASK_PLUGIN` and
|
|
418
|
+
* the rest), whose job leaves the sealed answer as its artifact, read here.
|
|
419
|
+
* Starting pipelines on the main branch needs Developer access, which
|
|
420
|
+
* editors have once `letEditorsPublish()` lets them publish.
|
|
421
|
+
*/
|
|
422
|
+
function gitlabTasks(api, project, branch, readSetup, options = {}) {
|
|
423
|
+
const base = `/projects/${encodeURIComponent(project)}`;
|
|
424
|
+
const wait = options.wait ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
|
|
425
|
+
const supported = async () => (await readSetup().catch(() => void 0) ?? "").includes("$GOODFELLOW_TASK_PLUGIN");
|
|
426
|
+
return {
|
|
427
|
+
supported,
|
|
428
|
+
secretsPlace: async () => ({
|
|
429
|
+
kind: "gitlab",
|
|
430
|
+
url: `${api.apiUrl.replace(/\/api\/v4\/?$/, "")}/${project}/-/settings/ci_cd#js-cicd-variables-settings`
|
|
431
|
+
}),
|
|
432
|
+
async run(request) {
|
|
433
|
+
const problem = taskRequestProblem(request);
|
|
434
|
+
if (problem) throw new PluginTaskError("failed", problem);
|
|
435
|
+
if (!await supported()) throw new PluginTaskError("missing", "The site's setup files don't run plugins' tasks: .gitlab-ci.yml needs the plugin-task job.");
|
|
436
|
+
const variable = (key, value) => ({
|
|
437
|
+
key,
|
|
438
|
+
variable_type: "env_var",
|
|
439
|
+
value
|
|
440
|
+
});
|
|
441
|
+
let pipeline;
|
|
442
|
+
try {
|
|
443
|
+
pipeline = await gitlabJson(api, `${base}/pipeline`, {
|
|
444
|
+
method: "POST",
|
|
445
|
+
body: {
|
|
446
|
+
ref: branch,
|
|
447
|
+
variables: [
|
|
448
|
+
variable("GOODFELLOW_TASK_REQUEST", request.id),
|
|
449
|
+
variable("GOODFELLOW_TASK_PLUGIN", request.plugin),
|
|
450
|
+
variable("GOODFELLOW_TASK_NAME", request.task),
|
|
451
|
+
variable("GOODFELLOW_TASK_INPUT", request.input),
|
|
452
|
+
variable("GOODFELLOW_TASK_KEY", request.key)
|
|
453
|
+
]
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
} catch (error) {
|
|
457
|
+
if (error instanceof GitApiError && (error.status === 403 || error.status === 401)) throw new PluginTaskError("not-allowed", "Running plugins' tasks needs a sign-in that can publish to the site.");
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
const deadline = Date.now() + (options.timeoutMs ?? TASK_TIMEOUT_MS);
|
|
461
|
+
for (;;) {
|
|
462
|
+
const job = (await gitlabJson(api, `${base}/pipelines/${pipeline.id}/jobs?per_page=100`)).find((candidate) => candidate.name === TASK_JOB);
|
|
463
|
+
if (job?.status === "success") {
|
|
464
|
+
const answer = await gitlabRequest(api, `${base}/jobs/${job.id}/artifacts/${TASK_RESULT_FILE}`, { allow: [404] });
|
|
465
|
+
if (answer.ok) return await answer.text();
|
|
466
|
+
throw new PluginTaskError("failed", "The site's build service ran the task but left no answer.");
|
|
467
|
+
}
|
|
468
|
+
if (job && [
|
|
469
|
+
"failed",
|
|
470
|
+
"canceled",
|
|
471
|
+
"skipped"
|
|
472
|
+
].includes(job.status)) throw new PluginTaskError("failed", "The site's build service couldn't run the task.");
|
|
473
|
+
if (Date.now() >= deadline) throw new PluginTaskError("timeout", "The site's build service didn't answer in time.");
|
|
474
|
+
await wait(options.pollMs ?? TASK_POLL_MS);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
//#endregion
|
|
414
480
|
//#region src/updates.ts
|
|
415
481
|
const CI_FILE = ".gitlab-ci.yml";
|
|
416
482
|
/** When the nightly schedule the Updates screen adds runs, in UTC, as the GitHub Pages workflow's does. */
|
|
@@ -635,11 +701,12 @@ const GRAPHQL_BATCH = 50;
|
|
|
635
701
|
const ANSI_CODES = new RegExp(`${String.fromCharCode(27)}\\[[\\d;]*[A-Za-z]`, "g");
|
|
636
702
|
/**
|
|
637
703
|
* Whether a status says when the site is live. A job that only asks another
|
|
638
|
-
* host to rebuild the site doesn't, and nor
|
|
639
|
-
*
|
|
704
|
+
* host to rebuild the site doesn't, and nor do the nightly update and the
|
|
705
|
+
* job that writes the lockfile where Cloudflare builds the site, which run
|
|
706
|
+
* on the newest commit too.
|
|
640
707
|
*/
|
|
641
708
|
function builds(status) {
|
|
642
|
-
return status.name !== REBUILD_JOB && status.name !== "update";
|
|
709
|
+
return status.name !== REBUILD_JOB && status.name !== "update" && status.name !== LOCKFILE_JOB;
|
|
643
710
|
}
|
|
644
711
|
function listFrom(tree, dir) {
|
|
645
712
|
const prefix = `${dir}/`;
|
|
@@ -658,6 +725,7 @@ var GitLabBackend = class GitLabBackend {
|
|
|
658
725
|
updates;
|
|
659
726
|
drafts;
|
|
660
727
|
scheduledBuilds;
|
|
728
|
+
tasks;
|
|
661
729
|
contentOnly;
|
|
662
730
|
options;
|
|
663
731
|
api;
|
|
@@ -696,6 +764,7 @@ var GitLabBackend = class GitLabBackend {
|
|
|
696
764
|
}
|
|
697
765
|
this.drafts = branchDrafts(this, this.draftHost());
|
|
698
766
|
this.scheduledBuilds = gitlabScheduledBuilds(this.api, options.project, this.branch, async () => this.readAt(".gitlab-ci.yml", this.pinned ?? await this.revision()));
|
|
767
|
+
this.tasks = gitlabTasks(this.api, options.project, this.branch, async () => this.readAt(".gitlab-ci.yml", this.pinned ?? await this.revision()), options.tasks);
|
|
699
768
|
}
|
|
700
769
|
async repositoryVisibility() {
|
|
701
770
|
const { visibility } = await gitlabJson(this.api, `/projects/${this.project}`);
|
package/dist/testing.d.ts
CHANGED
|
@@ -42,6 +42,12 @@ export interface FakeGitLabOptions {
|
|
|
42
42
|
accounts?: string[];
|
|
43
43
|
/** Who can see the project. Private by default. */
|
|
44
44
|
visibility?: "public" | "internal" | "private";
|
|
45
|
+
/**
|
|
46
|
+
* Answers plugins' tasks, as the site's `plugin-task` job would: given each
|
|
47
|
+
* pipeline's task variables, it gives the text the job leaves as its
|
|
48
|
+
* artifact, or nothing for a job that fails. Without it, task pipelines fail.
|
|
49
|
+
*/
|
|
50
|
+
tasks?: (variables: Record<string, string>) => string | undefined | Promise<string | undefined>;
|
|
45
51
|
}
|
|
46
52
|
/** A pipeline schedule, such as the nightly one or one the admin panel adds for a scheduled change. */
|
|
47
53
|
export interface FakeSchedule {
|
package/dist/testing.js
CHANGED
|
@@ -173,6 +173,8 @@ function fakeGitLab(options) {
|
|
|
173
173
|
updateRuns.push(added);
|
|
174
174
|
return added;
|
|
175
175
|
}
|
|
176
|
+
/** Pipelines started for plugins' tasks, with their `plugin-task` job's artifact. */
|
|
177
|
+
const taskRuns = [];
|
|
176
178
|
/** Failed jobs, by commit, which tests add with `failBuild()` and `cancelBuild()`. */
|
|
177
179
|
const failures = /* @__PURE__ */ new Map();
|
|
178
180
|
/**
|
|
@@ -691,10 +693,25 @@ function fakeGitLab(options) {
|
|
|
691
693
|
})));
|
|
692
694
|
if (rest === "/pipeline" && request.method === "POST") {
|
|
693
695
|
if (accessLevel < 30) return json({ message: "403 Forbidden" }, 403);
|
|
696
|
+
const body = await request.json();
|
|
697
|
+
const variables = Object.fromEntries((body.variables ?? []).map(({ key, value }) => [key, value]));
|
|
698
|
+
if (variables.GOODFELLOW_TASK_PLUGIN) {
|
|
699
|
+
const id = 9e4 + taskRuns.length;
|
|
700
|
+
taskRuns.push({
|
|
701
|
+
id,
|
|
702
|
+
job: id + 5e4,
|
|
703
|
+
variables,
|
|
704
|
+
answer: await options.tasks?.(variables)
|
|
705
|
+
});
|
|
706
|
+
return json({
|
|
707
|
+
id,
|
|
708
|
+
status: "created"
|
|
709
|
+
}, 201);
|
|
710
|
+
}
|
|
694
711
|
const run = addUpdateRun({
|
|
695
712
|
source: "api",
|
|
696
713
|
status: "created",
|
|
697
|
-
variables:
|
|
714
|
+
variables: body.variables
|
|
698
715
|
});
|
|
699
716
|
return json({
|
|
700
717
|
id: run.id,
|
|
@@ -787,6 +804,18 @@ function fakeGitLab(options) {
|
|
|
787
804
|
if (body.ci_push_repository_for_job_token_allowed !== void 0) updateSettings.jobTokenPush = body.ci_push_repository_for_job_token_allowed;
|
|
788
805
|
return json({ ci_push_repository_for_job_token_allowed: updateSettings.jobTokenPush });
|
|
789
806
|
}
|
|
807
|
+
const taskJobs = rest.match(/^\/pipelines\/(\d+)\/jobs$/);
|
|
808
|
+
const taskRun = taskJobs && taskRuns.find((candidate) => candidate.id === Number(taskJobs[1]));
|
|
809
|
+
if (taskRun) return json([{
|
|
810
|
+
id: taskRun.job,
|
|
811
|
+
name: "plugin-task",
|
|
812
|
+
status: taskRun.answer === void 0 ? "failed" : "success"
|
|
813
|
+
}]);
|
|
814
|
+
const artifact = rest.match(/^\/jobs\/(\d+)\/artifacts\/(.+)$/);
|
|
815
|
+
if (artifact) {
|
|
816
|
+
const run = taskRuns.find((candidate) => candidate.job === Number(artifact[1]));
|
|
817
|
+
return run?.answer !== void 0 && artifact[2] === "goodfellow-task-result.json" ? new Response(`${run.answer}\n`) : json({ message: "404 Not Found" }, 404);
|
|
818
|
+
}
|
|
790
819
|
const updateJobs = rest.match(/^\/pipelines\/(\d{4,})\/jobs$/);
|
|
791
820
|
if (updateJobs) {
|
|
792
821
|
const run = updateRuns.find((candidate) => candidate.id === Number(updateJobs[1]));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodfellow-cms/gitlab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.6.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@puckeditor/core": "0.23.0"
|