@realiizlabs/admin 0.15.9 → 0.17.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.
@@ -3,6 +3,7 @@
3
3
  require('server-only');
4
4
  var navigation = require('next/navigation');
5
5
  var ssr = require('@supabase/ssr');
6
+ var crypto = require('crypto');
6
7
  var matter = require('gray-matter');
7
8
  var unified = require('unified');
8
9
  var remarkParse = require('remark-parse');
@@ -183,15 +184,15 @@ var ChecksFailedError = class extends Error {
183
184
  }
184
185
  };
185
186
 
186
- // src/git/client.ts
187
- var API = "https://api.github.com";
187
+ // src/git/http.ts
188
+ var GITHUB_API = "https://api.github.com";
188
189
  var GITHUB_TIMEOUT_MS = 1e4;
189
- async function ghFetch(ctx, method, path, body) {
190
- const res = await fetch(API + path, {
190
+ async function githubRequest(method, path, authorization, body, fetchImpl = fetch) {
191
+ const res = await fetchImpl(GITHUB_API + path, {
191
192
  method,
192
193
  signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),
193
194
  headers: {
194
- Authorization: `Bearer ${ctx.token}`,
195
+ Authorization: authorization,
195
196
  Accept: "application/vnd.github+json",
196
197
  "X-GitHub-Api-Version": "2022-11-28",
197
198
  "User-Agent": "realiizlabs-admin",
@@ -210,6 +211,65 @@ async function ghFetch(ctx, method, path, body) {
210
211
  }
211
212
  return { ok: res.ok, status: res.status, json };
212
213
  }
214
+
215
+ // src/git/app-auth.ts
216
+ var JWT_TTL_S = 9 * 60;
217
+ var REFRESH_BEFORE_MS = 5 * 6e4;
218
+ var cache = /* @__PURE__ */ new Map();
219
+ function b64url(input) {
220
+ return Buffer.from(input).toString("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
221
+ }
222
+ function appJwt(creds, now = Math.floor(Date.now() / 1e3)) {
223
+ const header = b64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
224
+ const payload = b64url(JSON.stringify({ iat: now - 60, exp: now + JWT_TTL_S, iss: creds.appId }));
225
+ const signer = crypto.createSign("RSA-SHA256");
226
+ signer.update(`${header}.${payload}`);
227
+ const signature = signer.sign(creds.privateKey.replace(/\\n/g, "\n"));
228
+ return `${header}.${payload}.${b64url(signature)}`;
229
+ }
230
+ function forgetInstallationToken(creds, owner, repo) {
231
+ cache.delete(`${creds.appId}:${owner}/${repo}`);
232
+ }
233
+ async function installationToken(creds, owner, repo, fetchImpl = fetch) {
234
+ const key = `${creds.appId}:${owner}/${repo}`;
235
+ const hit = cache.get(key);
236
+ if (hit && hit.expiresAt - Date.now() > REFRESH_BEFORE_MS) return hit.token;
237
+ const bearer = `Bearer ${appJwt(creds)}`;
238
+ const repoPath2 = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
239
+ const found = await githubRequest("GET", `${repoPath2}/installation`, bearer, void 0, fetchImpl);
240
+ if (found.status === 404) {
241
+ throw new GitHubError(
242
+ `The Realiiz Studio GitHub App isn't installed on ${owner}/${repo}. The repository's owner installs it once \u2014 ask your web team for the link.`,
243
+ { status: 404, path: `${repoPath2}/installation`, body: found.json }
244
+ );
245
+ }
246
+ if (!found.ok) throw new GitHubError(`GitHub ${found.status} looking up the app installation`, { status: found.status, path: `${repoPath2}/installation`, body: found.json });
247
+ const minted = await githubRequest(
248
+ "POST",
249
+ `/app/installations/${found.json.id}/access_tokens`,
250
+ bearer,
251
+ void 0,
252
+ fetchImpl
253
+ );
254
+ if (!minted.ok) throw new GitHubError(`GitHub ${minted.status} minting an installation token`, { status: minted.status, path: "/app/installations/\u2026/access_tokens", body: minted.json });
255
+ cache.set(key, { token: minted.json.token, expiresAt: Date.parse(minted.json.expires_at) });
256
+ return minted.json.token;
257
+ }
258
+
259
+ // src/git/client.ts
260
+ async function credentialFor(ctx) {
261
+ if (ctx.app) return installationToken(ctx.app, ctx.owner, ctx.repo);
262
+ if (ctx.token) return ctx.token;
263
+ throw new GitHubError(`No GitHub credential for ${ctx.owner}/${ctx.repo}: set GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY, or GITHUB_CONTENT_TOKEN.`, { status: 0, path: "", body: null });
264
+ }
265
+ async function ghFetch(ctx, method, path, body) {
266
+ const res = await githubRequest(method, path, `Bearer ${await credentialFor(ctx)}`, body);
267
+ if (res.status === 401 && ctx.app) {
268
+ forgetInstallationToken(ctx.app, ctx.owner, ctx.repo);
269
+ return githubRequest(method, path, `Bearer ${await credentialFor(ctx)}`, body);
270
+ }
271
+ return res;
272
+ }
213
273
  async function ghFetchOrThrow(ctx, method, path, body) {
214
274
  const res = await ghFetch(ctx, method, path, body);
215
275
  if (!res.ok) throw toError(res, path);
@@ -416,6 +476,10 @@ function makeReaders(config, registry, pending) {
416
476
  const entries = await listDirectory(repo(), e.folder);
417
477
  return entries.find((x) => registry.slugOf(e, x.name) === slug)?.path ?? null;
418
478
  };
479
+ const listSlugs = async (typeId) => {
480
+ const items = await listItems(typeId);
481
+ return items.filter((i) => !i.isNew).map((i) => i.slug).sort();
482
+ };
419
483
  const listItems = async (typeId) => {
420
484
  const e = need(typeId);
421
485
  const ctx = repo();
@@ -491,7 +555,7 @@ function makeReaders(config, registry, pending) {
491
555
  }
492
556
  };
493
557
  const prStatus = (number) => getPullRequestStatus(repo(), number);
494
- return { pathOf, listItems, countItems, readItem, lastAuthor, getPullRequest: getPullRequest2, prStatus };
558
+ return { pathOf, listItems, listSlugs, countItems, readItem, lastAuthor, getPullRequest: getPullRequest2, prStatus };
495
559
  }
496
560
 
497
561
  // src/git/branch.ts
@@ -613,18 +677,22 @@ ${input.body.trim()}
613
677
  if (!pictures.ok) return { ok: false, error: pictures.error };
614
678
  const files = [{ path, content }, ...pictures.files];
615
679
  const repo = config.env().repo;
680
+ const oldPath = input.replaces ? await readers.pathOf(entry.id, input.replaces).catch(() => null) : null;
681
+ const remove = oldPath && oldPath !== path ? [oldPath] : [];
682
+ const renamed = remove.length ? `
683
+ Renamed from \`${oldPath}\` to \`${path}\`` : "";
616
684
  if (input.existing && await stillOpen(input.existing.number)) {
617
- await writeFiles(repo, input.existing.branch, files, `Update: ${title}`);
685
+ await writeFiles(repo, input.existing.branch, files, `Update: ${title}`, { remove });
618
686
  navigation.redirect(`/admin/pr/${input.existing.number}`);
619
687
  }
620
688
  const branch = `admin/${entry.id}/${fileName.replace(/\.mdx$/, "")}-${stamp()}`;
621
689
  await createBranch(repo, branch);
622
- await writeFiles(repo, branch, files, `Publish: ${title}`);
690
+ await writeFiles(repo, branch, files, `Publish: ${title}`, { remove });
623
691
  const pr = await openPullRequest(repo, branch, {
624
692
  title: `Publish: ${title}`,
625
693
  body: `Published from the dashboard by ${user.email ?? user.id}.
626
694
 
627
- File: \`${path}\``
695
+ File: \`${path}\`${renamed}`
628
696
  });
629
697
  navigation.redirect(`/admin/pr/${pr.number}`);
630
698
  };
@@ -1115,7 +1183,7 @@ function makeHandlers(ctx) {
1115
1183
  }
1116
1184
 
1117
1185
  // src/version.ts
1118
- var ADMIN_VERSION = "0.15.9" ;
1186
+ var ADMIN_VERSION = "0.17.0" ;
1119
1187
 
1120
1188
  // src/studio/update.ts
1121
1189
  var PACKAGE_NAME = "@realiizlabs/admin";