@nexpress/core 0.1.0 → 0.1.2
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/{chunk-DP2PREDU.js → chunk-3OFDMYTX.js} +2 -2
- package/dist/{chunk-ISLYFQWL.js → chunk-52UDBKVT.js} +6 -6
- package/dist/{chunk-XPVQIHAQ.js → chunk-63NNIJUF.js} +2 -2
- package/dist/{chunk-VGTPQXNQ.js → chunk-EJ2PIRZ6.js} +5 -5
- package/dist/{chunk-6YI5K2TI.js → chunk-VRBY465W.js} +2 -2
- package/dist/{chunk-QO7LAQZH.js → chunk-WBUFA53S.js} +8 -7
- package/dist/chunk-WBUFA53S.js.map +1 -0
- package/dist/community.js +3 -3
- package/dist/{config-2GDU7PCK.js → config-GCDRLEFE.js} +3 -3
- package/dist/{host-OBOI4MJK.js → host-NBWL65F5.js} +2 -2
- package/dist/index.js +6 -6
- package/dist/jobs.js +1 -1
- package/dist/{scheduled-CIQM57HT.js → scheduled-7P7S5X64.js} +3 -3
- package/dist/seo.js +2 -2
- package/package.json +4 -4
- package/dist/chunk-QO7LAQZH.js.map +0 -1
- /package/dist/{chunk-DP2PREDU.js.map → chunk-3OFDMYTX.js.map} +0 -0
- /package/dist/{chunk-ISLYFQWL.js.map → chunk-52UDBKVT.js.map} +0 -0
- /package/dist/{chunk-XPVQIHAQ.js.map → chunk-63NNIJUF.js.map} +0 -0
- /package/dist/{chunk-VGTPQXNQ.js.map → chunk-EJ2PIRZ6.js.map} +0 -0
- /package/dist/{chunk-6YI5K2TI.js.map → chunk-VRBY465W.js.map} +0 -0
- /package/dist/{config-2GDU7PCK.js.map → config-GCDRLEFE.js.map} +0 -0
- /package/dist/{host-OBOI4MJK.js.map → host-NBWL65F5.js.map} +0 -0
- /package/dist/{scheduled-CIQM57HT.js.map → scheduled-7P7S5X64.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
findDocuments
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EJ2PIRZ6.js";
|
|
4
4
|
import {
|
|
5
5
|
getI18nConfig
|
|
6
6
|
} from "./chunk-4ZLMEKFX.js";
|
|
@@ -594,4 +594,4 @@ export {
|
|
|
594
594
|
buildDiscussionForumPostingJsonLd,
|
|
595
595
|
buildPersonJsonLd
|
|
596
596
|
};
|
|
597
|
-
//# sourceMappingURL=chunk-
|
|
597
|
+
//# sourceMappingURL=chunk-3OFDMYTX.js.map
|
|
@@ -145,7 +145,7 @@ function registerBuiltinHandlers() {
|
|
|
145
145
|
registerJobHandler("notifications:sendDigest", handleNotificationsSendDigest);
|
|
146
146
|
}
|
|
147
147
|
async function handleContentPublishScheduled(_) {
|
|
148
|
-
const { publishScheduledDocuments } = await import("./scheduled-
|
|
148
|
+
const { publishScheduledDocuments } = await import("./scheduled-7P7S5X64.js");
|
|
149
149
|
const result = await publishScheduledDocuments();
|
|
150
150
|
if (result.published > 0) {
|
|
151
151
|
console.info(
|
|
@@ -193,7 +193,7 @@ async function handleMediaCleanup(data) {
|
|
|
193
193
|
async function handlePluginScheduledTask(data) {
|
|
194
194
|
if (isRecord(data) && typeof data.pluginId === "string" && typeof data.taskId === "string") {
|
|
195
195
|
try {
|
|
196
|
-
const { runPluginScheduledTask } = await import("./host-
|
|
196
|
+
const { runPluginScheduledTask } = await import("./host-NBWL65F5.js");
|
|
197
197
|
await runPluginScheduledTask(data.pluginId, data.taskId);
|
|
198
198
|
return;
|
|
199
199
|
} catch (err) {
|
|
@@ -673,7 +673,7 @@ var PgBossAdapter = class {
|
|
|
673
673
|
this.workRegistrations.push({ queueName, register });
|
|
674
674
|
await register();
|
|
675
675
|
}
|
|
676
|
-
const { getRegisteredPluginSchedules, runPluginScheduledTask } = await import("./host-
|
|
676
|
+
const { getRegisteredPluginSchedules, runPluginScheduledTask } = await import("./host-NBWL65F5.js");
|
|
677
677
|
for (const schedule of getRegisteredPluginSchedules()) {
|
|
678
678
|
const queueName = `${toQueueName("plugin:scheduledTask")}.${schedule.pluginId}.${schedule.taskId}`;
|
|
679
679
|
await this.boss.createQueue(queueName);
|
|
@@ -775,7 +775,7 @@ var PgBossAdapter = class {
|
|
|
775
775
|
});
|
|
776
776
|
await this.boss.schedule(digestQueue, "0 8 * * *", { cadence: "daily" }, { key: "daily" });
|
|
777
777
|
await this.boss.schedule(digestQueue, "0 8 * * 1", { cadence: "weekly" }, { key: "weekly" });
|
|
778
|
-
const { getRegisteredPluginSchedules } = await import("./host-
|
|
778
|
+
const { getRegisteredPluginSchedules } = await import("./host-NBWL65F5.js");
|
|
779
779
|
for (const schedule of getRegisteredPluginSchedules()) {
|
|
780
780
|
const pgBossName = `${toQueueName("plugin:scheduledTask")}.${schedule.pluginId}.${schedule.taskId}`;
|
|
781
781
|
await this.boss.schedule(pgBossName, schedule.cron, {
|
|
@@ -939,7 +939,7 @@ var PgBossAdapter = class {
|
|
|
939
939
|
* `workerOwnsRegistrations` so the admin UI can warn the operator.
|
|
940
940
|
*/
|
|
941
941
|
async reconcilePluginSchedules() {
|
|
942
|
-
const { getRegisteredPluginSchedules } = await import("./host-
|
|
942
|
+
const { getRegisteredPluginSchedules } = await import("./host-NBWL65F5.js");
|
|
943
943
|
const wantedList = getRegisteredPluginSchedules();
|
|
944
944
|
const wantedByName = /* @__PURE__ */ new Map();
|
|
945
945
|
for (const schedule of wantedList) {
|
|
@@ -1267,4 +1267,4 @@ export {
|
|
|
1267
1267
|
stopWorker,
|
|
1268
1268
|
stopProducer
|
|
1269
1269
|
};
|
|
1270
|
-
//# sourceMappingURL=chunk-
|
|
1270
|
+
//# sourceMappingURL=chunk-52UDBKVT.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runHook
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EJ2PIRZ6.js";
|
|
4
4
|
import {
|
|
5
5
|
getAllCollectionSlugs,
|
|
6
6
|
getCollectionConfig,
|
|
@@ -80,4 +80,4 @@ async function publishScheduledDocuments(atTime = /* @__PURE__ */ new Date()) {
|
|
|
80
80
|
export {
|
|
81
81
|
publishScheduledDocuments
|
|
82
82
|
};
|
|
83
|
-
//# sourceMappingURL=chunk-
|
|
83
|
+
//# sourceMappingURL=chunk-63NNIJUF.js.map
|
|
@@ -1885,7 +1885,7 @@ function createPluginRuntimeContext(options) {
|
|
|
1885
1885
|
return row.value;
|
|
1886
1886
|
},
|
|
1887
1887
|
async getPlugin() {
|
|
1888
|
-
const { getPluginConfig } = await import("./config-
|
|
1888
|
+
const { getPluginConfig } = await import("./config-GCDRLEFE.js");
|
|
1889
1889
|
const value = await getPluginConfig(pluginId);
|
|
1890
1890
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1891
1891
|
return value;
|
|
@@ -1893,8 +1893,8 @@ function createPluginRuntimeContext(options) {
|
|
|
1893
1893
|
return {};
|
|
1894
1894
|
},
|
|
1895
1895
|
async setPlugin(data) {
|
|
1896
|
-
const { setPluginConfig } = await import("./config-
|
|
1897
|
-
const { getPluginRegistration: getPluginRegistration2 } = await import("./host-
|
|
1896
|
+
const { setPluginConfig } = await import("./config-GCDRLEFE.js");
|
|
1897
|
+
const { getPluginRegistration: getPluginRegistration2 } = await import("./host-NBWL65F5.js");
|
|
1898
1898
|
const reg = getPluginRegistration2(pluginId);
|
|
1899
1899
|
if (reg?.configSchema) {
|
|
1900
1900
|
await setPluginConfig(pluginId, data, null);
|
|
@@ -2275,7 +2275,7 @@ function insertSortedByPriority(list, entry) {
|
|
|
2275
2275
|
list.sort((a, b) => a.priority - b.priority);
|
|
2276
2276
|
}
|
|
2277
2277
|
async function loadPluginConfig(pluginId) {
|
|
2278
|
-
const { getPluginConfig } = await import("./config-
|
|
2278
|
+
const { getPluginConfig } = await import("./config-GCDRLEFE.js");
|
|
2279
2279
|
const value = await getPluginConfig(pluginId);
|
|
2280
2280
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
2281
2281
|
return value;
|
|
@@ -2787,4 +2787,4 @@ export {
|
|
|
2787
2787
|
findDocuments,
|
|
2788
2788
|
getDocumentById
|
|
2789
2789
|
};
|
|
2790
|
-
//# sourceMappingURL=chunk-
|
|
2790
|
+
//# sourceMappingURL=chunk-EJ2PIRZ6.js.map
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
findDocuments,
|
|
27
27
|
getDocumentById,
|
|
28
28
|
saveDocument
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-EJ2PIRZ6.js";
|
|
30
30
|
import {
|
|
31
31
|
can
|
|
32
32
|
} from "./chunk-EQ2Z3KMD.js";
|
|
@@ -1956,4 +1956,4 @@ export {
|
|
|
1956
1956
|
revokeMemberRole,
|
|
1957
1957
|
purgeMemberContent
|
|
1958
1958
|
};
|
|
1959
|
-
//# sourceMappingURL=chunk-
|
|
1959
|
+
//# sourceMappingURL=chunk-VRBY465W.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getPluginRegistration
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EJ2PIRZ6.js";
|
|
4
4
|
import {
|
|
5
5
|
getCurrentSiteId
|
|
6
6
|
} from "./chunk-SBCVAC2Z.js";
|
|
@@ -202,10 +202,7 @@ async function getPluginConfig(pluginId) {
|
|
|
202
202
|
}
|
|
203
203
|
async function getPluginConfigWithStatus(pluginId) {
|
|
204
204
|
const registration = getPluginRegistration(pluginId);
|
|
205
|
-
|
|
206
|
-
return { pluginId, value: {}, hasPersisted: false };
|
|
207
|
-
}
|
|
208
|
-
const schema = registration.configSchema;
|
|
205
|
+
const schema = registration?.configSchema;
|
|
209
206
|
let row;
|
|
210
207
|
try {
|
|
211
208
|
const db = getDb();
|
|
@@ -242,7 +239,11 @@ async function getPluginConfigWithStatus(pluginId) {
|
|
|
242
239
|
const versioned = isVersionedPluginConfig(row.value) ? row.value : null;
|
|
243
240
|
const storedVersion = versioned ? versioned.__npVersion : 1;
|
|
244
241
|
const rawValue = versioned ? versioned.__npSettings : row.value;
|
|
245
|
-
const valueToParse = applyPluginConfigMigration(
|
|
242
|
+
const valueToParse = applyPluginConfigMigration(
|
|
243
|
+
registration ?? { configVersion: 1 },
|
|
244
|
+
rawValue,
|
|
245
|
+
storedVersion
|
|
246
|
+
);
|
|
246
247
|
const parsed = schema.safeParse(valueToParse);
|
|
247
248
|
if (parsed.success) {
|
|
248
249
|
return { pluginId, value: parsed.data, hasPersisted: true };
|
|
@@ -318,4 +319,4 @@ export {
|
|
|
318
319
|
setPluginConfig,
|
|
319
320
|
pluginConfigCacheTag
|
|
320
321
|
};
|
|
321
|
-
//# sourceMappingURL=chunk-
|
|
322
|
+
//# sourceMappingURL=chunk-WBUFA53S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugins/config.ts","../src/themes/settings-schema.ts"],"sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport type { ZodTypeAny } from \"zod\";\n\nimport { getDb } from \"../db/index.js\";\nimport { npSettings } from \"../db/schema/system.js\";\nimport { NpValidationError } from \"../errors.js\";\nimport { getCurrentSiteId } from \"../sites/context.js\";\nimport { getPluginRegistration } from \"./host.js\";\nimport {\n introspectThemeSettingsSchema,\n type NpThemeSettingsField,\n} from \"../themes/settings-schema.js\";\n\nconst DEFAULT_SITE = \"default\";\nconst CONFIG_KEY_PREFIX = \"plugin.config:\";\n\n/**\n * G.1 — per-plugin operator config.\n *\n * Stored at `np_settings.(site_id, key=\"plugin.config:<pluginId>\")`.\n * Mirrors theme settings storage exactly, including the `__npVersion` /\n * `__npSettings` envelope, so a future shared `getCachedSetting<T>(key)`\n * helper can read both surfaces. Cache invalidation rides a new\n * `np:plugin:<id>` tag (see `packages/next/src/cache.ts`).\n *\n * Per locked decision E (`docs/design/plugin-config-auto-form.md` § 2):\n * we store under `np_settings`, NOT `np_plugins.config` (the legacy\n * column was dropped in the same migration that introduced this module).\n */\n\nfunction configKey(pluginId: string): string {\n return `${CONFIG_KEY_PREFIX}${pluginId}`;\n}\n\n/**\n * Versioned envelope shape for persisted plugin config — identical to the\n * theme `NpVersionedSettings` shape. Two parallel definitions instead of a\n * shared one because (a) themes and plugins share zero schema surface\n * otherwise, (b) the type is only ~5 lines, and (c) collapsing them would\n * couple `themes/` and `plugins/` modules without functional benefit.\n */\nexport interface NpVersionedPluginConfig {\n __npVersion: number;\n __npSettings: unknown;\n}\n\nexport function isVersionedPluginConfig(\n value: unknown,\n): value is NpVersionedPluginConfig {\n if (!value || typeof value !== \"object\") return false;\n const candidate = value as Partial<NpVersionedPluginConfig>;\n return (\n typeof candidate.__npVersion === \"number\" &&\n Number.isFinite(candidate.__npVersion) &&\n \"__npSettings\" in candidate\n );\n}\n\n/**\n * Run the plugin's `configMigrate` from `from` to current schema version.\n * No-op when versions match or the plugin doesn't declare a migrator.\n * Defensive try/catch — a buggy migrate fn shouldn't blow up the read\n * path; we fall back to the original value and let `safeParse` decide.\n *\n * Mirrors `applyMigration` in `packages/core/src/themes/settings.ts` line\n * for line.\n */\nexport function applyPluginConfigMigration(\n registration: {\n configVersion?: number;\n configMigrate?: (old: unknown, fromVersion: number) => unknown;\n },\n rawValue: unknown,\n fromVersion: number,\n): unknown {\n const target = registration.configVersion ?? 1;\n if (fromVersion >= target) return rawValue;\n const migrate = registration.configMigrate;\n if (typeof migrate !== \"function\") return rawValue;\n try {\n return migrate(rawValue, fromVersion);\n } catch {\n return rawValue;\n }\n}\n\nfunction defaultsFrom(fields: NpThemeSettingsField[]): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const f of fields) {\n if (f.default !== undefined) {\n out[f.name] = f.default;\n continue;\n }\n if (f.type === \"object\") {\n out[f.name] = defaultsFrom(f.fields);\n }\n if (f.type === \"array\") {\n out[f.name] = [];\n }\n }\n return out;\n}\n\nexport interface NpPluginConfigResult {\n pluginId: string;\n /** Parsed config or schema defaults. Empty object when the plugin has\n * no configSchema. */\n value: unknown;\n /** True when there's a stored row, regardless of whether it passed\n * validation. */\n hasPersisted: boolean;\n /** Set when the persisted value failed `schema.parse()`. The admin\n * surface uses this to render a \"settings were reset\" banner. */\n parseError?: string;\n}\n\n/**\n * Read the persisted config for a plugin and parse it via the plugin's\n * `configSchema`. Returns the parsed value when valid; falls back to\n * schema defaults on parse failure (with the failure recorded for the\n * admin to surface, see `getPluginConfigWithStatus`).\n *\n * Return type is `unknown` because core can't type-narrow to the plugin's\n * `z.infer<typeof configSchema>` — the schema lives in the plugin\n * package, not in core. Plugin code that reads its own config should\n * cast at the call site, ideally against an exported type alias from the\n * plugin package itself:\n *\n * // packages/plugins/oauth-github/src/index.ts\n * export const configSchema = z.object({ ... });\n * export type GithubOauthConfig = z.infer<typeof configSchema>;\n *\n * // a plugin handler\n * const config = (await getPluginConfig(\"oauth-github\")) as GithubOauthConfig;\n */\nexport async function getPluginConfig(pluginId: string): Promise<unknown> {\n const result = await getPluginConfigWithStatus(pluginId);\n return result.value;\n}\n\nexport async function getPluginConfigWithStatus(\n pluginId: string,\n): Promise<NpPluginConfigResult> {\n // Registration is consulted for schema-driven validation + defaults\n // when present, but a missing registration MUST NOT short-circuit the\n // DB read. `ctx.settings.setPlugin` writes to `np_settings` for any\n // pluginId regardless of registration — bailing here would create a\n // read/write asymmetry where stored config silently disappears on\n // read. Treat \"not registered\" the same as \"registered with no\n // schema\": surface the row raw if it exists.\n const registration = getPluginRegistration(pluginId);\n const schema = registration?.configSchema as ZodTypeAny | undefined;\n\n let row: { value: unknown } | undefined;\n try {\n const db = getDb();\n const siteId = (await getCurrentSiteId()) ?? DEFAULT_SITE;\n const rows = (await db\n .select()\n .from(npSettings)\n .where(\n and(eq(npSettings.siteId, siteId), eq(npSettings.key, configKey(pluginId))),\n )\n .limit(1)) as Array<{ value: unknown }>;\n row = rows[0];\n } catch {\n // DB not ready — caller is asking before bootstrap. Return empty\n // shape; treats DB-not-ready the same as \"no row stored yet\".\n return { pluginId, value: schema ? defaultsFromSchema(schema) : {}, hasPersisted: false };\n }\n\n if (!schema) {\n // Plugin doesn't declare a configSchema. If a row exists (legacy\n // hand-coded UI saved into np_settings, or migrated from\n // np_plugins.config), surface it raw — callers can still read it.\n if (!row) {\n return { pluginId, value: {}, hasPersisted: false };\n }\n const versioned = isVersionedPluginConfig(row.value) ? row.value : null;\n const rawValue = versioned ? versioned.__npSettings : row.value;\n return {\n pluginId,\n value: rawValue ?? {},\n hasPersisted: true,\n };\n }\n\n const fields = introspectThemeSettingsSchema(schema);\n const defaults = defaultsFrom(fields);\n\n if (!row) {\n const parsed = schema.safeParse(defaults);\n return {\n pluginId,\n value: parsed.success ? parsed.data : defaults,\n hasPersisted: false,\n };\n }\n\n // Versioned envelope detection + lazy migration. Mirrors\n // `getThemeSettingsWithStatus` exactly. Registration is guaranteed\n // defined here: schema is only truthy when registration exists\n // (line ~152), and the `if (!schema) return` above narrows the rest\n // of the function — but TS can't infer that across `?.` so we\n // restate it for the migration helper.\n const versioned = isVersionedPluginConfig(row.value) ? row.value : null;\n const storedVersion = versioned ? versioned.__npVersion : 1;\n const rawValue = versioned ? versioned.__npSettings : row.value;\n const valueToParse = applyPluginConfigMigration(\n registration ?? { configVersion: 1 },\n rawValue,\n storedVersion,\n );\n\n const parsed = schema.safeParse(valueToParse);\n if (parsed.success) {\n return { pluginId, value: parsed.data, hasPersisted: true };\n }\n\n return {\n pluginId,\n value: defaults,\n hasPersisted: true,\n parseError: parsed.error.message,\n };\n}\n\nfunction defaultsFromSchema(schema: ZodTypeAny): Record<string, unknown> {\n return defaultsFrom(introspectThemeSettingsSchema(schema));\n}\n\n/**\n * Validate and persist a plugin's config. Throws `NpValidationError` when\n * `value` doesn't pass the schema — the admin form must surface\n * field-level errors before calling this.\n *\n * **Cache invalidation is the caller's responsibility.** This function\n * writes to `np_settings` only; it doesn't import `next/cache`. The\n * admin API route (`PUT /api/admin/plugins/[id]/config`) busts\n * `np:plugin:<id>` after a successful write.\n *\n * Mirrors `setThemeSettings` in `packages/core/src/themes/settings.ts`.\n */\nexport async function setPluginConfig(\n pluginId: string,\n value: unknown,\n updatedBy: string | null = null,\n): Promise<unknown> {\n const registration = getPluginRegistration(pluginId);\n if (!registration) {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"pluginId\",\n message: `Unknown plugin '${pluginId}'. Register it in nexpress.config.ts first.`,\n },\n ]);\n }\n const schema = registration.configSchema as ZodTypeAny | undefined;\n if (!schema) {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"pluginId\",\n message: `Plugin '${pluginId}' does not declare a configSchema.`,\n },\n ]);\n }\n\n const parsed = schema.safeParse(value);\n if (!parsed.success) {\n throw new NpValidationError(\n \"Config failed validation\",\n parsed.error.issues.map((i) => ({\n field: i.path.join(\".\"),\n message: i.message,\n })),\n );\n }\n\n const wrapped: NpVersionedPluginConfig = {\n __npVersion: registration.configVersion ?? 1,\n __npSettings: parsed.data,\n };\n\n const db = getDb();\n const now = new Date();\n const siteId = (await getCurrentSiteId()) ?? DEFAULT_SITE;\n await db\n .insert(npSettings)\n .values({\n siteId,\n key: configKey(pluginId),\n value: wrapped,\n updatedAt: now,\n updatedBy,\n })\n .onConflictDoUpdate({\n target: [npSettings.siteId, npSettings.key],\n set: { value: wrapped, updatedAt: now, updatedBy },\n });\n\n return parsed.data;\n}\n\n/** Cache tag for a plugin's config invalidation. Per the prefix policy\n * in CLAUDE.md (Naming convention table) every framework-owned tag\n * uses the `np` prefix. Distinct from the legacy `nx:theme:<siteId>`\n * tag — see `docs/design/plugin-config-auto-form.md` § 7. */\nexport function pluginConfigCacheTag(pluginId: string): string {\n return `np:plugin:${pluginId}`;\n}\n","import type { ZodTypeAny } from \"zod\";\n\n/**\n * Phase F.3 — server-side introspection of a theme's\n * `settingsSchema` Zod tree into a JSON metadata shape the\n * admin form generator consumes.\n *\n * The schema lives in the theme package (server bundle); we\n * don't ship the schema itself to the browser. Instead, this\n * function walks the tree once on the server, emits the\n * metadata as plain JSON, and the admin renders form fields\n * from the metadata. The browser doesn't need zod at runtime.\n *\n * Coverage in v0.2: text, url, color (regex heuristic), number,\n * boolean, enum, array(object), object. Anything else\n * introspects as `{ type: \"unsupported\" }` so the form generator\n * can render a JSON textarea fallback (operator can still edit;\n * a follow-up phase widens coverage).\n */\n\nexport type NpThemeSettingsField =\n | NpThemeSettingsTextField\n | NpThemeSettingsTextareaField\n | NpThemeSettingsPasswordField\n | NpThemeSettingsUrlField\n | NpThemeSettingsColorField\n | NpThemeSettingsNumberField\n | NpThemeSettingsBooleanField\n | NpThemeSettingsEnumField\n | NpThemeSettingsArrayField\n | NpThemeSettingsStringArrayField\n | NpThemeSettingsObjectField\n | NpThemeSettingsUnsupportedField;\n\ninterface NpThemeSettingsFieldBase {\n /** Field path key (\"hero\", \"social.0.url\", etc. — the\n * introspector returns flat keys per node; nested objects\n * carry their own children). */\n name: string;\n label?: string;\n description?: string;\n required: boolean;\n default?: unknown;\n}\n\nexport interface NpThemeSettingsTextField extends NpThemeSettingsFieldBase {\n type: \"text\";\n}\n\nexport interface NpThemeSettingsTextareaField extends NpThemeSettingsFieldBase {\n type: \"textarea\";\n /** Optional row count hint for the rendered `<textarea>`.\n * Theme authors set this via `.meta({ widget: \"textarea\",\n * rows: 6 })`. Defaults to 4 when unset. */\n rows?: number;\n}\n\nexport interface NpThemeSettingsPasswordField extends NpThemeSettingsFieldBase {\n type: \"password\";\n}\n\nexport interface NpThemeSettingsUrlField extends NpThemeSettingsFieldBase {\n type: \"url\";\n}\n\nexport interface NpThemeSettingsColorField extends NpThemeSettingsFieldBase {\n type: \"color\";\n}\n\nexport interface NpThemeSettingsNumberField extends NpThemeSettingsFieldBase {\n type: \"number\";\n int?: boolean;\n min?: number;\n max?: number;\n}\n\nexport interface NpThemeSettingsBooleanField extends NpThemeSettingsFieldBase {\n type: \"boolean\";\n}\n\nexport interface NpThemeSettingsEnumField extends NpThemeSettingsFieldBase {\n type: \"enum\";\n options: string[];\n}\n\nexport interface NpThemeSettingsArrayField extends NpThemeSettingsFieldBase {\n type: \"array\";\n /** v0.2 supports `z.array(z.object(...))`. The element\n * schema introspects as the array's child fields. */\n element: NpThemeSettingsField[];\n}\n\n/** Phase G follow-up — `z.array(z.string())`. Renders as a\n * one-item-per-line input. Surfaced for OAuth scopes and\n * similar string-list configs that don't fit the object-array\n * shape; previously fell through to the JSON-textarea\n * `unsupported` fallback. */\nexport interface NpThemeSettingsStringArrayField extends NpThemeSettingsFieldBase {\n type: \"string-array\";\n}\n\nexport interface NpThemeSettingsObjectField extends NpThemeSettingsFieldBase {\n type: \"object\";\n fields: NpThemeSettingsField[];\n}\n\nexport interface NpThemeSettingsUnsupportedField extends NpThemeSettingsFieldBase {\n type: \"unsupported\";\n /** Best-effort label for what was at this position so\n * operators can recognize their schema in the JSON fallback. */\n zodTypeName: string;\n}\n\n// Heuristic: regex sources that look like a hex color check.\n// We test against the regex `source` string (no flags, no\n// surrounding slashes), so e.g. `/^#[0-9a-f]{6}$/i` arrives\n// as `^#[0-9a-f]{6}$`. Matches both 6-digit and 3-to-8 digit\n// variants, case sensitivity-agnostic via the `i` flag on\n// the heuristic itself.\nconst COLOR_REGEX_PATTERNS = [\n /^\\^#\\[0-9a-f\\]\\{6\\}\\$$/i,\n /^\\^#\\[0-9a-f\\]\\{3,8\\}\\$$/i,\n /^\\^#\\[\\\\da-f\\]\\{6\\}\\$$/i,\n];\n\ninterface ZodCheck {\n _zod?: { def?: { format?: string; pattern?: { source: string }; check?: string; value?: number } };\n}\n\ninterface ZodDef {\n type: string;\n innerType?: { _def: ZodDef };\n defaultValue?: unknown;\n description?: string;\n shape?: Record<string, { _def: ZodDef; description?: string }>;\n entries?: Record<string, string>;\n element?: { _def: ZodDef };\n checks?: ZodCheck[];\n}\n\ninterface ZodNode {\n _def: ZodDef;\n description?: string;\n shape?: Record<string, ZodNode>;\n}\n\n/**\n * Strip `default` / `optional` / `nullable` wrappers, returning\n * the inner schema, the resolved default value, and whether\n * the field is required (i.e. neither optional nor nullable).\n */\nfunction unwrap(node: ZodNode): {\n inner: ZodNode;\n defaultValue: unknown;\n required: boolean;\n} {\n let current = node;\n let defaultValue: unknown = undefined;\n let required = true;\n\n while (true) {\n const t = current._def.type;\n if (t === \"default\") {\n defaultValue =\n typeof current._def.defaultValue === \"function\"\n ? (current._def.defaultValue as () => unknown)()\n : current._def.defaultValue;\n current = (current._def.innerType as ZodNode | undefined) ?? current;\n if (!current._def.innerType) break;\n continue;\n }\n if (t === \"optional\" || t === \"nullable\") {\n required = false;\n const next = current._def.innerType as ZodNode | undefined;\n if (!next) break;\n current = next;\n continue;\n }\n break;\n }\n\n return { inner: current, defaultValue, required };\n}\n\nfunction detectStringFormat(\n checks: ZodCheck[] | undefined,\n): \"url\" | \"color\" | \"text\" {\n if (!checks) return \"text\";\n for (const c of checks) {\n const fmt = c._zod?.def?.format;\n if (fmt === \"url\") return \"url\";\n if (fmt === \"regex\") {\n const src = c._zod?.def?.pattern?.source;\n if (src && COLOR_REGEX_PATTERNS.some((p) => p.test(src))) {\n return \"color\";\n }\n }\n }\n return \"text\";\n}\n\n/**\n * Phase F.3 follow-up — pull `.meta()` off a Zod node when\n * present. Used to read theme-author hints like\n * `{ widget: \"textarea\", rows: 6 }` that don't fit Zod's\n * narrow widget matrix (z.string() has no textarea variant\n * built in).\n */\nfunction readMeta(node: ZodNode): Record<string, unknown> | undefined {\n const fn = (node as unknown as { meta?: () => unknown }).meta;\n if (typeof fn !== \"function\") return undefined;\n const out = fn.call(node);\n return out && typeof out === \"object\" ? (out as Record<string, unknown>) : undefined;\n}\n\nfunction detectNumberConstraints(\n checks: ZodCheck[] | undefined,\n): { int?: boolean; min?: number; max?: number } {\n const out: { int?: boolean; min?: number; max?: number } = {};\n if (!checks) return out;\n for (const c of checks) {\n const def = c._zod?.def;\n if (!def) continue;\n if (def.format === \"safeint\" || def.check === \"int\") out.int = true;\n if (def.check === \"greater_than\" && typeof def.value === \"number\")\n out.min = def.value;\n if (def.check === \"less_than\" && typeof def.value === \"number\")\n out.max = def.value;\n }\n return out;\n}\n\nfunction introspectField(\n name: string,\n node: ZodNode,\n): NpThemeSettingsField {\n const description = node.description;\n const { inner, defaultValue, required } = unwrap(node);\n const innerDef = inner._def;\n const base: NpThemeSettingsFieldBase = {\n name,\n description,\n label: description,\n required,\n default: defaultValue,\n };\n\n switch (innerDef.type) {\n case \"string\": {\n // Phase F.3 follow-up — `.meta({ widget: \"textarea\" })`\n // opts a `z.string()` into multi-line rendering. Theme\n // authors pair it with `.describe()` for the field\n // label; row count is optional (defaults to 4).\n //\n // Check `node` (outer) first then `inner` because Zod v4's\n // `.meta()` returns a new instance, so the meta lives at\n // whichever level the author called .meta() at:\n //\n // z.string().meta({...}).optional() → meta on inner string\n // z.string().optional().meta({...}) → meta on outer optional\n //\n // Both patterns are valid in author code; both should work.\n const meta = readMeta(node) ?? readMeta(inner);\n if (meta && meta.sensitive === true) {\n return { ...base, type: \"password\" };\n }\n if (meta && meta.widget === \"textarea\") {\n const rows =\n typeof meta.rows === \"number\" && meta.rows > 0\n ? meta.rows\n : undefined;\n return {\n ...base,\n type: \"textarea\",\n ...(rows !== undefined ? { rows } : {}),\n };\n }\n const fmt = detectStringFormat(innerDef.checks);\n return { ...base, type: fmt };\n }\n case \"number\": {\n const c = detectNumberConstraints(innerDef.checks);\n return { ...base, type: \"number\", ...c };\n }\n case \"boolean\":\n return { ...base, type: \"boolean\" };\n case \"enum\": {\n const entries = innerDef.entries ?? {};\n return { ...base, type: \"enum\", options: Object.values(entries) };\n }\n case \"array\": {\n const element = innerDef.element as ZodNode | undefined;\n // v0.2 supports z.array(z.object(...)) — typed nested form\n // for each item.\n if (element?._def.type === \"object\" && element._def.shape) {\n const childFields = introspectShape(element._def.shape);\n return { ...base, type: \"array\", element: childFields };\n }\n // Phase G follow-up — z.array(z.string()) gets a dedicated\n // string-array widget (one item per line). Surfaced for\n // OAuth scopes and similar string-list configs.\n if (element?._def.type === \"string\") {\n return { ...base, type: \"string-array\" };\n }\n return { ...base, type: \"unsupported\", zodTypeName: \"array\" };\n }\n case \"object\": {\n const shape = innerDef.shape;\n if (shape) {\n return { ...base, type: \"object\", fields: introspectShape(shape) };\n }\n return { ...base, type: \"unsupported\", zodTypeName: \"object\" };\n }\n default:\n return {\n ...base,\n type: \"unsupported\",\n zodTypeName: innerDef.type ?? \"unknown\",\n };\n }\n}\n\nfunction introspectShape(\n shape: Record<string, { _def: ZodDef; description?: string }>,\n): NpThemeSettingsField[] {\n const out: NpThemeSettingsField[] = [];\n for (const [name, raw] of Object.entries(shape)) {\n out.push(introspectField(name, raw as ZodNode));\n }\n return out;\n}\n\n/**\n * Walk a theme's `settingsSchema` (top-level z.object) and emit\n * the form metadata. Returns an empty array when the schema\n * isn't a top-level object — themes are expected to ship\n * `settingsSchema: z.object({...})` (validated implicitly: a\n * non-object top schema yields an empty form, signalling\n * \"nothing to configure\").\n */\nexport function introspectThemeSettingsSchema(\n schema: ZodTypeAny | undefined,\n): NpThemeSettingsField[] {\n if (!schema) return [];\n // Strip any top-level default/optional/nullable wrapper before\n // checking for object shape — themes that wrap their whole\n // schema in `.default({...})` are unusual but valid; without\n // unwrap we'd silently render an empty form.\n const { inner } = unwrap(schema as unknown as ZodNode);\n if (inner._def.type !== \"object\" || !inner._def.shape) return [];\n return introspectShape(inner._def.shape);\n}\n"],"mappings":"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","names":["versioned","rawValue","parsed"]}
|
package/dist/community.js
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
unfollow,
|
|
34
34
|
unresolvedReportCount,
|
|
35
35
|
updateComment
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-VRBY465W.js";
|
|
37
37
|
import {
|
|
38
38
|
buildDigestEmail,
|
|
39
39
|
runDigestSweep
|
|
@@ -83,7 +83,7 @@ import {
|
|
|
83
83
|
resetProfanityAdapter,
|
|
84
84
|
setProfanityAdapter
|
|
85
85
|
} from "./chunk-KU5M27ZC.js";
|
|
86
|
-
import "./chunk-
|
|
86
|
+
import "./chunk-63NNIJUF.js";
|
|
87
87
|
import {
|
|
88
88
|
assertNotBanned,
|
|
89
89
|
getCommunityRole,
|
|
@@ -93,7 +93,7 @@ import {
|
|
|
93
93
|
resetCommunityRoles,
|
|
94
94
|
withMemberWrite
|
|
95
95
|
} from "./chunk-55FU6WED.js";
|
|
96
|
-
import "./chunk-
|
|
96
|
+
import "./chunk-EJ2PIRZ6.js";
|
|
97
97
|
import "./chunk-EQ2Z3KMD.js";
|
|
98
98
|
import {
|
|
99
99
|
listAuditEvents,
|
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
isVersionedPluginConfig,
|
|
6
6
|
pluginConfigCacheTag,
|
|
7
7
|
setPluginConfig
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-WBUFA53S.js";
|
|
9
|
+
import "./chunk-EJ2PIRZ6.js";
|
|
10
10
|
import "./chunk-TFJ4MKPH.js";
|
|
11
11
|
import "./chunk-PPAS4SZR.js";
|
|
12
12
|
import "./chunk-4ZLMEKFX.js";
|
|
@@ -29,4 +29,4 @@ export {
|
|
|
29
29
|
pluginConfigCacheTag,
|
|
30
30
|
setPluginConfig
|
|
31
31
|
};
|
|
32
|
-
//# sourceMappingURL=config-
|
|
32
|
+
//# sourceMappingURL=config-GCDRLEFE.js.map
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
runHookAndCollect,
|
|
17
17
|
runPluginScheduledTask,
|
|
18
18
|
schedulePluginTask
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-EJ2PIRZ6.js";
|
|
20
20
|
import "./chunk-4ZLMEKFX.js";
|
|
21
21
|
import "./chunk-FZ7O6DWI.js";
|
|
22
22
|
import "./chunk-SBCVAC2Z.js";
|
|
@@ -48,4 +48,4 @@ export {
|
|
|
48
48
|
runPluginScheduledTask,
|
|
49
49
|
schedulePluginTask
|
|
50
50
|
};
|
|
51
|
-
//# sourceMappingURL=host-
|
|
51
|
+
//# sourceMappingURL=host-NBWL65F5.js.map
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
introspectThemeSettingsSchema,
|
|
5
5
|
pluginConfigCacheTag,
|
|
6
6
|
setPluginConfig
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-WBUFA53S.js";
|
|
8
8
|
import {
|
|
9
9
|
getPluginTemplatesForCollection,
|
|
10
10
|
registerPluginTemplates,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
renderSitemapIndexXml,
|
|
39
39
|
renderSitemapXml,
|
|
40
40
|
validateSeoSettingsPatch
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-3OFDMYTX.js";
|
|
42
42
|
import {
|
|
43
43
|
ARGON2_OPTIONS,
|
|
44
44
|
authenticated,
|
|
@@ -126,7 +126,7 @@ import {
|
|
|
126
126
|
unfollow,
|
|
127
127
|
unresolvedReportCount,
|
|
128
128
|
updateComment
|
|
129
|
-
} from "./chunk-
|
|
129
|
+
} from "./chunk-VRBY465W.js";
|
|
130
130
|
import {
|
|
131
131
|
buildDigestEmail,
|
|
132
132
|
runDigestSweep
|
|
@@ -178,7 +178,7 @@ import {
|
|
|
178
178
|
} from "./chunk-KU5M27ZC.js";
|
|
179
179
|
import {
|
|
180
180
|
publishScheduledDocuments
|
|
181
|
-
} from "./chunk-
|
|
181
|
+
} from "./chunk-63NNIJUF.js";
|
|
182
182
|
import {
|
|
183
183
|
assertNotBanned,
|
|
184
184
|
getCommunityRole,
|
|
@@ -223,7 +223,7 @@ import {
|
|
|
223
223
|
saveDocument,
|
|
224
224
|
schedulePluginTask,
|
|
225
225
|
updateMemberDocument
|
|
226
|
-
} from "./chunk-
|
|
226
|
+
} from "./chunk-EJ2PIRZ6.js";
|
|
227
227
|
import {
|
|
228
228
|
can
|
|
229
229
|
} from "./chunk-EQ2Z3KMD.js";
|
|
@@ -342,7 +342,7 @@ import {
|
|
|
342
342
|
startWorker,
|
|
343
343
|
stopProducer,
|
|
344
344
|
stopWorker
|
|
345
|
-
} from "./chunk-
|
|
345
|
+
} from "./chunk-52UDBKVT.js";
|
|
346
346
|
import {
|
|
347
347
|
DEFAULT_JOB_LOG_RETENTION_MS,
|
|
348
348
|
countJobLogs,
|
package/dist/jobs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
publishScheduledDocuments
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-63NNIJUF.js";
|
|
4
|
+
import "./chunk-EJ2PIRZ6.js";
|
|
5
5
|
import "./chunk-4ZLMEKFX.js";
|
|
6
6
|
import "./chunk-FZ7O6DWI.js";
|
|
7
7
|
import "./chunk-SBCVAC2Z.js";
|
|
@@ -17,4 +17,4 @@ import "./chunk-PZ5AY32C.js";
|
|
|
17
17
|
export {
|
|
18
18
|
publishScheduledDocuments
|
|
19
19
|
};
|
|
20
|
-
//# sourceMappingURL=scheduled-
|
|
20
|
+
//# sourceMappingURL=scheduled-7P7S5X64.js.map
|
package/dist/seo.js
CHANGED
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
renderSitemapIndexXml,
|
|
13
13
|
renderSitemapXml,
|
|
14
14
|
validateSeoSettingsPatch
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-3OFDMYTX.js";
|
|
16
|
+
import "./chunk-EJ2PIRZ6.js";
|
|
17
17
|
import "./chunk-4ZLMEKFX.js";
|
|
18
18
|
import "./chunk-FZ7O6DWI.js";
|
|
19
19
|
import "./chunk-SBCVAC2Z.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexpress/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Server-side core for NexPress — collections pipeline, auth, jobs, media, plugins, observability.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nexpress",
|
|
@@ -103,12 +103,12 @@
|
|
|
103
103
|
"@node-rs/argon2": "^2.0.2",
|
|
104
104
|
"arctic": "^3.7.0",
|
|
105
105
|
"drizzle-orm": "^0.45.2",
|
|
106
|
-
"intl-messageformat": "^11.2.
|
|
106
|
+
"intl-messageformat": "^11.2.4",
|
|
107
107
|
"jose": "^6.2.2",
|
|
108
108
|
"pg": "^8.20.0",
|
|
109
|
-
"pg-boss": "^12.
|
|
109
|
+
"pg-boss": "^12.18.2",
|
|
110
110
|
"sharp": "^0.34.2",
|
|
111
|
-
"zod": "^4.3
|
|
111
|
+
"zod": "^4.4.3"
|
|
112
112
|
},
|
|
113
113
|
"devDependencies": {
|
|
114
114
|
"@types/pg": "^8.20.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugins/config.ts","../src/themes/settings-schema.ts"],"sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport type { ZodTypeAny } from \"zod\";\n\nimport { getDb } from \"../db/index.js\";\nimport { npSettings } from \"../db/schema/system.js\";\nimport { NpValidationError } from \"../errors.js\";\nimport { getCurrentSiteId } from \"../sites/context.js\";\nimport { getPluginRegistration } from \"./host.js\";\nimport {\n introspectThemeSettingsSchema,\n type NpThemeSettingsField,\n} from \"../themes/settings-schema.js\";\n\nconst DEFAULT_SITE = \"default\";\nconst CONFIG_KEY_PREFIX = \"plugin.config:\";\n\n/**\n * G.1 — per-plugin operator config.\n *\n * Stored at `np_settings.(site_id, key=\"plugin.config:<pluginId>\")`.\n * Mirrors theme settings storage exactly, including the `__npVersion` /\n * `__npSettings` envelope, so a future shared `getCachedSetting<T>(key)`\n * helper can read both surfaces. Cache invalidation rides a new\n * `np:plugin:<id>` tag (see `packages/next/src/cache.ts`).\n *\n * Per locked decision E (`docs/design/plugin-config-auto-form.md` § 2):\n * we store under `np_settings`, NOT `np_plugins.config` (the legacy\n * column was dropped in the same migration that introduced this module).\n */\n\nfunction configKey(pluginId: string): string {\n return `${CONFIG_KEY_PREFIX}${pluginId}`;\n}\n\n/**\n * Versioned envelope shape for persisted plugin config — identical to the\n * theme `NpVersionedSettings` shape. Two parallel definitions instead of a\n * shared one because (a) themes and plugins share zero schema surface\n * otherwise, (b) the type is only ~5 lines, and (c) collapsing them would\n * couple `themes/` and `plugins/` modules without functional benefit.\n */\nexport interface NpVersionedPluginConfig {\n __npVersion: number;\n __npSettings: unknown;\n}\n\nexport function isVersionedPluginConfig(\n value: unknown,\n): value is NpVersionedPluginConfig {\n if (!value || typeof value !== \"object\") return false;\n const candidate = value as Partial<NpVersionedPluginConfig>;\n return (\n typeof candidate.__npVersion === \"number\" &&\n Number.isFinite(candidate.__npVersion) &&\n \"__npSettings\" in candidate\n );\n}\n\n/**\n * Run the plugin's `configMigrate` from `from` to current schema version.\n * No-op when versions match or the plugin doesn't declare a migrator.\n * Defensive try/catch — a buggy migrate fn shouldn't blow up the read\n * path; we fall back to the original value and let `safeParse` decide.\n *\n * Mirrors `applyMigration` in `packages/core/src/themes/settings.ts` line\n * for line.\n */\nexport function applyPluginConfigMigration(\n registration: {\n configVersion?: number;\n configMigrate?: (old: unknown, fromVersion: number) => unknown;\n },\n rawValue: unknown,\n fromVersion: number,\n): unknown {\n const target = registration.configVersion ?? 1;\n if (fromVersion >= target) return rawValue;\n const migrate = registration.configMigrate;\n if (typeof migrate !== \"function\") return rawValue;\n try {\n return migrate(rawValue, fromVersion);\n } catch {\n return rawValue;\n }\n}\n\nfunction defaultsFrom(fields: NpThemeSettingsField[]): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const f of fields) {\n if (f.default !== undefined) {\n out[f.name] = f.default;\n continue;\n }\n if (f.type === \"object\") {\n out[f.name] = defaultsFrom(f.fields);\n }\n if (f.type === \"array\") {\n out[f.name] = [];\n }\n }\n return out;\n}\n\nexport interface NpPluginConfigResult {\n pluginId: string;\n /** Parsed config or schema defaults. Empty object when the plugin has\n * no configSchema. */\n value: unknown;\n /** True when there's a stored row, regardless of whether it passed\n * validation. */\n hasPersisted: boolean;\n /** Set when the persisted value failed `schema.parse()`. The admin\n * surface uses this to render a \"settings were reset\" banner. */\n parseError?: string;\n}\n\n/**\n * Read the persisted config for a plugin and parse it via the plugin's\n * `configSchema`. Returns the parsed value when valid; falls back to\n * schema defaults on parse failure (with the failure recorded for the\n * admin to surface, see `getPluginConfigWithStatus`).\n *\n * Return type is `unknown` because core can't type-narrow to the plugin's\n * `z.infer<typeof configSchema>` — the schema lives in the plugin\n * package, not in core. Plugin code that reads its own config should\n * cast at the call site, ideally against an exported type alias from the\n * plugin package itself:\n *\n * // packages/plugins/oauth-github/src/index.ts\n * export const configSchema = z.object({ ... });\n * export type GithubOauthConfig = z.infer<typeof configSchema>;\n *\n * // a plugin handler\n * const config = (await getPluginConfig(\"oauth-github\")) as GithubOauthConfig;\n */\nexport async function getPluginConfig(pluginId: string): Promise<unknown> {\n const result = await getPluginConfigWithStatus(pluginId);\n return result.value;\n}\n\nexport async function getPluginConfigWithStatus(\n pluginId: string,\n): Promise<NpPluginConfigResult> {\n const registration = getPluginRegistration(pluginId);\n if (!registration) {\n // Plugin not registered — return empty config so callers (plugin\n // hosts iterating contexts, route handlers reading their own config)\n // get a stable shape without having to special-case \"plugin not\n // found\". The admin surface checks registration separately.\n return { pluginId, value: {}, hasPersisted: false };\n }\n const schema = registration.configSchema as ZodTypeAny | undefined;\n\n let row: { value: unknown } | undefined;\n try {\n const db = getDb();\n const siteId = (await getCurrentSiteId()) ?? DEFAULT_SITE;\n const rows = (await db\n .select()\n .from(npSettings)\n .where(\n and(eq(npSettings.siteId, siteId), eq(npSettings.key, configKey(pluginId))),\n )\n .limit(1)) as Array<{ value: unknown }>;\n row = rows[0];\n } catch {\n // DB not ready — caller is asking before bootstrap. Return empty\n // shape; treats DB-not-ready the same as \"no row stored yet\".\n return { pluginId, value: schema ? defaultsFromSchema(schema) : {}, hasPersisted: false };\n }\n\n if (!schema) {\n // Plugin doesn't declare a configSchema. If a row exists (legacy\n // hand-coded UI saved into np_settings, or migrated from\n // np_plugins.config), surface it raw — callers can still read it.\n if (!row) {\n return { pluginId, value: {}, hasPersisted: false };\n }\n const versioned = isVersionedPluginConfig(row.value) ? row.value : null;\n const rawValue = versioned ? versioned.__npSettings : row.value;\n return {\n pluginId,\n value: rawValue ?? {},\n hasPersisted: true,\n };\n }\n\n const fields = introspectThemeSettingsSchema(schema);\n const defaults = defaultsFrom(fields);\n\n if (!row) {\n const parsed = schema.safeParse(defaults);\n return {\n pluginId,\n value: parsed.success ? parsed.data : defaults,\n hasPersisted: false,\n };\n }\n\n // Versioned envelope detection + lazy migration. Mirrors\n // `getThemeSettingsWithStatus` exactly.\n const versioned = isVersionedPluginConfig(row.value) ? row.value : null;\n const storedVersion = versioned ? versioned.__npVersion : 1;\n const rawValue = versioned ? versioned.__npSettings : row.value;\n const valueToParse = applyPluginConfigMigration(registration, rawValue, storedVersion);\n\n const parsed = schema.safeParse(valueToParse);\n if (parsed.success) {\n return { pluginId, value: parsed.data, hasPersisted: true };\n }\n\n return {\n pluginId,\n value: defaults,\n hasPersisted: true,\n parseError: parsed.error.message,\n };\n}\n\nfunction defaultsFromSchema(schema: ZodTypeAny): Record<string, unknown> {\n return defaultsFrom(introspectThemeSettingsSchema(schema));\n}\n\n/**\n * Validate and persist a plugin's config. Throws `NpValidationError` when\n * `value` doesn't pass the schema — the admin form must surface\n * field-level errors before calling this.\n *\n * **Cache invalidation is the caller's responsibility.** This function\n * writes to `np_settings` only; it doesn't import `next/cache`. The\n * admin API route (`PUT /api/admin/plugins/[id]/config`) busts\n * `np:plugin:<id>` after a successful write.\n *\n * Mirrors `setThemeSettings` in `packages/core/src/themes/settings.ts`.\n */\nexport async function setPluginConfig(\n pluginId: string,\n value: unknown,\n updatedBy: string | null = null,\n): Promise<unknown> {\n const registration = getPluginRegistration(pluginId);\n if (!registration) {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"pluginId\",\n message: `Unknown plugin '${pluginId}'. Register it in nexpress.config.ts first.`,\n },\n ]);\n }\n const schema = registration.configSchema as ZodTypeAny | undefined;\n if (!schema) {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"pluginId\",\n message: `Plugin '${pluginId}' does not declare a configSchema.`,\n },\n ]);\n }\n\n const parsed = schema.safeParse(value);\n if (!parsed.success) {\n throw new NpValidationError(\n \"Config failed validation\",\n parsed.error.issues.map((i) => ({\n field: i.path.join(\".\"),\n message: i.message,\n })),\n );\n }\n\n const wrapped: NpVersionedPluginConfig = {\n __npVersion: registration.configVersion ?? 1,\n __npSettings: parsed.data,\n };\n\n const db = getDb();\n const now = new Date();\n const siteId = (await getCurrentSiteId()) ?? DEFAULT_SITE;\n await db\n .insert(npSettings)\n .values({\n siteId,\n key: configKey(pluginId),\n value: wrapped,\n updatedAt: now,\n updatedBy,\n })\n .onConflictDoUpdate({\n target: [npSettings.siteId, npSettings.key],\n set: { value: wrapped, updatedAt: now, updatedBy },\n });\n\n return parsed.data;\n}\n\n/** Cache tag for a plugin's config invalidation. Per the prefix policy\n * in CLAUDE.md (Naming convention table) every framework-owned tag\n * uses the `np` prefix. Distinct from the legacy `nx:theme:<siteId>`\n * tag — see `docs/design/plugin-config-auto-form.md` § 7. */\nexport function pluginConfigCacheTag(pluginId: string): string {\n return `np:plugin:${pluginId}`;\n}\n","import type { ZodTypeAny } from \"zod\";\n\n/**\n * Phase F.3 — server-side introspection of a theme's\n * `settingsSchema` Zod tree into a JSON metadata shape the\n * admin form generator consumes.\n *\n * The schema lives in the theme package (server bundle); we\n * don't ship the schema itself to the browser. Instead, this\n * function walks the tree once on the server, emits the\n * metadata as plain JSON, and the admin renders form fields\n * from the metadata. The browser doesn't need zod at runtime.\n *\n * Coverage in v0.2: text, url, color (regex heuristic), number,\n * boolean, enum, array(object), object. Anything else\n * introspects as `{ type: \"unsupported\" }` so the form generator\n * can render a JSON textarea fallback (operator can still edit;\n * a follow-up phase widens coverage).\n */\n\nexport type NpThemeSettingsField =\n | NpThemeSettingsTextField\n | NpThemeSettingsTextareaField\n | NpThemeSettingsPasswordField\n | NpThemeSettingsUrlField\n | NpThemeSettingsColorField\n | NpThemeSettingsNumberField\n | NpThemeSettingsBooleanField\n | NpThemeSettingsEnumField\n | NpThemeSettingsArrayField\n | NpThemeSettingsStringArrayField\n | NpThemeSettingsObjectField\n | NpThemeSettingsUnsupportedField;\n\ninterface NpThemeSettingsFieldBase {\n /** Field path key (\"hero\", \"social.0.url\", etc. — the\n * introspector returns flat keys per node; nested objects\n * carry their own children). */\n name: string;\n label?: string;\n description?: string;\n required: boolean;\n default?: unknown;\n}\n\nexport interface NpThemeSettingsTextField extends NpThemeSettingsFieldBase {\n type: \"text\";\n}\n\nexport interface NpThemeSettingsTextareaField extends NpThemeSettingsFieldBase {\n type: \"textarea\";\n /** Optional row count hint for the rendered `<textarea>`.\n * Theme authors set this via `.meta({ widget: \"textarea\",\n * rows: 6 })`. Defaults to 4 when unset. */\n rows?: number;\n}\n\nexport interface NpThemeSettingsPasswordField extends NpThemeSettingsFieldBase {\n type: \"password\";\n}\n\nexport interface NpThemeSettingsUrlField extends NpThemeSettingsFieldBase {\n type: \"url\";\n}\n\nexport interface NpThemeSettingsColorField extends NpThemeSettingsFieldBase {\n type: \"color\";\n}\n\nexport interface NpThemeSettingsNumberField extends NpThemeSettingsFieldBase {\n type: \"number\";\n int?: boolean;\n min?: number;\n max?: number;\n}\n\nexport interface NpThemeSettingsBooleanField extends NpThemeSettingsFieldBase {\n type: \"boolean\";\n}\n\nexport interface NpThemeSettingsEnumField extends NpThemeSettingsFieldBase {\n type: \"enum\";\n options: string[];\n}\n\nexport interface NpThemeSettingsArrayField extends NpThemeSettingsFieldBase {\n type: \"array\";\n /** v0.2 supports `z.array(z.object(...))`. The element\n * schema introspects as the array's child fields. */\n element: NpThemeSettingsField[];\n}\n\n/** Phase G follow-up — `z.array(z.string())`. Renders as a\n * one-item-per-line input. Surfaced for OAuth scopes and\n * similar string-list configs that don't fit the object-array\n * shape; previously fell through to the JSON-textarea\n * `unsupported` fallback. */\nexport interface NpThemeSettingsStringArrayField extends NpThemeSettingsFieldBase {\n type: \"string-array\";\n}\n\nexport interface NpThemeSettingsObjectField extends NpThemeSettingsFieldBase {\n type: \"object\";\n fields: NpThemeSettingsField[];\n}\n\nexport interface NpThemeSettingsUnsupportedField extends NpThemeSettingsFieldBase {\n type: \"unsupported\";\n /** Best-effort label for what was at this position so\n * operators can recognize their schema in the JSON fallback. */\n zodTypeName: string;\n}\n\n// Heuristic: regex sources that look like a hex color check.\n// We test against the regex `source` string (no flags, no\n// surrounding slashes), so e.g. `/^#[0-9a-f]{6}$/i` arrives\n// as `^#[0-9a-f]{6}$`. Matches both 6-digit and 3-to-8 digit\n// variants, case sensitivity-agnostic via the `i` flag on\n// the heuristic itself.\nconst COLOR_REGEX_PATTERNS = [\n /^\\^#\\[0-9a-f\\]\\{6\\}\\$$/i,\n /^\\^#\\[0-9a-f\\]\\{3,8\\}\\$$/i,\n /^\\^#\\[\\\\da-f\\]\\{6\\}\\$$/i,\n];\n\ninterface ZodCheck {\n _zod?: { def?: { format?: string; pattern?: { source: string }; check?: string; value?: number } };\n}\n\ninterface ZodDef {\n type: string;\n innerType?: { _def: ZodDef };\n defaultValue?: unknown;\n description?: string;\n shape?: Record<string, { _def: ZodDef; description?: string }>;\n entries?: Record<string, string>;\n element?: { _def: ZodDef };\n checks?: ZodCheck[];\n}\n\ninterface ZodNode {\n _def: ZodDef;\n description?: string;\n shape?: Record<string, ZodNode>;\n}\n\n/**\n * Strip `default` / `optional` / `nullable` wrappers, returning\n * the inner schema, the resolved default value, and whether\n * the field is required (i.e. neither optional nor nullable).\n */\nfunction unwrap(node: ZodNode): {\n inner: ZodNode;\n defaultValue: unknown;\n required: boolean;\n} {\n let current = node;\n let defaultValue: unknown = undefined;\n let required = true;\n\n while (true) {\n const t = current._def.type;\n if (t === \"default\") {\n defaultValue =\n typeof current._def.defaultValue === \"function\"\n ? (current._def.defaultValue as () => unknown)()\n : current._def.defaultValue;\n current = (current._def.innerType as ZodNode | undefined) ?? current;\n if (!current._def.innerType) break;\n continue;\n }\n if (t === \"optional\" || t === \"nullable\") {\n required = false;\n const next = current._def.innerType as ZodNode | undefined;\n if (!next) break;\n current = next;\n continue;\n }\n break;\n }\n\n return { inner: current, defaultValue, required };\n}\n\nfunction detectStringFormat(\n checks: ZodCheck[] | undefined,\n): \"url\" | \"color\" | \"text\" {\n if (!checks) return \"text\";\n for (const c of checks) {\n const fmt = c._zod?.def?.format;\n if (fmt === \"url\") return \"url\";\n if (fmt === \"regex\") {\n const src = c._zod?.def?.pattern?.source;\n if (src && COLOR_REGEX_PATTERNS.some((p) => p.test(src))) {\n return \"color\";\n }\n }\n }\n return \"text\";\n}\n\n/**\n * Phase F.3 follow-up — pull `.meta()` off a Zod node when\n * present. Used to read theme-author hints like\n * `{ widget: \"textarea\", rows: 6 }` that don't fit Zod's\n * narrow widget matrix (z.string() has no textarea variant\n * built in).\n */\nfunction readMeta(node: ZodNode): Record<string, unknown> | undefined {\n const fn = (node as unknown as { meta?: () => unknown }).meta;\n if (typeof fn !== \"function\") return undefined;\n const out = fn.call(node);\n return out && typeof out === \"object\" ? (out as Record<string, unknown>) : undefined;\n}\n\nfunction detectNumberConstraints(\n checks: ZodCheck[] | undefined,\n): { int?: boolean; min?: number; max?: number } {\n const out: { int?: boolean; min?: number; max?: number } = {};\n if (!checks) return out;\n for (const c of checks) {\n const def = c._zod?.def;\n if (!def) continue;\n if (def.format === \"safeint\" || def.check === \"int\") out.int = true;\n if (def.check === \"greater_than\" && typeof def.value === \"number\")\n out.min = def.value;\n if (def.check === \"less_than\" && typeof def.value === \"number\")\n out.max = def.value;\n }\n return out;\n}\n\nfunction introspectField(\n name: string,\n node: ZodNode,\n): NpThemeSettingsField {\n const description = node.description;\n const { inner, defaultValue, required } = unwrap(node);\n const innerDef = inner._def;\n const base: NpThemeSettingsFieldBase = {\n name,\n description,\n label: description,\n required,\n default: defaultValue,\n };\n\n switch (innerDef.type) {\n case \"string\": {\n // Phase F.3 follow-up — `.meta({ widget: \"textarea\" })`\n // opts a `z.string()` into multi-line rendering. Theme\n // authors pair it with `.describe()` for the field\n // label; row count is optional (defaults to 4).\n //\n // Check `node` (outer) first then `inner` because Zod v4's\n // `.meta()` returns a new instance, so the meta lives at\n // whichever level the author called .meta() at:\n //\n // z.string().meta({...}).optional() → meta on inner string\n // z.string().optional().meta({...}) → meta on outer optional\n //\n // Both patterns are valid in author code; both should work.\n const meta = readMeta(node) ?? readMeta(inner);\n if (meta && meta.sensitive === true) {\n return { ...base, type: \"password\" };\n }\n if (meta && meta.widget === \"textarea\") {\n const rows =\n typeof meta.rows === \"number\" && meta.rows > 0\n ? meta.rows\n : undefined;\n return {\n ...base,\n type: \"textarea\",\n ...(rows !== undefined ? { rows } : {}),\n };\n }\n const fmt = detectStringFormat(innerDef.checks);\n return { ...base, type: fmt };\n }\n case \"number\": {\n const c = detectNumberConstraints(innerDef.checks);\n return { ...base, type: \"number\", ...c };\n }\n case \"boolean\":\n return { ...base, type: \"boolean\" };\n case \"enum\": {\n const entries = innerDef.entries ?? {};\n return { ...base, type: \"enum\", options: Object.values(entries) };\n }\n case \"array\": {\n const element = innerDef.element as ZodNode | undefined;\n // v0.2 supports z.array(z.object(...)) — typed nested form\n // for each item.\n if (element?._def.type === \"object\" && element._def.shape) {\n const childFields = introspectShape(element._def.shape);\n return { ...base, type: \"array\", element: childFields };\n }\n // Phase G follow-up — z.array(z.string()) gets a dedicated\n // string-array widget (one item per line). Surfaced for\n // OAuth scopes and similar string-list configs.\n if (element?._def.type === \"string\") {\n return { ...base, type: \"string-array\" };\n }\n return { ...base, type: \"unsupported\", zodTypeName: \"array\" };\n }\n case \"object\": {\n const shape = innerDef.shape;\n if (shape) {\n return { ...base, type: \"object\", fields: introspectShape(shape) };\n }\n return { ...base, type: \"unsupported\", zodTypeName: \"object\" };\n }\n default:\n return {\n ...base,\n type: \"unsupported\",\n zodTypeName: innerDef.type ?? \"unknown\",\n };\n }\n}\n\nfunction introspectShape(\n shape: Record<string, { _def: ZodDef; description?: string }>,\n): NpThemeSettingsField[] {\n const out: NpThemeSettingsField[] = [];\n for (const [name, raw] of Object.entries(shape)) {\n out.push(introspectField(name, raw as ZodNode));\n }\n return out;\n}\n\n/**\n * Walk a theme's `settingsSchema` (top-level z.object) and emit\n * the form metadata. Returns an empty array when the schema\n * isn't a top-level object — themes are expected to ship\n * `settingsSchema: z.object({...})` (validated implicitly: a\n * non-object top schema yields an empty form, signalling\n * \"nothing to configure\").\n */\nexport function introspectThemeSettingsSchema(\n schema: ZodTypeAny | undefined,\n): NpThemeSettingsField[] {\n if (!schema) return [];\n // Strip any top-level default/optional/nullable wrapper before\n // checking for object shape — themes that wrap their whole\n // schema in `.default({...})` are unusual but valid; without\n // unwrap we'd silently render an empty form.\n const { inner } = unwrap(schema as unknown as ZodNode);\n if (inner._def.type !== \"object\" || !inner._def.shape) return [];\n return introspectShape(inner._def.shape);\n}\n"],"mappings":"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","names":["versioned","rawValue","parsed"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|