@fluid-app/portal-sdk 0.1.64 → 0.1.66
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/{AppDownloadScreen-DUGGDlr4.mjs → AppDownloadScreen-BVrUR6lm.mjs} +1 -1
- package/dist/{AppDownloadScreen-pm8fLxZX.mjs → AppDownloadScreen-BXS02LRb.mjs} +2 -2
- package/dist/AppDownloadScreen-BXS02LRb.mjs.map +1 -0
- package/dist/{AppDownloadScreen-oMzxBETr.cjs → AppDownloadScreen-BXWBVnQM.cjs} +2 -2
- package/dist/AppDownloadScreen-BXWBVnQM.cjs.map +1 -0
- package/dist/{AppDownloadScreen-DW3cBnbI.cjs → AppDownloadScreen-BtwySuUY.cjs} +1 -1
- package/dist/{ShopScreen-DCKr0cbB.cjs → ShopScreen-Bo9Bbpg8.cjs} +1 -1
- package/dist/{ShopScreen-Bg696rqv.mjs → ShopScreen-CMkEtT1s.mjs} +1 -1
- package/dist/{ShopScreen-qgOXPdUf.cjs → ShopScreen-D-1WO4sB.cjs} +175 -10
- package/dist/ShopScreen-D-1WO4sB.cjs.map +1 -0
- package/dist/{ShopScreen-DFWQY8hT.mjs → ShopScreen-XGC180Ts.mjs} +176 -12
- package/dist/ShopScreen-XGC180Ts.mjs.map +1 -0
- package/dist/index.cjs +6 -6
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +6 -6
- package/dist/vite/index.cjs +92 -76
- package/dist/vite/index.cjs.map +1 -1
- package/dist/vite/index.d.cts +6 -11
- package/dist/vite/index.d.cts.map +1 -1
- package/dist/vite/index.d.mts +6 -11
- package/dist/vite/index.d.mts.map +1 -1
- package/dist/vite/index.mjs +92 -76
- package/dist/vite/index.mjs.map +1 -1
- package/package.json +11 -9
- package/dist/AppDownloadScreen-oMzxBETr.cjs.map +0 -1
- package/dist/AppDownloadScreen-pm8fLxZX.mjs.map +0 -1
- package/dist/ShopScreen-DFWQY8hT.mjs.map +0 -1
- package/dist/ShopScreen-qgOXPdUf.cjs.map +0 -1
package/dist/vite/index.cjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../chunk-DAgNkxik.cjs");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let node_fs = require("node:fs");
|
|
3
5
|
//#region src/vite/validate-manifest.ts
|
|
4
6
|
/**
|
|
5
7
|
* Lightweight manifest validation for the SDK vite plugin.
|
|
@@ -89,76 +91,6 @@ function validateManifest(input) {
|
|
|
89
91
|
};
|
|
90
92
|
}
|
|
91
93
|
//#endregion
|
|
92
|
-
//#region src/vite/manifest-plugin.ts
|
|
93
|
-
/**
|
|
94
|
-
* Vite plugin that serves widget manifest metadata.
|
|
95
|
-
*
|
|
96
|
-
* Dev mode: middleware serves /__manifests__ dynamically via ssrLoadModule.
|
|
97
|
-
* Re-reads portal.config.ts on every request (HMR-aware).
|
|
98
|
-
*
|
|
99
|
-
* Build mode: emits an empty __manifests__.json as a static asset in dist/.
|
|
100
|
-
* The CLI extraction utility (extract-manifests.ts) handles build-time
|
|
101
|
-
* extraction via tsx subprocess for `fluid build` and `fluid deploy`.
|
|
102
|
-
*
|
|
103
|
-
* The builder fetches this endpoint/file to discover custom widgets.
|
|
104
|
-
*/
|
|
105
|
-
function fluidManifestPlugin() {
|
|
106
|
-
return {
|
|
107
|
-
name: "fluid-manifest-plugin",
|
|
108
|
-
configureServer(server) {
|
|
109
|
-
server.middlewares.use("/__manifests__", async (_req, res) => {
|
|
110
|
-
try {
|
|
111
|
-
const serializable = await loadManifests(server, server.config.logger);
|
|
112
|
-
res.setHeader("Content-Type", "application/json");
|
|
113
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
114
|
-
res.end(JSON.stringify(serializable));
|
|
115
|
-
} catch (err) {
|
|
116
|
-
server.config.logger.error(`[fluid] Failed to load manifests: ${err}`);
|
|
117
|
-
res.statusCode = 500;
|
|
118
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
},
|
|
122
|
-
generateBundle() {
|
|
123
|
-
this.warn("[fluid] fluidManifestPlugin: emitting empty __manifests__.json. Run `fluid build` instead of `vite build` to include widget manifests.");
|
|
124
|
-
this.emitFile({
|
|
125
|
-
type: "asset",
|
|
126
|
-
fileName: "__manifests__.json",
|
|
127
|
-
source: JSON.stringify([])
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Load and serialize manifests from portal.config.ts via Vite's ssrLoadModule.
|
|
134
|
-
* Validates each manifest before stripping the `component` field.
|
|
135
|
-
* Returns an empty array if the config module or export is missing/invalid.
|
|
136
|
-
*/
|
|
137
|
-
async function loadManifests(server, logger) {
|
|
138
|
-
let mod;
|
|
139
|
-
try {
|
|
140
|
-
mod = await server.ssrLoadModule("/src/portal.config.ts");
|
|
141
|
-
} catch (err) {
|
|
142
|
-
logger?.warn(`[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`);
|
|
143
|
-
return [];
|
|
144
|
-
}
|
|
145
|
-
const rawWidgets = mod.customWidgets;
|
|
146
|
-
if (!rawWidgets) return [];
|
|
147
|
-
if (!Array.isArray(rawWidgets)) {
|
|
148
|
-
logger?.warn(`[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`);
|
|
149
|
-
return [];
|
|
150
|
-
}
|
|
151
|
-
const manifests = rawWidgets;
|
|
152
|
-
if (logger) for (const manifest of manifests) {
|
|
153
|
-
const result = validateManifest(manifest);
|
|
154
|
-
if (!result.success) {
|
|
155
|
-
const type = manifest.type ?? "unknown";
|
|
156
|
-
logger.warn(`[fluid] Invalid manifest for "${type}":\n` + result.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n"));
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return manifests.map(({ component: _component, ...rest }) => rest);
|
|
160
|
-
}
|
|
161
|
-
//#endregion
|
|
162
94
|
//#region src/vite/builder-preview-plugin.ts
|
|
163
95
|
const VIRTUAL_ENTRY_ID = "virtual:builder-preview-entry";
|
|
164
96
|
const RESOLVED_VIRTUAL_ID = "\0" + VIRTUAL_ENTRY_ID;
|
|
@@ -174,8 +106,7 @@ const RAW_HTML = `<!doctype html>
|
|
|
174
106
|
</head>
|
|
175
107
|
<body>
|
|
176
108
|
<div id="builder-preview-root"></div>
|
|
177
|
-
<script type="module" src="/@id/
|
|
178
|
-
<script type="module">import "/src/index.css";<\/script>
|
|
109
|
+
<script type="module" src="/@id/virtual:builder-preview-entry"><\/script>
|
|
179
110
|
</body>
|
|
180
111
|
</html>`;
|
|
181
112
|
/**
|
|
@@ -183,19 +114,29 @@ const RAW_HTML = `<!doctype html>
|
|
|
183
114
|
*
|
|
184
115
|
* Dev mode only. Renders all customWidgets from portal.config.ts with
|
|
185
116
|
* a live preview and property editor — no auth, no iframe, no fluid-admin needed.
|
|
186
|
-
*
|
|
187
|
-
* Uses a virtual module to bridge the user's portal.config.ts into the preview app.
|
|
188
117
|
*/
|
|
189
118
|
function fluidBuilderPreviewPlugin() {
|
|
119
|
+
let configPath;
|
|
120
|
+
let cssPath;
|
|
190
121
|
return {
|
|
191
|
-
name: "fluid-builder-preview
|
|
122
|
+
name: "fluid-builder-preview",
|
|
192
123
|
apply: "serve",
|
|
124
|
+
configResolved(config) {
|
|
125
|
+
const root = config.root;
|
|
126
|
+
configPath = ["src/portal.config.ts", "portal.config.ts"].find((c) => (0, node_fs.existsSync)((0, node_path.join)(root, c))) ?? "src/portal.config.ts";
|
|
127
|
+
cssPath = [
|
|
128
|
+
"src/index.css",
|
|
129
|
+
"src/styles/index.css",
|
|
130
|
+
"index.css"
|
|
131
|
+
].find((c) => (0, node_fs.existsSync)((0, node_path.join)(root, c))) ?? "src/index.css";
|
|
132
|
+
},
|
|
193
133
|
resolveId(id) {
|
|
194
134
|
if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;
|
|
195
135
|
},
|
|
196
136
|
load(id) {
|
|
197
137
|
if (id === RESOLVED_VIRTUAL_ID) return `
|
|
198
|
-
import
|
|
138
|
+
import "/${cssPath}";
|
|
139
|
+
import * as portalConfig from "/${configPath}";
|
|
199
140
|
import { createRoot } from "react-dom/client";
|
|
200
141
|
import { createElement } from "react";
|
|
201
142
|
import { BuilderPreviewApp } from "@fluid-app/portal-preview";
|
|
@@ -227,6 +168,81 @@ if (root) {
|
|
|
227
168
|
};
|
|
228
169
|
}
|
|
229
170
|
//#endregion
|
|
171
|
+
//#region src/vite/manifest-plugin.ts
|
|
172
|
+
/**
|
|
173
|
+
* Vite plugin bundle that serves widget manifest metadata and the builder preview.
|
|
174
|
+
*
|
|
175
|
+
* Returns an array of plugins:
|
|
176
|
+
* 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)
|
|
177
|
+
* 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)
|
|
178
|
+
*
|
|
179
|
+
* Every portal using `fluidManifestPlugin()` automatically gets the builder preview.
|
|
180
|
+
*/
|
|
181
|
+
function fluidManifestPlugin() {
|
|
182
|
+
return [fluidManifestPluginInternal(), fluidBuilderPreviewPlugin()];
|
|
183
|
+
}
|
|
184
|
+
function fluidManifestPluginInternal() {
|
|
185
|
+
let configPath;
|
|
186
|
+
return {
|
|
187
|
+
name: "fluid-manifest-plugin",
|
|
188
|
+
configResolved(config) {
|
|
189
|
+
const root = config.root;
|
|
190
|
+
configPath = "/" + (["src/portal.config.ts", "portal.config.ts"].find((c) => (0, node_fs.existsSync)((0, node_path.join)(root, c))) ?? "src/portal.config.ts");
|
|
191
|
+
},
|
|
192
|
+
configureServer(server) {
|
|
193
|
+
server.middlewares.use("/__manifests__", async (_req, res) => {
|
|
194
|
+
try {
|
|
195
|
+
const serializable = await loadManifests(server, server.config.logger, configPath);
|
|
196
|
+
res.setHeader("Content-Type", "application/json");
|
|
197
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
198
|
+
res.end(JSON.stringify(serializable));
|
|
199
|
+
} catch (err) {
|
|
200
|
+
server.config.logger.error(`[fluid] Failed to load manifests: ${err}`);
|
|
201
|
+
res.statusCode = 500;
|
|
202
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
generateBundle() {
|
|
207
|
+
this.warn("[fluid] fluidManifestPlugin: emitting empty __manifests__.json. Run `fluid build` instead of `vite build` to include widget manifests.");
|
|
208
|
+
this.emitFile({
|
|
209
|
+
type: "asset",
|
|
210
|
+
fileName: "__manifests__.json",
|
|
211
|
+
source: JSON.stringify([])
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Load and serialize manifests from portal.config.ts via Vite's ssrLoadModule.
|
|
218
|
+
* Validates each manifest before stripping the `component` field.
|
|
219
|
+
* Returns an empty array if the config module or export is missing/invalid.
|
|
220
|
+
*/
|
|
221
|
+
async function loadManifests(server, logger, portalConfigPath) {
|
|
222
|
+
let mod;
|
|
223
|
+
try {
|
|
224
|
+
mod = await server.ssrLoadModule(portalConfigPath ?? "/src/portal.config.ts");
|
|
225
|
+
} catch (err) {
|
|
226
|
+
logger?.warn(`[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`);
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
const rawWidgets = mod.customWidgets;
|
|
230
|
+
if (!rawWidgets) return [];
|
|
231
|
+
if (!Array.isArray(rawWidgets)) {
|
|
232
|
+
logger?.warn(`[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`);
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
const manifests = rawWidgets;
|
|
236
|
+
if (logger) for (const manifest of manifests) {
|
|
237
|
+
const result = validateManifest(manifest);
|
|
238
|
+
if (!result.success) {
|
|
239
|
+
const type = manifest.type ?? "unknown";
|
|
240
|
+
logger.warn(`[fluid] Invalid manifest for "${type}":\n` + result.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n"));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return manifests.map(({ component: _component, ...rest }) => rest);
|
|
244
|
+
}
|
|
245
|
+
//#endregion
|
|
230
246
|
exports.fluidBuilderPreviewPlugin = fluidBuilderPreviewPlugin;
|
|
231
247
|
exports.fluidManifestPlugin = fluidManifestPlugin;
|
|
232
248
|
|
package/dist/vite/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/vite/validate-manifest.ts","../../src/vite/manifest-plugin.ts","../../src/vite/builder-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, ViteDevServer, Logger } from \"vite\";\nimport { validateManifest } from \"./validate-manifest\";\n\n/**\n * Vite plugin that serves widget manifest metadata.\n *\n * Dev mode: middleware serves /__manifests__ dynamically via ssrLoadModule.\n * Re-reads portal.config.ts on every request (HMR-aware).\n *\n * Build mode: emits an empty __manifests__.json as a static asset in dist/.\n * The CLI extraction utility (extract-manifests.ts) handles build-time\n * extraction via tsx subprocess for `fluid build` and `fluid deploy`.\n *\n * The builder fetches this endpoint/file to discover custom widgets.\n */\nexport function fluidManifestPlugin(): Plugin {\n return {\n name: \"fluid-manifest-plugin\",\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 );\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 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): Promise<unknown[]> {\n let mod: Record<string, unknown>;\n try {\n mod = await server.ssrLoadModule(\"/src/portal.config.ts\");\n } catch (err) {\n logger?.warn(\n `[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`,\n );\n return [];\n }\n\n const rawWidgets = mod.customWidgets;\n if (!rawWidgets) return [];\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","import type { Plugin } from \"vite\";\n\nconst VIRTUAL_ENTRY_ID = \"virtual:builder-preview-entry\";\nconst RESOLVED_VIRTUAL_ID = \"\\0\" + VIRTUAL_ENTRY_ID;\nconst VIRTUAL_MODULE_URL = \"/@id/__x00__virtual:builder-preview-entry\";\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=\"${VIRTUAL_MODULE_URL}\"></script>\n <script type=\"module\">import \"/src/index.css\";</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 *\n * Uses a virtual module to bridge the user's portal.config.ts into the preview app.\n */\nexport function fluidBuilderPreviewPlugin(): Plugin {\n return {\n name: \"fluid-builder-preview-plugin\",\n apply: \"serve\",\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 * as portalConfig from \"/src/portal.config.ts\";\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"],"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"],"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 = [\"src/portal.config.ts\", \"portal.config.ts\"];\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 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 portalConfigPath?: string,\n): Promise<unknown[]> {\n let mod: Record<string, unknown>;\n try {\n mod = await server.ssrLoadModule(\n portalConfigPath ?? \"/src/portal.config.ts\",\n );\n } catch (err) {\n logger?.warn(\n `[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`,\n );\n return [];\n }\n\n const rawWidgets = mod.customWidgets;\n if (!rawWidgets) return [];\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"],"mappings":"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"}
|
package/dist/vite/index.d.cts
CHANGED
|
@@ -2,18 +2,15 @@ import { Plugin } from "vite";
|
|
|
2
2
|
|
|
3
3
|
//#region src/vite/manifest-plugin.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* Vite plugin that serves widget manifest metadata.
|
|
5
|
+
* Vite plugin bundle that serves widget manifest metadata and the builder preview.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Returns an array of plugins:
|
|
8
|
+
* 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)
|
|
9
|
+
* 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
* The CLI extraction utility (extract-manifests.ts) handles build-time
|
|
12
|
-
* extraction via tsx subprocess for `fluid build` and `fluid deploy`.
|
|
13
|
-
*
|
|
14
|
-
* The builder fetches this endpoint/file to discover custom widgets.
|
|
11
|
+
* Every portal using `fluidManifestPlugin()` automatically gets the builder preview.
|
|
15
12
|
*/
|
|
16
|
-
declare function fluidManifestPlugin(): Plugin;
|
|
13
|
+
declare function fluidManifestPlugin(): Plugin[];
|
|
17
14
|
//#endregion
|
|
18
15
|
//#region src/vite/builder-preview-plugin.d.ts
|
|
19
16
|
/**
|
|
@@ -21,8 +18,6 @@ declare function fluidManifestPlugin(): Plugin;
|
|
|
21
18
|
*
|
|
22
19
|
* Dev mode only. Renders all customWidgets from portal.config.ts with
|
|
23
20
|
* a live preview and property editor — no auth, no iframe, no fluid-admin needed.
|
|
24
|
-
*
|
|
25
|
-
* Uses a virtual module to bridge the user's portal.config.ts into the preview app.
|
|
26
21
|
*/
|
|
27
22
|
declare function fluidBuilderPreviewPlugin(): Plugin;
|
|
28
23
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts"],"mappings":";;;;;AAeA
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts"],"mappings":";;;;;AAeA;;;;;;;iBAAgB,mBAAA,CAAA,GAAuB,MAAA;;;;;AAAvC;;;;iBCcgB,yBAAA,CAAA,GAA6B,MAAA"}
|
package/dist/vite/index.d.mts
CHANGED
|
@@ -2,18 +2,15 @@ import { Plugin } from "vite";
|
|
|
2
2
|
|
|
3
3
|
//#region src/vite/manifest-plugin.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* Vite plugin that serves widget manifest metadata.
|
|
5
|
+
* Vite plugin bundle that serves widget manifest metadata and the builder preview.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Returns an array of plugins:
|
|
8
|
+
* 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)
|
|
9
|
+
* 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
* The CLI extraction utility (extract-manifests.ts) handles build-time
|
|
12
|
-
* extraction via tsx subprocess for `fluid build` and `fluid deploy`.
|
|
13
|
-
*
|
|
14
|
-
* The builder fetches this endpoint/file to discover custom widgets.
|
|
11
|
+
* Every portal using `fluidManifestPlugin()` automatically gets the builder preview.
|
|
15
12
|
*/
|
|
16
|
-
declare function fluidManifestPlugin(): Plugin;
|
|
13
|
+
declare function fluidManifestPlugin(): Plugin[];
|
|
17
14
|
//#endregion
|
|
18
15
|
//#region src/vite/builder-preview-plugin.d.ts
|
|
19
16
|
/**
|
|
@@ -21,8 +18,6 @@ declare function fluidManifestPlugin(): Plugin;
|
|
|
21
18
|
*
|
|
22
19
|
* Dev mode only. Renders all customWidgets from portal.config.ts with
|
|
23
20
|
* a live preview and property editor — no auth, no iframe, no fluid-admin needed.
|
|
24
|
-
*
|
|
25
|
-
* Uses a virtual module to bridge the user's portal.config.ts into the preview app.
|
|
26
21
|
*/
|
|
27
22
|
declare function fluidBuilderPreviewPlugin(): Plugin;
|
|
28
23
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts"],"mappings":";;;;;AAeA
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vite/manifest-plugin.ts","../../src/vite/builder-preview-plugin.ts"],"mappings":";;;;;AAeA;;;;;;;iBAAgB,mBAAA,CAAA,GAAuB,MAAA;;;;;AAAvC;;;;iBCcgB,yBAAA,CAAA,GAA6B,MAAA"}
|
package/dist/vite/index.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
//#region src/vite/validate-manifest.ts
|
|
2
4
|
/**
|
|
3
5
|
* Lightweight manifest validation for the SDK vite plugin.
|
|
@@ -87,76 +89,6 @@ function validateManifest(input) {
|
|
|
87
89
|
};
|
|
88
90
|
}
|
|
89
91
|
//#endregion
|
|
90
|
-
//#region src/vite/manifest-plugin.ts
|
|
91
|
-
/**
|
|
92
|
-
* Vite plugin that serves widget manifest metadata.
|
|
93
|
-
*
|
|
94
|
-
* Dev mode: middleware serves /__manifests__ dynamically via ssrLoadModule.
|
|
95
|
-
* Re-reads portal.config.ts on every request (HMR-aware).
|
|
96
|
-
*
|
|
97
|
-
* Build mode: emits an empty __manifests__.json as a static asset in dist/.
|
|
98
|
-
* The CLI extraction utility (extract-manifests.ts) handles build-time
|
|
99
|
-
* extraction via tsx subprocess for `fluid build` and `fluid deploy`.
|
|
100
|
-
*
|
|
101
|
-
* The builder fetches this endpoint/file to discover custom widgets.
|
|
102
|
-
*/
|
|
103
|
-
function fluidManifestPlugin() {
|
|
104
|
-
return {
|
|
105
|
-
name: "fluid-manifest-plugin",
|
|
106
|
-
configureServer(server) {
|
|
107
|
-
server.middlewares.use("/__manifests__", async (_req, res) => {
|
|
108
|
-
try {
|
|
109
|
-
const serializable = await loadManifests(server, server.config.logger);
|
|
110
|
-
res.setHeader("Content-Type", "application/json");
|
|
111
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
112
|
-
res.end(JSON.stringify(serializable));
|
|
113
|
-
} catch (err) {
|
|
114
|
-
server.config.logger.error(`[fluid] Failed to load manifests: ${err}`);
|
|
115
|
-
res.statusCode = 500;
|
|
116
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
generateBundle() {
|
|
121
|
-
this.warn("[fluid] fluidManifestPlugin: emitting empty __manifests__.json. Run `fluid build` instead of `vite build` to include widget manifests.");
|
|
122
|
-
this.emitFile({
|
|
123
|
-
type: "asset",
|
|
124
|
-
fileName: "__manifests__.json",
|
|
125
|
-
source: JSON.stringify([])
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Load and serialize manifests from portal.config.ts via Vite's ssrLoadModule.
|
|
132
|
-
* Validates each manifest before stripping the `component` field.
|
|
133
|
-
* Returns an empty array if the config module or export is missing/invalid.
|
|
134
|
-
*/
|
|
135
|
-
async function loadManifests(server, logger) {
|
|
136
|
-
let mod;
|
|
137
|
-
try {
|
|
138
|
-
mod = await server.ssrLoadModule("/src/portal.config.ts");
|
|
139
|
-
} catch (err) {
|
|
140
|
-
logger?.warn(`[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`);
|
|
141
|
-
return [];
|
|
142
|
-
}
|
|
143
|
-
const rawWidgets = mod.customWidgets;
|
|
144
|
-
if (!rawWidgets) return [];
|
|
145
|
-
if (!Array.isArray(rawWidgets)) {
|
|
146
|
-
logger?.warn(`[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`);
|
|
147
|
-
return [];
|
|
148
|
-
}
|
|
149
|
-
const manifests = rawWidgets;
|
|
150
|
-
if (logger) for (const manifest of manifests) {
|
|
151
|
-
const result = validateManifest(manifest);
|
|
152
|
-
if (!result.success) {
|
|
153
|
-
const type = manifest.type ?? "unknown";
|
|
154
|
-
logger.warn(`[fluid] Invalid manifest for "${type}":\n` + result.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n"));
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return manifests.map(({ component: _component, ...rest }) => rest);
|
|
158
|
-
}
|
|
159
|
-
//#endregion
|
|
160
92
|
//#region src/vite/builder-preview-plugin.ts
|
|
161
93
|
const VIRTUAL_ENTRY_ID = "virtual:builder-preview-entry";
|
|
162
94
|
const RESOLVED_VIRTUAL_ID = "\0" + VIRTUAL_ENTRY_ID;
|
|
@@ -172,8 +104,7 @@ const RAW_HTML = `<!doctype html>
|
|
|
172
104
|
</head>
|
|
173
105
|
<body>
|
|
174
106
|
<div id="builder-preview-root"></div>
|
|
175
|
-
<script type="module" src="/@id/
|
|
176
|
-
<script type="module">import "/src/index.css";<\/script>
|
|
107
|
+
<script type="module" src="/@id/virtual:builder-preview-entry"><\/script>
|
|
177
108
|
</body>
|
|
178
109
|
</html>`;
|
|
179
110
|
/**
|
|
@@ -181,19 +112,29 @@ const RAW_HTML = `<!doctype html>
|
|
|
181
112
|
*
|
|
182
113
|
* Dev mode only. Renders all customWidgets from portal.config.ts with
|
|
183
114
|
* a live preview and property editor — no auth, no iframe, no fluid-admin needed.
|
|
184
|
-
*
|
|
185
|
-
* Uses a virtual module to bridge the user's portal.config.ts into the preview app.
|
|
186
115
|
*/
|
|
187
116
|
function fluidBuilderPreviewPlugin() {
|
|
117
|
+
let configPath;
|
|
118
|
+
let cssPath;
|
|
188
119
|
return {
|
|
189
|
-
name: "fluid-builder-preview
|
|
120
|
+
name: "fluid-builder-preview",
|
|
190
121
|
apply: "serve",
|
|
122
|
+
configResolved(config) {
|
|
123
|
+
const root = config.root;
|
|
124
|
+
configPath = ["src/portal.config.ts", "portal.config.ts"].find((c) => existsSync(join(root, c))) ?? "src/portal.config.ts";
|
|
125
|
+
cssPath = [
|
|
126
|
+
"src/index.css",
|
|
127
|
+
"src/styles/index.css",
|
|
128
|
+
"index.css"
|
|
129
|
+
].find((c) => existsSync(join(root, c))) ?? "src/index.css";
|
|
130
|
+
},
|
|
191
131
|
resolveId(id) {
|
|
192
132
|
if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;
|
|
193
133
|
},
|
|
194
134
|
load(id) {
|
|
195
135
|
if (id === RESOLVED_VIRTUAL_ID) return `
|
|
196
|
-
import
|
|
136
|
+
import "/${cssPath}";
|
|
137
|
+
import * as portalConfig from "/${configPath}";
|
|
197
138
|
import { createRoot } from "react-dom/client";
|
|
198
139
|
import { createElement } from "react";
|
|
199
140
|
import { BuilderPreviewApp } from "@fluid-app/portal-preview";
|
|
@@ -225,6 +166,81 @@ if (root) {
|
|
|
225
166
|
};
|
|
226
167
|
}
|
|
227
168
|
//#endregion
|
|
169
|
+
//#region src/vite/manifest-plugin.ts
|
|
170
|
+
/**
|
|
171
|
+
* Vite plugin bundle that serves widget manifest metadata and the builder preview.
|
|
172
|
+
*
|
|
173
|
+
* Returns an array of plugins:
|
|
174
|
+
* 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)
|
|
175
|
+
* 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)
|
|
176
|
+
*
|
|
177
|
+
* Every portal using `fluidManifestPlugin()` automatically gets the builder preview.
|
|
178
|
+
*/
|
|
179
|
+
function fluidManifestPlugin() {
|
|
180
|
+
return [fluidManifestPluginInternal(), fluidBuilderPreviewPlugin()];
|
|
181
|
+
}
|
|
182
|
+
function fluidManifestPluginInternal() {
|
|
183
|
+
let configPath;
|
|
184
|
+
return {
|
|
185
|
+
name: "fluid-manifest-plugin",
|
|
186
|
+
configResolved(config) {
|
|
187
|
+
const root = config.root;
|
|
188
|
+
configPath = "/" + (["src/portal.config.ts", "portal.config.ts"].find((c) => existsSync(join(root, c))) ?? "src/portal.config.ts");
|
|
189
|
+
},
|
|
190
|
+
configureServer(server) {
|
|
191
|
+
server.middlewares.use("/__manifests__", async (_req, res) => {
|
|
192
|
+
try {
|
|
193
|
+
const serializable = await loadManifests(server, server.config.logger, configPath);
|
|
194
|
+
res.setHeader("Content-Type", "application/json");
|
|
195
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
196
|
+
res.end(JSON.stringify(serializable));
|
|
197
|
+
} catch (err) {
|
|
198
|
+
server.config.logger.error(`[fluid] Failed to load manifests: ${err}`);
|
|
199
|
+
res.statusCode = 500;
|
|
200
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
generateBundle() {
|
|
205
|
+
this.warn("[fluid] fluidManifestPlugin: emitting empty __manifests__.json. Run `fluid build` instead of `vite build` to include widget manifests.");
|
|
206
|
+
this.emitFile({
|
|
207
|
+
type: "asset",
|
|
208
|
+
fileName: "__manifests__.json",
|
|
209
|
+
source: JSON.stringify([])
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Load and serialize manifests from portal.config.ts via Vite's ssrLoadModule.
|
|
216
|
+
* Validates each manifest before stripping the `component` field.
|
|
217
|
+
* Returns an empty array if the config module or export is missing/invalid.
|
|
218
|
+
*/
|
|
219
|
+
async function loadManifests(server, logger, portalConfigPath) {
|
|
220
|
+
let mod;
|
|
221
|
+
try {
|
|
222
|
+
mod = await server.ssrLoadModule(portalConfigPath ?? "/src/portal.config.ts");
|
|
223
|
+
} catch (err) {
|
|
224
|
+
logger?.warn(`[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`);
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
const rawWidgets = mod.customWidgets;
|
|
228
|
+
if (!rawWidgets) return [];
|
|
229
|
+
if (!Array.isArray(rawWidgets)) {
|
|
230
|
+
logger?.warn(`[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`);
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
const manifests = rawWidgets;
|
|
234
|
+
if (logger) for (const manifest of manifests) {
|
|
235
|
+
const result = validateManifest(manifest);
|
|
236
|
+
if (!result.success) {
|
|
237
|
+
const type = manifest.type ?? "unknown";
|
|
238
|
+
logger.warn(`[fluid] Invalid manifest for "${type}":\n` + result.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n"));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return manifests.map(({ component: _component, ...rest }) => rest);
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
228
244
|
export { fluidBuilderPreviewPlugin, fluidManifestPlugin };
|
|
229
245
|
|
|
230
246
|
//# sourceMappingURL=index.mjs.map
|
package/dist/vite/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/vite/validate-manifest.ts","../../src/vite/manifest-plugin.ts","../../src/vite/builder-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, ViteDevServer, Logger } from \"vite\";\nimport { validateManifest } from \"./validate-manifest\";\n\n/**\n * Vite plugin that serves widget manifest metadata.\n *\n * Dev mode: middleware serves /__manifests__ dynamically via ssrLoadModule.\n * Re-reads portal.config.ts on every request (HMR-aware).\n *\n * Build mode: emits an empty __manifests__.json as a static asset in dist/.\n * The CLI extraction utility (extract-manifests.ts) handles build-time\n * extraction via tsx subprocess for `fluid build` and `fluid deploy`.\n *\n * The builder fetches this endpoint/file to discover custom widgets.\n */\nexport function fluidManifestPlugin(): Plugin {\n return {\n name: \"fluid-manifest-plugin\",\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 );\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 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): Promise<unknown[]> {\n let mod: Record<string, unknown>;\n try {\n mod = await server.ssrLoadModule(\"/src/portal.config.ts\");\n } catch (err) {\n logger?.warn(\n `[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`,\n );\n return [];\n }\n\n const rawWidgets = mod.customWidgets;\n if (!rawWidgets) return [];\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","import type { Plugin } from \"vite\";\n\nconst VIRTUAL_ENTRY_ID = \"virtual:builder-preview-entry\";\nconst RESOLVED_VIRTUAL_ID = \"\\0\" + VIRTUAL_ENTRY_ID;\nconst VIRTUAL_MODULE_URL = \"/@id/__x00__virtual:builder-preview-entry\";\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=\"${VIRTUAL_MODULE_URL}\"></script>\n <script type=\"module\">import \"/src/index.css\";</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 *\n * Uses a virtual module to bridge the user's portal.config.ts into the preview app.\n */\nexport function fluidBuilderPreviewPlugin(): Plugin {\n return {\n name: \"fluid-builder-preview-plugin\",\n apply: \"serve\",\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 * as portalConfig from \"/src/portal.config.ts\";\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"],"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"],"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 = [\"src/portal.config.ts\", \"portal.config.ts\"];\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 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 portalConfigPath?: string,\n): Promise<unknown[]> {\n let mod: Record<string, unknown>;\n try {\n mod = await server.ssrLoadModule(\n portalConfigPath ?? \"/src/portal.config.ts\",\n );\n } catch (err) {\n logger?.warn(\n `[fluid] Could not load portal.config.ts: ${err instanceof Error ? err.message : err}`,\n );\n return [];\n }\n\n const rawWidgets = mod.customWidgets;\n if (!rawWidgets) return [];\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"],"mappings":"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"}
|