@goodfellow-cms/gitlab 0.9.0 → 0.10.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/dist/index.js CHANGED
@@ -1368,27 +1368,79 @@ function gitlabSetup(options = {}) {
1368
1368
  async function isAvailable(owner, name) {
1369
1369
  return (await gitlabRequest(api, `/projects/${encodeURIComponent(`${owner.path}/${name}`)}`, { allow: [404] })).status === 404;
1370
1370
  }
1371
+ const projectPath = (path) => `/projects/${encodeURIComponent(path)}`;
1372
+ /**
1373
+ * Makes one connection between a site's projects, giving what's left to do
1374
+ * by hand where GitLab refuses it, with what it answered.
1375
+ */
1376
+ async function attempt(unmade, request, left) {
1377
+ try {
1378
+ await request();
1379
+ } catch (error) {
1380
+ if (!(error instanceof GitApiError)) throw error;
1381
+ left.push({
1382
+ ...unmade,
1383
+ reason: error.message
1384
+ });
1385
+ }
1386
+ }
1371
1387
  /**
1372
1388
  * Lets each project's pipelines reach the other, as their setup files say:
1373
1389
  * a job token is refused by a project whose job token allowlist doesn't
1374
1390
  * name its project, even a public one. Also lets the code project's job
1375
- * token publish updates, and runs pipelines each night where they need it.
1376
- * Returns whether it worked.
1391
+ * token publish updates. Runs before the project's files are written,
1392
+ * since their first pipeline reads the other project. Each is tried on its
1393
+ * own, so one GitLab refuses doesn't keep the others from being made.
1377
1394
  */
1378
- async function connectProjects(link) {
1379
- const project = (path) => `/projects/${encodeURIComponent(path)}`;
1380
- try {
1381
- const ids = /* @__PURE__ */ new Map();
1382
- for (const which of ["code", "content"]) ids.set(which, (await gitlabJson(api, project(link[which]))).id);
1383
- for (const { reader, read } of link.links.reads) await gitlabRequest(api, `${project(link[read])}/job_token_scope/allowlist`, {
1384
- method: "POST",
1385
- body: { target_project_id: ids.get(reader) }
1386
- });
1387
- if (link.links.updates) await gitlabRequest(api, project(link[link.links.updates]), {
1395
+ async function connectProjects(link, left) {
1396
+ const ids = /* @__PURE__ */ new Map();
1397
+ for (const which of ["code", "content"]) try {
1398
+ ids.set(which, (await gitlabJson(api, projectPath(link[which]))).id);
1399
+ } catch (error) {
1400
+ if (!(error instanceof GitApiError)) throw error;
1401
+ }
1402
+ for (const { reader, read } of link.links.reads) {
1403
+ const target = ids.get(reader);
1404
+ const source = ids.get(read);
1405
+ await attempt({
1406
+ kind: "read",
1407
+ reader: link[reader],
1408
+ read: link[read],
1409
+ reason: ""
1410
+ }, async () => {
1411
+ if (target === void 0) throw new GitApiError(404, `GitLab couldn't find ${link[reader]}.`);
1412
+ if (source === void 0) throw new GitApiError(404, `GitLab couldn't find ${link[read]}.`);
1413
+ await gitlabRequest(api, `/projects/${source}/job_token_scope/allowlist`, {
1414
+ method: "POST",
1415
+ body: { target_project_id: target }
1416
+ });
1417
+ }, left);
1418
+ }
1419
+ if (link.links.updates) {
1420
+ const repo = link[link.links.updates];
1421
+ await attempt({
1422
+ kind: "push",
1423
+ repo,
1424
+ reason: ""
1425
+ }, () => gitlabRequest(api, projectPath(repo), {
1388
1426
  method: "PUT",
1389
1427
  body: { ci_push_repository_for_job_token_allowed: true }
1390
- });
1391
- for (const which of link.links.nightly) await gitlabRequest(api, `${project(link[which])}/pipeline_schedules`, {
1428
+ }), left);
1429
+ }
1430
+ }
1431
+ /**
1432
+ * Runs the pipelines that need it each night. After the project's files
1433
+ * are written, since a schedule names the main branch, which an empty
1434
+ * project doesn't have yet.
1435
+ */
1436
+ async function scheduleNightly(link, left) {
1437
+ for (const which of link.links.nightly) {
1438
+ const repo = link[which];
1439
+ await attempt({
1440
+ kind: "nightly",
1441
+ repo,
1442
+ reason: ""
1443
+ }, () => gitlabRequest(api, `${projectPath(repo)}/pipeline_schedules`, {
1392
1444
  method: "POST",
1393
1445
  body: {
1394
1446
  description: which === link.links.updates ? "Rebuild the site and install Goodfellow's fixes each night" : "Rebuild the site each night",
@@ -1397,15 +1449,7 @@ function gitlabSetup(options = {}) {
1397
1449
  cron_timezone: "UTC",
1398
1450
  active: true
1399
1451
  }
1400
- });
1401
- return true;
1402
- } catch (error) {
1403
- if (error instanceof GitApiError && [
1404
- 400,
1405
- 403,
1406
- 404
1407
- ].includes(error.status)) return false;
1408
- throw error;
1452
+ }), left);
1409
1453
  }
1410
1454
  }
1411
1455
  return {
@@ -1446,9 +1490,10 @@ function gitlabSetup(options = {}) {
1446
1490
  throw error;
1447
1491
  }
1448
1492
  const warnings = [];
1493
+ const unconnected = [];
1449
1494
  if (site.link) {
1450
1495
  onStep?.("connect");
1451
- if (!await connectProjects(site.link)) warnings.push("connect-unavailable");
1496
+ await connectProjects(site.link, unconnected);
1452
1497
  }
1453
1498
  let siteUrl;
1454
1499
  if (site.pages && site.domain) {
@@ -1491,13 +1536,16 @@ function gitlabSetup(options = {}) {
1491
1536
  }])
1492
1537
  }
1493
1538
  });
1539
+ if (site.link) await scheduleNightly(site.link, unconnected);
1540
+ if (unconnected.length) warnings.push("connect-unavailable");
1494
1541
  return {
1495
1542
  repo: project.path_with_namespace,
1496
1543
  webUrl: project.web_url,
1497
1544
  ...siteUrl && { siteUrl },
1498
1545
  buildsUrl: `${project.web_url}/-/pipelines`,
1499
1546
  pagesUrl: site.pages ? `${project.web_url}/pages` : void 0,
1500
- warnings
1547
+ warnings,
1548
+ ...unconnected.length && { unconnected }
1501
1549
  };
1502
1550
  }
1503
1551
  };
package/dist/testing.d.ts CHANGED
@@ -40,6 +40,8 @@ export interface FakeGitLabOptions {
40
40
  };
41
41
  /** Domains another GitLab Pages site already uses. */
42
42
  takenDomains?: string[];
43
+ /** What connecting created projects GitLab refuses, as a plan or a sign-in might: job token allowlists, job token pushes or pipeline schedules. */
44
+ refuseConnections?: Array<"allowlist" | "push" | "schedule">;
43
45
  /** Other GitLab accounts that exist, which can be added to the project. Every token's user exists and is a member. */
44
46
  accounts?: string[];
45
47
  /** Who can see the project. Private by default. */
package/dist/testing.js CHANGED
@@ -375,6 +375,7 @@ function fakeGitLab(options) {
375
375
  });
376
376
  if (rest === "" && request.method === "PUT") {
377
377
  const body = await request.json();
378
+ if (options.refuseConnections?.includes("push")) return json({ message: "403 Forbidden" }, 403);
378
379
  if (body.ci_push_repository_for_job_token_allowed !== void 0) project.jobTokenPush = body.ci_push_repository_for_job_token_allowed;
379
380
  return json({
380
381
  id: project.id,
@@ -382,7 +383,9 @@ function fakeGitLab(options) {
382
383
  });
383
384
  }
384
385
  if (rest === "/job_token_scope/allowlist" && request.method === "POST") {
386
+ if (!/^\d+$/.test(key)) return json({ error: "id is invalid" }, 400);
385
387
  const body = await request.json();
388
+ if (options.refuseConnections?.includes("allowlist")) return json({ message: "403 Forbidden" }, 403);
386
389
  if (![...created.values()].some((candidate) => candidate.id === body.target_project_id)) return json({ message: "404 Project Not Found" }, 404);
387
390
  if (!project.jobTokenAllowlist.includes(body.target_project_id)) project.jobTokenAllowlist.push(body.target_project_id);
388
391
  return json({
@@ -401,6 +404,8 @@ function fakeGitLab(options) {
401
404
  }
402
405
  if (rest === "/pipeline_schedules" && request.method === "POST") {
403
406
  const body = await request.json();
407
+ if (options.refuseConnections?.includes("schedule")) return json({ message: { ref: ["is ambiguous"] } }, 400);
408
+ if (project.repo.files().size === 0) return json({ message: { ref: ["is ambiguous"] } }, 400);
404
409
  project.schedules.push({
405
410
  ...body,
406
411
  active: body.active !== false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodfellow-cms/gitlab",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "Edit a Goodfellow CMS site stored on GitLab, straight from the browser.",
5
5
  "keywords": [
6
6
  "goodfellow",
@@ -39,7 +39,7 @@
39
39
  "dist"
40
40
  ],
41
41
  "dependencies": {
42
- "@goodfellow-cms/core": "0.9.0"
42
+ "@goodfellow-cms/core": "0.10.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@puckeditor/core": "0.23.0"