@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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  findDocuments
3
- } from "./chunk-VGTPQXNQ.js";
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-DP2PREDU.js.map
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-CIQM57HT.js");
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-OBOI4MJK.js");
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-OBOI4MJK.js");
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-OBOI4MJK.js");
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-OBOI4MJK.js");
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-ISLYFQWL.js.map
1270
+ //# sourceMappingURL=chunk-52UDBKVT.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runHook
3
- } from "./chunk-VGTPQXNQ.js";
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-XPVQIHAQ.js.map
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-2GDU7PCK.js");
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-2GDU7PCK.js");
1897
- const { getPluginRegistration: getPluginRegistration2 } = await import("./host-OBOI4MJK.js");
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-2GDU7PCK.js");
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-VGTPQXNQ.js.map
2790
+ //# sourceMappingURL=chunk-EJ2PIRZ6.js.map
@@ -26,7 +26,7 @@ import {
26
26
  findDocuments,
27
27
  getDocumentById,
28
28
  saveDocument
29
- } from "./chunk-VGTPQXNQ.js";
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-6YI5K2TI.js.map
1959
+ //# sourceMappingURL=chunk-VRBY465W.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getPluginRegistration
3
- } from "./chunk-VGTPQXNQ.js";
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
- if (!registration) {
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(registration, rawValue, storedVersion);
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-QO7LAQZH.js.map
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-6YI5K2TI.js";
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-XPVQIHAQ.js";
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-VGTPQXNQ.js";
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-QO7LAQZH.js";
9
- import "./chunk-VGTPQXNQ.js";
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-2GDU7PCK.js.map
32
+ //# sourceMappingURL=config-GCDRLEFE.js.map
@@ -16,7 +16,7 @@ import {
16
16
  runHookAndCollect,
17
17
  runPluginScheduledTask,
18
18
  schedulePluginTask
19
- } from "./chunk-VGTPQXNQ.js";
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-OBOI4MJK.js.map
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-QO7LAQZH.js";
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-DP2PREDU.js";
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-6YI5K2TI.js";
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-XPVQIHAQ.js";
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-VGTPQXNQ.js";
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-ISLYFQWL.js";
345
+ } from "./chunk-52UDBKVT.js";
346
346
  import {
347
347
  DEFAULT_JOB_LOG_RETENTION_MS,
348
348
  countJobLogs,
package/dist/jobs.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  startWorker,
19
19
  stopProducer,
20
20
  stopWorker
21
- } from "./chunk-ISLYFQWL.js";
21
+ } from "./chunk-52UDBKVT.js";
22
22
  import {
23
23
  DEFAULT_JOB_LOG_RETENTION_MS,
24
24
  countJobLogs,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  publishScheduledDocuments
3
- } from "./chunk-XPVQIHAQ.js";
4
- import "./chunk-VGTPQXNQ.js";
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-CIQM57HT.js.map
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-DP2PREDU.js";
16
- import "./chunk-VGTPQXNQ.js";
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.0",
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.2",
106
+ "intl-messageformat": "^11.2.4",
107
107
  "jose": "^6.2.2",
108
108
  "pg": "^8.20.0",
109
- "pg-boss": "^12.15.0",
109
+ "pg-boss": "^12.18.2",
110
110
  "sharp": "^0.34.2",
111
- "zod": "^4.3.6"
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"]}