@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.
- package/dist/git/index.cjs +67 -5
- package/dist/git/index.cjs.map +1 -1
- package/dist/git/index.d.cts +31 -3
- package/dist/git/index.d.ts +31 -3
- package/dist/git/index.js +66 -6
- package/dist/git/index.js.map +1 -1
- package/dist/shell/index.cjs +22 -17
- package/dist/shell/index.cjs.map +1 -1
- package/dist/shell/index.d.cts +1 -1
- package/dist/shell/index.d.ts +1 -1
- package/dist/shell/index.js +22 -17
- package/dist/shell/index.js.map +1 -1
- package/dist/studio/index.cjs +78 -10
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.cts +14 -2
- package/dist/studio/index.d.ts +14 -2
- package/dist/studio/index.js +78 -10
- package/dist/studio/index.js.map +1 -1
- package/dist/studio-ui/index.cjs +34 -23
- package/dist/studio-ui/index.cjs.map +1 -1
- package/dist/studio-ui/index.d.cts +18 -4
- package/dist/studio-ui/index.d.ts +18 -4
- package/dist/studio-ui/index.js +35 -24
- package/dist/studio-ui/index.js.map +1 -1
- package/dist/{types-BP5G1myE.d.cts → types-Cy9UDz--.d.cts} +11 -4
- package/dist/{types-BP5G1myE.d.ts → types-Cy9UDz--.d.ts} +11 -4
- package/package.json +2 -2
package/dist/studio/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { c as ImagePayload } from '../validate-C84Ll1cD.cjs';
|
|
2
2
|
export { I as IMAGE_ERRORS, a as IMAGE_LIMITS, b as ImageLimits, V as ValidatedImages, d as decodedSize, v as validateImages } from '../validate-C84Ll1cD.cjs';
|
|
3
3
|
import { a as IdentityUser, f as SiteRole, C as CookieStore, d as SiteMember, I as IdentityClient, c as SiteBranding } from '../members-nhbRSYyr.cjs';
|
|
4
|
-
import { a as PullRequestStatus } from '../types-
|
|
4
|
+
import { a as PullRequestStatus } from '../types-Cy9UDz--.cjs';
|
|
5
5
|
import { C as ContentTypeEntry } from '../types-DeHdasdP.cjs';
|
|
6
6
|
import * as zod from 'zod';
|
|
7
7
|
import * as zod_v4_core from 'zod/v4/core';
|
|
@@ -15,10 +15,15 @@ import '@supabase/supabase-js';
|
|
|
15
15
|
* arrives here, lazily, so `next build` with no env set still succeeds.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
/** The site's repo and how Studio reaches it — the GitHub App (preferred) or a personal access token. */
|
|
18
19
|
interface RepoConfig {
|
|
19
20
|
owner: string;
|
|
20
21
|
repo: string;
|
|
21
|
-
|
|
22
|
+
app?: {
|
|
23
|
+
appId: string;
|
|
24
|
+
privateKey: string;
|
|
25
|
+
} | null;
|
|
26
|
+
token?: string | null;
|
|
22
27
|
}
|
|
23
28
|
interface IdentityConfig {
|
|
24
29
|
url: string;
|
|
@@ -151,6 +156,12 @@ interface PublishInput {
|
|
|
151
156
|
} | null;
|
|
152
157
|
/** Pictures chosen in this session, already shrunk in the browser; committed with the MDX. */
|
|
153
158
|
images?: ImagePayload[];
|
|
159
|
+
/**
|
|
160
|
+
* The slug this item was opened as (edits only). When the new frontmatter produces a
|
|
161
|
+
* different file name — a date-prefixed event whose date moved — the old file is removed
|
|
162
|
+
* in the same commit, so an edit never leaves a stale copy behind.
|
|
163
|
+
*/
|
|
164
|
+
replaces?: string | null;
|
|
154
165
|
}
|
|
155
166
|
type PublishResult = {
|
|
156
167
|
ok: false;
|
|
@@ -249,6 +260,7 @@ declare function createStudio(config: StudioConfig): {
|
|
|
249
260
|
pendingFor: (path: string) => Promise<PendingChange | null>;
|
|
250
261
|
pathOf: (typeId: string, slug: string) => Promise<string | null>;
|
|
251
262
|
listItems: (typeId: string) => Promise<ListItem[]>;
|
|
263
|
+
listSlugs: (typeId: string) => Promise<string[]>;
|
|
252
264
|
countItems: (typeId: string) => Promise<number>;
|
|
253
265
|
readItem: (typeId: string, slug: string, opts?: {
|
|
254
266
|
ref?: string;
|
package/dist/studio/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { c as ImagePayload } from '../validate-C84Ll1cD.js';
|
|
2
2
|
export { I as IMAGE_ERRORS, a as IMAGE_LIMITS, b as ImageLimits, V as ValidatedImages, d as decodedSize, v as validateImages } from '../validate-C84Ll1cD.js';
|
|
3
3
|
import { a as IdentityUser, f as SiteRole, C as CookieStore, d as SiteMember, I as IdentityClient, c as SiteBranding } from '../members-nhbRSYyr.js';
|
|
4
|
-
import { a as PullRequestStatus } from '../types-
|
|
4
|
+
import { a as PullRequestStatus } from '../types-Cy9UDz--.js';
|
|
5
5
|
import { C as ContentTypeEntry } from '../types-DeHdasdP.js';
|
|
6
6
|
import * as zod from 'zod';
|
|
7
7
|
import * as zod_v4_core from 'zod/v4/core';
|
|
@@ -15,10 +15,15 @@ import '@supabase/supabase-js';
|
|
|
15
15
|
* arrives here, lazily, so `next build` with no env set still succeeds.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
/** The site's repo and how Studio reaches it — the GitHub App (preferred) or a personal access token. */
|
|
18
19
|
interface RepoConfig {
|
|
19
20
|
owner: string;
|
|
20
21
|
repo: string;
|
|
21
|
-
|
|
22
|
+
app?: {
|
|
23
|
+
appId: string;
|
|
24
|
+
privateKey: string;
|
|
25
|
+
} | null;
|
|
26
|
+
token?: string | null;
|
|
22
27
|
}
|
|
23
28
|
interface IdentityConfig {
|
|
24
29
|
url: string;
|
|
@@ -151,6 +156,12 @@ interface PublishInput {
|
|
|
151
156
|
} | null;
|
|
152
157
|
/** Pictures chosen in this session, already shrunk in the browser; committed with the MDX. */
|
|
153
158
|
images?: ImagePayload[];
|
|
159
|
+
/**
|
|
160
|
+
* The slug this item was opened as (edits only). When the new frontmatter produces a
|
|
161
|
+
* different file name — a date-prefixed event whose date moved — the old file is removed
|
|
162
|
+
* in the same commit, so an edit never leaves a stale copy behind.
|
|
163
|
+
*/
|
|
164
|
+
replaces?: string | null;
|
|
154
165
|
}
|
|
155
166
|
type PublishResult = {
|
|
156
167
|
ok: false;
|
|
@@ -249,6 +260,7 @@ declare function createStudio(config: StudioConfig): {
|
|
|
249
260
|
pendingFor: (path: string) => Promise<PendingChange | null>;
|
|
250
261
|
pathOf: (typeId: string, slug: string) => Promise<string | null>;
|
|
251
262
|
listItems: (typeId: string) => Promise<ListItem[]>;
|
|
263
|
+
listSlugs: (typeId: string) => Promise<string[]>;
|
|
252
264
|
countItems: (typeId: string) => Promise<number>;
|
|
253
265
|
readItem: (typeId: string, slug: string, opts?: {
|
|
254
266
|
ref?: string;
|
package/dist/studio/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import 'server-only';
|
|
2
2
|
import { redirect } from 'next/navigation';
|
|
3
3
|
import { createServerClient } from '@supabase/ssr';
|
|
4
|
+
import { createSign } from 'crypto';
|
|
4
5
|
import matter from 'gray-matter';
|
|
5
6
|
import { unified } from 'unified';
|
|
6
7
|
import remarkParse from 'remark-parse';
|
|
@@ -171,15 +172,15 @@ var ChecksFailedError = class extends Error {
|
|
|
171
172
|
}
|
|
172
173
|
};
|
|
173
174
|
|
|
174
|
-
// src/git/
|
|
175
|
-
var
|
|
175
|
+
// src/git/http.ts
|
|
176
|
+
var GITHUB_API = "https://api.github.com";
|
|
176
177
|
var GITHUB_TIMEOUT_MS = 1e4;
|
|
177
|
-
async function
|
|
178
|
-
const res = await
|
|
178
|
+
async function githubRequest(method, path, authorization, body, fetchImpl = fetch) {
|
|
179
|
+
const res = await fetchImpl(GITHUB_API + path, {
|
|
179
180
|
method,
|
|
180
181
|
signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),
|
|
181
182
|
headers: {
|
|
182
|
-
Authorization:
|
|
183
|
+
Authorization: authorization,
|
|
183
184
|
Accept: "application/vnd.github+json",
|
|
184
185
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
185
186
|
"User-Agent": "realiizlabs-admin",
|
|
@@ -198,6 +199,65 @@ async function ghFetch(ctx, method, path, body) {
|
|
|
198
199
|
}
|
|
199
200
|
return { ok: res.ok, status: res.status, json };
|
|
200
201
|
}
|
|
202
|
+
|
|
203
|
+
// src/git/app-auth.ts
|
|
204
|
+
var JWT_TTL_S = 9 * 60;
|
|
205
|
+
var REFRESH_BEFORE_MS = 5 * 6e4;
|
|
206
|
+
var cache = /* @__PURE__ */ new Map();
|
|
207
|
+
function b64url(input) {
|
|
208
|
+
return Buffer.from(input).toString("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
209
|
+
}
|
|
210
|
+
function appJwt(creds, now = Math.floor(Date.now() / 1e3)) {
|
|
211
|
+
const header = b64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
|
|
212
|
+
const payload = b64url(JSON.stringify({ iat: now - 60, exp: now + JWT_TTL_S, iss: creds.appId }));
|
|
213
|
+
const signer = createSign("RSA-SHA256");
|
|
214
|
+
signer.update(`${header}.${payload}`);
|
|
215
|
+
const signature = signer.sign(creds.privateKey.replace(/\\n/g, "\n"));
|
|
216
|
+
return `${header}.${payload}.${b64url(signature)}`;
|
|
217
|
+
}
|
|
218
|
+
function forgetInstallationToken(creds, owner, repo) {
|
|
219
|
+
cache.delete(`${creds.appId}:${owner}/${repo}`);
|
|
220
|
+
}
|
|
221
|
+
async function installationToken(creds, owner, repo, fetchImpl = fetch) {
|
|
222
|
+
const key = `${creds.appId}:${owner}/${repo}`;
|
|
223
|
+
const hit = cache.get(key);
|
|
224
|
+
if (hit && hit.expiresAt - Date.now() > REFRESH_BEFORE_MS) return hit.token;
|
|
225
|
+
const bearer = `Bearer ${appJwt(creds)}`;
|
|
226
|
+
const repoPath2 = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
|
|
227
|
+
const found = await githubRequest("GET", `${repoPath2}/installation`, bearer, void 0, fetchImpl);
|
|
228
|
+
if (found.status === 404) {
|
|
229
|
+
throw new GitHubError(
|
|
230
|
+
`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.`,
|
|
231
|
+
{ status: 404, path: `${repoPath2}/installation`, body: found.json }
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (!found.ok) throw new GitHubError(`GitHub ${found.status} looking up the app installation`, { status: found.status, path: `${repoPath2}/installation`, body: found.json });
|
|
235
|
+
const minted = await githubRequest(
|
|
236
|
+
"POST",
|
|
237
|
+
`/app/installations/${found.json.id}/access_tokens`,
|
|
238
|
+
bearer,
|
|
239
|
+
void 0,
|
|
240
|
+
fetchImpl
|
|
241
|
+
);
|
|
242
|
+
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 });
|
|
243
|
+
cache.set(key, { token: minted.json.token, expiresAt: Date.parse(minted.json.expires_at) });
|
|
244
|
+
return minted.json.token;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/git/client.ts
|
|
248
|
+
async function credentialFor(ctx) {
|
|
249
|
+
if (ctx.app) return installationToken(ctx.app, ctx.owner, ctx.repo);
|
|
250
|
+
if (ctx.token) return ctx.token;
|
|
251
|
+
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 });
|
|
252
|
+
}
|
|
253
|
+
async function ghFetch(ctx, method, path, body) {
|
|
254
|
+
const res = await githubRequest(method, path, `Bearer ${await credentialFor(ctx)}`, body);
|
|
255
|
+
if (res.status === 401 && ctx.app) {
|
|
256
|
+
forgetInstallationToken(ctx.app, ctx.owner, ctx.repo);
|
|
257
|
+
return githubRequest(method, path, `Bearer ${await credentialFor(ctx)}`, body);
|
|
258
|
+
}
|
|
259
|
+
return res;
|
|
260
|
+
}
|
|
201
261
|
async function ghFetchOrThrow(ctx, method, path, body) {
|
|
202
262
|
const res = await ghFetch(ctx, method, path, body);
|
|
203
263
|
if (!res.ok) throw toError(res, path);
|
|
@@ -404,6 +464,10 @@ function makeReaders(config, registry, pending) {
|
|
|
404
464
|
const entries = await listDirectory(repo(), e.folder);
|
|
405
465
|
return entries.find((x) => registry.slugOf(e, x.name) === slug)?.path ?? null;
|
|
406
466
|
};
|
|
467
|
+
const listSlugs = async (typeId) => {
|
|
468
|
+
const items = await listItems(typeId);
|
|
469
|
+
return items.filter((i) => !i.isNew).map((i) => i.slug).sort();
|
|
470
|
+
};
|
|
407
471
|
const listItems = async (typeId) => {
|
|
408
472
|
const e = need(typeId);
|
|
409
473
|
const ctx = repo();
|
|
@@ -479,7 +543,7 @@ function makeReaders(config, registry, pending) {
|
|
|
479
543
|
}
|
|
480
544
|
};
|
|
481
545
|
const prStatus = (number) => getPullRequestStatus(repo(), number);
|
|
482
|
-
return { pathOf, listItems, countItems, readItem, lastAuthor, getPullRequest: getPullRequest2, prStatus };
|
|
546
|
+
return { pathOf, listItems, listSlugs, countItems, readItem, lastAuthor, getPullRequest: getPullRequest2, prStatus };
|
|
483
547
|
}
|
|
484
548
|
|
|
485
549
|
// src/git/branch.ts
|
|
@@ -601,18 +665,22 @@ ${input.body.trim()}
|
|
|
601
665
|
if (!pictures.ok) return { ok: false, error: pictures.error };
|
|
602
666
|
const files = [{ path, content }, ...pictures.files];
|
|
603
667
|
const repo = config.env().repo;
|
|
668
|
+
const oldPath = input.replaces ? await readers.pathOf(entry.id, input.replaces).catch(() => null) : null;
|
|
669
|
+
const remove = oldPath && oldPath !== path ? [oldPath] : [];
|
|
670
|
+
const renamed = remove.length ? `
|
|
671
|
+
Renamed from \`${oldPath}\` to \`${path}\`` : "";
|
|
604
672
|
if (input.existing && await stillOpen(input.existing.number)) {
|
|
605
|
-
await writeFiles(repo, input.existing.branch, files, `Update: ${title}
|
|
673
|
+
await writeFiles(repo, input.existing.branch, files, `Update: ${title}`, { remove });
|
|
606
674
|
redirect(`/admin/pr/${input.existing.number}`);
|
|
607
675
|
}
|
|
608
676
|
const branch = `admin/${entry.id}/${fileName.replace(/\.mdx$/, "")}-${stamp()}`;
|
|
609
677
|
await createBranch(repo, branch);
|
|
610
|
-
await writeFiles(repo, branch, files, `Publish: ${title}
|
|
678
|
+
await writeFiles(repo, branch, files, `Publish: ${title}`, { remove });
|
|
611
679
|
const pr = await openPullRequest(repo, branch, {
|
|
612
680
|
title: `Publish: ${title}`,
|
|
613
681
|
body: `Published from the dashboard by ${user.email ?? user.id}.
|
|
614
682
|
|
|
615
|
-
File: \`${path}
|
|
683
|
+
File: \`${path}\`${renamed}`
|
|
616
684
|
});
|
|
617
685
|
redirect(`/admin/pr/${pr.number}`);
|
|
618
686
|
};
|
|
@@ -1103,7 +1171,7 @@ function makeHandlers(ctx) {
|
|
|
1103
1171
|
}
|
|
1104
1172
|
|
|
1105
1173
|
// src/version.ts
|
|
1106
|
-
var ADMIN_VERSION = "0.
|
|
1174
|
+
var ADMIN_VERSION = "0.17.0" ;
|
|
1107
1175
|
|
|
1108
1176
|
// src/studio/update.ts
|
|
1109
1177
|
var PACKAGE_NAME = "@realiizlabs/admin";
|