@fluid-app/portal-sdk 0.1.128 → 0.1.129
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/{ShopScreen-B0VkBuQI.cjs → ShopScreen-8OQhLeLt.cjs} +1 -1
- package/dist/{ShopScreen-B4Tmn4pJ.cjs → ShopScreen-CHH0cx2P.cjs} +2 -2
- package/dist/{ShopScreen-B4Tmn4pJ.cjs.map → ShopScreen-CHH0cx2P.cjs.map} +1 -1
- package/dist/{ShopScreen-DJt1rKw3.mjs → ShopScreen-Yi9sOX_2.mjs} +2 -2
- package/dist/{ShopScreen-DJt1rKw3.mjs.map → ShopScreen-Yi9sOX_2.mjs.map} +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.mjs +3 -3
- package/dist/vite/index.cjs +44 -2
- package/dist/vite/index.cjs.map +1 -1
- package/dist/vite/index.d.cts +2 -2
- package/dist/vite/index.d.cts.map +1 -1
- package/dist/vite/index.d.mts +2 -2
- package/dist/vite/index.d.mts.map +1 -1
- package/dist/vite/index.mjs +44 -2
- package/dist/vite/index.mjs.map +1 -1
- package/package.json +9 -9
package/dist/index.cjs
CHANGED
|
@@ -55,7 +55,7 @@ require("./src-BJdOxgpp.cjs");
|
|
|
55
55
|
require("./dist-BF_4vk1z.cjs");
|
|
56
56
|
const require_ProductsScreen = require("./ProductsScreen-C8UfVLRr.cjs");
|
|
57
57
|
const require_MySiteScreen = require("./MySiteScreen-nV8x9xyy.cjs");
|
|
58
|
-
const require_ShopScreen = require("./ShopScreen-
|
|
58
|
+
const require_ShopScreen = require("./ShopScreen-CHH0cx2P.cjs");
|
|
59
59
|
require("./UpgradeScreen-CKQhKJzK.cjs");
|
|
60
60
|
require("./AppDownloadScreen-DTxo4z3_.cjs");
|
|
61
61
|
let react = require("react");
|
|
@@ -3005,7 +3005,7 @@ const OrdersScreen$1 = (0, react.lazy)(() => Promise.resolve().then(() => requir
|
|
|
3005
3005
|
const SubscriptionsScreen$1 = (0, react.lazy)(() => Promise.resolve().then(() => require("./SubscriptionsScreen-C2F3HNJS.cjs")).then((m) => ({ default: m.SubscriptionsScreen })));
|
|
3006
3006
|
const MessagingScreen$1 = (0, react.lazy)(() => Promise.resolve().then(() => require("./MessagingScreen-bzzXjQMu.cjs")).then((m) => ({ default: m.MessagingScreen })));
|
|
3007
3007
|
const ContactsScreen$1 = (0, react.lazy)(() => Promise.resolve().then(() => require("./ContactsScreen-FrVLbjGO.cjs")).then((m) => ({ default: m.ContactsScreen })));
|
|
3008
|
-
const ShopScreen$1 = (0, react.lazy)(() => Promise.resolve().then(() => require("./ShopScreen-
|
|
3008
|
+
const ShopScreen$1 = (0, react.lazy)(() => Promise.resolve().then(() => require("./ShopScreen-8OQhLeLt.cjs")).then((m) => ({ default: m.ShopScreen })));
|
|
3009
3009
|
const CustomersScreen$1 = (0, react.lazy)(() => Promise.resolve().then(() => require("./CustomersScreen-Bd9eK18y.cjs")).then((n) => n.CustomersScreen_exports).then((m) => ({ default: m.CustomersScreen })));
|
|
3010
3010
|
const ProductsScreen$1 = (0, react.lazy)(() => Promise.resolve().then(() => require("./ProductsScreen-BIYHPaBZ.cjs")).then((m) => ({ default: m.ProductsScreen })));
|
|
3011
3011
|
const ShareablesScreen$1 = (0, react.lazy)(() => Promise.resolve().then(() => require("./ShareablesScreen-BrC5LGtU.cjs")).then((m) => ({ default: m.ShareablesScreen })));
|
|
@@ -4485,7 +4485,7 @@ const screenPropertySchemas = {
|
|
|
4485
4485
|
ProductsScreen: () => Promise.resolve().then(() => require("./ProductsScreen-BIYHPaBZ.cjs")).then((m) => m.productsScreenPropertySchema),
|
|
4486
4486
|
MySiteScreen: () => Promise.resolve().then(() => require("./MySiteScreen-CYZpUYTn.cjs")).then((m) => m.mySiteScreenPropertySchema),
|
|
4487
4487
|
ShareablesScreen: () => Promise.resolve().then(() => require("./ShareablesScreen-BrC5LGtU.cjs")).then((m) => m.shareablesScreenPropertySchema),
|
|
4488
|
-
ShopScreen: () => Promise.resolve().then(() => require("./ShopScreen-
|
|
4488
|
+
ShopScreen: () => Promise.resolve().then(() => require("./ShopScreen-8OQhLeLt.cjs")).then((m) => m.shopScreenPropertySchema),
|
|
4489
4489
|
UpgradeScreen: () => Promise.resolve().then(() => require("./UpgradeScreen-DchSU0sW.cjs")).then((m) => m.upgradeScreenPropertySchema),
|
|
4490
4490
|
AppDownloadScreen: () => Promise.resolve().then(() => require("./AppDownloadScreen-Chxavsr_.cjs")).then((m) => m.appDownloadScreenPropertySchema)
|
|
4491
4491
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -53,7 +53,7 @@ import "./src-BMUEjfhg.mjs";
|
|
|
53
53
|
import "./sortable.esm-Cz-CP2N8.mjs";
|
|
54
54
|
import { n as productsScreenPropertySchema, t as ProductsScreen } from "./ProductsScreen-COwahI-V.mjs";
|
|
55
55
|
import { r as mySiteScreenPropertySchema, t as MySiteScreen } from "./MySiteScreen-BJH5-RNT.mjs";
|
|
56
|
-
import { r as shopScreenPropertySchema, t as ShopScreen } from "./ShopScreen-
|
|
56
|
+
import { r as shopScreenPropertySchema, t as ShopScreen } from "./ShopScreen-Yi9sOX_2.mjs";
|
|
57
57
|
import "./UpgradeScreen-4f-hNhTh.mjs";
|
|
58
58
|
import "./AppDownloadScreen-BoGSdsJk.mjs";
|
|
59
59
|
import * as React$1 from "react";
|
|
@@ -3003,7 +3003,7 @@ const OrdersScreen$1 = lazy(() => import("./OrdersScreen-BLb3_KtI.mjs").then((n)
|
|
|
3003
3003
|
const SubscriptionsScreen$1 = lazy(() => import("./SubscriptionsScreen-CZ-1jSO2.mjs").then((n) => n.n).then((m) => ({ default: m.SubscriptionsScreen })));
|
|
3004
3004
|
const MessagingScreen$1 = lazy(() => import("./MessagingScreen-DMDXiH97.mjs").then((m) => ({ default: m.MessagingScreen })));
|
|
3005
3005
|
const ContactsScreen$1 = lazy(() => import("./ContactsScreen-BKOHursc.mjs").then((n) => n.n).then((m) => ({ default: m.ContactsScreen })));
|
|
3006
|
-
const ShopScreen$1 = lazy(() => import("./ShopScreen-
|
|
3006
|
+
const ShopScreen$1 = lazy(() => import("./ShopScreen-Yi9sOX_2.mjs").then((n) => n.n).then((m) => ({ default: m.ShopScreen })));
|
|
3007
3007
|
const CustomersScreen$1 = lazy(() => import("./CustomersScreen-DEFY3mRL.mjs").then((n) => n.n).then((m) => ({ default: m.CustomersScreen })));
|
|
3008
3008
|
const ProductsScreen$1 = lazy(() => import("./ProductsScreen-D6h-r9ht.mjs").then((m) => ({ default: m.ProductsScreen })));
|
|
3009
3009
|
const ShareablesScreen$1 = lazy(() => import("./ShareablesScreen-aMnwEOAH.mjs").then((m) => ({ default: m.ShareablesScreen })));
|
|
@@ -4483,7 +4483,7 @@ const screenPropertySchemas = {
|
|
|
4483
4483
|
ProductsScreen: () => import("./ProductsScreen-D6h-r9ht.mjs").then((m) => m.productsScreenPropertySchema),
|
|
4484
4484
|
MySiteScreen: () => import("./MySiteScreen-BJH5-RNT.mjs").then((n) => n.n).then((m) => m.mySiteScreenPropertySchema),
|
|
4485
4485
|
ShareablesScreen: () => import("./ShareablesScreen-aMnwEOAH.mjs").then((m) => m.shareablesScreenPropertySchema),
|
|
4486
|
-
ShopScreen: () => import("./ShopScreen-
|
|
4486
|
+
ShopScreen: () => import("./ShopScreen-Yi9sOX_2.mjs").then((n) => n.n).then((m) => m.shopScreenPropertySchema),
|
|
4487
4487
|
UpgradeScreen: () => import("./UpgradeScreen-4f-hNhTh.mjs").then((n) => n.t).then((m) => m.upgradeScreenPropertySchema),
|
|
4488
4488
|
AppDownloadScreen: () => import("./AppDownloadScreen-BoGSdsJk.mjs").then((n) => n.t).then((m) => m.appDownloadScreenPropertySchema)
|
|
4489
4489
|
};
|
package/dist/vite/index.cjs
CHANGED
|
@@ -279,12 +279,54 @@ function isAllowedOrigin(origin) {
|
|
|
279
279
|
* Dev mode: serves `/__preview__` by transforming preview.html through
|
|
280
280
|
* Vite's HTML pipeline (so import resolution and HMR work).
|
|
281
281
|
*
|
|
282
|
-
* Build mode:
|
|
283
|
-
* it
|
|
282
|
+
* Build mode: adds preview.html as a second Rollup input so `vite build`
|
|
283
|
+
* produces it alongside index.html for production deployment.
|
|
284
284
|
*/
|
|
285
285
|
function fluidPreviewPlugin() {
|
|
286
286
|
return {
|
|
287
287
|
name: "fluid-preview-plugin",
|
|
288
|
+
config(_config, { command }) {
|
|
289
|
+
if (command !== "build") return;
|
|
290
|
+
const projectRoot = process.cwd();
|
|
291
|
+
const previewHtml = (0, node_path.resolve)(projectRoot, "preview.html");
|
|
292
|
+
if (!(0, node_fs.existsSync)(previewHtml)) return;
|
|
293
|
+
return { build: { rollupOptions: { input: {
|
|
294
|
+
main: (0, node_path.resolve)(projectRoot, "index.html"),
|
|
295
|
+
preview: previewHtml
|
|
296
|
+
} } } };
|
|
297
|
+
},
|
|
298
|
+
configurePreviewServer(server) {
|
|
299
|
+
server.middlewares.use("/__preview__", async (req, res) => {
|
|
300
|
+
try {
|
|
301
|
+
const distDir = server.config.build.outDir;
|
|
302
|
+
const html = await (0, node_fs_promises.readFile)((0, node_path.resolve)(distDir, "preview.html"), "utf-8");
|
|
303
|
+
res.setHeader("Content-Type", "text/html");
|
|
304
|
+
const origin = req.headers.origin;
|
|
305
|
+
if (origin && isAllowedOrigin(origin)) {
|
|
306
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
307
|
+
res.setHeader("Vary", "Origin");
|
|
308
|
+
}
|
|
309
|
+
res.statusCode = 200;
|
|
310
|
+
res.end(html);
|
|
311
|
+
} catch {
|
|
312
|
+
res.statusCode = 404;
|
|
313
|
+
res.end("preview.html not found in build output");
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
server.middlewares.use("/__manifests__", async (_req, res) => {
|
|
317
|
+
try {
|
|
318
|
+
const distDir = server.config.build.outDir;
|
|
319
|
+
const json = await (0, node_fs_promises.readFile)((0, node_path.resolve)(distDir, "__manifests__.json"), "utf-8");
|
|
320
|
+
res.setHeader("Content-Type", "application/json");
|
|
321
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
322
|
+
res.statusCode = 200;
|
|
323
|
+
res.end(json);
|
|
324
|
+
} catch {
|
|
325
|
+
res.statusCode = 404;
|
|
326
|
+
res.end("__manifests__.json not found in build output");
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
},
|
|
288
330
|
configureServer(server) {
|
|
289
331
|
server.middlewares.use("/__preview__", async (req, res) => {
|
|
290
332
|
try {
|
package/dist/vite/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/vite/validate-manifest.ts","../../src/vite/builder-preview-plugin.ts","../../src/vite/manifest-plugin.ts","../../../core/src/preview/protocol.ts","../../src/vite/preview-plugin.ts"],"sourcesContent":["/**\n * Lightweight manifest validation for the SDK vite plugin.\n *\n * Inlined here (rather than imported from @fluid-app/portal-core) because\n * portal-core is private and not published to npm. This avoids a runtime\n * ERR_MODULE_NOT_FOUND for portals installed from npm.\n */\n\nconst VALID_FIELD_TYPES = [\n \"text\",\n \"textarea\",\n \"number\",\n \"boolean\",\n \"select\",\n \"color\",\n \"range\",\n \"dataSource\",\n \"resource\",\n \"image\",\n \"alignment\",\n \"slider\",\n \"colorPicker\",\n \"sectionHeader\",\n \"separator\",\n \"buttonGroup\",\n \"colorSelect\",\n \"sectionLayoutSelect\",\n \"background\",\n \"contentPosition\",\n \"textSizeSelect\",\n \"cssUnit\",\n \"fontPicker\",\n \"stringArray\",\n \"borderRadius\",\n \"screenPicker\",\n] as const;\n\ninterface ValidationError {\n path: string;\n message: string;\n}\n\ntype ValidationResult =\n | { success: true }\n | { success: false; errors: ValidationError[] };\n\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: ValidationError[] = [];\n const m = input as Record<string, unknown>;\n\n if (!m || typeof m !== \"object\") {\n return {\n success: false,\n errors: [{ path: \"\", message: \"Manifest must be an object\" }],\n };\n }\n\n // Required string fields\n for (const key of [\n \"type\",\n \"displayName\",\n \"description\",\n \"icon\",\n \"category\",\n ]) {\n if (typeof m[key] !== \"string\" || (m[key] as string).length === 0) {\n errors.push({\n path: key,\n message: `${key} is required and must be a non-empty string`,\n });\n }\n }\n\n if (typeof m.manifestVersion !== \"number\" || m.manifestVersion < 1) {\n errors.push({\n path: \"manifestVersion\",\n message: \"manifestVersion must be a positive integer\",\n });\n }\n\n if (typeof m.component !== \"function\") {\n errors.push({\n path: \"component\",\n message: \"component must be a React component (function)\",\n });\n }\n\n // Property schema validation\n const schema = m.propertySchema as Record<string, unknown> | undefined;\n if (schema && typeof schema === \"object\") {\n if (typeof schema.widgetType !== \"string\" || !schema.widgetType) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"widgetType is required\",\n });\n }\n if (typeof m.type === \"string\" && schema.widgetType !== m.type) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"manifest.type must match manifest.propertySchema.widgetType\",\n });\n }\n if (Array.isArray(schema.fields)) {\n for (let i = 0; i < schema.fields.length; i++) {\n const field = schema.fields[i] as Record<string, unknown>;\n if (!field || typeof field.type !== \"string\") continue;\n if (\n !VALID_FIELD_TYPES.includes(\n field.type as (typeof VALID_FIELD_TYPES)[number],\n )\n ) {\n errors.push({\n path: `propertySchema.fields.${i}.type`,\n message: `Invalid field type \"${field.type}\". Valid types: ${VALID_FIELD_TYPES.join(\", \")}`,\n });\n }\n }\n }\n }\n\n return errors.length === 0 ? { success: true } : { success: false, errors };\n}\n","import type { Plugin, ResolvedConfig } from \"vite\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst VIRTUAL_ENTRY_ID = \"virtual:builder-preview-entry\";\nconst RESOLVED_VIRTUAL_ID = \"\\0\" + VIRTUAL_ENTRY_ID;\n\nconst RAW_HTML = `<!doctype html>\n<html lang=\"en\" data-theme-mode=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Custom Widget Preview</title>\n <style>\n body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }\n </style>\n </head>\n <body>\n <div id=\"builder-preview-root\"></div>\n <script type=\"module\" src=\"/@id/virtual:builder-preview-entry\"></script>\n </body>\n</html>`;\n\n/**\n * Vite plugin that serves a standalone widget preview page at /builder-preview.\n *\n * Dev mode only. Renders all customWidgets from portal.config.ts with\n * a live preview and property editor — no auth, no iframe, no fluid-admin needed.\n */\nexport function fluidBuilderPreviewPlugin(): Plugin {\n let configPath: string;\n let cssPath: string;\n\n return {\n name: \"fluid-builder-preview\",\n apply: \"serve\",\n\n configResolved(config: ResolvedConfig) {\n const root = config.root;\n const candidates = [\"src/portal.config.ts\", \"portal.config.ts\"];\n configPath =\n candidates.find((c) => existsSync(join(root, c))) ??\n \"src/portal.config.ts\";\n const cssCandidates = [\n \"src/index.css\",\n \"src/styles/index.css\",\n \"index.css\",\n ];\n cssPath =\n cssCandidates.find((c) => existsSync(join(root, c))) ?? \"src/index.css\";\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_ID) {\n return `\nimport \"/${cssPath}\";\nimport * as portalConfig from \"/${configPath}\";\nimport { createRoot } from \"react-dom/client\";\nimport { createElement } from \"react\";\nimport { BuilderPreviewApp } from \"@fluid-app/portal-preview\";\n\nconst widgets = portalConfig.customWidgets || [];\nconst root = document.getElementById(\"builder-preview-root\");\nif (root) {\n createRoot(root).render(\n createElement(BuilderPreviewApp, { widgets })\n );\n}\n`;\n }\n },\n\n configureServer(server) {\n server.middlewares.use(async (req, res, next) => {\n const pathname = (req.url ?? \"\").split(\"?\")[0];\n if (pathname !== \"/builder-preview\" && pathname !== \"/builder-preview/\")\n return next();\n try {\n const transformed = await server.transformIndexHtml(\n \"/builder-preview\",\n RAW_HTML,\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(transformed);\n } catch (e) {\n server.config.logger.error(\n `[fluid] Failed to serve builder preview: ${e}`,\n );\n res.statusCode = 500;\n res.end(\"Builder preview failed to load\");\n }\n });\n },\n };\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer, Logger } from \"vite\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { validateManifest } from \"./validate-manifest\";\nimport { fluidBuilderPreviewPlugin } from \"./builder-preview-plugin\";\n\n/**\n * Vite plugin bundle that serves widget manifest metadata and the builder preview.\n *\n * Returns an array of plugins:\n * 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)\n * 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)\n *\n * Every portal using `fluidManifestPlugin()` automatically gets the builder preview.\n */\nexport function fluidManifestPlugin(): Plugin[] {\n return [fluidManifestPluginInternal(), fluidBuilderPreviewPlugin()];\n}\n\nfunction fluidManifestPluginInternal(): Plugin {\n let configPath: string;\n\n return {\n name: \"fluid-manifest-plugin\",\n\n configResolved(config: ResolvedConfig) {\n const root = config.root;\n const candidates = [\n \"src/widgets.config.ts\",\n \"src/portal.config.ts\",\n \"portal.config.ts\",\n ];\n configPath =\n \"/\" +\n (candidates.find((c) => existsSync(join(root, c))) ??\n \"src/portal.config.ts\");\n },\n\n configureServer(server) {\n server.middlewares.use(\"/__manifests__\", async (_req, res) => {\n try {\n const serializable = await loadManifests(\n server,\n server.config.logger,\n configPath,\n );\n\n res.setHeader(\"Content-Type\", \"application/json\");\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.end(JSON.stringify(serializable));\n } catch (err) {\n server.config.logger.error(\n `[fluid] Failed to load manifests: ${err}`,\n );\n res.statusCode = 500;\n res.end(JSON.stringify({ error: String(err) }));\n }\n });\n },\n\n generateBundle() {\n // Build mode: emit placeholder. The CLI extraction utility handles\n // actual build-time manifest extraction via tsx subprocess.\n this.warn(\n \"[fluid] fluidManifestPlugin: emitting empty __manifests__.json. \" +\n \"Run `fluid build` instead of `vite build` to include widget manifests.\",\n );\n this.emitFile({\n type: \"asset\",\n fileName: \"__manifests__.json\",\n source: JSON.stringify([]),\n });\n },\n };\n}\n\n/**\n * Load and serialize manifests from the resolved widget config\n * (widgets.config.ts or portal.config.ts) via Vite's ssrLoadModule.\n * Validates each manifest before stripping the `component` field.\n * Returns an empty array if the config module or export is missing/invalid.\n */\nasync function loadManifests(\n server: ViteDevServer,\n logger?: Logger,\n configFilePath?: string,\n): Promise<unknown[]> {\n let mod: Record<string, unknown>;\n try {\n mod = await server.ssrLoadModule(configFilePath ?? \"/src/portal.config.ts\");\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const isSSRError =\n /(document|window|navigator|localStorage|sessionStorage|location|history|HTMLElement) is not defined/.test(\n message,\n );\n\n if (isSSRError) {\n const configFile = configFilePath ?? \"/src/portal.config.ts\";\n const isPortalConfig = configFile.includes(\"portal.config\");\n const fixHint = isPortalConfig\n ? ` Fix: Create src/widgets.config.ts with only your customWidgets export.\\n` +\n ` The manifest plugin will load it instead of portal.config.ts.\\n`\n : ` Fix: Ensure ${configFile} does not import browser-only code.\\n`;\n\n logger?.warn(\n `[fluid] Cannot load widget manifests — ${configFile} imports ` +\n `browser-only code that fails during server-side evaluation.\\n` +\n ` Custom widgets will not appear in the builder.\\n` +\n fixHint +\n ` Widget components are fine — the issue is usually screen imports\\n` +\n ` (e.g. DashboardScreen) that pull in the SDK barrel export.`,\n );\n } else {\n logger?.warn(`[fluid] Could not load widget config: ${message}`);\n }\n return [];\n }\n\n const rawWidgets = mod.customWidgets;\n if (!rawWidgets) {\n if (configFilePath?.includes(\"widgets.config\")) {\n logger?.warn(\n `[fluid] widgets.config.ts was loaded but exports no customWidgets. ` +\n `Custom widgets will not appear in the builder.`,\n );\n }\n return [];\n }\n\n if (!Array.isArray(rawWidgets)) {\n logger?.warn(\n `[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`,\n );\n return [];\n }\n\n const manifests = rawWidgets as Record<string, unknown>[];\n\n // Validate full manifests (with component) before stripping\n if (logger) {\n for (const manifest of manifests) {\n const result = validateManifest(manifest);\n if (!result.success) {\n const type = (manifest as { type?: string }).type ?? \"unknown\";\n logger.warn(\n `[fluid] Invalid manifest for \"${type}\":\\n` +\n result.errors.map((e) => ` - ${e.path}: ${e.message}`).join(\"\\n\"),\n );\n }\n }\n }\n\n return manifests.map(\n ({ component: _component, ...rest }: Record<string, unknown>) => rest,\n );\n}\n","/**\n * PostMessage protocol for builder ↔ iframe widget preview communication.\n *\n * Four-step handshake:\n * 1. Iframe sends `ready` once mounted\n * 2. Builder sends `auth` with token (NEVER via URL)\n * 3. Iframe sends `auth-ready` once FluidProvider settles\n * 4. Builder sends `render-widget` with type + props\n *\n * This prevents the race condition where render-widget arrives\n * before the provider stack has initialized with the auth token.\n */\n\n// ---------------------------------------------------------------------------\n// Builder → Iframe commands\n// ---------------------------------------------------------------------------\n\nexport interface AuthCommand {\n type: \"auth\";\n token: string;\n}\n\nexport interface RenderWidgetCommand {\n type: \"render-widget\";\n widgetType: string;\n props: Record<string, unknown>;\n}\n\nexport interface UnmountWidgetCommand {\n type: \"unmount-widget\";\n}\n\nexport interface SelectWidgetCommand {\n type: \"select-widget\";\n widgetType: string;\n}\n\nexport type PreviewCommand =\n | AuthCommand\n | RenderWidgetCommand\n | UnmountWidgetCommand\n | SelectWidgetCommand;\n\n// ---------------------------------------------------------------------------\n// Iframe → Builder events\n// ---------------------------------------------------------------------------\n\nexport interface ReadyEvent {\n type: \"ready\";\n}\n\nexport interface AuthReadyEvent {\n type: \"auth-ready\";\n}\n\nexport interface WidgetRenderedEvent {\n type: \"widget-rendered\";\n widgetType: string;\n dimensions: { width: number; height: number };\n}\n\nexport interface WidgetErrorEvent {\n type: \"widget-error\";\n widgetType: string;\n error: string;\n}\n\nexport interface WidgetClickedEvent {\n type: \"widget-clicked\";\n widgetType: string;\n}\n\nexport interface WidgetHmrEvent {\n type: \"widget-hmr\";\n}\n\nexport type PreviewEvent =\n | ReadyEvent\n | AuthReadyEvent\n | WidgetRenderedEvent\n | WidgetErrorEvent\n | WidgetClickedEvent\n | WidgetHmrEvent;\n\n// ---------------------------------------------------------------------------\n// Origin validation\n// ---------------------------------------------------------------------------\n\nconst ALLOWED_ORIGIN_PATTERNS = [\n /^https?:\\/\\/localhost(:\\d+)?$/,\n /^https?:\\/\\/127\\.0\\.0\\.1(:\\d+)?$/,\n /^https:\\/\\/[a-z0-9-]+\\.portal\\.fluid\\.app$/,\n /^https:\\/\\/[a-z0-9-]+\\.fluid\\.app$/,\n];\n\n/**\n * Validate that a postMessage origin is trusted.\n * Accepts localhost (any port), *.portal.fluid.app, and *.fluid.app.\n */\nexport function isAllowedOrigin(origin: string): boolean {\n return ALLOWED_ORIGIN_PATTERNS.some((pattern) => pattern.test(origin));\n}\n\n// ---------------------------------------------------------------------------\n// Type guards\n// ---------------------------------------------------------------------------\n\n/** Check if a message is a valid PreviewCommand (builder → iframe). */\nexport function isPreviewCommand(data: unknown): data is PreviewCommand {\n if (typeof data !== \"object\" || data === null) return false;\n const msg = data as Record<string, unknown>;\n switch (msg[\"type\"]) {\n case \"auth\":\n return typeof msg[\"token\"] === \"string\";\n case \"render-widget\":\n return (\n typeof msg[\"widgetType\"] === \"string\" &&\n typeof msg[\"props\"] === \"object\" &&\n msg[\"props\"] !== null &&\n !Array.isArray(msg[\"props\"])\n );\n case \"unmount-widget\":\n return true;\n case \"select-widget\":\n return typeof msg[\"widgetType\"] === \"string\";\n default:\n return false;\n }\n}\n\n/** Check if a message is a valid PreviewEvent (iframe → builder). */\nexport function isPreviewEvent(data: unknown): data is PreviewEvent {\n if (typeof data !== \"object\" || data === null) return false;\n const msg = data as Record<string, unknown>;\n switch (msg[\"type\"]) {\n case \"ready\":\n case \"auth-ready\":\n case \"widget-hmr\":\n return true;\n case \"widget-rendered\":\n return (\n typeof msg[\"widgetType\"] === \"string\" &&\n typeof msg[\"dimensions\"] === \"object\" &&\n msg[\"dimensions\"] !== null &&\n Number.isFinite(\n (msg[\"dimensions\"] as Record<string, unknown>)[\"width\"],\n ) &&\n Number.isFinite(\n (msg[\"dimensions\"] as Record<string, unknown>)[\"height\"],\n )\n );\n case \"widget-error\":\n return (\n typeof msg[\"widgetType\"] === \"string\" &&\n typeof msg[\"error\"] === \"string\"\n );\n case \"widget-clicked\":\n return typeof msg[\"widgetType\"] === \"string\";\n default:\n return false;\n }\n}\n","import { resolve } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport type { Plugin } from \"vite\";\nimport { isAllowedOrigin } from \"@fluid-app/portal-core/preview/protocol\";\n\n/**\n * Vite plugin that serves the widget preview route.\n *\n * Dev mode: serves `/__preview__` by transforming preview.html through\n * Vite's HTML pipeline (so import resolution and HMR work).\n *\n * Build mode: no-op — the production preview.html is handled by adding\n * it as a second Rollup input in vite.config.ts.\n */\nexport function fluidPreviewPlugin(): Plugin {\n return {\n name: \"fluid-preview-plugin\",\n\n configureServer(server) {\n // Serve the preview HTML shell at /__preview__\n server.middlewares.use(\"/__preview__\", async (req, res) => {\n try {\n // Read the preview.html from the project root (copied from template)\n const htmlPath = resolve(server.config.root, \"preview.html\");\n let html: string;\n try {\n html = await readFile(htmlPath, \"utf-8\");\n } catch {\n // Fallback: serve a minimal preview shell\n html = getDefaultPreviewHtml();\n }\n\n // Transform through Vite's HTML pipeline for HMR + import resolution\n html = await server.transformIndexHtml(\"/__preview__\", html);\n\n res.setHeader(\"Content-Type\", \"text/html\");\n const origin = req.headers.origin;\n if (origin && isAllowedOrigin(origin)) {\n res.setHeader(\"Access-Control-Allow-Origin\", origin);\n res.setHeader(\"Vary\", \"Origin\");\n }\n res.statusCode = 200;\n res.end(html);\n } catch (err) {\n server.config.logger.error(`[fluid] Failed to serve preview: ${err}`);\n res.statusCode = 500;\n res.end(\"Preview failed to load\");\n }\n });\n },\n };\n}\n\nfunction getDefaultPreviewHtml(): string {\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Widget Preview</title>\n <meta name=\"referrer\" content=\"strict-origin-when-cross-origin\" />\n <style>body { margin: 0; padding: 0; }</style>\n </head>\n <body>\n <div id=\"preview-root\"></div>\n <script type=\"module\" src=\"/src/preview-entry.tsx\"></script>\n </body>\n</html>`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/vite/validate-manifest.ts","../../src/vite/builder-preview-plugin.ts","../../src/vite/manifest-plugin.ts","../../../core/src/preview/protocol.ts","../../src/vite/preview-plugin.ts"],"sourcesContent":["/**\n * Lightweight manifest validation for the SDK vite plugin.\n *\n * Inlined here (rather than imported from @fluid-app/portal-core) because\n * portal-core is private and not published to npm. This avoids a runtime\n * ERR_MODULE_NOT_FOUND for portals installed from npm.\n */\n\nconst VALID_FIELD_TYPES = [\n \"text\",\n \"textarea\",\n \"number\",\n \"boolean\",\n \"select\",\n \"color\",\n \"range\",\n \"dataSource\",\n \"resource\",\n \"image\",\n \"alignment\",\n \"slider\",\n \"colorPicker\",\n \"sectionHeader\",\n \"separator\",\n \"buttonGroup\",\n \"colorSelect\",\n \"sectionLayoutSelect\",\n \"background\",\n \"contentPosition\",\n \"textSizeSelect\",\n \"cssUnit\",\n \"fontPicker\",\n \"stringArray\",\n \"borderRadius\",\n \"screenPicker\",\n] as const;\n\ninterface ValidationError {\n path: string;\n message: string;\n}\n\ntype ValidationResult =\n | { success: true }\n | { success: false; errors: ValidationError[] };\n\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: ValidationError[] = [];\n const m = input as Record<string, unknown>;\n\n if (!m || typeof m !== \"object\") {\n return {\n success: false,\n errors: [{ path: \"\", message: \"Manifest must be an object\" }],\n };\n }\n\n // Required string fields\n for (const key of [\n \"type\",\n \"displayName\",\n \"description\",\n \"icon\",\n \"category\",\n ]) {\n if (typeof m[key] !== \"string\" || (m[key] as string).length === 0) {\n errors.push({\n path: key,\n message: `${key} is required and must be a non-empty string`,\n });\n }\n }\n\n if (typeof m.manifestVersion !== \"number\" || m.manifestVersion < 1) {\n errors.push({\n path: \"manifestVersion\",\n message: \"manifestVersion must be a positive integer\",\n });\n }\n\n if (typeof m.component !== \"function\") {\n errors.push({\n path: \"component\",\n message: \"component must be a React component (function)\",\n });\n }\n\n // Property schema validation\n const schema = m.propertySchema as Record<string, unknown> | undefined;\n if (schema && typeof schema === \"object\") {\n if (typeof schema.widgetType !== \"string\" || !schema.widgetType) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"widgetType is required\",\n });\n }\n if (typeof m.type === \"string\" && schema.widgetType !== m.type) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"manifest.type must match manifest.propertySchema.widgetType\",\n });\n }\n if (Array.isArray(schema.fields)) {\n for (let i = 0; i < schema.fields.length; i++) {\n const field = schema.fields[i] as Record<string, unknown>;\n if (!field || typeof field.type !== \"string\") continue;\n if (\n !VALID_FIELD_TYPES.includes(\n field.type as (typeof VALID_FIELD_TYPES)[number],\n )\n ) {\n errors.push({\n path: `propertySchema.fields.${i}.type`,\n message: `Invalid field type \"${field.type}\". Valid types: ${VALID_FIELD_TYPES.join(\", \")}`,\n });\n }\n }\n }\n }\n\n return errors.length === 0 ? { success: true } : { success: false, errors };\n}\n","import type { Plugin, ResolvedConfig } from \"vite\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst VIRTUAL_ENTRY_ID = \"virtual:builder-preview-entry\";\nconst RESOLVED_VIRTUAL_ID = \"\\0\" + VIRTUAL_ENTRY_ID;\n\nconst RAW_HTML = `<!doctype html>\n<html lang=\"en\" data-theme-mode=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Custom Widget Preview</title>\n <style>\n body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }\n </style>\n </head>\n <body>\n <div id=\"builder-preview-root\"></div>\n <script type=\"module\" src=\"/@id/virtual:builder-preview-entry\"></script>\n </body>\n</html>`;\n\n/**\n * Vite plugin that serves a standalone widget preview page at /builder-preview.\n *\n * Dev mode only. Renders all customWidgets from portal.config.ts with\n * a live preview and property editor — no auth, no iframe, no fluid-admin needed.\n */\nexport function fluidBuilderPreviewPlugin(): Plugin {\n let configPath: string;\n let cssPath: string;\n\n return {\n name: \"fluid-builder-preview\",\n apply: \"serve\",\n\n configResolved(config: ResolvedConfig) {\n const root = config.root;\n const candidates = [\"src/portal.config.ts\", \"portal.config.ts\"];\n configPath =\n candidates.find((c) => existsSync(join(root, c))) ??\n \"src/portal.config.ts\";\n const cssCandidates = [\n \"src/index.css\",\n \"src/styles/index.css\",\n \"index.css\",\n ];\n cssPath =\n cssCandidates.find((c) => existsSync(join(root, c))) ?? \"src/index.css\";\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_ID) {\n return `\nimport \"/${cssPath}\";\nimport * as portalConfig from \"/${configPath}\";\nimport { createRoot } from \"react-dom/client\";\nimport { createElement } from \"react\";\nimport { BuilderPreviewApp } from \"@fluid-app/portal-preview\";\n\nconst widgets = portalConfig.customWidgets || [];\nconst root = document.getElementById(\"builder-preview-root\");\nif (root) {\n createRoot(root).render(\n createElement(BuilderPreviewApp, { widgets })\n );\n}\n`;\n }\n },\n\n configureServer(server) {\n server.middlewares.use(async (req, res, next) => {\n const pathname = (req.url ?? \"\").split(\"?\")[0];\n if (pathname !== \"/builder-preview\" && pathname !== \"/builder-preview/\")\n return next();\n try {\n const transformed = await server.transformIndexHtml(\n \"/builder-preview\",\n RAW_HTML,\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(transformed);\n } catch (e) {\n server.config.logger.error(\n `[fluid] Failed to serve builder preview: ${e}`,\n );\n res.statusCode = 500;\n res.end(\"Builder preview failed to load\");\n }\n });\n },\n };\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer, Logger } from \"vite\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { validateManifest } from \"./validate-manifest\";\nimport { fluidBuilderPreviewPlugin } from \"./builder-preview-plugin\";\n\n/**\n * Vite plugin bundle that serves widget manifest metadata and the builder preview.\n *\n * Returns an array of plugins:\n * 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)\n * 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)\n *\n * Every portal using `fluidManifestPlugin()` automatically gets the builder preview.\n */\nexport function fluidManifestPlugin(): Plugin[] {\n return [fluidManifestPluginInternal(), fluidBuilderPreviewPlugin()];\n}\n\nfunction fluidManifestPluginInternal(): Plugin {\n let configPath: string;\n\n return {\n name: \"fluid-manifest-plugin\",\n\n configResolved(config: ResolvedConfig) {\n const root = config.root;\n const candidates = [\n \"src/widgets.config.ts\",\n \"src/portal.config.ts\",\n \"portal.config.ts\",\n ];\n configPath =\n \"/\" +\n (candidates.find((c) => existsSync(join(root, c))) ??\n \"src/portal.config.ts\");\n },\n\n configureServer(server) {\n server.middlewares.use(\"/__manifests__\", async (_req, res) => {\n try {\n const serializable = await loadManifests(\n server,\n server.config.logger,\n configPath,\n );\n\n res.setHeader(\"Content-Type\", \"application/json\");\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.end(JSON.stringify(serializable));\n } catch (err) {\n server.config.logger.error(\n `[fluid] Failed to load manifests: ${err}`,\n );\n res.statusCode = 500;\n res.end(JSON.stringify({ error: String(err) }));\n }\n });\n },\n\n generateBundle() {\n // Build mode: emit placeholder. The CLI extraction utility handles\n // actual build-time manifest extraction via tsx subprocess.\n this.warn(\n \"[fluid] fluidManifestPlugin: emitting empty __manifests__.json. \" +\n \"Run `fluid build` instead of `vite build` to include widget manifests.\",\n );\n this.emitFile({\n type: \"asset\",\n fileName: \"__manifests__.json\",\n source: JSON.stringify([]),\n });\n },\n };\n}\n\n/**\n * Load and serialize manifests from the resolved widget config\n * (widgets.config.ts or portal.config.ts) via Vite's ssrLoadModule.\n * Validates each manifest before stripping the `component` field.\n * Returns an empty array if the config module or export is missing/invalid.\n */\nasync function loadManifests(\n server: ViteDevServer,\n logger?: Logger,\n configFilePath?: string,\n): Promise<unknown[]> {\n let mod: Record<string, unknown>;\n try {\n mod = await server.ssrLoadModule(configFilePath ?? \"/src/portal.config.ts\");\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const isSSRError =\n /(document|window|navigator|localStorage|sessionStorage|location|history|HTMLElement) is not defined/.test(\n message,\n );\n\n if (isSSRError) {\n const configFile = configFilePath ?? \"/src/portal.config.ts\";\n const isPortalConfig = configFile.includes(\"portal.config\");\n const fixHint = isPortalConfig\n ? ` Fix: Create src/widgets.config.ts with only your customWidgets export.\\n` +\n ` The manifest plugin will load it instead of portal.config.ts.\\n`\n : ` Fix: Ensure ${configFile} does not import browser-only code.\\n`;\n\n logger?.warn(\n `[fluid] Cannot load widget manifests — ${configFile} imports ` +\n `browser-only code that fails during server-side evaluation.\\n` +\n ` Custom widgets will not appear in the builder.\\n` +\n fixHint +\n ` Widget components are fine — the issue is usually screen imports\\n` +\n ` (e.g. DashboardScreen) that pull in the SDK barrel export.`,\n );\n } else {\n logger?.warn(`[fluid] Could not load widget config: ${message}`);\n }\n return [];\n }\n\n const rawWidgets = mod.customWidgets;\n if (!rawWidgets) {\n if (configFilePath?.includes(\"widgets.config\")) {\n logger?.warn(\n `[fluid] widgets.config.ts was loaded but exports no customWidgets. ` +\n `Custom widgets will not appear in the builder.`,\n );\n }\n return [];\n }\n\n if (!Array.isArray(rawWidgets)) {\n logger?.warn(\n `[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`,\n );\n return [];\n }\n\n const manifests = rawWidgets as Record<string, unknown>[];\n\n // Validate full manifests (with component) before stripping\n if (logger) {\n for (const manifest of manifests) {\n const result = validateManifest(manifest);\n if (!result.success) {\n const type = (manifest as { type?: string }).type ?? \"unknown\";\n logger.warn(\n `[fluid] Invalid manifest for \"${type}\":\\n` +\n result.errors.map((e) => ` - ${e.path}: ${e.message}`).join(\"\\n\"),\n );\n }\n }\n }\n\n return manifests.map(\n ({ component: _component, ...rest }: Record<string, unknown>) => rest,\n );\n}\n","/**\n * PostMessage protocol for builder ↔ iframe widget preview communication.\n *\n * Four-step handshake:\n * 1. Iframe sends `ready` once mounted\n * 2. Builder sends `auth` with token (NEVER via URL)\n * 3. Iframe sends `auth-ready` once FluidProvider settles\n * 4. Builder sends `render-widget` with type + props\n *\n * This prevents the race condition where render-widget arrives\n * before the provider stack has initialized with the auth token.\n */\n\n// ---------------------------------------------------------------------------\n// Builder → Iframe commands\n// ---------------------------------------------------------------------------\n\nexport interface AuthCommand {\n type: \"auth\";\n token: string;\n}\n\nexport interface RenderWidgetCommand {\n type: \"render-widget\";\n widgetType: string;\n props: Record<string, unknown>;\n}\n\nexport interface UnmountWidgetCommand {\n type: \"unmount-widget\";\n}\n\nexport interface SelectWidgetCommand {\n type: \"select-widget\";\n widgetType: string;\n}\n\nexport type PreviewCommand =\n | AuthCommand\n | RenderWidgetCommand\n | UnmountWidgetCommand\n | SelectWidgetCommand;\n\n// ---------------------------------------------------------------------------\n// Iframe → Builder events\n// ---------------------------------------------------------------------------\n\nexport interface ReadyEvent {\n type: \"ready\";\n}\n\nexport interface AuthReadyEvent {\n type: \"auth-ready\";\n}\n\nexport interface WidgetRenderedEvent {\n type: \"widget-rendered\";\n widgetType: string;\n dimensions: { width: number; height: number };\n}\n\nexport interface WidgetErrorEvent {\n type: \"widget-error\";\n widgetType: string;\n error: string;\n}\n\nexport interface WidgetClickedEvent {\n type: \"widget-clicked\";\n widgetType: string;\n}\n\nexport interface WidgetHmrEvent {\n type: \"widget-hmr\";\n}\n\nexport type PreviewEvent =\n | ReadyEvent\n | AuthReadyEvent\n | WidgetRenderedEvent\n | WidgetErrorEvent\n | WidgetClickedEvent\n | WidgetHmrEvent;\n\n// ---------------------------------------------------------------------------\n// Origin validation\n// ---------------------------------------------------------------------------\n\nconst ALLOWED_ORIGIN_PATTERNS = [\n /^https?:\\/\\/localhost(:\\d+)?$/,\n /^https?:\\/\\/127\\.0\\.0\\.1(:\\d+)?$/,\n /^https:\\/\\/[a-z0-9-]+\\.portal\\.fluid\\.app$/,\n /^https:\\/\\/[a-z0-9-]+\\.fluid\\.app$/,\n];\n\n/**\n * Validate that a postMessage origin is trusted.\n * Accepts localhost (any port), *.portal.fluid.app, and *.fluid.app.\n */\nexport function isAllowedOrigin(origin: string): boolean {\n return ALLOWED_ORIGIN_PATTERNS.some((pattern) => pattern.test(origin));\n}\n\n// ---------------------------------------------------------------------------\n// Type guards\n// ---------------------------------------------------------------------------\n\n/** Check if a message is a valid PreviewCommand (builder → iframe). */\nexport function isPreviewCommand(data: unknown): data is PreviewCommand {\n if (typeof data !== \"object\" || data === null) return false;\n const msg = data as Record<string, unknown>;\n switch (msg[\"type\"]) {\n case \"auth\":\n return typeof msg[\"token\"] === \"string\";\n case \"render-widget\":\n return (\n typeof msg[\"widgetType\"] === \"string\" &&\n typeof msg[\"props\"] === \"object\" &&\n msg[\"props\"] !== null &&\n !Array.isArray(msg[\"props\"])\n );\n case \"unmount-widget\":\n return true;\n case \"select-widget\":\n return typeof msg[\"widgetType\"] === \"string\";\n default:\n return false;\n }\n}\n\n/** Check if a message is a valid PreviewEvent (iframe → builder). */\nexport function isPreviewEvent(data: unknown): data is PreviewEvent {\n if (typeof data !== \"object\" || data === null) return false;\n const msg = data as Record<string, unknown>;\n switch (msg[\"type\"]) {\n case \"ready\":\n case \"auth-ready\":\n case \"widget-hmr\":\n return true;\n case \"widget-rendered\":\n return (\n typeof msg[\"widgetType\"] === \"string\" &&\n typeof msg[\"dimensions\"] === \"object\" &&\n msg[\"dimensions\"] !== null &&\n Number.isFinite(\n (msg[\"dimensions\"] as Record<string, unknown>)[\"width\"],\n ) &&\n Number.isFinite(\n (msg[\"dimensions\"] as Record<string, unknown>)[\"height\"],\n )\n );\n case \"widget-error\":\n return (\n typeof msg[\"widgetType\"] === \"string\" &&\n typeof msg[\"error\"] === \"string\"\n );\n case \"widget-clicked\":\n return typeof msg[\"widgetType\"] === \"string\";\n default:\n return false;\n }\n}\n","import { resolve } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport type { Plugin, UserConfig } from \"vite\";\nimport { isAllowedOrigin } from \"@fluid-app/portal-core/preview/protocol\";\n\n/**\n * Vite plugin that serves the widget preview route.\n *\n * Dev mode: serves `/__preview__` by transforming preview.html through\n * Vite's HTML pipeline (so import resolution and HMR work).\n *\n * Build mode: adds preview.html as a second Rollup input so `vite build`\n * produces it alongside index.html for production deployment.\n */\nexport function fluidPreviewPlugin(): Plugin {\n return {\n name: \"fluid-preview-plugin\",\n\n config(_config: UserConfig, { command }) {\n if (command !== \"build\") return;\n\n // config() runs before configResolved, so use process.cwd()\n // which is the project root when invoked via `vite build`.\n const projectRoot = process.cwd();\n const previewHtml = resolve(projectRoot, \"preview.html\");\n if (!existsSync(previewHtml)) return;\n\n // Add preview.html as a second Rollup entry point\n return {\n build: {\n rollupOptions: {\n input: {\n main: resolve(projectRoot, \"index.html\"),\n preview: previewHtml,\n },\n },\n },\n };\n },\n\n // vite preview: static file server with no rewrite rules.\n // Map /__preview__ → preview.html and /__manifests__ → __manifests__.json\n // so local production testing works the same as nginx in production.\n configurePreviewServer(server) {\n server.middlewares.use(\"/__preview__\", async (req, res) => {\n try {\n const distDir = server.config.build.outDir;\n const html = await readFile(\n resolve(distDir, \"preview.html\"),\n \"utf-8\",\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n const origin = req.headers.origin;\n if (origin && isAllowedOrigin(origin)) {\n res.setHeader(\"Access-Control-Allow-Origin\", origin);\n res.setHeader(\"Vary\", \"Origin\");\n }\n res.statusCode = 200;\n res.end(html);\n } catch {\n res.statusCode = 404;\n res.end(\"preview.html not found in build output\");\n }\n });\n\n server.middlewares.use(\"/__manifests__\", async (_req, res) => {\n try {\n const distDir = server.config.build.outDir;\n const json = await readFile(\n resolve(distDir, \"__manifests__.json\"),\n \"utf-8\",\n );\n res.setHeader(\"Content-Type\", \"application/json\");\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.statusCode = 200;\n res.end(json);\n } catch {\n res.statusCode = 404;\n res.end(\"__manifests__.json not found in build output\");\n }\n });\n },\n\n // vite dev: serve /__preview__ with Vite's HTML transform pipeline\n configureServer(server) {\n // Serve the preview HTML shell at /__preview__\n server.middlewares.use(\"/__preview__\", async (req, res) => {\n try {\n // Read the preview.html from the project root (copied from template)\n const htmlPath = resolve(server.config.root, \"preview.html\");\n let html: string;\n try {\n html = await readFile(htmlPath, \"utf-8\");\n } catch {\n // Fallback: serve a minimal preview shell\n html = getDefaultPreviewHtml();\n }\n\n // Transform through Vite's HTML pipeline for HMR + import resolution\n html = await server.transformIndexHtml(\"/__preview__\", html);\n\n res.setHeader(\"Content-Type\", \"text/html\");\n const origin = req.headers.origin;\n if (origin && isAllowedOrigin(origin)) {\n res.setHeader(\"Access-Control-Allow-Origin\", origin);\n res.setHeader(\"Vary\", \"Origin\");\n }\n res.statusCode = 200;\n res.end(html);\n } catch (err) {\n server.config.logger.error(`[fluid] Failed to serve preview: ${err}`);\n res.statusCode = 500;\n res.end(\"Preview failed to load\");\n }\n });\n },\n };\n}\n\nfunction getDefaultPreviewHtml(): string {\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Widget Preview</title>\n <meta name=\"referrer\" content=\"strict-origin-when-cross-origin\" />\n <style>body { margin: 0; padding: 0; }</style>\n </head>\n <body>\n <div id=\"preview-root\"></div>\n <script type=\"module\" src=\"/src/preview-entry.tsx\"></script>\n </body>\n</html>`;\n}\n"],"mappings":"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"}
|
package/dist/vite/index.d.cts
CHANGED
|
@@ -28,8 +28,8 @@ declare function fluidBuilderPreviewPlugin(): Plugin;
|
|
|
28
28
|
* Dev mode: serves `/__preview__` by transforming preview.html through
|
|
29
29
|
* Vite's HTML pipeline (so import resolution and HMR work).
|
|
30
30
|
*
|
|
31
|
-
* Build mode:
|
|
32
|
-
* it
|
|
31
|
+
* Build mode: adds preview.html as a second Rollup input so `vite build`
|
|
32
|
+
* produces it alongside index.html for production deployment.
|
|
33
33
|
*/
|
|
34
34
|
declare function fluidPreviewPlugin(): Plugin;
|
|
35
35
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts","../../src/vite/preview-plugin.ts"],"mappings":";;;;;AAeA;;;;;;;iBAAgB,mBAAA,CAAA,GAAuB,MAAA;;;;;AAAvC;;;;iBCcgB,yBAAA,CAAA,GAA6B,MAAA;;;;;ADd7C;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts","../../src/vite/preview-plugin.ts"],"mappings":";;;;;AAeA;;;;;;;iBAAgB,mBAAA,CAAA,GAAuB,MAAA;;;;;AAAvC;;;;iBCcgB,yBAAA,CAAA,GAA6B,MAAA;;;;;ADd7C;;;;;;;iBEAgB,kBAAA,CAAA,GAAsB,MAAA"}
|
package/dist/vite/index.d.mts
CHANGED
|
@@ -28,8 +28,8 @@ declare function fluidBuilderPreviewPlugin(): Plugin;
|
|
|
28
28
|
* Dev mode: serves `/__preview__` by transforming preview.html through
|
|
29
29
|
* Vite's HTML pipeline (so import resolution and HMR work).
|
|
30
30
|
*
|
|
31
|
-
* Build mode:
|
|
32
|
-
* it
|
|
31
|
+
* Build mode: adds preview.html as a second Rollup input so `vite build`
|
|
32
|
+
* produces it alongside index.html for production deployment.
|
|
33
33
|
*/
|
|
34
34
|
declare function fluidPreviewPlugin(): Plugin;
|
|
35
35
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts","../../src/vite/preview-plugin.ts"],"mappings":";;;;;AAeA;;;;;;;iBAAgB,mBAAA,CAAA,GAAuB,MAAA;;;;;AAAvC;;;;iBCcgB,yBAAA,CAAA,GAA6B,MAAA;;;;;ADd7C;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts","../../src/vite/preview-plugin.ts"],"mappings":";;;;;AAeA;;;;;;;iBAAgB,mBAAA,CAAA,GAAuB,MAAA;;;;;AAAvC;;;;iBCcgB,yBAAA,CAAA,GAA6B,MAAA;;;;;ADd7C;;;;;;;iBEAgB,kBAAA,CAAA,GAAsB,MAAA"}
|
package/dist/vite/index.mjs
CHANGED
|
@@ -277,12 +277,54 @@ function isAllowedOrigin(origin) {
|
|
|
277
277
|
* Dev mode: serves `/__preview__` by transforming preview.html through
|
|
278
278
|
* Vite's HTML pipeline (so import resolution and HMR work).
|
|
279
279
|
*
|
|
280
|
-
* Build mode:
|
|
281
|
-
* it
|
|
280
|
+
* Build mode: adds preview.html as a second Rollup input so `vite build`
|
|
281
|
+
* produces it alongside index.html for production deployment.
|
|
282
282
|
*/
|
|
283
283
|
function fluidPreviewPlugin() {
|
|
284
284
|
return {
|
|
285
285
|
name: "fluid-preview-plugin",
|
|
286
|
+
config(_config, { command }) {
|
|
287
|
+
if (command !== "build") return;
|
|
288
|
+
const projectRoot = process.cwd();
|
|
289
|
+
const previewHtml = resolve(projectRoot, "preview.html");
|
|
290
|
+
if (!existsSync(previewHtml)) return;
|
|
291
|
+
return { build: { rollupOptions: { input: {
|
|
292
|
+
main: resolve(projectRoot, "index.html"),
|
|
293
|
+
preview: previewHtml
|
|
294
|
+
} } } };
|
|
295
|
+
},
|
|
296
|
+
configurePreviewServer(server) {
|
|
297
|
+
server.middlewares.use("/__preview__", async (req, res) => {
|
|
298
|
+
try {
|
|
299
|
+
const distDir = server.config.build.outDir;
|
|
300
|
+
const html = await readFile(resolve(distDir, "preview.html"), "utf-8");
|
|
301
|
+
res.setHeader("Content-Type", "text/html");
|
|
302
|
+
const origin = req.headers.origin;
|
|
303
|
+
if (origin && isAllowedOrigin(origin)) {
|
|
304
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
305
|
+
res.setHeader("Vary", "Origin");
|
|
306
|
+
}
|
|
307
|
+
res.statusCode = 200;
|
|
308
|
+
res.end(html);
|
|
309
|
+
} catch {
|
|
310
|
+
res.statusCode = 404;
|
|
311
|
+
res.end("preview.html not found in build output");
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
server.middlewares.use("/__manifests__", async (_req, res) => {
|
|
315
|
+
try {
|
|
316
|
+
const distDir = server.config.build.outDir;
|
|
317
|
+
const json = await readFile(resolve(distDir, "__manifests__.json"), "utf-8");
|
|
318
|
+
res.setHeader("Content-Type", "application/json");
|
|
319
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
320
|
+
res.statusCode = 200;
|
|
321
|
+
res.end(json);
|
|
322
|
+
} catch {
|
|
323
|
+
res.statusCode = 404;
|
|
324
|
+
res.end("__manifests__.json not found in build output");
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
},
|
|
286
328
|
configureServer(server) {
|
|
287
329
|
server.middlewares.use("/__preview__", async (req, res) => {
|
|
288
330
|
try {
|
package/dist/vite/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/vite/validate-manifest.ts","../../src/vite/builder-preview-plugin.ts","../../src/vite/manifest-plugin.ts","../../../core/src/preview/protocol.ts","../../src/vite/preview-plugin.ts"],"sourcesContent":["/**\n * Lightweight manifest validation for the SDK vite plugin.\n *\n * Inlined here (rather than imported from @fluid-app/portal-core) because\n * portal-core is private and not published to npm. This avoids a runtime\n * ERR_MODULE_NOT_FOUND for portals installed from npm.\n */\n\nconst VALID_FIELD_TYPES = [\n \"text\",\n \"textarea\",\n \"number\",\n \"boolean\",\n \"select\",\n \"color\",\n \"range\",\n \"dataSource\",\n \"resource\",\n \"image\",\n \"alignment\",\n \"slider\",\n \"colorPicker\",\n \"sectionHeader\",\n \"separator\",\n \"buttonGroup\",\n \"colorSelect\",\n \"sectionLayoutSelect\",\n \"background\",\n \"contentPosition\",\n \"textSizeSelect\",\n \"cssUnit\",\n \"fontPicker\",\n \"stringArray\",\n \"borderRadius\",\n \"screenPicker\",\n] as const;\n\ninterface ValidationError {\n path: string;\n message: string;\n}\n\ntype ValidationResult =\n | { success: true }\n | { success: false; errors: ValidationError[] };\n\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: ValidationError[] = [];\n const m = input as Record<string, unknown>;\n\n if (!m || typeof m !== \"object\") {\n return {\n success: false,\n errors: [{ path: \"\", message: \"Manifest must be an object\" }],\n };\n }\n\n // Required string fields\n for (const key of [\n \"type\",\n \"displayName\",\n \"description\",\n \"icon\",\n \"category\",\n ]) {\n if (typeof m[key] !== \"string\" || (m[key] as string).length === 0) {\n errors.push({\n path: key,\n message: `${key} is required and must be a non-empty string`,\n });\n }\n }\n\n if (typeof m.manifestVersion !== \"number\" || m.manifestVersion < 1) {\n errors.push({\n path: \"manifestVersion\",\n message: \"manifestVersion must be a positive integer\",\n });\n }\n\n if (typeof m.component !== \"function\") {\n errors.push({\n path: \"component\",\n message: \"component must be a React component (function)\",\n });\n }\n\n // Property schema validation\n const schema = m.propertySchema as Record<string, unknown> | undefined;\n if (schema && typeof schema === \"object\") {\n if (typeof schema.widgetType !== \"string\" || !schema.widgetType) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"widgetType is required\",\n });\n }\n if (typeof m.type === \"string\" && schema.widgetType !== m.type) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"manifest.type must match manifest.propertySchema.widgetType\",\n });\n }\n if (Array.isArray(schema.fields)) {\n for (let i = 0; i < schema.fields.length; i++) {\n const field = schema.fields[i] as Record<string, unknown>;\n if (!field || typeof field.type !== \"string\") continue;\n if (\n !VALID_FIELD_TYPES.includes(\n field.type as (typeof VALID_FIELD_TYPES)[number],\n )\n ) {\n errors.push({\n path: `propertySchema.fields.${i}.type`,\n message: `Invalid field type \"${field.type}\". Valid types: ${VALID_FIELD_TYPES.join(\", \")}`,\n });\n }\n }\n }\n }\n\n return errors.length === 0 ? { success: true } : { success: false, errors };\n}\n","import type { Plugin, ResolvedConfig } from \"vite\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst VIRTUAL_ENTRY_ID = \"virtual:builder-preview-entry\";\nconst RESOLVED_VIRTUAL_ID = \"\\0\" + VIRTUAL_ENTRY_ID;\n\nconst RAW_HTML = `<!doctype html>\n<html lang=\"en\" data-theme-mode=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Custom Widget Preview</title>\n <style>\n body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }\n </style>\n </head>\n <body>\n <div id=\"builder-preview-root\"></div>\n <script type=\"module\" src=\"/@id/virtual:builder-preview-entry\"></script>\n </body>\n</html>`;\n\n/**\n * Vite plugin that serves a standalone widget preview page at /builder-preview.\n *\n * Dev mode only. Renders all customWidgets from portal.config.ts with\n * a live preview and property editor — no auth, no iframe, no fluid-admin needed.\n */\nexport function fluidBuilderPreviewPlugin(): Plugin {\n let configPath: string;\n let cssPath: string;\n\n return {\n name: \"fluid-builder-preview\",\n apply: \"serve\",\n\n configResolved(config: ResolvedConfig) {\n const root = config.root;\n const candidates = [\"src/portal.config.ts\", \"portal.config.ts\"];\n configPath =\n candidates.find((c) => existsSync(join(root, c))) ??\n \"src/portal.config.ts\";\n const cssCandidates = [\n \"src/index.css\",\n \"src/styles/index.css\",\n \"index.css\",\n ];\n cssPath =\n cssCandidates.find((c) => existsSync(join(root, c))) ?? \"src/index.css\";\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_ID) {\n return `\nimport \"/${cssPath}\";\nimport * as portalConfig from \"/${configPath}\";\nimport { createRoot } from \"react-dom/client\";\nimport { createElement } from \"react\";\nimport { BuilderPreviewApp } from \"@fluid-app/portal-preview\";\n\nconst widgets = portalConfig.customWidgets || [];\nconst root = document.getElementById(\"builder-preview-root\");\nif (root) {\n createRoot(root).render(\n createElement(BuilderPreviewApp, { widgets })\n );\n}\n`;\n }\n },\n\n configureServer(server) {\n server.middlewares.use(async (req, res, next) => {\n const pathname = (req.url ?? \"\").split(\"?\")[0];\n if (pathname !== \"/builder-preview\" && pathname !== \"/builder-preview/\")\n return next();\n try {\n const transformed = await server.transformIndexHtml(\n \"/builder-preview\",\n RAW_HTML,\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(transformed);\n } catch (e) {\n server.config.logger.error(\n `[fluid] Failed to serve builder preview: ${e}`,\n );\n res.statusCode = 500;\n res.end(\"Builder preview failed to load\");\n }\n });\n },\n };\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer, Logger } from \"vite\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { validateManifest } from \"./validate-manifest\";\nimport { fluidBuilderPreviewPlugin } from \"./builder-preview-plugin\";\n\n/**\n * Vite plugin bundle that serves widget manifest metadata and the builder preview.\n *\n * Returns an array of plugins:\n * 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)\n * 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)\n *\n * Every portal using `fluidManifestPlugin()` automatically gets the builder preview.\n */\nexport function fluidManifestPlugin(): Plugin[] {\n return [fluidManifestPluginInternal(), fluidBuilderPreviewPlugin()];\n}\n\nfunction fluidManifestPluginInternal(): Plugin {\n let configPath: string;\n\n return {\n name: \"fluid-manifest-plugin\",\n\n configResolved(config: ResolvedConfig) {\n const root = config.root;\n const candidates = [\n \"src/widgets.config.ts\",\n \"src/portal.config.ts\",\n \"portal.config.ts\",\n ];\n configPath =\n \"/\" +\n (candidates.find((c) => existsSync(join(root, c))) ??\n \"src/portal.config.ts\");\n },\n\n configureServer(server) {\n server.middlewares.use(\"/__manifests__\", async (_req, res) => {\n try {\n const serializable = await loadManifests(\n server,\n server.config.logger,\n configPath,\n );\n\n res.setHeader(\"Content-Type\", \"application/json\");\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.end(JSON.stringify(serializable));\n } catch (err) {\n server.config.logger.error(\n `[fluid] Failed to load manifests: ${err}`,\n );\n res.statusCode = 500;\n res.end(JSON.stringify({ error: String(err) }));\n }\n });\n },\n\n generateBundle() {\n // Build mode: emit placeholder. The CLI extraction utility handles\n // actual build-time manifest extraction via tsx subprocess.\n this.warn(\n \"[fluid] fluidManifestPlugin: emitting empty __manifests__.json. \" +\n \"Run `fluid build` instead of `vite build` to include widget manifests.\",\n );\n this.emitFile({\n type: \"asset\",\n fileName: \"__manifests__.json\",\n source: JSON.stringify([]),\n });\n },\n };\n}\n\n/**\n * Load and serialize manifests from the resolved widget config\n * (widgets.config.ts or portal.config.ts) via Vite's ssrLoadModule.\n * Validates each manifest before stripping the `component` field.\n * Returns an empty array if the config module or export is missing/invalid.\n */\nasync function loadManifests(\n server: ViteDevServer,\n logger?: Logger,\n configFilePath?: string,\n): Promise<unknown[]> {\n let mod: Record<string, unknown>;\n try {\n mod = await server.ssrLoadModule(configFilePath ?? \"/src/portal.config.ts\");\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const isSSRError =\n /(document|window|navigator|localStorage|sessionStorage|location|history|HTMLElement) is not defined/.test(\n message,\n );\n\n if (isSSRError) {\n const configFile = configFilePath ?? \"/src/portal.config.ts\";\n const isPortalConfig = configFile.includes(\"portal.config\");\n const fixHint = isPortalConfig\n ? ` Fix: Create src/widgets.config.ts with only your customWidgets export.\\n` +\n ` The manifest plugin will load it instead of portal.config.ts.\\n`\n : ` Fix: Ensure ${configFile} does not import browser-only code.\\n`;\n\n logger?.warn(\n `[fluid] Cannot load widget manifests — ${configFile} imports ` +\n `browser-only code that fails during server-side evaluation.\\n` +\n ` Custom widgets will not appear in the builder.\\n` +\n fixHint +\n ` Widget components are fine — the issue is usually screen imports\\n` +\n ` (e.g. DashboardScreen) that pull in the SDK barrel export.`,\n );\n } else {\n logger?.warn(`[fluid] Could not load widget config: ${message}`);\n }\n return [];\n }\n\n const rawWidgets = mod.customWidgets;\n if (!rawWidgets) {\n if (configFilePath?.includes(\"widgets.config\")) {\n logger?.warn(\n `[fluid] widgets.config.ts was loaded but exports no customWidgets. ` +\n `Custom widgets will not appear in the builder.`,\n );\n }\n return [];\n }\n\n if (!Array.isArray(rawWidgets)) {\n logger?.warn(\n `[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`,\n );\n return [];\n }\n\n const manifests = rawWidgets as Record<string, unknown>[];\n\n // Validate full manifests (with component) before stripping\n if (logger) {\n for (const manifest of manifests) {\n const result = validateManifest(manifest);\n if (!result.success) {\n const type = (manifest as { type?: string }).type ?? \"unknown\";\n logger.warn(\n `[fluid] Invalid manifest for \"${type}\":\\n` +\n result.errors.map((e) => ` - ${e.path}: ${e.message}`).join(\"\\n\"),\n );\n }\n }\n }\n\n return manifests.map(\n ({ component: _component, ...rest }: Record<string, unknown>) => rest,\n );\n}\n","/**\n * PostMessage protocol for builder ↔ iframe widget preview communication.\n *\n * Four-step handshake:\n * 1. Iframe sends `ready` once mounted\n * 2. Builder sends `auth` with token (NEVER via URL)\n * 3. Iframe sends `auth-ready` once FluidProvider settles\n * 4. Builder sends `render-widget` with type + props\n *\n * This prevents the race condition where render-widget arrives\n * before the provider stack has initialized with the auth token.\n */\n\n// ---------------------------------------------------------------------------\n// Builder → Iframe commands\n// ---------------------------------------------------------------------------\n\nexport interface AuthCommand {\n type: \"auth\";\n token: string;\n}\n\nexport interface RenderWidgetCommand {\n type: \"render-widget\";\n widgetType: string;\n props: Record<string, unknown>;\n}\n\nexport interface UnmountWidgetCommand {\n type: \"unmount-widget\";\n}\n\nexport interface SelectWidgetCommand {\n type: \"select-widget\";\n widgetType: string;\n}\n\nexport type PreviewCommand =\n | AuthCommand\n | RenderWidgetCommand\n | UnmountWidgetCommand\n | SelectWidgetCommand;\n\n// ---------------------------------------------------------------------------\n// Iframe → Builder events\n// ---------------------------------------------------------------------------\n\nexport interface ReadyEvent {\n type: \"ready\";\n}\n\nexport interface AuthReadyEvent {\n type: \"auth-ready\";\n}\n\nexport interface WidgetRenderedEvent {\n type: \"widget-rendered\";\n widgetType: string;\n dimensions: { width: number; height: number };\n}\n\nexport interface WidgetErrorEvent {\n type: \"widget-error\";\n widgetType: string;\n error: string;\n}\n\nexport interface WidgetClickedEvent {\n type: \"widget-clicked\";\n widgetType: string;\n}\n\nexport interface WidgetHmrEvent {\n type: \"widget-hmr\";\n}\n\nexport type PreviewEvent =\n | ReadyEvent\n | AuthReadyEvent\n | WidgetRenderedEvent\n | WidgetErrorEvent\n | WidgetClickedEvent\n | WidgetHmrEvent;\n\n// ---------------------------------------------------------------------------\n// Origin validation\n// ---------------------------------------------------------------------------\n\nconst ALLOWED_ORIGIN_PATTERNS = [\n /^https?:\\/\\/localhost(:\\d+)?$/,\n /^https?:\\/\\/127\\.0\\.0\\.1(:\\d+)?$/,\n /^https:\\/\\/[a-z0-9-]+\\.portal\\.fluid\\.app$/,\n /^https:\\/\\/[a-z0-9-]+\\.fluid\\.app$/,\n];\n\n/**\n * Validate that a postMessage origin is trusted.\n * Accepts localhost (any port), *.portal.fluid.app, and *.fluid.app.\n */\nexport function isAllowedOrigin(origin: string): boolean {\n return ALLOWED_ORIGIN_PATTERNS.some((pattern) => pattern.test(origin));\n}\n\n// ---------------------------------------------------------------------------\n// Type guards\n// ---------------------------------------------------------------------------\n\n/** Check if a message is a valid PreviewCommand (builder → iframe). */\nexport function isPreviewCommand(data: unknown): data is PreviewCommand {\n if (typeof data !== \"object\" || data === null) return false;\n const msg = data as Record<string, unknown>;\n switch (msg[\"type\"]) {\n case \"auth\":\n return typeof msg[\"token\"] === \"string\";\n case \"render-widget\":\n return (\n typeof msg[\"widgetType\"] === \"string\" &&\n typeof msg[\"props\"] === \"object\" &&\n msg[\"props\"] !== null &&\n !Array.isArray(msg[\"props\"])\n );\n case \"unmount-widget\":\n return true;\n case \"select-widget\":\n return typeof msg[\"widgetType\"] === \"string\";\n default:\n return false;\n }\n}\n\n/** Check if a message is a valid PreviewEvent (iframe → builder). */\nexport function isPreviewEvent(data: unknown): data is PreviewEvent {\n if (typeof data !== \"object\" || data === null) return false;\n const msg = data as Record<string, unknown>;\n switch (msg[\"type\"]) {\n case \"ready\":\n case \"auth-ready\":\n case \"widget-hmr\":\n return true;\n case \"widget-rendered\":\n return (\n typeof msg[\"widgetType\"] === \"string\" &&\n typeof msg[\"dimensions\"] === \"object\" &&\n msg[\"dimensions\"] !== null &&\n Number.isFinite(\n (msg[\"dimensions\"] as Record<string, unknown>)[\"width\"],\n ) &&\n Number.isFinite(\n (msg[\"dimensions\"] as Record<string, unknown>)[\"height\"],\n )\n );\n case \"widget-error\":\n return (\n typeof msg[\"widgetType\"] === \"string\" &&\n typeof msg[\"error\"] === \"string\"\n );\n case \"widget-clicked\":\n return typeof msg[\"widgetType\"] === \"string\";\n default:\n return false;\n }\n}\n","import { resolve } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport type { Plugin } from \"vite\";\nimport { isAllowedOrigin } from \"@fluid-app/portal-core/preview/protocol\";\n\n/**\n * Vite plugin that serves the widget preview route.\n *\n * Dev mode: serves `/__preview__` by transforming preview.html through\n * Vite's HTML pipeline (so import resolution and HMR work).\n *\n * Build mode: no-op — the production preview.html is handled by adding\n * it as a second Rollup input in vite.config.ts.\n */\nexport function fluidPreviewPlugin(): Plugin {\n return {\n name: \"fluid-preview-plugin\",\n\n configureServer(server) {\n // Serve the preview HTML shell at /__preview__\n server.middlewares.use(\"/__preview__\", async (req, res) => {\n try {\n // Read the preview.html from the project root (copied from template)\n const htmlPath = resolve(server.config.root, \"preview.html\");\n let html: string;\n try {\n html = await readFile(htmlPath, \"utf-8\");\n } catch {\n // Fallback: serve a minimal preview shell\n html = getDefaultPreviewHtml();\n }\n\n // Transform through Vite's HTML pipeline for HMR + import resolution\n html = await server.transformIndexHtml(\"/__preview__\", html);\n\n res.setHeader(\"Content-Type\", \"text/html\");\n const origin = req.headers.origin;\n if (origin && isAllowedOrigin(origin)) {\n res.setHeader(\"Access-Control-Allow-Origin\", origin);\n res.setHeader(\"Vary\", \"Origin\");\n }\n res.statusCode = 200;\n res.end(html);\n } catch (err) {\n server.config.logger.error(`[fluid] Failed to serve preview: ${err}`);\n res.statusCode = 500;\n res.end(\"Preview failed to load\");\n }\n });\n },\n };\n}\n\nfunction getDefaultPreviewHtml(): string {\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Widget Preview</title>\n <meta name=\"referrer\" content=\"strict-origin-when-cross-origin\" />\n <style>body { margin: 0; padding: 0; }</style>\n </head>\n <body>\n <div id=\"preview-root\"></div>\n <script type=\"module\" src=\"/src/preview-entry.tsx\"></script>\n </body>\n</html>`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/vite/validate-manifest.ts","../../src/vite/builder-preview-plugin.ts","../../src/vite/manifest-plugin.ts","../../../core/src/preview/protocol.ts","../../src/vite/preview-plugin.ts"],"sourcesContent":["/**\n * Lightweight manifest validation for the SDK vite plugin.\n *\n * Inlined here (rather than imported from @fluid-app/portal-core) because\n * portal-core is private and not published to npm. This avoids a runtime\n * ERR_MODULE_NOT_FOUND for portals installed from npm.\n */\n\nconst VALID_FIELD_TYPES = [\n \"text\",\n \"textarea\",\n \"number\",\n \"boolean\",\n \"select\",\n \"color\",\n \"range\",\n \"dataSource\",\n \"resource\",\n \"image\",\n \"alignment\",\n \"slider\",\n \"colorPicker\",\n \"sectionHeader\",\n \"separator\",\n \"buttonGroup\",\n \"colorSelect\",\n \"sectionLayoutSelect\",\n \"background\",\n \"contentPosition\",\n \"textSizeSelect\",\n \"cssUnit\",\n \"fontPicker\",\n \"stringArray\",\n \"borderRadius\",\n \"screenPicker\",\n] as const;\n\ninterface ValidationError {\n path: string;\n message: string;\n}\n\ntype ValidationResult =\n | { success: true }\n | { success: false; errors: ValidationError[] };\n\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: ValidationError[] = [];\n const m = input as Record<string, unknown>;\n\n if (!m || typeof m !== \"object\") {\n return {\n success: false,\n errors: [{ path: \"\", message: \"Manifest must be an object\" }],\n };\n }\n\n // Required string fields\n for (const key of [\n \"type\",\n \"displayName\",\n \"description\",\n \"icon\",\n \"category\",\n ]) {\n if (typeof m[key] !== \"string\" || (m[key] as string).length === 0) {\n errors.push({\n path: key,\n message: `${key} is required and must be a non-empty string`,\n });\n }\n }\n\n if (typeof m.manifestVersion !== \"number\" || m.manifestVersion < 1) {\n errors.push({\n path: \"manifestVersion\",\n message: \"manifestVersion must be a positive integer\",\n });\n }\n\n if (typeof m.component !== \"function\") {\n errors.push({\n path: \"component\",\n message: \"component must be a React component (function)\",\n });\n }\n\n // Property schema validation\n const schema = m.propertySchema as Record<string, unknown> | undefined;\n if (schema && typeof schema === \"object\") {\n if (typeof schema.widgetType !== \"string\" || !schema.widgetType) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"widgetType is required\",\n });\n }\n if (typeof m.type === \"string\" && schema.widgetType !== m.type) {\n errors.push({\n path: \"propertySchema.widgetType\",\n message: \"manifest.type must match manifest.propertySchema.widgetType\",\n });\n }\n if (Array.isArray(schema.fields)) {\n for (let i = 0; i < schema.fields.length; i++) {\n const field = schema.fields[i] as Record<string, unknown>;\n if (!field || typeof field.type !== \"string\") continue;\n if (\n !VALID_FIELD_TYPES.includes(\n field.type as (typeof VALID_FIELD_TYPES)[number],\n )\n ) {\n errors.push({\n path: `propertySchema.fields.${i}.type`,\n message: `Invalid field type \"${field.type}\". Valid types: ${VALID_FIELD_TYPES.join(\", \")}`,\n });\n }\n }\n }\n }\n\n return errors.length === 0 ? { success: true } : { success: false, errors };\n}\n","import type { Plugin, ResolvedConfig } from \"vite\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst VIRTUAL_ENTRY_ID = \"virtual:builder-preview-entry\";\nconst RESOLVED_VIRTUAL_ID = \"\\0\" + VIRTUAL_ENTRY_ID;\n\nconst RAW_HTML = `<!doctype html>\n<html lang=\"en\" data-theme-mode=\"dark\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Custom Widget Preview</title>\n <style>\n body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }\n </style>\n </head>\n <body>\n <div id=\"builder-preview-root\"></div>\n <script type=\"module\" src=\"/@id/virtual:builder-preview-entry\"></script>\n </body>\n</html>`;\n\n/**\n * Vite plugin that serves a standalone widget preview page at /builder-preview.\n *\n * Dev mode only. Renders all customWidgets from portal.config.ts with\n * a live preview and property editor — no auth, no iframe, no fluid-admin needed.\n */\nexport function fluidBuilderPreviewPlugin(): Plugin {\n let configPath: string;\n let cssPath: string;\n\n return {\n name: \"fluid-builder-preview\",\n apply: \"serve\",\n\n configResolved(config: ResolvedConfig) {\n const root = config.root;\n const candidates = [\"src/portal.config.ts\", \"portal.config.ts\"];\n configPath =\n candidates.find((c) => existsSync(join(root, c))) ??\n \"src/portal.config.ts\";\n const cssCandidates = [\n \"src/index.css\",\n \"src/styles/index.css\",\n \"index.css\",\n ];\n cssPath =\n cssCandidates.find((c) => existsSync(join(root, c))) ?? \"src/index.css\";\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_ID) {\n return `\nimport \"/${cssPath}\";\nimport * as portalConfig from \"/${configPath}\";\nimport { createRoot } from \"react-dom/client\";\nimport { createElement } from \"react\";\nimport { BuilderPreviewApp } from \"@fluid-app/portal-preview\";\n\nconst widgets = portalConfig.customWidgets || [];\nconst root = document.getElementById(\"builder-preview-root\");\nif (root) {\n createRoot(root).render(\n createElement(BuilderPreviewApp, { widgets })\n );\n}\n`;\n }\n },\n\n configureServer(server) {\n server.middlewares.use(async (req, res, next) => {\n const pathname = (req.url ?? \"\").split(\"?\")[0];\n if (pathname !== \"/builder-preview\" && pathname !== \"/builder-preview/\")\n return next();\n try {\n const transformed = await server.transformIndexHtml(\n \"/builder-preview\",\n RAW_HTML,\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(transformed);\n } catch (e) {\n server.config.logger.error(\n `[fluid] Failed to serve builder preview: ${e}`,\n );\n res.statusCode = 500;\n res.end(\"Builder preview failed to load\");\n }\n });\n },\n };\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer, Logger } from \"vite\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { validateManifest } from \"./validate-manifest\";\nimport { fluidBuilderPreviewPlugin } from \"./builder-preview-plugin\";\n\n/**\n * Vite plugin bundle that serves widget manifest metadata and the builder preview.\n *\n * Returns an array of plugins:\n * 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)\n * 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)\n *\n * Every portal using `fluidManifestPlugin()` automatically gets the builder preview.\n */\nexport function fluidManifestPlugin(): Plugin[] {\n return [fluidManifestPluginInternal(), fluidBuilderPreviewPlugin()];\n}\n\nfunction fluidManifestPluginInternal(): Plugin {\n let configPath: string;\n\n return {\n name: \"fluid-manifest-plugin\",\n\n configResolved(config: ResolvedConfig) {\n const root = config.root;\n const candidates = [\n \"src/widgets.config.ts\",\n \"src/portal.config.ts\",\n \"portal.config.ts\",\n ];\n configPath =\n \"/\" +\n (candidates.find((c) => existsSync(join(root, c))) ??\n \"src/portal.config.ts\");\n },\n\n configureServer(server) {\n server.middlewares.use(\"/__manifests__\", async (_req, res) => {\n try {\n const serializable = await loadManifests(\n server,\n server.config.logger,\n configPath,\n );\n\n res.setHeader(\"Content-Type\", \"application/json\");\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.end(JSON.stringify(serializable));\n } catch (err) {\n server.config.logger.error(\n `[fluid] Failed to load manifests: ${err}`,\n );\n res.statusCode = 500;\n res.end(JSON.stringify({ error: String(err) }));\n }\n });\n },\n\n generateBundle() {\n // Build mode: emit placeholder. The CLI extraction utility handles\n // actual build-time manifest extraction via tsx subprocess.\n this.warn(\n \"[fluid] fluidManifestPlugin: emitting empty __manifests__.json. \" +\n \"Run `fluid build` instead of `vite build` to include widget manifests.\",\n );\n this.emitFile({\n type: \"asset\",\n fileName: \"__manifests__.json\",\n source: JSON.stringify([]),\n });\n },\n };\n}\n\n/**\n * Load and serialize manifests from the resolved widget config\n * (widgets.config.ts or portal.config.ts) via Vite's ssrLoadModule.\n * Validates each manifest before stripping the `component` field.\n * Returns an empty array if the config module or export is missing/invalid.\n */\nasync function loadManifests(\n server: ViteDevServer,\n logger?: Logger,\n configFilePath?: string,\n): Promise<unknown[]> {\n let mod: Record<string, unknown>;\n try {\n mod = await server.ssrLoadModule(configFilePath ?? \"/src/portal.config.ts\");\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const isSSRError =\n /(document|window|navigator|localStorage|sessionStorage|location|history|HTMLElement) is not defined/.test(\n message,\n );\n\n if (isSSRError) {\n const configFile = configFilePath ?? \"/src/portal.config.ts\";\n const isPortalConfig = configFile.includes(\"portal.config\");\n const fixHint = isPortalConfig\n ? ` Fix: Create src/widgets.config.ts with only your customWidgets export.\\n` +\n ` The manifest plugin will load it instead of portal.config.ts.\\n`\n : ` Fix: Ensure ${configFile} does not import browser-only code.\\n`;\n\n logger?.warn(\n `[fluid] Cannot load widget manifests — ${configFile} imports ` +\n `browser-only code that fails during server-side evaluation.\\n` +\n ` Custom widgets will not appear in the builder.\\n` +\n fixHint +\n ` Widget components are fine — the issue is usually screen imports\\n` +\n ` (e.g. DashboardScreen) that pull in the SDK barrel export.`,\n );\n } else {\n logger?.warn(`[fluid] Could not load widget config: ${message}`);\n }\n return [];\n }\n\n const rawWidgets = mod.customWidgets;\n if (!rawWidgets) {\n if (configFilePath?.includes(\"widgets.config\")) {\n logger?.warn(\n `[fluid] widgets.config.ts was loaded but exports no customWidgets. ` +\n `Custom widgets will not appear in the builder.`,\n );\n }\n return [];\n }\n\n if (!Array.isArray(rawWidgets)) {\n logger?.warn(\n `[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`,\n );\n return [];\n }\n\n const manifests = rawWidgets as Record<string, unknown>[];\n\n // Validate full manifests (with component) before stripping\n if (logger) {\n for (const manifest of manifests) {\n const result = validateManifest(manifest);\n if (!result.success) {\n const type = (manifest as { type?: string }).type ?? \"unknown\";\n logger.warn(\n `[fluid] Invalid manifest for \"${type}\":\\n` +\n result.errors.map((e) => ` - ${e.path}: ${e.message}`).join(\"\\n\"),\n );\n }\n }\n }\n\n return manifests.map(\n ({ component: _component, ...rest }: Record<string, unknown>) => rest,\n );\n}\n","/**\n * PostMessage protocol for builder ↔ iframe widget preview communication.\n *\n * Four-step handshake:\n * 1. Iframe sends `ready` once mounted\n * 2. Builder sends `auth` with token (NEVER via URL)\n * 3. Iframe sends `auth-ready` once FluidProvider settles\n * 4. Builder sends `render-widget` with type + props\n *\n * This prevents the race condition where render-widget arrives\n * before the provider stack has initialized with the auth token.\n */\n\n// ---------------------------------------------------------------------------\n// Builder → Iframe commands\n// ---------------------------------------------------------------------------\n\nexport interface AuthCommand {\n type: \"auth\";\n token: string;\n}\n\nexport interface RenderWidgetCommand {\n type: \"render-widget\";\n widgetType: string;\n props: Record<string, unknown>;\n}\n\nexport interface UnmountWidgetCommand {\n type: \"unmount-widget\";\n}\n\nexport interface SelectWidgetCommand {\n type: \"select-widget\";\n widgetType: string;\n}\n\nexport type PreviewCommand =\n | AuthCommand\n | RenderWidgetCommand\n | UnmountWidgetCommand\n | SelectWidgetCommand;\n\n// ---------------------------------------------------------------------------\n// Iframe → Builder events\n// ---------------------------------------------------------------------------\n\nexport interface ReadyEvent {\n type: \"ready\";\n}\n\nexport interface AuthReadyEvent {\n type: \"auth-ready\";\n}\n\nexport interface WidgetRenderedEvent {\n type: \"widget-rendered\";\n widgetType: string;\n dimensions: { width: number; height: number };\n}\n\nexport interface WidgetErrorEvent {\n type: \"widget-error\";\n widgetType: string;\n error: string;\n}\n\nexport interface WidgetClickedEvent {\n type: \"widget-clicked\";\n widgetType: string;\n}\n\nexport interface WidgetHmrEvent {\n type: \"widget-hmr\";\n}\n\nexport type PreviewEvent =\n | ReadyEvent\n | AuthReadyEvent\n | WidgetRenderedEvent\n | WidgetErrorEvent\n | WidgetClickedEvent\n | WidgetHmrEvent;\n\n// ---------------------------------------------------------------------------\n// Origin validation\n// ---------------------------------------------------------------------------\n\nconst ALLOWED_ORIGIN_PATTERNS = [\n /^https?:\\/\\/localhost(:\\d+)?$/,\n /^https?:\\/\\/127\\.0\\.0\\.1(:\\d+)?$/,\n /^https:\\/\\/[a-z0-9-]+\\.portal\\.fluid\\.app$/,\n /^https:\\/\\/[a-z0-9-]+\\.fluid\\.app$/,\n];\n\n/**\n * Validate that a postMessage origin is trusted.\n * Accepts localhost (any port), *.portal.fluid.app, and *.fluid.app.\n */\nexport function isAllowedOrigin(origin: string): boolean {\n return ALLOWED_ORIGIN_PATTERNS.some((pattern) => pattern.test(origin));\n}\n\n// ---------------------------------------------------------------------------\n// Type guards\n// ---------------------------------------------------------------------------\n\n/** Check if a message is a valid PreviewCommand (builder → iframe). */\nexport function isPreviewCommand(data: unknown): data is PreviewCommand {\n if (typeof data !== \"object\" || data === null) return false;\n const msg = data as Record<string, unknown>;\n switch (msg[\"type\"]) {\n case \"auth\":\n return typeof msg[\"token\"] === \"string\";\n case \"render-widget\":\n return (\n typeof msg[\"widgetType\"] === \"string\" &&\n typeof msg[\"props\"] === \"object\" &&\n msg[\"props\"] !== null &&\n !Array.isArray(msg[\"props\"])\n );\n case \"unmount-widget\":\n return true;\n case \"select-widget\":\n return typeof msg[\"widgetType\"] === \"string\";\n default:\n return false;\n }\n}\n\n/** Check if a message is a valid PreviewEvent (iframe → builder). */\nexport function isPreviewEvent(data: unknown): data is PreviewEvent {\n if (typeof data !== \"object\" || data === null) return false;\n const msg = data as Record<string, unknown>;\n switch (msg[\"type\"]) {\n case \"ready\":\n case \"auth-ready\":\n case \"widget-hmr\":\n return true;\n case \"widget-rendered\":\n return (\n typeof msg[\"widgetType\"] === \"string\" &&\n typeof msg[\"dimensions\"] === \"object\" &&\n msg[\"dimensions\"] !== null &&\n Number.isFinite(\n (msg[\"dimensions\"] as Record<string, unknown>)[\"width\"],\n ) &&\n Number.isFinite(\n (msg[\"dimensions\"] as Record<string, unknown>)[\"height\"],\n )\n );\n case \"widget-error\":\n return (\n typeof msg[\"widgetType\"] === \"string\" &&\n typeof msg[\"error\"] === \"string\"\n );\n case \"widget-clicked\":\n return typeof msg[\"widgetType\"] === \"string\";\n default:\n return false;\n }\n}\n","import { resolve } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport type { Plugin, UserConfig } from \"vite\";\nimport { isAllowedOrigin } from \"@fluid-app/portal-core/preview/protocol\";\n\n/**\n * Vite plugin that serves the widget preview route.\n *\n * Dev mode: serves `/__preview__` by transforming preview.html through\n * Vite's HTML pipeline (so import resolution and HMR work).\n *\n * Build mode: adds preview.html as a second Rollup input so `vite build`\n * produces it alongside index.html for production deployment.\n */\nexport function fluidPreviewPlugin(): Plugin {\n return {\n name: \"fluid-preview-plugin\",\n\n config(_config: UserConfig, { command }) {\n if (command !== \"build\") return;\n\n // config() runs before configResolved, so use process.cwd()\n // which is the project root when invoked via `vite build`.\n const projectRoot = process.cwd();\n const previewHtml = resolve(projectRoot, \"preview.html\");\n if (!existsSync(previewHtml)) return;\n\n // Add preview.html as a second Rollup entry point\n return {\n build: {\n rollupOptions: {\n input: {\n main: resolve(projectRoot, \"index.html\"),\n preview: previewHtml,\n },\n },\n },\n };\n },\n\n // vite preview: static file server with no rewrite rules.\n // Map /__preview__ → preview.html and /__manifests__ → __manifests__.json\n // so local production testing works the same as nginx in production.\n configurePreviewServer(server) {\n server.middlewares.use(\"/__preview__\", async (req, res) => {\n try {\n const distDir = server.config.build.outDir;\n const html = await readFile(\n resolve(distDir, \"preview.html\"),\n \"utf-8\",\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n const origin = req.headers.origin;\n if (origin && isAllowedOrigin(origin)) {\n res.setHeader(\"Access-Control-Allow-Origin\", origin);\n res.setHeader(\"Vary\", \"Origin\");\n }\n res.statusCode = 200;\n res.end(html);\n } catch {\n res.statusCode = 404;\n res.end(\"preview.html not found in build output\");\n }\n });\n\n server.middlewares.use(\"/__manifests__\", async (_req, res) => {\n try {\n const distDir = server.config.build.outDir;\n const json = await readFile(\n resolve(distDir, \"__manifests__.json\"),\n \"utf-8\",\n );\n res.setHeader(\"Content-Type\", \"application/json\");\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.statusCode = 200;\n res.end(json);\n } catch {\n res.statusCode = 404;\n res.end(\"__manifests__.json not found in build output\");\n }\n });\n },\n\n // vite dev: serve /__preview__ with Vite's HTML transform pipeline\n configureServer(server) {\n // Serve the preview HTML shell at /__preview__\n server.middlewares.use(\"/__preview__\", async (req, res) => {\n try {\n // Read the preview.html from the project root (copied from template)\n const htmlPath = resolve(server.config.root, \"preview.html\");\n let html: string;\n try {\n html = await readFile(htmlPath, \"utf-8\");\n } catch {\n // Fallback: serve a minimal preview shell\n html = getDefaultPreviewHtml();\n }\n\n // Transform through Vite's HTML pipeline for HMR + import resolution\n html = await server.transformIndexHtml(\"/__preview__\", html);\n\n res.setHeader(\"Content-Type\", \"text/html\");\n const origin = req.headers.origin;\n if (origin && isAllowedOrigin(origin)) {\n res.setHeader(\"Access-Control-Allow-Origin\", origin);\n res.setHeader(\"Vary\", \"Origin\");\n }\n res.statusCode = 200;\n res.end(html);\n } catch (err) {\n server.config.logger.error(`[fluid] Failed to serve preview: ${err}`);\n res.statusCode = 500;\n res.end(\"Preview failed to load\");\n }\n });\n },\n };\n}\n\nfunction getDefaultPreviewHtml(): string {\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Widget Preview</title>\n <meta name=\"referrer\" content=\"strict-origin-when-cross-origin\" />\n <style>body { margin: 0; padding: 0; }</style>\n </head>\n <body>\n <div id=\"preview-root\"></div>\n <script type=\"module\" src=\"/src/preview-entry.tsx\"></script>\n </body>\n</html>`;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluid-app/portal-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.129",
|
|
4
4
|
"description": "SDK for building custom Fluid portals",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -72,36 +72,36 @@
|
|
|
72
72
|
"typescript": "^5",
|
|
73
73
|
"zod": "4.3.5",
|
|
74
74
|
"@fluid-app/api-client-core": "0.1.0",
|
|
75
|
-
"@fluid-app/cart-ui": "0.1.13",
|
|
76
|
-
"@fluid-app/company-switcher-core": "0.1.0",
|
|
77
75
|
"@fluid-app/auth": "0.1.0",
|
|
76
|
+
"@fluid-app/company-switcher-core": "0.1.0",
|
|
78
77
|
"@fluid-app/company-switcher-ui": "0.1.0",
|
|
79
|
-
"@fluid-app/
|
|
78
|
+
"@fluid-app/cart-ui": "0.1.13",
|
|
80
79
|
"@fluid-app/contacts-api-client": "0.1.0",
|
|
80
|
+
"@fluid-app/contacts-core": "0.1.0",
|
|
81
81
|
"@fluid-app/contacts-ui": "0.1.0",
|
|
82
82
|
"@fluid-app/file-picker-api-client": "0.1.0",
|
|
83
|
-
"@fluid-app/fluid-pay-api-client": "0.1.0",
|
|
84
83
|
"@fluid-app/fluid-pay-core": "0.1.0",
|
|
84
|
+
"@fluid-app/fluid-pay-api-client": "0.1.0",
|
|
85
85
|
"@fluid-app/fluidos-api-client": "0.1.0",
|
|
86
86
|
"@fluid-app/messaging-api-client": "0.1.0",
|
|
87
|
+
"@fluid-app/messaging-core": "0.1.0",
|
|
87
88
|
"@fluid-app/messaging-ui": "0.1.0",
|
|
88
89
|
"@fluid-app/mysite-ui": "0.1.0",
|
|
89
|
-
"@fluid-app/messaging-core": "0.1.0",
|
|
90
90
|
"@fluid-app/orders-api-client": "0.1.0",
|
|
91
91
|
"@fluid-app/orders-core": "0.1.0",
|
|
92
92
|
"@fluid-app/orders-ui": "0.1.0",
|
|
93
93
|
"@fluid-app/permissions": "0.1.0",
|
|
94
94
|
"@fluid-app/portal-app-download-ui": "0.1.0",
|
|
95
95
|
"@fluid-app/portal-core": "0.1.23",
|
|
96
|
-
"@fluid-app/portal-pro-upgrade-ui": "0.1.0",
|
|
97
96
|
"@fluid-app/portal-preview": "0.1.0",
|
|
97
|
+
"@fluid-app/portal-pro-upgrade-ui": "0.1.0",
|
|
98
98
|
"@fluid-app/portal-react": "0.1.0",
|
|
99
99
|
"@fluid-app/portal-tenant-api-client": "0.1.0",
|
|
100
100
|
"@fluid-app/portal-tenant-contacts-api-client": "0.1.0",
|
|
101
|
-
"@fluid-app/portal-widgets": "0.1.22",
|
|
102
101
|
"@fluid-app/portal-tenant-mysite-api-client": "0.1.0",
|
|
103
|
-
"@fluid-app/
|
|
102
|
+
"@fluid-app/portal-widgets": "0.1.22",
|
|
104
103
|
"@fluid-app/products-api-client": "0.1.0",
|
|
104
|
+
"@fluid-app/products-core": "0.1.0",
|
|
105
105
|
"@fluid-app/profile-core": "0.1.0",
|
|
106
106
|
"@fluid-app/profile-ui": "0.1.0",
|
|
107
107
|
"@fluid-app/query-persister": "0.1.0",
|