Make every plugin field live, and support draft plugins that bypass git entirely
This commit is contained in:
parent
420bc89ad3
commit
90eb694948
4 changed files with 285 additions and 91 deletions
|
|
@ -1,11 +1,28 @@
|
||||||
-- The overlay is deliberately thin: site/meta.json (git-tracked) stays the source of truth for
|
-- Every displayed field can now be overridden here, not just status/broken/tags - editing anything
|
||||||
-- real plugin content (descriptions, features, howItWorks). This table only holds the bits meant
|
-- from the admin plugin should show up on the live site immediately, no git push needed. A row can
|
||||||
-- to be flipped from a phone without a git push: status label, a "broken" flag + reason, and tags.
|
-- also stand entirely on its own (is_draft = 1): a plugin that doesn't exist in the git repo at all,
|
||||||
CREATE TABLE IF NOT EXISTS overlay (
|
-- created straight from the admin plugin for quick testing, with its own manifest/JS served directly
|
||||||
|
-- from here instead of from the built dist/ output.
|
||||||
|
DROP TABLE IF EXISTS overlay;
|
||||||
|
CREATE TABLE overlay (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT,
|
||||||
|
description TEXT,
|
||||||
|
category TEXT,
|
||||||
status TEXT,
|
status TEXT,
|
||||||
|
accent TEXT,
|
||||||
|
tagline TEXT,
|
||||||
|
note TEXT,
|
||||||
|
how_it_works TEXT,
|
||||||
|
features TEXT, -- JSON array of strings
|
||||||
|
commands TEXT, -- JSON array of {cmd, desc}
|
||||||
|
limitations TEXT,
|
||||||
|
authors TEXT, -- JSON array of strings
|
||||||
broken_reason TEXT,
|
broken_reason TEXT,
|
||||||
broken_since TEXT,
|
broken_since TEXT,
|
||||||
tags TEXT NOT NULL DEFAULT '[]',
|
tags TEXT NOT NULL DEFAULT '[]',
|
||||||
|
is_draft INTEGER NOT NULL DEFAULT 0,
|
||||||
|
manifest TEXT, -- full manifest.json content - only used when is_draft = 1
|
||||||
|
main_js TEXT, -- index.js source - only used when is_draft = 1
|
||||||
updated_at TEXT NOT NULL
|
updated_at TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
|
||||||
326
api/src/index.ts
326
api/src/index.ts
|
|
@ -5,15 +5,58 @@ export interface Env {
|
||||||
ADMIN_TOKEN: string;
|
ADMIN_TOKEN: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OverlayEntry {
|
interface Command {
|
||||||
status?: string | null;
|
cmd: string;
|
||||||
broken?: { reason: string; since?: string } | null;
|
desc: string;
|
||||||
tags?: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short TTL, not a long-lived cache - the whole point of this overlay is that a status/broken/tag
|
// Every field is optional - a PUT only needs to send what it wants to change, and whatever isn't
|
||||||
// flip from a phone should be visible almost immediately, not after a long edge-cache window.
|
// sent falls back to the git-tracked base (for a plugin that exists in git) or a sane default (for
|
||||||
const CACHE_KEY = "overlay:v1";
|
// a brand-new draft-only plugin, which has no base to fall back to at all).
|
||||||
|
interface OverlayEntry {
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
category?: string;
|
||||||
|
status?: string | null;
|
||||||
|
accent?: string;
|
||||||
|
tagline?: string;
|
||||||
|
note?: string;
|
||||||
|
howItWorks?: string;
|
||||||
|
features?: string[];
|
||||||
|
commands?: Command[];
|
||||||
|
limitations?: string;
|
||||||
|
authors?: string[];
|
||||||
|
broken?: { reason: string; since?: string } | null;
|
||||||
|
tags?: string[];
|
||||||
|
draft?: boolean;
|
||||||
|
mainJs?: string; // raw plugin source - only meaningful for a draft, ignored otherwise
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Row {
|
||||||
|
id: string;
|
||||||
|
name: string | null;
|
||||||
|
description: string | null;
|
||||||
|
category: string | null;
|
||||||
|
status: string | null;
|
||||||
|
accent: string | null;
|
||||||
|
tagline: string | null;
|
||||||
|
note: string | null;
|
||||||
|
how_it_works: string | null;
|
||||||
|
features: string | null;
|
||||||
|
commands: string | null;
|
||||||
|
limitations: string | null;
|
||||||
|
authors: string | null;
|
||||||
|
broken_reason: string | null;
|
||||||
|
broken_since: string | null;
|
||||||
|
tags: string;
|
||||||
|
is_draft: number;
|
||||||
|
manifest: string | null;
|
||||||
|
main_js: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short TTL, not a long-lived cache - the whole point of this overlay is that a change from a phone
|
||||||
|
// should be visible almost immediately, not after a long edge-cache window.
|
||||||
|
const CACHE_KEY = "overlay:v2";
|
||||||
const CACHE_TTL_SECONDS = 60; // Workers KV's enforced minimum
|
const CACHE_TTL_SECONDS = 60; // Workers KV's enforced minimum
|
||||||
|
|
||||||
const CORS_HEADERS = {
|
const CORS_HEADERS = {
|
||||||
|
|
@ -29,22 +72,52 @@ function json(data: unknown, status = 200): Response {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readAll(db: D1Database): Promise<Record<string, OverlayEntry>> {
|
function rowToOverlay(row: Row): OverlayEntry & { draft: boolean } {
|
||||||
const { results } = await db.prepare("SELECT * FROM overlay").all<Record<string, unknown>>();
|
return {
|
||||||
const out: Record<string, OverlayEntry> = {};
|
name: row.name ?? undefined,
|
||||||
for (const row of results) {
|
description: row.description ?? undefined,
|
||||||
const id = row.id as string;
|
category: row.category ?? undefined,
|
||||||
out[id] = {
|
status: row.status ?? undefined,
|
||||||
status: (row.status as string) ?? undefined,
|
accent: row.accent ?? undefined,
|
||||||
broken: row.broken_reason
|
tagline: row.tagline ?? undefined,
|
||||||
? { reason: row.broken_reason as string, since: row.broken_since as string }
|
note: row.note ?? undefined,
|
||||||
: null,
|
howItWorks: row.how_it_works ?? undefined,
|
||||||
tags: row.tags ? JSON.parse(row.tags as string) : [],
|
features: row.features ? JSON.parse(row.features) : undefined,
|
||||||
};
|
commands: row.commands ? JSON.parse(row.commands) : undefined,
|
||||||
}
|
limitations: row.limitations ?? undefined,
|
||||||
|
authors: row.authors ? JSON.parse(row.authors) : undefined,
|
||||||
|
broken: row.broken_reason ? { reason: row.broken_reason, since: row.broken_since ?? undefined } : null,
|
||||||
|
tags: row.tags ? JSON.parse(row.tags) : [],
|
||||||
|
draft: !!row.is_draft,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readAll(db: D1Database): Promise<Record<string, OverlayEntry & { draft: boolean }>> {
|
||||||
|
const { results } = await db.prepare("SELECT * FROM overlay").all<Row>();
|
||||||
|
const out: Record<string, OverlayEntry & { draft: boolean }> = {};
|
||||||
|
for (const row of results) out[row.id] = rowToOverlay(row);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared by every read path (GET /api/overlay, GET /plugins-data.json, and install-file serving for
|
||||||
|
// draft plugins) so a change from the admin plugin shows up everywhere on the next request, not just
|
||||||
|
// on the raw overlay endpoint.
|
||||||
|
async function getOverlay(env: Env): Promise<Record<string, OverlayEntry & { draft: boolean }>> {
|
||||||
|
const cached = await env.OVERLAY_CACHE.get(CACHE_KEY, "json");
|
||||||
|
if (cached) return cached as Record<string, OverlayEntry & { draft: boolean }>;
|
||||||
|
|
||||||
|
const data = await readAll(env.DB);
|
||||||
|
await env.OVERLAY_CACHE.put(CACHE_KEY, JSON.stringify(data), { expirationTtl: CACHE_TTL_SECONDS });
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sha256Hex(text: string): Promise<string> {
|
||||||
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
|
||||||
|
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_MAIN_JS = `export default {\n onLoad: () => {},\n onUnload: () => {}\n};\n`;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async fetch(request: Request, env: Env): Promise<Response> {
|
async fetch(request: Request, env: Env): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
|
|
@ -57,67 +130,180 @@ export default {
|
||||||
};
|
};
|
||||||
|
|
||||||
async function handle(request: Request, env: Env): Promise<Response> {
|
async function handle(request: Request, env: Env): Promise<Response> {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
|
|
||||||
if (request.method === "OPTIONS" && url.pathname.startsWith("/api/")) {
|
if (request.method === "OPTIONS" && url.pathname.startsWith("/api/")) {
|
||||||
return new Response(null, { headers: CORS_HEADERS });
|
return new Response(null, { headers: CORS_HEADERS });
|
||||||
|
}
|
||||||
|
|
||||||
|
// The site's actual data file - computed fresh on every request by merging the git-tracked base
|
||||||
|
// (plugins-base.json, written by generate-site.mjs at build time) with the live overlay, so any
|
||||||
|
// change from the admin plugin is visible immediately, with no rebuild. Draft-only plugins (no
|
||||||
|
// git base at all) are appended as their own entries.
|
||||||
|
if (url.pathname === "/plugins-data.json") {
|
||||||
|
const baseRes = await env.ASSETS.fetch(new URL("/plugins-base.json", request.url));
|
||||||
|
const base = baseRes.ok ? await baseRes.json<any[]>() : [];
|
||||||
|
const overlay = await getOverlay(env);
|
||||||
|
const baseIds = new Set(base.map((p) => p.id));
|
||||||
|
|
||||||
|
const merged = base.map((p) => applyOverride(p, overlay[p.id]));
|
||||||
|
const draftOnly = Object.entries(overlay)
|
||||||
|
.filter(([id, o]) => o.draft && !baseIds.has(id))
|
||||||
|
.map(([id, o]) => synthesizeDraft(id, o));
|
||||||
|
|
||||||
|
return json([...merged, ...draftOnly]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic install files for draft-only plugins (created via PUT /api/overlay/:id with draft:true
|
||||||
|
// and mainJs). Checked against D1 *first*, not by probing the static asset's response status -
|
||||||
|
// with SPA fallback enabled (see wrangler.toml's not_found_handling), a missing asset no longer
|
||||||
|
// 404s, it serves index.html with a 200, so that status can't be used to detect "not found" here.
|
||||||
|
// A real git-tracked plugin has no row here, so this always falls through to ASSETS for it.
|
||||||
|
const installMatch = url.pathname.match(/^\/([\w-]+)\/install\/(manifest\.json|index\.js)$/);
|
||||||
|
if (installMatch) {
|
||||||
|
const [, id, file] = installMatch;
|
||||||
|
const row = await env.DB.prepare("SELECT manifest, main_js FROM overlay WHERE id = ?1 AND is_draft = 1")
|
||||||
|
.bind(id).first<{ manifest: string; main_js: string }>();
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
if (file === "manifest.json") return json(JSON.parse(row.manifest));
|
||||||
|
return new Response(row.main_js, { headers: { "content-type": "application/javascript", ...CORS_HEADERS } });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url.pathname.startsWith("/api/")) {
|
return env.ASSETS.fetch(request);
|
||||||
return env.ASSETS.fetch(request);
|
}
|
||||||
|
|
||||||
|
if (!url.pathname.startsWith("/api/")) {
|
||||||
|
return env.ASSETS.fetch(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /api/overlay - public read, used by the admin plugin/site to show current values before
|
||||||
|
// editing.
|
||||||
|
if (url.pathname === "/api/overlay" && request.method === "GET") {
|
||||||
|
return json(await getOverlay(env));
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = url.pathname.match(/^\/api\/overlay\/([\w-]+)$/);
|
||||||
|
if (match) {
|
||||||
|
const id = match[1];
|
||||||
|
const auth = request.headers.get("authorization");
|
||||||
|
if (auth !== `Bearer ${env.ADMIN_TOKEN}`) {
|
||||||
|
return json({ error: "unauthorized" }, 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/overlay - public read, used at site build time (generate-site.mjs) and by the
|
if (request.method === "PUT") {
|
||||||
// admin plugin/site to show current values before editing.
|
const body = await request.json<OverlayEntry>().catch(() => null);
|
||||||
if (url.pathname === "/api/overlay" && request.method === "GET") {
|
if (!body) return json({ error: "invalid body" }, 400);
|
||||||
const cached = await env.OVERLAY_CACHE.get(CACHE_KEY, "json");
|
|
||||||
if (cached) return json(cached);
|
|
||||||
|
|
||||||
const data = await readAll(env.DB);
|
const existing = await env.DB.prepare("SELECT manifest, main_js FROM overlay WHERE id = ?1").bind(id).first<{ manifest: string; main_js: string }>();
|
||||||
await env.OVERLAY_CACHE.put(CACHE_KEY, JSON.stringify(data), { expirationTtl: CACHE_TTL_SECONDS });
|
|
||||||
return json(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = url.pathname.match(/^\/api\/overlay\/([\w-]+)$/);
|
let manifest = existing?.manifest ?? null;
|
||||||
if (match) {
|
let mainJs = existing?.main_js ?? null;
|
||||||
const id = match[1];
|
if (body.draft) {
|
||||||
const auth = request.headers.get("authorization");
|
mainJs = body.mainJs ?? mainJs ?? DEFAULT_MAIN_JS;
|
||||||
if (auth !== `Bearer ${env.ADMIN_TOKEN}`) {
|
const manifestObj = {
|
||||||
return json({ error: "unauthorized" }, 401);
|
name: body.name ?? id,
|
||||||
|
description: body.description ?? "",
|
||||||
|
authors: (body.authors ?? []).map((n) => ({ name: n })),
|
||||||
|
main: "index.js",
|
||||||
|
hash: await sha256Hex(mainJs),
|
||||||
|
};
|
||||||
|
manifest = JSON.stringify(manifestObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.method === "PUT") {
|
await env.DB.prepare(
|
||||||
const body = await request.json<OverlayEntry>().catch(() => null);
|
`INSERT INTO overlay (
|
||||||
if (!body) return json({ error: "invalid body" }, 400);
|
id, name, description, category, status, accent, tagline, note, how_it_works,
|
||||||
|
features, commands, limitations, authors, broken_reason, broken_since, tags,
|
||||||
|
is_draft, manifest, main_js, updated_at
|
||||||
|
) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
|
name = excluded.name, description = excluded.description, category = excluded.category,
|
||||||
|
status = excluded.status, accent = excluded.accent, tagline = excluded.tagline,
|
||||||
|
note = excluded.note, how_it_works = excluded.how_it_works, features = excluded.features,
|
||||||
|
commands = excluded.commands, limitations = excluded.limitations, authors = excluded.authors,
|
||||||
|
broken_reason = excluded.broken_reason, broken_since = excluded.broken_since, tags = excluded.tags,
|
||||||
|
is_draft = excluded.is_draft, manifest = excluded.manifest, main_js = excluded.main_js,
|
||||||
|
updated_at = excluded.updated_at`,
|
||||||
|
).bind(
|
||||||
|
id,
|
||||||
|
body.name ?? null,
|
||||||
|
body.description ?? null,
|
||||||
|
body.category ?? null,
|
||||||
|
body.status ?? null,
|
||||||
|
body.accent ?? null,
|
||||||
|
body.tagline ?? null,
|
||||||
|
body.note ?? null,
|
||||||
|
body.howItWorks ?? null,
|
||||||
|
body.features ? JSON.stringify(body.features) : null,
|
||||||
|
body.commands ? JSON.stringify(body.commands) : null,
|
||||||
|
body.limitations ?? null,
|
||||||
|
body.authors ? JSON.stringify(body.authors) : null,
|
||||||
|
body.broken?.reason ?? null,
|
||||||
|
body.broken ? (body.broken.since ?? new Date().toISOString()) : null,
|
||||||
|
JSON.stringify(body.tags ?? []),
|
||||||
|
body.draft ? 1 : 0,
|
||||||
|
manifest,
|
||||||
|
mainJs,
|
||||||
|
new Date().toISOString(),
|
||||||
|
).run();
|
||||||
|
|
||||||
await env.DB.prepare(
|
await env.OVERLAY_CACHE.delete(CACHE_KEY);
|
||||||
`INSERT INTO overlay (id, status, broken_reason, broken_since, tags, updated_at)
|
return json({ ok: true });
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6)
|
|
||||||
ON CONFLICT(id) DO UPDATE SET
|
|
||||||
status = excluded.status,
|
|
||||||
broken_reason = excluded.broken_reason,
|
|
||||||
broken_since = excluded.broken_since,
|
|
||||||
tags = excluded.tags,
|
|
||||||
updated_at = excluded.updated_at`,
|
|
||||||
).bind(
|
|
||||||
id,
|
|
||||||
body.status ?? null,
|
|
||||||
body.broken?.reason ?? null,
|
|
||||||
body.broken ? (body.broken.since ?? new Date().toISOString()) : null,
|
|
||||||
JSON.stringify(body.tags ?? []),
|
|
||||||
new Date().toISOString(),
|
|
||||||
).run();
|
|
||||||
|
|
||||||
await env.OVERLAY_CACHE.delete(CACHE_KEY);
|
|
||||||
return json({ ok: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.method === "DELETE") {
|
|
||||||
await env.DB.prepare("DELETE FROM overlay WHERE id = ?1").bind(id).run();
|
|
||||||
await env.OVERLAY_CACHE.delete(CACHE_KEY);
|
|
||||||
return json({ ok: true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.method === "DELETE") {
|
||||||
|
await env.DB.prepare("DELETE FROM overlay WHERE id = ?1").bind(id).run();
|
||||||
|
await env.OVERLAY_CACHE.delete(CACHE_KEY);
|
||||||
|
return json({ ok: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return json({ error: "not found" }, 404);
|
return json({ error: "not found" }, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyOverride(base: any, over: (OverlayEntry & { draft: boolean }) | undefined) {
|
||||||
|
if (!over) return { ...base, broken: null, tags: [] };
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
name: over.name ?? base.name,
|
||||||
|
description: over.description ?? base.description,
|
||||||
|
category: over.category ?? base.category,
|
||||||
|
status: over.status ?? base.status,
|
||||||
|
accent: over.accent ?? base.accent,
|
||||||
|
tagline: over.tagline ?? base.tagline,
|
||||||
|
note: over.note ?? base.note,
|
||||||
|
howItWorks: over.howItWorks ?? base.howItWorks,
|
||||||
|
features: over.features ?? base.features,
|
||||||
|
commands: over.commands ?? base.commands,
|
||||||
|
limitations: over.limitations ?? base.limitations,
|
||||||
|
authors: over.authors ?? base.authors,
|
||||||
|
broken: over.broken ?? null,
|
||||||
|
tags: over.tags ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function synthesizeDraft(id: string, over: OverlayEntry & { draft: boolean }) {
|
||||||
|
const base = "https://rp.jarviscli.dev";
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: over.name ?? id,
|
||||||
|
description: over.description ?? "",
|
||||||
|
authors: over.authors ?? [],
|
||||||
|
category: over.category ?? "Other",
|
||||||
|
status: over.status ?? "new",
|
||||||
|
broken: over.broken ?? null,
|
||||||
|
tags: over.tags ?? [],
|
||||||
|
accent: over.accent ?? "#5865f2",
|
||||||
|
tagline: over.tagline ?? over.description ?? "",
|
||||||
|
note: over.note ?? "Draft plugin - created from the admin plugin, not tracked in git.",
|
||||||
|
howItWorks: over.howItWorks ?? "",
|
||||||
|
features: over.features ?? [],
|
||||||
|
commands: over.commands ?? [],
|
||||||
|
limitations: over.limitations ?? "",
|
||||||
|
pageUrl: `${base}/${id}/`,
|
||||||
|
installUrl: `${base}/${id}/install/`,
|
||||||
|
sourceUrl: "https://github.com/everestmcarthur/revenge-plugins",
|
||||||
|
issueUrl: `https://github.com/everestmcarthur/revenge-plugins/issues/new`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ compatibility_date = "2024-11-01"
|
||||||
[assets]
|
[assets]
|
||||||
directory = "../dist"
|
directory = "../dist"
|
||||||
binding = "ASSETS"
|
binding = "ASSETS"
|
||||||
|
not_found_handling = "single-page-application"
|
||||||
|
|
||||||
[[d1_databases]]
|
[[d1_databases]]
|
||||||
binding = "DB"
|
binding = "DB"
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,11 @@ const REPO_BASE = "https://github.com/everestmcarthur/revenge-plugins";
|
||||||
const meta = JSON.parse(await readFile("./site/meta.json", "utf8"));
|
const meta = JSON.parse(await readFile("./site/meta.json", "utf8"));
|
||||||
const plugins = [];
|
const plugins = [];
|
||||||
|
|
||||||
// The overlay holds the fast-changing bits (status, "broken" flag/reason, tags) meant to be flipped
|
// This file intentionally does NOT merge in the live overlay (status/broken/tags) anymore - that
|
||||||
// from the admin plugin without a git push - meta.json above stays the source of truth for real
|
// used to happen here at build time, which meant flipping something broken from the admin plugin
|
||||||
// content. A fetch failure here (API down, first-ever build before it exists) must never break the
|
// only showed up on the site after the next git push. The Worker (api/src/index.ts) now merges
|
||||||
// public build, so this always falls back to an empty overlay.
|
// plugins-base.json (written below, git-tracked content only) with the live overlay on every
|
||||||
let overlay = {};
|
// request to /plugins-data.json, so admin changes are visible immediately with no rebuild needed.
|
||||||
try {
|
|
||||||
const res = await fetch(`${PAGES_BASE}/api/overlay`);
|
|
||||||
if (res.ok) overlay = await res.json();
|
|
||||||
} catch {
|
|
||||||
console.warn("Couldn't reach the overlay API - building with meta.json alone.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const id of await readdir("./dist")) {
|
for (const id of await readdir("./dist")) {
|
||||||
const dir = `./dist/${id}`;
|
const dir = `./dist/${id}`;
|
||||||
if (!(await stat(dir)).isDirectory()) continue;
|
if (!(await stat(dir)).isDirectory()) continue;
|
||||||
|
|
@ -30,7 +23,6 @@ for (const id of await readdir("./dist")) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const info = meta[id] ?? {};
|
const info = meta[id] ?? {};
|
||||||
const over = overlay[id] ?? {};
|
|
||||||
|
|
||||||
plugins.push({
|
plugins.push({
|
||||||
id,
|
id,
|
||||||
|
|
@ -38,9 +30,7 @@ for (const id of await readdir("./dist")) {
|
||||||
description: manifest.description,
|
description: manifest.description,
|
||||||
authors: (manifest.authors ?? []).map((a) => a.name),
|
authors: (manifest.authors ?? []).map((a) => a.name),
|
||||||
category: info.category ?? "Other",
|
category: info.category ?? "Other",
|
||||||
status: over.status ?? info.status ?? "new",
|
status: info.status ?? "new",
|
||||||
broken: over.broken ?? null,
|
|
||||||
tags: over.tags ?? [],
|
|
||||||
accent: info.accent ?? "#5865f2",
|
accent: info.accent ?? "#5865f2",
|
||||||
tagline: info.tagline ?? manifest.description,
|
tagline: info.tagline ?? manifest.description,
|
||||||
note: info.note ?? "",
|
note: info.note ?? "",
|
||||||
|
|
@ -57,7 +47,7 @@ for (const id of await readdir("./dist")) {
|
||||||
|
|
||||||
plugins.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
|
plugins.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
|
||||||
|
|
||||||
await writeFile("./dist/plugins-data.json", JSON.stringify(plugins, null, 2));
|
await writeFile("./dist/plugins-base.json", JSON.stringify(plugins, null, 2));
|
||||||
await copyFile("./site/index.html", "./dist/index.html");
|
await copyFile("./site/index.html", "./dist/index.html");
|
||||||
await copyFile("./site/index.html", "./dist/404.html");
|
await copyFile("./site/index.html", "./dist/404.html");
|
||||||
await copyFile("./site/CNAME", "./dist/CNAME");
|
await copyFile("./site/CNAME", "./dist/CNAME");
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue