@emdash-cms/admin 0.35.0 → 0.37.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/{LocaleDirectionProvider-CnKC3o_O.js → LocaleDirectionProvider-DWBtquoB.js} +29 -29
- package/dist/{LocaleDirectionProvider-CnKC3o_O.js.map → LocaleDirectionProvider-DWBtquoB.js.map} +1 -1
- package/dist/index.d.ts +205 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13658 -4602
- package/dist/index.js.map +1 -1
- package/dist/locales/ar/messages.mjs +1 -1
- package/dist/locales/ca/messages.mjs +1 -1
- package/dist/locales/cs/messages.mjs +1 -1
- package/dist/locales/de/messages.mjs +1 -1
- package/dist/locales/en/messages.mjs +1 -1
- package/dist/locales/en-GB/messages.mjs +1 -1
- package/dist/locales/es-419/messages.mjs +1 -1
- package/dist/locales/es-ES/messages.mjs +1 -1
- package/dist/locales/eu/messages.mjs +1 -1
- package/dist/locales/fa/messages.mjs +1 -1
- package/dist/locales/fr/messages.mjs +1 -1
- package/dist/locales/hi/messages.mjs +1 -1
- package/dist/locales/hu/messages.mjs +1 -1
- package/dist/locales/id/messages.mjs +1 -1
- package/dist/locales/index.js +1 -1
- package/dist/locales/ja/messages.mjs +1 -1
- package/dist/locales/ko/messages.mjs +1 -1
- package/dist/locales/nb/messages.mjs +1 -1
- package/dist/locales/nl/messages.mjs +1 -1
- package/dist/locales/pl/messages.mjs +1 -1
- package/dist/locales/pseudo/messages.mjs +1 -1
- package/dist/locales/pt-BR/messages.mjs +1 -1
- package/dist/locales/sr-Latn/messages.mjs +1 -1
- package/dist/locales/sv/messages.mjs +1 -1
- package/dist/locales/th/messages.mjs +1 -1
- package/dist/locales/tr/messages.mjs +1 -1
- package/dist/locales/uk/messages.mjs +1 -1
- package/dist/locales/zh-CN/messages.mjs +1 -1
- package/dist/locales/zh-TW/messages.mjs +1 -1
- package/dist/messages--4H_uJJx.js +6 -0
- package/dist/messages--4H_uJJx.js.map +1 -0
- package/dist/messages-1HW-Clzo.js +6 -0
- package/dist/messages-1HW-Clzo.js.map +1 -0
- package/dist/messages-3g7OPPLD.js +6 -0
- package/dist/messages-3g7OPPLD.js.map +1 -0
- package/dist/messages-3y7urMNZ.js +6 -0
- package/dist/messages-3y7urMNZ.js.map +1 -0
- package/dist/messages-Bi8Q6hQh.js +6 -0
- package/dist/messages-Bi8Q6hQh.js.map +1 -0
- package/dist/messages-C5M2v621.js +6 -0
- package/dist/messages-C5M2v621.js.map +1 -0
- package/dist/messages-CJVEZ_A5.js +6 -0
- package/dist/messages-CJVEZ_A5.js.map +1 -0
- package/dist/messages-CNbPYtov.js +6 -0
- package/dist/messages-CNbPYtov.js.map +1 -0
- package/dist/messages-CXOtPqxW.js +6 -0
- package/dist/messages-CXOtPqxW.js.map +1 -0
- package/dist/messages-CkNuItb-.js +6 -0
- package/dist/messages-CkNuItb-.js.map +1 -0
- package/dist/messages-ClabBphB.js +6 -0
- package/dist/messages-ClabBphB.js.map +1 -0
- package/dist/messages-Cqj_YnUc.js +6 -0
- package/dist/messages-Cqj_YnUc.js.map +1 -0
- package/dist/messages-Cy_HI78n.js +6 -0
- package/dist/messages-Cy_HI78n.js.map +1 -0
- package/dist/messages-D-WF9xWO.js +6 -0
- package/dist/messages-D-WF9xWO.js.map +1 -0
- package/dist/messages-D2cvoQzD.js +6 -0
- package/dist/messages-D2cvoQzD.js.map +1 -0
- package/dist/messages-DC8BlS4V.js +6 -0
- package/dist/messages-DC8BlS4V.js.map +1 -0
- package/dist/messages-DMQToQxw.js +6 -0
- package/dist/messages-DMQToQxw.js.map +1 -0
- package/dist/messages-DWBTgm-y.js +6 -0
- package/dist/messages-DWBTgm-y.js.map +1 -0
- package/dist/messages-DjoDBrf1.js +6 -0
- package/dist/messages-DjoDBrf1.js.map +1 -0
- package/dist/messages-Dkr-4VFi.js +6 -0
- package/dist/messages-Dkr-4VFi.js.map +1 -0
- package/dist/messages-DmQsSz7M.js +6 -0
- package/dist/messages-DmQsSz7M.js.map +1 -0
- package/dist/messages-Dnpi1ztm.js +6 -0
- package/dist/messages-Dnpi1ztm.js.map +1 -0
- package/dist/messages-OdBFlKpa.js +6 -0
- package/dist/messages-OdBFlKpa.js.map +1 -0
- package/dist/messages-RBIsUSWq.js +6 -0
- package/dist/messages-RBIsUSWq.js.map +1 -0
- package/dist/messages-_6oZo840.js +6 -0
- package/dist/messages-_6oZo840.js.map +1 -0
- package/dist/messages-kpo6a8yB.js +6 -0
- package/dist/messages-kpo6a8yB.js.map +1 -0
- package/dist/messages-oZUnGK0y.js +6 -0
- package/dist/messages-oZUnGK0y.js.map +1 -0
- package/dist/messages-u4KSs1DK.js +6 -0
- package/dist/messages-u4KSs1DK.js.map +1 -0
- package/dist/{plugins-CQAV8Ap0.js → plugins-BPVhvih_.js} +47 -3
- package/dist/plugins-BPVhvih_.js.map +1 -0
- package/dist/styles.css +1 -1
- package/package.json +10 -5
- package/dist/messages-1TWaf8a2.js +0 -6
- package/dist/messages-1TWaf8a2.js.map +0 -1
- package/dist/messages-B5sF4YmM.js +0 -6
- package/dist/messages-B5sF4YmM.js.map +0 -1
- package/dist/messages-B9T8Kc4V.js +0 -6
- package/dist/messages-B9T8Kc4V.js.map +0 -1
- package/dist/messages-BOwqz7Dx.js +0 -6
- package/dist/messages-BOwqz7Dx.js.map +0 -1
- package/dist/messages-B_M5MNtr.js +0 -6
- package/dist/messages-B_M5MNtr.js.map +0 -1
- package/dist/messages-Bh-BQAdD.js +0 -6
- package/dist/messages-Bh-BQAdD.js.map +0 -1
- package/dist/messages-ByjvPBKi.js +0 -6
- package/dist/messages-ByjvPBKi.js.map +0 -1
- package/dist/messages-C4xWRT0x.js +0 -6
- package/dist/messages-C4xWRT0x.js.map +0 -1
- package/dist/messages-CAdtKwS0.js +0 -6
- package/dist/messages-CAdtKwS0.js.map +0 -1
- package/dist/messages-CEdjuD3T.js +0 -6
- package/dist/messages-CEdjuD3T.js.map +0 -1
- package/dist/messages-CYCJB96m.js +0 -6
- package/dist/messages-CYCJB96m.js.map +0 -1
- package/dist/messages-C_ry9289.js +0 -6
- package/dist/messages-C_ry9289.js.map +0 -1
- package/dist/messages-CbGBMnfZ.js +0 -6
- package/dist/messages-CbGBMnfZ.js.map +0 -1
- package/dist/messages-Cpt4WQXi.js +0 -6
- package/dist/messages-Cpt4WQXi.js.map +0 -1
- package/dist/messages-D0Q9lbJr.js +0 -6
- package/dist/messages-D0Q9lbJr.js.map +0 -1
- package/dist/messages-D6SjorpS.js +0 -6
- package/dist/messages-D6SjorpS.js.map +0 -1
- package/dist/messages-DACzFKkc.js +0 -6
- package/dist/messages-DACzFKkc.js.map +0 -1
- package/dist/messages-DD7UzBCg.js +0 -6
- package/dist/messages-DD7UzBCg.js.map +0 -1
- package/dist/messages-DQhv3VpQ.js +0 -6
- package/dist/messages-DQhv3VpQ.js.map +0 -1
- package/dist/messages-DkmI0StQ.js +0 -6
- package/dist/messages-DkmI0StQ.js.map +0 -1
- package/dist/messages-DwVVrprv.js +0 -6
- package/dist/messages-DwVVrprv.js.map +0 -1
- package/dist/messages-LPfCWgcA.js +0 -6
- package/dist/messages-LPfCWgcA.js.map +0 -1
- package/dist/messages-NbBfxfDD.js +0 -6
- package/dist/messages-NbBfxfDD.js.map +0 -1
- package/dist/messages-UfXVNVGm.js +0 -6
- package/dist/messages-UfXVNVGm.js.map +0 -1
- package/dist/messages-kV2LI902.js +0 -6
- package/dist/messages-kV2LI902.js.map +0 -1
- package/dist/messages-lZ7KdJCT.js +0 -6
- package/dist/messages-lZ7KdJCT.js.map +0 -1
- package/dist/messages-nYVfh2dw.js +0 -6
- package/dist/messages-nYVfh2dw.js.map +0 -1
- package/dist/messages-ttfIlVd2.js +0 -6
- package/dist/messages-ttfIlVd2.js.map +0 -1
- package/dist/plugins-CQAV8Ap0.js.map +0 -1
|
@@ -34,6 +34,15 @@ function apiFetch(input, init) {
|
|
|
34
34
|
function isRecord(value) {
|
|
35
35
|
return typeof value === "object" && value !== null;
|
|
36
36
|
}
|
|
37
|
+
var ApiResponseError = class extends Error {
|
|
38
|
+
constructor(status, code, message, details) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.status = status;
|
|
41
|
+
this.code = code;
|
|
42
|
+
this.details = details;
|
|
43
|
+
this.name = "ApiResponseError";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
37
46
|
/**
|
|
38
47
|
* Extract per-field validation issue messages from a `VALIDATION_ERROR`
|
|
39
48
|
* response's `error.details.issues` array (see `packages/core/src/api/parse.ts`).
|
|
@@ -53,6 +62,36 @@ function formatValidationIssues(error) {
|
|
|
53
62
|
}).filter((m) => m !== void 0);
|
|
54
63
|
return messages.length > 0 ? messages.join("; ") : void 0;
|
|
55
64
|
}
|
|
65
|
+
function formatSandboxedSaveRejection(error) {
|
|
66
|
+
if (error.code !== "SAVE_REJECTED" || !isRecord(error.details)) return void 0;
|
|
67
|
+
const { pluginId, reason } = error.details;
|
|
68
|
+
if (typeof pluginId !== "string" || typeof reason !== "string") return void 0;
|
|
69
|
+
if (pluginId.length === 0 || reason.length === 0) return void 0;
|
|
70
|
+
return i18n._({
|
|
71
|
+
id: "7KcTZB",
|
|
72
|
+
message: "Plugin {pluginId} rejected the save: {reason}",
|
|
73
|
+
values: {
|
|
74
|
+
pluginId,
|
|
75
|
+
reason
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Client errors that pass no verdict on the request body, so resending it
|
|
81
|
+
* unchanged can still succeed. Every other 4xx repeats its verdict on every
|
|
82
|
+
* attempt.
|
|
83
|
+
*/
|
|
84
|
+
const RETRYABLE_CLIENT_ERROR_STATUSES = new Set([
|
|
85
|
+
408,
|
|
86
|
+
421,
|
|
87
|
+
425,
|
|
88
|
+
429
|
|
89
|
+
]);
|
|
90
|
+
/** Whether retrying the same request unchanged can never succeed. */
|
|
91
|
+
function isTerminalRequestError(error) {
|
|
92
|
+
if (!(error instanceof ApiResponseError)) return false;
|
|
93
|
+
return error.status >= 400 && error.status < 500 && !RETRYABLE_CLIENT_ERROR_STATUSES.has(error.status);
|
|
94
|
+
}
|
|
56
95
|
/**
|
|
57
96
|
* Throw an error with the message from the API response body if available,
|
|
58
97
|
* falling back to a generic message. All API error responses use the shape
|
|
@@ -63,12 +102,17 @@ function formatValidationIssues(error) {
|
|
|
63
102
|
async function throwResponseError(res, fallback) {
|
|
64
103
|
const body = await res.json().catch(() => ({}));
|
|
65
104
|
let message;
|
|
105
|
+
let code = "UNKNOWN_ERROR";
|
|
106
|
+
let details;
|
|
66
107
|
if (isRecord(body) && isRecord(body.error)) {
|
|
67
108
|
const { error } = body;
|
|
68
109
|
message = formatValidationIssues(error);
|
|
110
|
+
if (!message) message = formatSandboxedSaveRejection(error);
|
|
69
111
|
if (!message && typeof error.message === "string") message = error.message;
|
|
112
|
+
if (typeof error.code === "string") code = error.code;
|
|
113
|
+
if (isRecord(error.details)) details = error.details;
|
|
70
114
|
}
|
|
71
|
-
throw new
|
|
115
|
+
throw new ApiResponseError(res.status, code, message || `${fallback}: ${res.statusText}`, details);
|
|
72
116
|
}
|
|
73
117
|
/**
|
|
74
118
|
* Parse an API response with the { success, data: T } envelope.
|
|
@@ -201,5 +245,5 @@ async function setPluginMcpEnabled(pluginId, enabled) {
|
|
|
201
245
|
}
|
|
202
246
|
|
|
203
247
|
//#endregion
|
|
204
|
-
export { fetchPlugins as a, updatePluginSettings as c,
|
|
205
|
-
//# sourceMappingURL=plugins-
|
|
248
|
+
export { fetchPlugins as a, updatePluginSettings as c, apiFetch as d, fetchAuthMode as f, throwResponseError as g, parseApiResponse as h, fetchPluginSettings as i, API_BASE as l, isTerminalRequestError as m, enablePlugin as n, plugins_exports as o, fetchManifest as p, fetchPlugin as r, setPluginMcpEnabled as s, disablePlugin as t, ApiResponseError as u };
|
|
249
|
+
//# sourceMappingURL=plugins-BPVhvih_.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugins-BPVhvih_.js","names":[],"sources":["../src/lib/api/client.ts","../src/lib/api/plugins.ts"],"sourcesContent":["/**\n * Base API client configuration and shared types\n */\n\nimport type { Element } from \"@emdash-cms/blocks\";\nimport { i18n } from \"@lingui/core\";\nimport { msg } from \"@lingui/core/macro\";\n\nexport const API_BASE = \"/_emdash/api\";\n\n/**\n * Fetch wrapper that adds the X-EmDash-Request CSRF protection header\n * to all requests. All API calls should use this instead of raw fetch().\n */\nexport function apiFetch(input: string | URL | Request, init?: RequestInit): Promise<Response> {\n\tconst headers = new Headers(init?.headers);\n\theaders.set(\"X-EmDash-Request\", \"1\");\n\treturn fetch(input, { ...init, headers });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nexport class ApiResponseError extends Error {\n\tconstructor(\n\t\tpublic status: number,\n\t\tpublic code: string,\n\t\tmessage: string,\n\t\tpublic details?: Record<string, unknown>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"ApiResponseError\";\n\t}\n}\n\n/**\n * Extract per-field validation issue messages from a `VALIDATION_ERROR`\n * response's `error.details.issues` array (see `packages/core/src/api/parse.ts`).\n * Returns undefined when the shape doesn't match, so callers can fall back\n * to the generic top-level message.\n */\nfunction formatValidationIssues(error: Record<string, unknown>): string | undefined {\n\tif (error.code !== \"VALIDATION_ERROR\") return undefined;\n\tif (!isRecord(error.details)) return undefined;\n\tconst issues = error.details.issues;\n\tif (!Array.isArray(issues) || issues.length === 0) return undefined;\n\n\tconst messages = issues\n\t\t.map((issue: unknown) => {\n\t\t\tif (!isRecord(issue)) return undefined;\n\t\t\tconst { path, message } = issue;\n\t\t\tif (typeof message !== \"string\") return undefined;\n\t\t\treturn typeof path === \"string\" && path.length > 0 ? `${path}: ${message}` : message;\n\t\t})\n\t\t.filter((m): m is string => m !== undefined);\n\n\treturn messages.length > 0 ? messages.join(\"; \") : undefined;\n}\n\nfunction formatSandboxedSaveRejection(error: Record<string, unknown>): string | undefined {\n\tif (error.code !== \"SAVE_REJECTED\" || !isRecord(error.details)) return undefined;\n\tconst { pluginId, reason } = error.details;\n\tif (typeof pluginId !== \"string\" || typeof reason !== \"string\") return undefined;\n\tif (pluginId.length === 0 || reason.length === 0) return undefined;\n\treturn i18n._(msg`Plugin ${pluginId} rejected the save: ${reason}`);\n}\n\n/**\n * Client errors that pass no verdict on the request body, so resending it\n * unchanged can still succeed. Every other 4xx repeats its verdict on every\n * attempt.\n */\nconst RETRYABLE_CLIENT_ERROR_STATUSES: ReadonlySet<number> = new Set([\n\t408, // Request Timeout: the server gave up waiting for the request\n\t421, // Misdirected Request: another connection can be routed correctly\n\t425, // Too Early: sent as TLS early data, replayable after the handshake\n\t429, // Too Many Requests: succeeds once the rate limit window has passed\n]);\n\n/** Whether retrying the same request unchanged can never succeed. */\nexport function isTerminalRequestError(error: unknown): boolean {\n\tif (!(error instanceof ApiResponseError)) return false;\n\treturn (\n\t\terror.status >= 400 && error.status < 500 && !RETRYABLE_CLIENT_ERROR_STATUSES.has(error.status)\n\t);\n}\n\n/**\n * Throw an error with the message from the API response body if available,\n * falling back to a generic message. All API error responses use the shape\n * `{ success: false, error: { code, message, details? } }`. For validation\n * errors, the field-level messages in `error.details.issues` are surfaced\n * instead of the generic \"Invalid request data\" top-level message.\n */\nexport async function throwResponseError(res: Response, fallback: string): Promise<never> {\n\tconst body: unknown = await res.json().catch(() => ({}));\n\tlet message: string | undefined;\n\tlet code = \"UNKNOWN_ERROR\";\n\tlet details: Record<string, unknown> | undefined;\n\tif (isRecord(body) && isRecord(body.error)) {\n\t\tconst { error } = body;\n\t\tmessage = formatValidationIssues(error);\n\t\tif (!message) message = formatSandboxedSaveRejection(error);\n\t\tif (!message && typeof error.message === \"string\") message = error.message;\n\t\tif (typeof error.code === \"string\") code = error.code;\n\t\tif (isRecord(error.details)) details = error.details;\n\t}\n\tthrow new ApiResponseError(\n\t\tres.status,\n\t\tcode,\n\t\tmessage || `${fallback}: ${res.statusText}`,\n\t\tdetails,\n\t);\n}\n\n/**\n * Generic paginated result\n */\nexport interface FindManyResult<T> {\n\titems: T[];\n\tnextCursor?: string;\n\t/**\n\t * Total number of rows matching the filters (ignoring pagination).\n\t * Optional because older servers may not return it.\n\t */\n\ttotal?: number;\n}\n\n/**\n * Admin manifest describing available collections and plugins\n */\nexport interface AdminManifest {\n\tversion: string;\n\t/** Version of Astro the host is built with, when resolvable. */\n\tastroVersion?: string;\n\thash: string;\n\tcollections: Record<\n\t\tstring,\n\t\t{\n\t\t\tlabel: string;\n\t\t\tlabelSingular: string;\n\t\t\tsupports: string[];\n\t\t\thasSeo: boolean;\n\t\t\turlPattern?: string;\n\t\t\troutable?: boolean;\n\t\t\ttitleField?: string;\n\t\t\tdateField?: string;\n\t\t\thidden?: boolean;\n\t\t\tlistColumns?: string[];\n\t\t\tfields: Record<\n\t\t\t\tstring,\n\t\t\t\t{\n\t\t\t\t\t/** Database row ID (ULID) for the field. Used to widen MIME allowlists on upload/media-list calls. */\n\t\t\t\t\tid?: string;\n\t\t\t\t\tkind: string;\n\t\t\t\t\tlabel?: string;\n\t\t\t\t\trequired?: boolean;\n\t\t\t\t\twidget?: string;\n\t\t\t\t\t/**\n\t\t\t\t\t * For `select` / `multiSelect`: the list of enum choices.\n\t\t\t\t\t * For `json` fields driven by a plugin `widget`: arbitrary widget config.\n\t\t\t\t\t */\n\t\t\t\t\toptions?: Array<{ value: string; label: string }> | Record<string, unknown>;\n\t\t\t\t\tvalidation?: Record<string, unknown>;\n\t\t\t\t}\n\t\t\t>;\n\t\t}\n\t>;\n\tplugins: Record<\n\t\tstring,\n\t\t{\n\t\t\tname?: string;\n\t\t\tversion?: string;\n\t\t\t/** Package name for dynamic import (e.g., \"@emdash-cms/plugin-audit-log\") */\n\t\t\tpackage?: string;\n\t\t\t/** Whether the plugin is enabled */\n\t\t\tenabled?: boolean;\n\t\t\t/**\n\t\t\t * How this plugin renders its admin UI:\n\t\t\t * - \"react\": Trusted plugin with React components\n\t\t\t * - \"blocks\": Declarative Block Kit UI via admin route handler\n\t\t\t * - \"none\": No admin UI\n\t\t\t */\n\t\t\tadminMode?: \"react\" | \"blocks\" | \"none\";\n\t\t\tadminPages?: Array<{\n\t\t\t\tpath: string;\n\t\t\t\tlabel?: string;\n\t\t\t\ticon?: string;\n\t\t\t}>;\n\t\t\tdashboardWidgets?: Array<{\n\t\t\t\tid: string;\n\t\t\t\ttitle?: string;\n\t\t\t\tsize?: \"full\" | \"half\" | \"third\";\n\t\t\t}>;\n\t\t\tfieldWidgets?: Array<{\n\t\t\t\tname: string;\n\t\t\t\tlabel: string;\n\t\t\t\tfieldTypes: string[];\n\t\t\t\telements?: import(\"@emdash-cms/blocks\").Element[];\n\t\t\t}>;\n\t\t\t/** Block types for Portable Text editor */\n\t\t\tportableTextBlocks?: Array<{\n\t\t\t\ttype: string;\n\t\t\t\tlabel: string;\n\t\t\t\ticon?: string;\n\t\t\t\tdescription?: string;\n\t\t\t\tplaceholder?: string;\n\t\t\t\tfields?: Element[];\n\t\t\t\tcategory?: string;\n\t\t\t}>;\n\t\t}\n\t>;\n\t/**\n\t * Auth mode for the admin UI. When \"passkey\", the security settings\n\t * (passkey management, self-signup domains) are shown. When using\n\t * external auth (e.g., \"cloudflare-access\"), these are hidden since\n\t * authentication is handled externally.\n\t */\n\tauthMode: string;\n\t/**\n\t * Whether self-signup is enabled (at least one allowed domain is active).\n\t * Used by the login page to conditionally show the \"Sign up\" link.\n\t */\n\tsignupEnabled?: boolean;\n\t/**\n\t * i18n configuration. Present when multiple locales are configured.\n\t */\n\ti18n?: {\n\t\tdefaultLocale: string;\n\t\tlocales: string[];\n\t\tprefixDefaultLocale?: boolean;\n\t};\n\t/** Stored-content locale policy, independent from the admin UI language. */\n\tcontentLocale?: {\n\t\tdefaultLocale: string;\n\t\timplicit: boolean;\n\t};\n\t/**\n\t * Taxonomy definitions for the admin sidebar.\n\t */\n\ttaxonomies: Array<{\n\t\tid?: string;\n\t\tname: string;\n\t\tlabel: string;\n\t\tlabelSingular?: string;\n\t\thierarchical: boolean;\n\t\tcollections: string[];\n\t\tlocale?: string;\n\t\ttranslationGroup?: string | null;\n\t}>;\n\t/**\n\t * Marketplace registry URL. Present when `marketplace` is configured\n\t * in the EmDash integration. Enables marketplace features in the UI.\n\t */\n\tmarketplace?: string;\n\t/**\n\t * Experimental decentralized plugin registry. Present when\n\t * `experimental.registry` is configured in the EmDash integration.\n\t * When present, the admin UI uses the registry instead of the\n\t * centralized marketplace for browse and install.\n\t */\n\tregistry?: {\n\t\taggregatorUrl: string;\n\t\tacceptLabelers?: string;\n\t\tpolicy?: {\n\t\t\tminimumReleaseAgeSeconds?: number;\n\t\t\tminimumReleaseAgeExclude?: string[];\n\t\t};\n\t};\n\t/**\n\t * Admin branding overrides for white-labeling.\n\t * Set via the `admin` config in `astro.config.mjs`.\n\t */\n\tadmin?: {\n\t\tlogo?: string;\n\t\tsiteName?: string;\n\t\tfavicon?: string;\n\t};\n}\n\n/**\n * Parse an API response with the { success, data: T } envelope.\n *\n * Handles error responses via throwResponseError, then unwraps the data envelope.\n * Replaces both bare `response.json()` and field-unwrap patterns.\n */\nexport async function parseApiResponse<T>(\n\tresponse: Response,\n\tfallbackMessage = i18n._(msg`Request failed`),\n): Promise<T> {\n\tif (!response.ok) await throwResponseError(response, fallbackMessage);\n\tconst body: { data: T } = await response.json();\n\treturn body.data;\n}\n\n/**\n * Fetch admin manifest\n */\nexport async function fetchManifest(): Promise<AdminManifest> {\n\tconst response = await apiFetch(`${API_BASE}/manifest`);\n\treturn parseApiResponse<AdminManifest>(response, i18n._(msg`Failed to fetch manifest`));\n}\n\n/**\n * Fetch auth mode (public endpoint — works without authentication).\n * Used by the login page to determine which login UI to render.\n */\nexport async function fetchAuthMode(): Promise<{\n\tauthMode: string;\n\tsignupEnabled?: boolean;\n\tproviders?: Array<{ id: string; label: string }>;\n}> {\n\tconst response = await apiFetch(`${API_BASE}/auth/mode`);\n\treturn parseApiResponse<{\n\t\tauthMode: string;\n\t\tsignupEnabled?: boolean;\n\t\tproviders?: Array<{ id: string; label: string }>;\n\t}>(response, i18n._(msg`Failed to fetch auth mode`));\n}\n","/**\n * Plugin management APIs\n */\n\nimport { i18n } from \"@lingui/core\";\nimport { msg } from \"@lingui/core/macro\";\n\nimport { API_BASE, apiFetch, parseApiResponse, throwResponseError } from \"./client.js\";\n\nexport interface PluginInfo {\n\tid: string;\n\tname: string;\n\tversion: string;\n\tpackage?: string;\n\tenabled: boolean;\n\tstatus: \"installed\" | \"active\" | \"inactive\";\n\tcapabilities: string[];\n\thasAdminPages: boolean;\n\thasDashboardWidgets: boolean;\n\thasHooks: boolean;\n\t/** True when the plugin declares `admin.settingsSchema` (auto-generated settings form) */\n\thasSettings: boolean;\n\tinstalledAt?: string;\n\tactivatedAt?: string;\n\tdeactivatedAt?: string;\n\t/** Plugin source: 'config' (declared in astro.config), 'marketplace', or 'registry' */\n\tsource?: \"config\" | \"marketplace\" | \"registry\";\n\t/** Installed marketplace version (set when source = 'marketplace') */\n\tmarketplaceVersion?: string;\n\t/** Publisher DID, for registry-source plugins. */\n\tregistryPublisherDid?: string;\n\t/** Publisher slug, for registry-source plugins. */\n\tregistrySlug?: string;\n\t/** Description of what the plugin does */\n\tdescription?: string;\n\t/** URL to the plugin icon (marketplace plugins use the icon proxy) */\n\ticonUrl?: string;\n\t/** Absent when talking to an older core that predates plugin MCP tools. */\n\tmcpToolsEnabled?: boolean;\n\tmcpTools?: Array<{\n\t\tname: string;\n\t\tdescription: string;\n\t\troute: string;\n\t\tpermission: string;\n\t\tdestructive: boolean;\n\t}>;\n}\n\n/**\n * Fetch all plugins\n */\nexport async function fetchPlugins(): Promise<PluginInfo[]> {\n\tconst response = await apiFetch(`${API_BASE}/admin/plugins`);\n\tconst result = await parseApiResponse<{ items: PluginInfo[] }>(\n\t\tresponse,\n\t\ti18n._(msg`Failed to fetch plugins`),\n\t);\n\treturn result.items;\n}\n\n/**\n * Fetch a single plugin\n */\nexport async function fetchPlugin(pluginId: string): Promise<PluginInfo> {\n\tconst response = await apiFetch(`${API_BASE}/admin/plugins/${pluginId}`);\n\tif (!response.ok) {\n\t\tif (response.status === 404) {\n\t\t\tthrow new Error(i18n._(msg`Plugin \"${pluginId}\" not found`));\n\t\t}\n\t\tawait throwResponseError(response, i18n._(msg`Failed to fetch plugin`));\n\t}\n\tconst result = await parseApiResponse<{ item: PluginInfo }>(\n\t\tresponse,\n\t\ti18n._(msg`Failed to fetch plugin`),\n\t);\n\treturn result.item;\n}\n\n// ── Plugin settings (auto-generated from settingsSchema) ─────────\n\ninterface BaseSettingField {\n\tlabel: string;\n\tdescription?: string;\n}\n\nexport type SettingField =\n\t| (BaseSettingField & { type: \"string\"; default?: string; multiline?: boolean })\n\t| (BaseSettingField & { type: \"number\"; default?: number; min?: number; max?: number })\n\t| (BaseSettingField & { type: \"boolean\"; default?: boolean })\n\t| (BaseSettingField & {\n\t\t\ttype: \"select\";\n\t\t\toptions: Array<{ value: string; label: string }>;\n\t\t\tdefault?: string;\n\t })\n\t| (BaseSettingField & { type: \"secret\" })\n\t| (BaseSettingField & { type: \"url\"; default?: string; placeholder?: string })\n\t| (BaseSettingField & { type: \"email\"; default?: string; placeholder?: string });\n\nexport interface PluginSettingsResponse {\n\tschema: Record<string, SettingField>;\n\t/** Current values; secret fields are never included (see secretsSet) */\n\tvalues: Record<string, unknown>;\n\t/** For secret fields: whether a value is currently stored */\n\tsecretsSet: Record<string, boolean>;\n}\n\n/**\n * Fetch a plugin's settings schema and current values\n */\nexport async function fetchPluginSettings(pluginId: string): Promise<PluginSettingsResponse> {\n\tconst response = await apiFetch(`${API_BASE}/admin/plugins/${pluginId}/settings`);\n\treturn parseApiResponse<PluginSettingsResponse>(\n\t\tresponse,\n\t\ti18n._(msg`Failed to fetch plugin settings`),\n\t);\n}\n\n/**\n * Update a plugin's settings. Only keys present in `values` are written;\n * `null` clears a stored value (reverting to the schema default).\n */\nexport async function updatePluginSettings(\n\tpluginId: string,\n\tvalues: Record<string, unknown>,\n): Promise<PluginSettingsResponse> {\n\tconst response = await apiFetch(`${API_BASE}/admin/plugins/${pluginId}/settings`, {\n\t\tmethod: \"PUT\",\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tbody: JSON.stringify({ values }),\n\t});\n\treturn parseApiResponse<PluginSettingsResponse>(\n\t\tresponse,\n\t\ti18n._(msg`Failed to update plugin settings`),\n\t);\n}\n\n/**\n * Enable a plugin\n */\nexport async function enablePlugin(pluginId: string): Promise<PluginInfo> {\n\tconst response = await apiFetch(`${API_BASE}/admin/plugins/${pluginId}/enable`, {\n\t\tmethod: \"POST\",\n\t});\n\tconst result = await parseApiResponse<{ item: PluginInfo }>(\n\t\tresponse,\n\t\ti18n._(msg`Failed to enable plugin`),\n\t);\n\treturn result.item;\n}\n\n/**\n * Disable a plugin\n */\nexport async function disablePlugin(pluginId: string): Promise<PluginInfo> {\n\tconst response = await apiFetch(`${API_BASE}/admin/plugins/${pluginId}/disable`, {\n\t\tmethod: \"POST\",\n\t});\n\tconst result = await parseApiResponse<{ item: PluginInfo }>(\n\t\tresponse,\n\t\ti18n._(msg`Failed to disable plugin`),\n\t);\n\treturn result.item;\n}\n\nexport async function setPluginMcpEnabled(pluginId: string, enabled: boolean): Promise<void> {\n\tconst response = await apiFetch(`${API_BASE}/admin/plugins/${pluginId}/mcp`, {\n\t\tmethod: \"PUT\",\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tbody: JSON.stringify({ enabled }),\n\t});\n\tif (!response.ok) {\n\t\tawait throwResponseError(response, i18n._(msg`Failed to update plugin MCP access`));\n\t}\n}\n"],"mappings":"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"}
|