Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
require("../chunk-9hOWP6kD.cjs");
const require_authoring = require("../authoring-HytiVeJw.cjs");
const require_protocol = require("../protocol-DIE_XooL.cjs");
let node_path = require("node:path");
let node_url = require("node:url");
let node_fs_promises = require("node:fs/promises");
let node_fs = require("node:fs");
//#region src/vite/validate-manifest.ts
/**
* Lightweight manifest validation for the SDK vite plugin.
*
* Inlined here (rather than imported from @fluid-app/portal-core) because
* portal-core is private and not published to npm. This avoids a runtime
* ERR_MODULE_NOT_FOUND for portals installed from npm.
*/
const VALID_FIELD_TYPES = [
	"text",
	"textarea",
	"number",
	"boolean",
	"select",
	"color",
	"range",
	"dataSource",
	"resource",
	"image",
	"alignment",
	"slider",
	"colorPicker",
	"sectionHeader",
	"separator",
	"buttonGroup",
	"colorSelect",
	"sectionLayoutSelect",
	"background",
	"contentPosition",
	"textSizeSelect",
	"cssUnit",
	"fontPicker",
	"stringArray",
	"borderRadius",
	"screenPicker"
];
function validateManifest(input) {
	const errors = [];
	const m = input;
	if (!m || typeof m !== "object") return {
		success: false,
		errors: [{
			path: "",
			message: "Manifest must be an object"
		}]
	};
	for (const key of [
		"type",
		"displayName",
		"description",
		"icon",
		"category"
	]) if (typeof m[key] !== "string" || m[key].length === 0) errors.push({
		path: key,
		message: `${key} is required and must be a non-empty string`
	});
	if (typeof m.manifestVersion !== "number" || m.manifestVersion < 1) errors.push({
		path: "manifestVersion",
		message: "manifestVersion must be a positive integer"
	});
	if (typeof m.component !== "function") errors.push({
		path: "component",
		message: "component must be a React component (function)"
	});
	const schema = m.propertySchema;
	if (schema && typeof schema === "object") {
		if (typeof schema.widgetType !== "string" || !schema.widgetType) errors.push({
			path: "propertySchema.widgetType",
			message: "widgetType is required"
		});
		if (typeof m.type === "string" && schema.widgetType !== m.type) errors.push({
			path: "propertySchema.widgetType",
			message: "manifest.type must match manifest.propertySchema.widgetType"
		});
		if (Array.isArray(schema.fields)) for (let i = 0; i < schema.fields.length; i++) {
			const field = schema.fields[i];
			if (!field || typeof field.type !== "string") continue;
			if (!VALID_FIELD_TYPES.includes(field.type)) errors.push({
				path: `propertySchema.fields.${i}.type`,
				message: `Invalid field type "${field.type}". Valid types: ${VALID_FIELD_TYPES.join(", ")}`
			});
		}
	}
	return errors.length === 0 ? { success: true } : {
		success: false,
		errors
	};
}
//#endregion
//#region src/vite/config-paths.ts
const WIDGET_CONFIG_CANDIDATES = [
	"src/widgets.config.ts",
	"src/portal.config.ts",
	"portal.config.ts"
];
function resolveWidgetConfigPath(root) {
	return WIDGET_CONFIG_CANDIDATES.find((candidate) => (0, node_fs.existsSync)((0, node_path.join)(root, candidate))) ?? "src/portal.config.ts";
}
//#endregion
//#region src/vite/builder-preview-plugin.ts
const VIRTUAL_ENTRY_ID = "virtual:builder-preview-entry";
const RESOLVED_VIRTUAL_ID = "\0" + VIRTUAL_ENTRY_ID;
const RAW_HTML = `<!doctype html>
<html lang="en" data-theme-mode="dark">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Custom Widget Preview</title>
    <style>
      body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }
    </style>
  </head>
  <body>
    <div id="builder-preview-root"></div>
    <script type="module" src="/@id/virtual:builder-preview-entry"><\/script>
  </body>
</html>`;
/**
* Vite plugin that serves a standalone widget preview page at /builder-preview.
*
* Dev mode only. Renders all customWidgets from portal.config.ts with
* a live preview and property editor — no auth, no iframe, no fluid-admin needed.
*/
function fluidBuilderPreviewPlugin() {
	let configPath;
	let cssPath;
	let builderPreviewEntryUrl;
	return {
		name: "fluid-builder-preview",
		apply: "serve",
		configResolved(config) {
			const root = config.root;
			configPath = resolveWidgetConfigPath(root);
			cssPath = [
				"src/index.css",
				"src/styles/index.css",
				"index.css"
			].find((c) => (0, node_fs.existsSync)((0, node_path.join)(root, c))) ?? "src/index.css";
			builderPreviewEntryUrl = toViteFsPath(resolveBuilderPreviewEntryPath());
		},
		resolveId(id) {
			if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;
		},
		load(id) {
			if (id === RESOLVED_VIRTUAL_ID) return `
import "/${cssPath}";
import * as portalConfig from "/${configPath}";
import { renderBuilderPreview } from "${builderPreviewEntryUrl}";

renderBuilderPreview(portalConfig);
`;
		},
		configureServer(server) {
			server.middlewares.use(async (req, res, next) => {
				const pathname = (req.url ?? "").split("?")[0];
				if (pathname !== "/builder-preview" && pathname !== "/builder-preview/") return next();
				try {
					const transformed = await server.transformIndexHtml("/builder-preview", RAW_HTML);
					res.setHeader("Content-Type", "text/html");
					res.end(transformed);
				} catch (e) {
					server.config.logger.error(`[fluid] Failed to serve builder preview: ${e}`);
					res.statusCode = 500;
					res.end("Builder preview failed to load");
				}
			});
		}
	};
}
function resolveBuilderPreviewEntryPath() {
	const sourcePath = (0, node_url.fileURLToPath)(new URL("./builder-preview-entry.tsx", require("url").pathToFileURL(__filename).href));
	if ((0, node_fs.existsSync)(sourcePath)) return sourcePath;
	const builtPath = (0, node_url.fileURLToPath)(new URL("./builder-preview-entry.mjs", require("url").pathToFileURL(__filename).href));
	if ((0, node_fs.existsSync)(builtPath)) return builtPath;
	throw new Error(`[fluid-builder-preview] Could not locate builder-preview-entry. Expected source at ${sourcePath} or built output at ${builtPath}.`);
}
function toViteFsPath(path) {
	const normalized = path.replace(/\\/g, "/");
	return normalized.startsWith("/") ? `/@fs${normalized}` : `/@fs/${normalized}`;
}
//#endregion
//#region src/vite/manifest-plugin.ts
const DEV_WIDGET_PACKAGE_ID = "company.local-portal-widgets";
const DEV_WIDGET_PACKAGE_VERSION = "0.0.0-dev";
const DEV_WIDGET_PACKAGE_TYPE = "company";
const WIDGET_PACKAGE_DESCRIPTOR_MANIFEST_VERSION = 1;
/**
* Vite plugin bundle that serves widget manifest metadata and the builder preview.
*
* Returns an array of plugins:
* 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)
* 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)
*
* Every portal using `fluidManifestPlugin()` automatically gets the builder preview.
*/
function fluidManifestPlugin() {
	return [fluidManifestPluginInternal(), fluidBuilderPreviewPlugin()];
}
function fluidManifestPluginInternal() {
	let configPath;
	return {
		name: "fluid-manifest-plugin",
		configResolved(config) {
			configPath = `/${resolveWidgetConfigPath(config.root)}`;
		},
		configureServer(server) {
			server.middlewares.use("/__manifests__", async (req, res) => {
				if (handleDevCors(req, res)) return;
				try {
					sendJson(res, await loadManifests(server, server.config.logger, configPath));
				} catch (err) {
					server.config.logger.error(`[fluid] Failed to load manifests: ${err}`);
					sendJson(res, { error: String(err) }, 500);
				}
			});
			server.middlewares.use("/__widget-packages__", async (req, res) => {
				if (handleDevCors(req, res)) return;
				try {
					const { legacyManifests, sourcePackages, cssUrls, remoteEntryUrl } = await loadWidgetConfig(server, server.config.logger, configPath, { includeWidgetPackageRemoteEntryUrl: true });
					sendJson(res, [...manifestsToWidgetPackageDescriptors(legacyManifests, {
						cssUrls,
						remoteEntryUrl
					}), ...require_authoring.sourceWidgetPackagesToDescriptors(sourcePackages, {
						cssUrls,
						remoteEntryUrl
					})]);
				} catch (err) {
					server.config.logger.error(`[fluid] Failed to load widget package descriptors: ${err}`);
					sendJson(res, { error: String(err) }, 500);
				}
			});
		},
		generateBundle() {
			this.warn("[fluid] fluidManifestPlugin: emitting empty __manifests__.json. Run `fluid build` instead of `vite build` to include widget manifests.");
			this.emitFile({
				type: "asset",
				fileName: "__manifests__.json",
				source: JSON.stringify([])
			});
		}
	};
}
/**
* Load and serialize manifests from the resolved widget config
* (widgets.config.ts or portal.config.ts) via Vite's ssrLoadModule.
* Validates each manifest before stripping the `component` field.
* Returns an empty array if the config module or export is missing/invalid.
*/
async function loadManifests(server, logger, configFilePath) {
	const { manifests } = await loadWidgetConfig(server, logger, configFilePath);
	return manifests;
}
async function loadWidgetConfig(server, logger, configFilePath, options = {}) {
	let mod;
	try {
		mod = await server.ssrLoadModule(configFilePath ?? "/src/portal.config.ts");
	} catch (err) {
		const message = err instanceof Error ? err.message : String(err);
		if (/(document|window|navigator|localStorage|sessionStorage|location|history|HTMLElement) is not defined/.test(message)) {
			const configFile = configFilePath ?? "/src/portal.config.ts";
			const fixHint = configFile.includes("portal.config") ? "  Fix: Create src/widgets.config.ts with only your customWidgets/widgetPackage exports.\n  The manifest plugin will load it instead of portal.config.ts.\n" : `  Fix: Ensure ${configFile} does not import browser-only code.\n`;
			logger?.warn(`[fluid] Cannot load widget manifests — ${configFile} imports browser-only code that fails during server-side evaluation.\n  Custom widgets will not appear in the builder.\n` + fixHint + "  Widget components are fine — the issue is usually screen imports\n  (e.g. DashboardScreen) that pull in the SDK barrel export.");
		} else logger?.warn(`[fluid] Could not load widget config: ${message}`);
		return emptyLoadedWidgetConfig();
	}
	const sourcePackages = readSourceWidgetPackages(mod, logger);
	const legacyManifests = readLegacyCustomWidgets(mod, logger, configFilePath, sourcePackages.length > 0);
	const sourceManifests = require_authoring.sourceWidgetPackagesToManifests(sourcePackages).map(widgetManifestToRecord);
	const fullManifests = [...legacyManifests, ...sourceManifests];
	if (logger) for (const manifest of fullManifests) {
		const result = validateManifest(manifest);
		if (result.success === false) {
			const type = manifest.type ?? "unknown";
			logger.warn(`[fluid] Invalid manifest for "${type}":\n` + result.errors.map((e) => `  - ${e.path}: ${e.message}`).join("\n"));
		}
	}
	return {
		manifests: stripComponents(fullManifests),
		legacyManifests: stripComponents(legacyManifests),
		sourcePackages,
		cssUrls: options.includeWidgetPackageRemoteEntryUrl ? readCssUrls(mod, logger) : [],
		remoteEntryUrl: options.includeWidgetPackageRemoteEntryUrl ? readWidgetPackageRemoteEntryUrl(mod, logger) : void 0
	};
}
function emptyLoadedWidgetConfig() {
	return {
		manifests: [],
		legacyManifests: [],
		sourcePackages: [],
		cssUrls: [],
		remoteEntryUrl: void 0
	};
}
function widgetManifestToRecord(manifest) {
	return {
		manifestVersion: manifest.manifestVersion,
		type: manifest.type,
		component: manifest.component,
		displayName: manifest.displayName,
		description: manifest.description,
		icon: manifest.icon,
		category: manifest.category,
		propertySchema: manifest.propertySchema,
		defaultProps: manifest.defaultProps,
		container: manifest.container,
		minSdkVersion: manifest.minSdkVersion,
		resizable: manifest.resizable
	};
}
function readLegacyCustomWidgets(mod, logger, configFilePath, hasSourcePackages) {
	const rawWidgets = mod.customWidgets;
	if (!rawWidgets) {
		if (configFilePath?.includes("widgets.config") && !hasSourcePackages) logger?.warn("[fluid] widgets.config.ts was loaded but exports no customWidgets or widgetPackage. Custom widgets will not appear in the builder.");
		return [];
	}
	if (!Array.isArray(rawWidgets)) {
		logger?.warn(`[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`);
		return [];
	}
	return rawWidgets;
}
function readSourceWidgetPackages(mod, logger) {
	const packages = [];
	collectSourceWidgetPackageExport(packages, mod.widgetPackage, "widgetPackage", logger);
	collectSourceWidgetPackageExport(packages, mod.widgetPackages, "widgetPackages", logger);
	collectSourceWidgetPackageExport(packages, mod.default, "default", logger, { warnOnInvalid: false });
	const byPackageId = /* @__PURE__ */ new Map();
	for (const sourcePackage of packages) {
		if (byPackageId.has(sourcePackage.packageId)) {
			logger?.warn(`[fluid] Duplicate widget package "${sourcePackage.packageId}" found. Keeping the first export.`);
			continue;
		}
		byPackageId.set(sourcePackage.packageId, sourcePackage);
	}
	return Array.from(byPackageId.values());
}
function collectSourceWidgetPackageExport(packages, value, exportName, logger, options = {}) {
	if (value === void 0) return;
	if (require_authoring.isSourceWidgetPackage(value)) {
		packages.push(value);
		return;
	}
	if (Array.isArray(value)) {
		for (const entry of value) {
			if (require_authoring.isSourceWidgetPackage(entry)) {
				packages.push(entry);
				continue;
			}
			if (options.warnOnInvalid !== false) logger?.warn(`[fluid] ${exportName} contains a non-source widget package entry. Skipping entry.`);
		}
		return;
	}
	if (options.warnOnInvalid !== false) logger?.warn(`[fluid] ${exportName} export is not a source widget package or array. Skipping source packages.`);
}
function stripComponents(manifests) {
	return manifests.map(({ component: _component, ...rest }) => rest);
}
function manifestsToWidgetPackageDescriptors(manifests, options = {}) {
	const remoteEntryUrl = require_authoring.readNonEmptyTrimmedString(options.remoteEntryUrl);
	if (manifests.length === 0 || !remoteEntryUrl) return [];
	return [{
		manifestVersion: WIDGET_PACKAGE_DESCRIPTOR_MANIFEST_VERSION,
		packageId: DEV_WIDGET_PACKAGE_ID,
		packageType: DEV_WIDGET_PACKAGE_TYPE,
		version: DEV_WIDGET_PACKAGE_VERSION,
		remoteEntryUrl,
		cssUrls: options.cssUrls ?? [],
		widgets: manifests.map(manifestToWidgetDescriptor)
	}];
}
function manifestToWidgetDescriptor(manifest, index) {
	const type = readNonEmptyString(manifest.type) ?? `custom-widget-${index + 1}`;
	const displayName = readNonEmptyString(manifest.displayName) ?? type;
	return {
		type,
		name: type,
		displayName,
		description: readNonEmptyString(manifest.description) ?? `Custom widget ${displayName}`,
		icon: readNonEmptyString(manifest.icon) ?? "box",
		category: readNonEmptyString(manifest.category) ?? "components",
		propertySchema: normalizePropertySchema(manifest.propertySchema, type),
		defaultProps: isObjectRecord(manifest.defaultProps) ? manifest.defaultProps : {},
		container: normalizeContainer(manifest.container),
		minSdkVersion: readNonEmptyString(manifest.minSdkVersion) ?? "0.0.0",
		resizable: normalizeResizable(manifest.resizable)
	};
}
/**
* Runtime package descriptors must point to an actually served classic-script
* remote entry. Local SDK legacy widgets continue to use the legacy
* /__manifests__ + /__preview__ flow, so legacy /__widget-packages__ output
* stays empty unless the widget config explicitly exports a non-empty
* `widgetPackageRemoteEntryUrl` string. Source packages authored with
* defineWidgetPackage still contribute local preview/manifests, but are emitted
* as runtime descriptors only when the package or widget config explicitly
* provides a remote entry URL.
*/
function readWidgetPackageRemoteEntryUrl(mod, logger) {
	const rawRemoteEntryUrl = mod.widgetPackageRemoteEntryUrl;
	if (rawRemoteEntryUrl === void 0) return void 0;
	const remoteEntryUrl = require_authoring.readNonEmptyTrimmedString(rawRemoteEntryUrl);
	if (!remoteEntryUrl) {
		logger?.warn("[fluid] widgetPackageRemoteEntryUrl export must be a non-empty string. Legacy customWidgets will not be included in /__widget-packages__.");
		return;
	}
	return remoteEntryUrl;
}
function readCssUrls(mod, logger) {
	const rawCssUrls = mod.widgetPackageCssUrls;
	if (rawCssUrls === void 0) return [];
	if (!Array.isArray(rawCssUrls)) {
		logger?.warn(`[fluid] widget package cssUrls export is not an array (got ${typeof rawCssUrls}). Skipping CSS URLs.`);
		return [];
	}
	const cssUrls = [];
	for (const cssUrl of rawCssUrls) {
		const value = readNonEmptyString(cssUrl);
		if (value) cssUrls.push(value);
	}
	return cssUrls;
}
function normalizePropertySchema(value, widgetType) {
	if (!isObjectRecord(value)) return { widgetType };
	return {
		...value,
		widgetType
	};
}
function normalizeContainer(value) {
	if (value === "inline" || value === "block" || value === "card" || value === "fullscreen") return value;
	return "block";
}
function normalizeResizable(value) {
	if (typeof value === "boolean") return value;
	if (value === "horizontal" || value === "vertical" || value === "both") return value;
	if (!isObjectRecord(value)) return false;
	const horizontal = value.horizontal === true;
	const vertical = value.vertical === true;
	if (horizontal && vertical) return "both";
	if (horizontal) return "horizontal";
	if (vertical) return "vertical";
	return false;
}
function handleDevCors(req, res) {
	setDevCorsHeaders(res);
	if (req.method !== "OPTIONS") return false;
	res.statusCode = 204;
	res.end();
	return true;
}
function setDevCorsHeaders(res) {
	res.setHeader("Access-Control-Allow-Origin", "*");
	res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
	res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
	res.setHeader("Access-Control-Max-Age", "86400");
}
function sendJson(res, body, statusCode = 200) {
	res.setHeader("Content-Type", "application/json");
	res.statusCode = statusCode;
	res.end(JSON.stringify(body));
}
function readNonEmptyString(value) {
	return typeof value === "string" && value.length > 0 ? value : void 0;
}
function isObjectRecord(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
//#endregion
//#region src/vite/preview-plugin.ts
/**
* Vite plugin that serves the widget preview route.
*
* Dev mode: serves `/__preview__` by transforming preview.html through
* Vite's HTML pipeline (so import resolution and HMR work).
*
* Build mode: adds preview.html as a second Rollup input so `vite build`
* produces it alongside index.html for production deployment.
*/
function fluidPreviewPlugin() {
	return {
		name: "fluid-preview-plugin",
		config(_config, { command }) {
			if (command !== "build") return;
			const projectRoot = process.cwd();
			const previewHtml = (0, node_path.resolve)(projectRoot, "preview.html");
			if (!(0, node_fs.existsSync)(previewHtml)) return;
			return { build: { rollupOptions: { input: {
				main: (0, node_path.resolve)(projectRoot, "index.html"),
				preview: previewHtml
			} } } };
		},
		configurePreviewServer(server) {
			server.middlewares.use("/__preview__", async (req, res) => {
				try {
					const distDir = server.config.build.outDir;
					const html = await (0, node_fs_promises.readFile)((0, node_path.resolve)(distDir, "preview.html"), "utf-8");
					res.setHeader("Content-Type", "text/html");
					const origin = req.headers.origin;
					if (origin && require_protocol.isAllowedOrigin(origin)) {
						res.setHeader("Access-Control-Allow-Origin", origin);
						res.setHeader("Vary", "Origin");
					}
					res.statusCode = 200;
					res.end(html);
				} catch {
					res.statusCode = 404;
					res.end("preview.html not found in build output");
				}
			});
			server.middlewares.use("/__manifests__", async (_req, res) => {
				try {
					const distDir = server.config.build.outDir;
					const json = await (0, node_fs_promises.readFile)((0, node_path.resolve)(distDir, "__manifests__.json"), "utf-8");
					res.setHeader("Content-Type", "application/json");
					res.setHeader("Access-Control-Allow-Origin", "*");
					res.statusCode = 200;
					res.end(json);
				} catch {
					res.statusCode = 404;
					res.end("__manifests__.json not found in build output");
				}
			});
		},
		configureServer(server) {
			server.middlewares.use("/__preview__", async (req, res) => {
				try {
					const htmlPath = (0, node_path.resolve)(server.config.root, "preview.html");
					let html;
					try {
						html = await (0, node_fs_promises.readFile)(htmlPath, "utf-8");
					} catch {
						html = getDefaultPreviewHtml();
					}
					html = await server.transformIndexHtml("/__preview__", html);
					res.setHeader("Content-Type", "text/html");
					const origin = req.headers.origin;
					if (origin && require_protocol.isAllowedOrigin(origin)) {
						res.setHeader("Access-Control-Allow-Origin", origin);
						res.setHeader("Vary", "Origin");
					}
					res.statusCode = 200;
					res.end(html);
				} catch (err) {
					server.config.logger.error(`[fluid] Failed to serve preview: ${err}`);
					res.statusCode = 500;
					res.end("Preview failed to load");
				}
			});
		}
	};
}
function getDefaultPreviewHtml() {
	return `<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Widget Preview</title>
    <meta name="referrer" content="strict-origin-when-cross-origin" />
    <style>body { margin: 0; padding: 0; }</style>
  </head>
  <body>
    <div id="preview-root"></div>
    <script type="module" src="/src/preview-entry.tsx"><\/script>
  </body>
</html>`;
}
//#endregion
exports.fluidBuilderPreviewPlugin = fluidBuilderPreviewPlugin;
exports.fluidManifestPlugin = fluidManifestPlugin;
exports.fluidPreviewPlugin = fluidPreviewPlugin;

//# sourceMappingURL=index.cjs.map