@goodfellow-cms/github 0.3.0 → 0.5.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/testing.d.ts CHANGED
@@ -20,6 +20,10 @@ export interface FakeGitHubUser {
20
20
  actions?: boolean;
21
21
  /** Whether the token may run workflows, as owner tokens can. Defaults to the `administration` setting. */
22
22
  actionsWrite?: boolean;
23
+ /** Whether the token may read and change Actions variables, as owner tokens can. Defaults to the `administration` setting. */
24
+ variables?: boolean;
25
+ /** Whether the token may set Actions secrets and add deploy keys. Defaults to true. */
26
+ secrets?: boolean;
23
27
  }
24
28
  /** A GitHub Actions workflow run, with its jobs and their steps. */
25
29
  export interface FakeRun {
@@ -32,6 +36,8 @@ export interface FakeRun {
32
36
  createdAt?: string;
33
37
  status: "queued" | "in_progress" | "completed";
34
38
  conclusion: "success" | "failure" | "startup_failure" | "cancelled" | null;
39
+ /** The workflow's file, `.github/workflows/deploy.yml` by default. */
40
+ path?: string;
35
41
  jobs: Array<{
36
42
  name: string;
37
43
  conclusion: string | null;
@@ -70,6 +76,17 @@ export interface FakeGitHubOptions {
70
76
  * reports success after being checked this many times. Off by default.
71
77
  */
72
78
  deployAfterChecks?: number;
79
+ /**
80
+ * Simulates Cloudflare Pages: every commit to the default branch gets a
81
+ * build, which reports success after being checked this many times, as a
82
+ * check run of Cloudflare's GitHub App, or as a commit status. Off by default.
83
+ */
84
+ cloudflare?: {
85
+ afterChecks: number;
86
+ report?: "check-runs" | "statuses";
87
+ };
88
+ /** Answers 403 to reading check runs, as GitHub does for fine-grained tokens, which can't have the permission. */
89
+ checkRunsForbidden?: boolean;
73
90
  /** Organizations the users belong to, which new repositories can be created in. */
74
91
  orgs?: string[];
75
92
  /** The default branch of repositories created through the API, as an account's settings choose. Defaults to `main`. */
@@ -84,6 +101,8 @@ export interface FakeGitHubOptions {
84
101
  accounts?: string[];
85
102
  /** GitHub Apps, which the sign-in worker signs people in with. */
86
103
  apps?: FakeGitHubApp[];
104
+ /** Whether the site's repository is private. Public by default. */
105
+ private?: boolean;
87
106
  }
88
107
  /** A GitHub App, as the sign-in worker uses one: its JWTs, installations, OAuth sign-in and tokens. */
89
108
  export interface FakeGitHubApp {
@@ -125,8 +144,21 @@ export interface FakeCreatedRepo {
125
144
  repo: FakeRepo;
126
145
  pages?: {
127
146
  build_type: string;
147
+ cname?: string;
128
148
  };
129
149
  rulesets: unknown[];
150
+ /** Deploy keys added to it, by their public key. */
151
+ deployKeys: Array<{
152
+ title: string;
153
+ key: string;
154
+ read_only: boolean;
155
+ }>;
156
+ /** Its Actions secrets, opened. */
157
+ secrets: Map<string, string>;
158
+ /** The key pair GitHub seals secrets with. */
159
+ secretKey?: CryptoKeyPair;
160
+ /** People's permission on it, by login, as `collaborators/{login}/permission` says; others have none. */
161
+ collaborators?: Map<string, "admin" | "write" | "read">;
130
162
  }
131
163
  interface Deployment {
132
164
  id: number;
@@ -134,6 +166,11 @@ interface Deployment {
134
166
  statuses: FakeDeploymentStatus[];
135
167
  checks: number;
136
168
  }
169
+ /** A build Cloudflare Pages reports on a commit, which tests can fail with `failCloudflare()`. */
170
+ export interface FakeCloudflareBuild {
171
+ checks: number;
172
+ failed?: boolean;
173
+ }
137
174
  export declare function fakeGitHub(options: FakeGitHubOptions): {
138
175
  repo: FakeRepo;
139
176
  /** How many GraphQL requests read files, as `readFiles()` makes. */
@@ -150,8 +187,13 @@ export declare function fakeGitHub(options: FakeGitHubOptions): {
150
187
  code: string;
151
188
  installation: number;
152
189
  };
153
- /** Repositories created through the API, by `owner/name`. */
190
+ /** Repositories created through the API, or added with `addRepo()`, by `owner/name`. */
154
191
  created: Map<string, FakeCreatedRepo>;
192
+ addRepo: (fullName: string, repoOptions?: {
193
+ files?: Record<string, string>;
194
+ private?: boolean;
195
+ collaborators?: Record<string, "admin" | "write" | "read">;
196
+ }) => FakeCreatedRepo;
155
197
  /** The site's GitHub Pages settings, which tests can change, such as to issue the certificate. */
156
198
  pages: {
157
199
  current?: FakePages;
@@ -161,9 +203,15 @@ export declare function fakeGitHub(options: FakeGitHubOptions): {
161
203
  invitations: FakeInvitation[];
162
204
  accept: (login: string) => void;
163
205
  deployments: Deployment[];
164
- /** GitHub Actions workflow runs, which tests add with `failBuild()`. */
206
+ /** Cloudflare Pages' builds, by commit, with `cloudflare`; `failCloudflare()` makes one fail. */
207
+ cloudflareBuilds: Map<string, FakeCloudflareBuild>;
208
+ failCloudflare: (sha: string) => void;
209
+ /** GitHub Actions workflow runs, which tests add with `failBuild()` and `cancelBuild()`. */
165
210
  runs: FakeRun[];
211
+ /** The repository's Actions variables, such as `GOODFELLOW_NIGHTLY`. */
212
+ variables: Map<string, string>;
166
213
  failBuild: (sha: string, step?: string, job?: string) => FakeRun;
214
+ cancelBuild: (sha: string) => FakeRun;
167
215
  /** Every request received, for checking what the backend sent. */
168
216
  requests: Request[];
169
217
  /** Simulates someone else publishing. */
package/dist/testing.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { n as openSealedSecret } from "./deploy-keys-DAj2oX-N.js";
1
2
  import { decodeBase64, encodeBase64Bytes } from "@goodfellow-cms/core";
2
3
  import { FakeConflictError, FakeRepo, fakeFileBytes, fakeFileFromBytes } from "@goodfellow-cms/core/testing";
3
4
  //#region src/testing.ts
@@ -52,7 +53,10 @@ function fakeGitHub(options) {
52
53
  let nextToken = 1;
53
54
  let nextRepoId = 100;
54
55
  const deployments = [];
56
+ const cloudflareBuilds = /* @__PURE__ */ new Map();
55
57
  const runs = [];
58
+ /** The repository's Actions variables, by name. */
59
+ const variables = /* @__PURE__ */ new Map();
56
60
  const requests = [];
57
61
  let nextDeployment = 1;
58
62
  let graphqlReads = 0;
@@ -186,12 +190,44 @@ function fakeGitHub(options) {
186
190
  runs.push(run);
187
191
  return run;
188
192
  }
193
+ /**
194
+ * Simulates the deploy workflow's run for a commit being cancelled, as a
195
+ * newer run in its concurrency group replaces one still waiting: it never
196
+ * deploys.
197
+ */
198
+ function cancelBuild(sha) {
199
+ for (const [index, deployment] of [...deployments.entries()].reverse()) if (deployment.sha === sha) deployments.splice(index, 1);
200
+ const run = {
201
+ id: runs.length + 1,
202
+ sha,
203
+ status: "completed",
204
+ conclusion: "cancelled",
205
+ jobs: []
206
+ };
207
+ runs.push(run);
208
+ return run;
209
+ }
189
210
  /** Simulates an invited person accepting the invitation. */
190
211
  function accept(login) {
191
212
  const index = invitations.findIndex((invitation) => invitation.login === login);
192
213
  const [invitation] = index === -1 ? [] : invitations.splice(index, 1);
193
214
  if (invitation) collaborators.set(login, invitation.permission === "admin" ? "admin" : "push");
194
215
  }
216
+ /** Adds another repository, as if someone had made it, such as a site's code repository beside its content's. */
217
+ function addRepo(fullName, repoOptions = {}) {
218
+ const added = {
219
+ id: nextRepoId++,
220
+ fullName,
221
+ private: repoOptions.private === true,
222
+ repo: new FakeRepo(repoOptions.files ?? {}, "main"),
223
+ rulesets: [],
224
+ deployKeys: [],
225
+ secrets: /* @__PURE__ */ new Map(),
226
+ collaborators: new Map(Object.entries(repoOptions.collaborators ?? {}))
227
+ };
228
+ created.set(fullName, added);
229
+ return added;
230
+ }
195
231
  /** Repository creation and the Git data API, for repositories created through the fake. */
196
232
  async function handleCreated(request, path, principal) {
197
233
  const login = principal.login;
@@ -215,7 +251,9 @@ function fakeGitHub(options) {
215
251
  private: body.private === true,
216
252
  description: body.description,
217
253
  repo,
218
- rulesets: []
254
+ rulesets: [],
255
+ deployKeys: [],
256
+ secrets: /* @__PURE__ */ new Map()
219
257
  });
220
258
  return json({
221
259
  id,
@@ -234,12 +272,48 @@ function fakeGitHub(options) {
234
272
  if (rest === "" && method === "GET") return json({
235
273
  id: site.id,
236
274
  full_name: site.fullName,
275
+ private: site.private,
237
276
  default_branch: repo.defaultBranch,
238
277
  permissions: {
239
278
  push: true,
240
279
  admin: true
241
280
  }
242
281
  });
282
+ const tree = rest.match(/^\/git\/trees\/([0-9a-f]{40})$/);
283
+ if (tree && method === "GET" && repo.commitAt(tree[1] ?? "")) {
284
+ const entries = [...repo.files(tree[1] ?? "")].map(([file, content]) => ({
285
+ path: file,
286
+ type: "blob",
287
+ sha: repo.blobSha(content),
288
+ size: fakeFileBytes(content).length
289
+ }));
290
+ return json({
291
+ sha: tree[1],
292
+ tree: entries,
293
+ truncated: false
294
+ });
295
+ }
296
+ const blob = rest.match(/^\/git\/blobs\/([0-9a-f]{40})$/);
297
+ if (blob && method === "GET") {
298
+ const content = repo.blobs.get(blob[1] ?? "");
299
+ if (content === void 0) return json({ message: "Not Found" }, 404);
300
+ return new Response(fakeFileBytes(content));
301
+ }
302
+ const contents = rest.match(/^\/contents\/(.+)$/);
303
+ if (contents && method === "GET") {
304
+ if (principal.app && !principal.permissions?.contents) return json({ message: "Resource not accessible" }, 403);
305
+ const file = repo.files().get(decodeURIComponent(contents[1] ?? ""));
306
+ if (file === void 0) return json({ message: "Not Found" }, 404);
307
+ return new Response(typeof file === "string" ? file : fakeFileBytes(file));
308
+ }
309
+ const permission = rest.match(/^\/collaborators\/([^/]+)\/permission$/);
310
+ if (permission && method === "GET") {
311
+ const login = decodeURIComponent(permission[1] ?? "");
312
+ return json({
313
+ permission: site.collaborators?.get(login) ?? "none",
314
+ user: { login }
315
+ });
316
+ }
243
317
  if (rest === "" && method === "PATCH") {
244
318
  const body = await request.json();
245
319
  if (body.default_branch) {
@@ -279,6 +353,48 @@ function fakeGitHub(options) {
279
353
  build_type: site.pages.build_type
280
354
  }, 201);
281
355
  }
356
+ if (rest === "/pages" && method === "PUT" && site.pages) {
357
+ const body = await request.json();
358
+ if (body.cname && options.takenDomains?.includes(body.cname)) return json({ message: `The CNAME \`${body.cname}\` is already taken.` }, 422);
359
+ if (body.cname) site.pages.cname = body.cname;
360
+ return new Response(null, { status: 204 });
361
+ }
362
+ const notAllowed = (permission) => principal.app ? principal.permissions?.[permission] !== "write" : principal.secrets === false;
363
+ if (rest === "/keys" && method === "POST") {
364
+ if (notAllowed("administration")) return json({ message: "Resource not accessible by integration" }, 403);
365
+ const body = await request.json();
366
+ if (!/^ssh-ed25519 [A-Za-z0-9+/=]+( |$)/.test(body.key)) return json({ message: "key is invalid" }, 422);
367
+ site.deployKeys.push({
368
+ title: body.title,
369
+ key: body.key,
370
+ read_only: body.read_only === true
371
+ });
372
+ return json({
373
+ id: site.deployKeys.length,
374
+ ...body
375
+ }, 201);
376
+ }
377
+ if (rest === "/actions/secrets/public-key" && method === "GET") {
378
+ if (notAllowed("secrets")) return json({ message: "Resource not accessible by integration" }, 403);
379
+ site.secretKey ??= await crypto.subtle.generateKey({ name: "X25519" }, true, ["deriveBits"]);
380
+ const raw = new Uint8Array(await crypto.subtle.exportKey("raw", site.secretKey.publicKey));
381
+ return json({
382
+ key_id: `key-${site.id}`,
383
+ key: encodeBase64Bytes(raw)
384
+ });
385
+ }
386
+ const secret = rest.match(/^\/actions\/secrets\/([A-Z0-9_]+)$/);
387
+ if (secret && method === "PUT") {
388
+ if (notAllowed("secrets")) return json({ message: "Resource not accessible by integration" }, 403);
389
+ const body = await request.json();
390
+ if (!site.secretKey || body.key_id !== `key-${site.id}`) return json({ message: "Bad key id" }, 422);
391
+ try {
392
+ site.secrets.set(secret[1] ?? "", await openSealedSecret(decodeBase64(body.encrypted_value), site.secretKey));
393
+ } catch {
394
+ return json({ message: "Bad encrypted value" }, 422);
395
+ }
396
+ return new Response(null, { status: 201 });
397
+ }
282
398
  if (rest === "/rulesets" && method === "POST") {
283
399
  if (site.private && !options.paidPlan) return json({ message: "Upgrade to GitHub Pro" }, 403);
284
400
  site.rulesets.push(await request.json());
@@ -357,6 +473,7 @@ function fakeGitHub(options) {
357
473
  }
358
474
  }
359
475
  function deploy(sha) {
476
+ if (options.cloudflare) cloudflareBuilds.set(sha, { checks: 0 });
360
477
  if (options.deployAfterChecks === void 0) return;
361
478
  deployments.unshift({
362
479
  id: nextDeployment++,
@@ -365,6 +482,20 @@ function fakeGitHub(options) {
365
482
  checks: 0
366
483
  });
367
484
  }
485
+ /** Where a Cloudflare build is up to, counting the check. */
486
+ function cloudflareState(sha) {
487
+ const build = cloudflareBuilds.get(sha);
488
+ if (!build) return void 0;
489
+ build.checks += 1;
490
+ if (build.failed) return "failure";
491
+ return build.checks > (options.cloudflare?.afterChecks ?? 0) ? "success" : "building";
492
+ }
493
+ function failCloudflare(sha) {
494
+ cloudflareBuilds.set(sha, {
495
+ checks: 0,
496
+ failed: true
497
+ });
498
+ }
368
499
  function commit(changes, message, expectedHead, author, branch = repo.defaultBranch) {
369
500
  const sha = repo.commit(changes, message, {
370
501
  expectedHead,
@@ -427,7 +558,8 @@ function fakeGitHub(options) {
427
558
  pages: app.permissions.pages === "write",
428
559
  workflow: app.permissions.workflows === "write",
429
560
  actions: app.permissions.actions !== void 0,
430
- actionsWrite: app.permissions.actions === "write"
561
+ actionsWrite: app.permissions.actions === "write",
562
+ variables: app.permissions.variables === "write"
431
563
  });
432
564
  refreshTokens.set(refresh, {
433
565
  app,
@@ -535,7 +667,8 @@ function fakeGitHub(options) {
535
667
  administration: permissions.administration === "write",
536
668
  admin: permissions.administration === "write",
537
669
  actions: permissions.actions !== void 0,
538
- actionsWrite: permissions.actions === "write"
670
+ actionsWrite: permissions.actions === "write",
671
+ variables: permissions.variables === "write"
539
672
  });
540
673
  issued.push({
541
674
  token,
@@ -699,6 +832,17 @@ function fakeGitHub(options) {
699
832
  object: { sha: body.sha }
700
833
  }, 201);
701
834
  }
835
+ const matching = rest.match(/^\/git\/matching-refs\/heads\/(.*)$/);
836
+ if (matching) {
837
+ const prefix = decodeURIComponent(matching[1] ?? "");
838
+ return json([...repo.branches].filter(([branch]) => branch.startsWith(prefix)).map(([branch, sha]) => ({
839
+ ref: `refs/heads/${branch}`,
840
+ object: {
841
+ sha,
842
+ type: "commit"
843
+ }
844
+ })));
845
+ }
702
846
  const branchRef = rest.match(/^\/git\/refs\/heads\/(.+)$/);
703
847
  if (branchRef && request.method === "DELETE") {
704
848
  if (user.push === false) return json({ message: "Resource not accessible by integration" }, 403);
@@ -756,6 +900,8 @@ function fakeGitHub(options) {
756
900
  if (rest === "") return json({
757
901
  id: 1,
758
902
  full_name: options.repo,
903
+ private: options.private === true,
904
+ visibility: options.private === true ? "private" : "public",
759
905
  default_branch: repo.defaultBranch,
760
906
  permissions: {
761
907
  push: user.push !== false,
@@ -814,7 +960,35 @@ function fakeGitHub(options) {
814
960
  author: null
815
961
  })));
816
962
  }
817
- if (rest === "/actions/workflows/deploy.yml/dispatches" && request.method === "POST") {
963
+ const variable = rest.match(/^\/actions\/variables(?:\/([\w-]+))?$/);
964
+ if (variable) {
965
+ if (!(user.variables ?? user.administration)) return json({ message: "Resource not accessible by personal access token" }, 403);
966
+ const [, name] = variable;
967
+ if (!name && request.method === "POST") {
968
+ const body = await request.json();
969
+ if (variables.has(body.name)) return json({ message: "Already exists" }, 409);
970
+ variables.set(body.name, body.value);
971
+ return new Response(null, { status: 201 });
972
+ }
973
+ if (name && !variables.has(name)) return json({ message: "Not Found" }, 404);
974
+ if (name && request.method === "GET") return json({
975
+ name,
976
+ value: variables.get(name)
977
+ });
978
+ if (name && request.method === "PATCH") {
979
+ const body = await request.json();
980
+ variables.set(name, body.value);
981
+ return new Response(null, { status: 204 });
982
+ }
983
+ if (name && request.method === "DELETE") {
984
+ variables.delete(name);
985
+ return new Response(null, { status: 204 });
986
+ }
987
+ }
988
+ const workflowFile = rest.match(/^\/actions\/workflows\/([\w.-]+)\/(?:dispatches|runs)$/)?.[1];
989
+ const workflowPath = `.github/workflows/${workflowFile}`;
990
+ const ofWorkflow = (run) => (run.path ?? ".github/workflows/deploy.yml") === workflowPath;
991
+ if (workflowFile && rest.endsWith("/dispatches") && request.method === "POST") {
818
992
  if (!(user.actionsWrite ?? user.administration)) return json({ message: "Resource not accessible by personal access token" }, 403);
819
993
  const body = await request.json();
820
994
  runs.push({
@@ -824,15 +998,17 @@ function fakeGitHub(options) {
824
998
  inputs: body.inputs,
825
999
  status: "queued",
826
1000
  conclusion: null,
1001
+ path: workflowPath,
827
1002
  jobs: []
828
1003
  });
829
1004
  return new Response(null, { status: 204 });
830
1005
  }
831
- if (rest === "/actions/workflows/deploy.yml/runs") {
1006
+ if (workflowFile && rest.endsWith("/runs")) {
832
1007
  if (user.actions === false) return json({ message: "Resource not accessible by personal access token" }, 403);
1008
+ const found = runs.filter(ofWorkflow);
833
1009
  return json({
834
- total_count: runs.length,
835
- workflow_runs: [...runs].reverse().map((run) => ({
1010
+ total_count: found.length,
1011
+ workflow_runs: [...found].reverse().map((run) => ({
836
1012
  id: run.id,
837
1013
  event: run.event ?? "push",
838
1014
  head_sha: run.sha,
@@ -856,8 +1032,8 @@ function fakeGitHub(options) {
856
1032
  head_sha: run.sha,
857
1033
  status: run.status,
858
1034
  conclusion: run.conclusion,
859
- name: "Deploy to GitHub Pages",
860
- path: ".github/workflows/deploy.yml",
1035
+ name: run.path ? "Rebuild on Vercel" : "Deploy to GitHub Pages",
1036
+ path: run.path ?? ".github/workflows/deploy.yml",
861
1037
  html_url: htmlUrl(run)
862
1038
  }))
863
1039
  });
@@ -881,6 +1057,43 @@ function fakeGitHub(options) {
881
1057
  files: repo.changedPaths(base, b).map((filename) => ({ filename }))
882
1058
  });
883
1059
  }
1060
+ const commitSha = rest.match(/^\/commits\/([0-9a-f]{40})$/)?.[1];
1061
+ if (commitSha) {
1062
+ const found = repo.commitAt(commitSha);
1063
+ if (!found) return json({ message: "No commit found for SHA" }, 422);
1064
+ return json({
1065
+ sha: commitSha,
1066
+ parents: found.parent ? [{ sha: found.parent }] : []
1067
+ });
1068
+ }
1069
+ const checkRunsSha = rest.match(/^\/commits\/([0-9a-f]{40})\/check-runs$/)?.[1];
1070
+ if (checkRunsSha) {
1071
+ if (options.checkRunsForbidden) return json({ message: "Resource not accessible by personal access token" }, 403);
1072
+ const state = options.cloudflare?.report === "statuses" ? void 0 : cloudflareState(checkRunsSha);
1073
+ const runsFound = state ? [{
1074
+ id: 1,
1075
+ name: "Cloudflare Pages",
1076
+ app: { slug: "cloudflare-workers-and-pages" },
1077
+ status: state === "building" ? "in_progress" : "completed",
1078
+ conclusion: state === "building" ? null : state,
1079
+ details_url: `https://dash.cloudflare.com/builds/${checkRunsSha}`,
1080
+ output: { title: state === "failure" ? "Build failed" : "Deployed" }
1081
+ }] : [];
1082
+ return json({
1083
+ total_count: runsFound.length,
1084
+ check_runs: runsFound
1085
+ });
1086
+ }
1087
+ const statusesSha = rest.match(/^\/commits\/([0-9a-f]{40})\/statuses$/)?.[1];
1088
+ if (statusesSha) {
1089
+ const state = options.cloudflare?.report === "statuses" ? cloudflareState(statusesSha) : void 0;
1090
+ return json(state ? [{
1091
+ context: "Cloudflare Pages",
1092
+ state: state === "building" ? "pending" : state,
1093
+ target_url: `https://dash.cloudflare.com/builds/${statusesSha}`,
1094
+ description: state === "failure" ? "Build failed" : "Deployed"
1095
+ }] : []);
1096
+ }
884
1097
  if (rest === "/deployments") {
885
1098
  const sha = url.searchParams.get("sha");
886
1099
  return json(deployments.filter((deployment) => !sha || deployment.sha === sha).slice(0, Number(url.searchParams.get("per_page") ?? 30)).map(({ id, sha }) => ({
@@ -913,8 +1126,9 @@ function fakeGitHub(options) {
913
1126
  commitAuthors,
914
1127
  authorize,
915
1128
  install,
916
- /** Repositories created through the API, by `owner/name`. */
1129
+ /** Repositories created through the API, or added with `addRepo()`, by `owner/name`. */
917
1130
  created,
1131
+ addRepo,
918
1132
  /** The site's GitHub Pages settings, which tests can change, such as to issue the certificate. */
919
1133
  pages,
920
1134
  /** The repository's collaborators, by login, and invitations waiting to be accepted. */
@@ -922,9 +1136,15 @@ function fakeGitHub(options) {
922
1136
  invitations,
923
1137
  accept,
924
1138
  deployments,
925
- /** GitHub Actions workflow runs, which tests add with `failBuild()`. */
1139
+ /** Cloudflare Pages' builds, by commit, with `cloudflare`; `failCloudflare()` makes one fail. */
1140
+ cloudflareBuilds,
1141
+ failCloudflare,
1142
+ /** GitHub Actions workflow runs, which tests add with `failBuild()` and `cancelBuild()`. */
926
1143
  runs,
1144
+ /** The repository's Actions variables, such as `GOODFELLOW_NIGHTLY`. */
1145
+ variables,
927
1146
  failBuild,
1147
+ cancelBuild,
928
1148
  /** Every request received, for checking what the backend sent. */
929
1149
  requests,
930
1150
  /** Simulates someone else publishing. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodfellow-cms/github",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Edit a Goodfellow site stored on GitHub, straight from the browser.",
5
5
  "keywords": [
6
6
  "goodfellow",
@@ -35,13 +35,15 @@
35
35
  "dist"
36
36
  ],
37
37
  "dependencies": {
38
- "@goodfellow-cms/core": "0.3.0"
38
+ "@goodfellow-cms/core": "0.5.0",
39
+ "@noble/ciphers": "2.4.0",
40
+ "@noble/hashes": "2.4.0"
39
41
  },
40
42
  "devDependencies": {
41
43
  "@puckeditor/core": "0.23.0"
42
44
  },
43
45
  "engines": {
44
- "node": ">=22"
46
+ "node": "^22.23.4 || ^24.22.1 || >=26.12.1"
45
47
  },
46
48
  "publishConfig": {
47
49
  "access": "public"