@fluid-app/fluid-cli-portal 0.1.40 → 0.1.42

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/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as fluid_os_v0_create_fluid_osversion, B as fluid_os_v0_update_fluid_osnavigation, C as writeMappings, D as fluid_os_v0_create_fluid_osprofile, E as fluid_os_v0_create_fluid_osnavigation_item, F as fluid_os_v0_delete_fluid_osscreen, G as fluid_os_v0_update_fluid_osversion, H as fluid_os_v0_update_fluid_osprofile, I as fluid_os_v0_delete_fluid_ostheme, K as createFetchClient, L as fluid_os_v0_list_fluid_osdefinitions, M as fluid_os_v0_delete_fluid_osnavigation, N as fluid_os_v0_delete_fluid_osnavigation_item, O as fluid_os_v0_create_fluid_osscreen, P as fluid_os_v0_delete_fluid_osprofile, R as fluid_os_v0_list_fluid_osnavigation_items, S as updateMapping, T as fluid_os_v0_create_fluid_osnavigation, U as fluid_os_v0_update_fluid_osscreen, V as fluid_os_v0_update_fluid_osnavigation_item, W as fluid_os_v0_update_fluid_ostheme, _ as deriveSlug, a as buildThemeIdToSlugMap, b as resolveIdToSlug, c as transformNavigationItems, d as transformTheme, f as buildSnapshot, g as writeSnapshot, h as readSnapshot, i as buildNavigationIdToSlugMap, j as fluid_os_v0_create_widget_package_version, k as fluid_os_v0_create_fluid_ostheme, l as transformProfile, m as diffAgainstSnapshot, o as deriveScreenSlug, p as computeFileHash, r as buildIdToSlugMap, s as transformNavigation, t as pullCommand, u as transformScreen, v as readMappings, w as fluid_os_v0_complete_widget_package_version_upload, x as resolveSlugToId, y as removeMapping, z as fluid_os_v0_list_fluid_osversions } from "./pull-CG5dELOX.mjs";
1
+ import { C as writeMappings, S as updateMapping, _ as deriveSlug, a as buildThemeIdToSlugMap, b as resolveIdToSlug, c as transformNavigationItems, d as transformTheme, f as buildSnapshot, g as writeSnapshot, h as readSnapshot, i as buildNavigationIdToSlugMap, l as transformProfile, m as diffAgainstSnapshot, o as deriveScreenSlug, p as computeFileHash, r as buildIdToSlugMap, s as transformNavigation, t as pullCommand, u as transformScreen, v as readMappings, x as resolveSlugToId, y as removeMapping } from "./pull-Cl7omGLf.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { Command } from "commander";
4
4
  import chalk from "chalk";
@@ -12,9 +12,10 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileS
12
12
  import Handlebars from "handlebars";
13
13
  import { execa } from "execa";
14
14
  import os from "node:os";
15
- import fs from "fs-extra";
16
- import path$1 from "path";
15
+ import { createFetchClient, fluidOs } from "@fluid-app/fluidos-api-client";
17
16
  import { createHash } from "node:crypto";
17
+ import fs from "fs-extra";
18
+ import { isJsonValue } from "@fluid-app/portal-core/remote-dom-widget-package";
18
19
  //#region src/types.ts
19
20
  /**
20
21
  * Available project templates
@@ -1149,7 +1150,7 @@ async function autoPull(cwd) {
1149
1150
  console.log(chalk.yellow("No portal/ directory found.") + " Attempting to pull content...");
1150
1151
  console.log();
1151
1152
  try {
1152
- const { pullCommand } = await import("./pull-CG5dELOX.mjs").then((n) => n.n);
1153
+ const { pullCommand } = await import("./pull-Cl7omGLf.mjs").then((n) => n.n);
1153
1154
  await pullCommand.parseAsync([], { from: "user" });
1154
1155
  return hasPortalContent(cwd);
1155
1156
  } catch (err) {
@@ -1204,615 +1205,173 @@ const devCommand = new Command("dev").description("Start the development server
1204
1205
  }
1205
1206
  });
1206
1207
  //#endregion
1207
- //#region src/utils/widget-package-config.ts
1208
- const CONFIG_CANDIDATES = [
1209
- "src/widgets.config.ts",
1210
- "src/portal.config.ts",
1211
- "portal.config.ts"
1212
- ];
1213
- const SOURCE_PACKAGE_EXTRACT_FILENAME = "extract-widget-packages.ts";
1214
- const SOURCE_PACKAGE_OUTPUT_FILENAME = "source-widget-packages.json";
1215
- const SOURCE_PACKAGE_OUTPUT_SENTINEL = "fluid-widget-source-packages:v1";
1216
- const FLUID_WIDGET_AUTHORING_NODE_CONDITIONS = ["--conditions=fluid-widget-authoring", "--conditions=production"];
1217
- const TSX_CLI_PATH$1 = createRequire(import.meta.url).resolve("tsx/cli");
1218
- function withFluidWidgetAuthoringNodeCondition(nodeOptions) {
1219
- const existingOptions = nodeOptions?.trim();
1220
- const options = existingOptions ? existingOptions.split(/\s+/) : [];
1221
- const optionSet = new Set(options);
1222
- for (const condition of FLUID_WIDGET_AUTHORING_NODE_CONDITIONS) if (!optionSet.has(condition)) options.push(condition);
1223
- return options.join(" ");
1224
- }
1225
- async function resolvePortalWidgetSourceConfig(projectDir) {
1226
- for (const relativePath of CONFIG_CANDIDATES) {
1227
- const candidate = path.join(projectDir, relativePath);
1228
- if (await fs.pathExists(candidate)) return {
1229
- path: candidate,
1230
- relativePath
1231
- };
1208
+ //#region src/commands/build.ts
1209
+ const buildCommand = new Command("build").description("Build the application for production").option("-o, --out-dir <dir>", "Output directory", "dist").action(async (options) => {
1210
+ const cwd = process.cwd();
1211
+ if (!existsSync(join(cwd, "package.json"))) {
1212
+ console.error(chalk.red("Error: No package.json found in current directory"));
1213
+ console.error(chalk.yellow("Make sure you're in a Fluid project directory"));
1214
+ process.exit(1);
1232
1215
  }
1233
- }
1234
- async function loadSourceWidgetPackages(projectDir) {
1235
- const config = await resolvePortalWidgetSourceConfig(projectDir);
1236
- if (!config) return success([]);
1237
- let tempDir;
1216
+ if (!existsSync(join(cwd, "vite.config.ts"))) {
1217
+ console.error(chalk.red("Error: No vite.config.ts found"));
1218
+ console.error(chalk.yellow("This command must be run from a Fluid project directory"));
1219
+ process.exit(1);
1220
+ }
1221
+ console.log();
1222
+ console.log(chalk.bold("Building for production..."));
1223
+ console.log();
1224
+ const spinner = ora("Building...").start();
1238
1225
  try {
1239
- tempDir = await createTempDirectory$1(projectDir);
1240
- const extractFile = path.join(tempDir, SOURCE_PACKAGE_EXTRACT_FILENAME);
1241
- const outputFile = path.join(tempDir, SOURCE_PACKAGE_OUTPUT_FILENAME);
1242
- await fs.writeFile(extractFile, createSourcePackageExtractorScript({
1243
- projectDir,
1244
- configPath: config.path
1245
- }), {
1246
- encoding: "utf-8",
1247
- flag: "wx"
1248
- });
1249
- await execa(process.execPath, [
1250
- TSX_CLI_PATH$1,
1251
- extractFile,
1252
- outputFile
1253
- ], {
1254
- cwd: projectDir,
1255
- stdio: "pipe",
1256
- env: {
1257
- ...process.env,
1258
- NODE_ENV: "production",
1259
- NODE_OPTIONS: withFluidWidgetAuthoringNodeCondition(process.env.NODE_OPTIONS)
1260
- }
1261
- });
1262
- const outputResult = await readSourcePackageExtractorOutput(outputFile);
1263
- if (!outputResult.success) return outputResult;
1264
- const parsed = outputResult.value;
1265
- if (parsed.length === 0) return success([]);
1266
- return success(parsed);
1267
- } catch (err) {
1268
- const error = err;
1269
- return failure({
1270
- code: "CONFIG_LOAD_FAILED",
1271
- message: `Failed to load widget packages from ${config.relativePath}`,
1272
- details: error.stderr ?? error.message ?? String(err)
1226
+ await execa("pnpm", ["run", "build"], {
1227
+ cwd,
1228
+ stdio: "pipe"
1273
1229
  });
1274
- } finally {
1275
- if (tempDir) await fs.remove(tempDir).catch(() => {});
1230
+ spinner.succeed("Build completed");
1231
+ const outDir = options.outDir ?? "dist";
1232
+ console.log();
1233
+ console.log(`Output written to ${chalk.cyan(outDir)}/`);
1234
+ console.log();
1235
+ console.log("To preview the build locally:");
1236
+ console.log(chalk.cyan(" pnpm vite preview"));
1237
+ console.log();
1238
+ } catch (error) {
1239
+ spinner.fail("Build failed");
1240
+ const execaError = error;
1241
+ if (execaError.stderr) console.error(execaError.stderr);
1242
+ process.exit(1);
1276
1243
  }
1244
+ });
1245
+ //#endregion
1246
+ //#region src/utils/push-validation.ts
1247
+ /**
1248
+ * Cross-reference validation and change categorization utilities for the push command.
1249
+ *
1250
+ * Extracted into a standalone utility so that pure logic can be tested
1251
+ * without pulling in CLI dependencies (ora, chalk, prompts, etc.).
1252
+ */
1253
+ /**
1254
+ * Extract the slug from a file path (e.g., "screens/home.json" -> "home").
1255
+ */
1256
+ function slugFromPath(filePath) {
1257
+ return basename(filePath, ".json");
1277
1258
  }
1278
- async function createTempDirectory$1(projectDir) {
1279
- const projectTmpDir = path.join(projectDir, ".fluid", "tmp");
1280
- try {
1281
- await fs.ensureDir(projectTmpDir);
1282
- return await fs.mkdtemp(path.join(projectTmpDir, "widget-packages-"));
1283
- } catch (err) {
1284
- const message = err instanceof Error ? err.message : String(err);
1285
- throw new Error(`Unable to create project-local temporary directory at ${projectTmpDir}: ${message}`);
1259
+ /**
1260
+ * Extract the resource type directory from a file path (e.g., "screens/home.json" -> "screens").
1261
+ */
1262
+ function resourceTypeFromPath(filePath) {
1263
+ const dir = filePath.split("/")[0];
1264
+ if (dir === "screens" || dir === "themes" || dir === "navigations" || dir === "profiles") return dir;
1265
+ return null;
1266
+ }
1267
+ /**
1268
+ * Read and parse a JSON file from the portal directory.
1269
+ */
1270
+ async function readPortalFile$1(portalDir, relativePath) {
1271
+ const content = await readFile(join(portalDir, relativePath), "utf-8");
1272
+ return JSON.parse(content);
1273
+ }
1274
+ /**
1275
+ * Categorize a snapshot diff into resource-type-specific change lists.
1276
+ */
1277
+ function categorizeChanges(diff) {
1278
+ const result = {
1279
+ screens: {
1280
+ new: [],
1281
+ changed: [],
1282
+ deleted: []
1283
+ },
1284
+ themes: {
1285
+ new: [],
1286
+ changed: [],
1287
+ deleted: []
1288
+ },
1289
+ navigations: {
1290
+ new: [],
1291
+ changed: [],
1292
+ deleted: []
1293
+ },
1294
+ profiles: {
1295
+ new: [],
1296
+ changed: [],
1297
+ deleted: []
1298
+ }
1299
+ };
1300
+ for (const file of diff.new) {
1301
+ const type = resourceTypeFromPath(file);
1302
+ if (type) result[type].new.push(file);
1286
1303
  }
1304
+ for (const file of diff.changed) {
1305
+ const type = resourceTypeFromPath(file);
1306
+ if (type) result[type].changed.push(file);
1307
+ }
1308
+ for (const file of diff.deleted) {
1309
+ const type = resourceTypeFromPath(file);
1310
+ if (type) result[type].deleted.push(file);
1311
+ }
1312
+ return result;
1287
1313
  }
1288
- async function readSourcePackageExtractorOutput(outputFile) {
1289
- let output;
1290
- try {
1291
- output = await fs.readFile(outputFile, "utf-8");
1292
- } catch (err) {
1293
- return failure({
1294
- code: "CONFIG_LOAD_FAILED",
1295
- message: "Widget package extractor did not write an output file",
1296
- details: err instanceof Error ? err.message : String(err)
1314
+ /**
1315
+ * Validate that all cross-references between local portal files are valid.
1316
+ *
1317
+ * Checks:
1318
+ * - Navigation items' "screen" slugs reference existing screen files or mappings
1319
+ * - Profile "navigation" and "mobile_navigation" slugs reference existing nav files or mappings
1320
+ * - Profile "themes" slugs reference existing theme files or mappings
1321
+ */
1322
+ async function validateCrossReferences(portalDir, mappings, changes) {
1323
+ const errors = [];
1324
+ const validScreenSlugs = buildValidSlugsSet(portalDir, "screens", mappings);
1325
+ const validNavSlugs = buildValidSlugsSet(portalDir, "navigations", mappings);
1326
+ const validThemeSlugs = buildValidSlugsSet(portalDir, "themes", mappings);
1327
+ for (const file of changes.screens.deleted) validScreenSlugs.delete(slugFromPath(file));
1328
+ for (const file of changes.navigations.deleted) validNavSlugs.delete(slugFromPath(file));
1329
+ for (const file of changes.themes.deleted) validThemeSlugs.delete(slugFromPath(file));
1330
+ const navFilesToCheck = [...changes.navigations.new, ...changes.navigations.changed];
1331
+ for (const file of navFilesToCheck) try {
1332
+ validateNavigationItems((await readPortalFile$1(portalDir, file)).navigation_items, file, validScreenSlugs, errors);
1333
+ } catch {
1334
+ errors.push({
1335
+ file,
1336
+ message: "Failed to read navigation file"
1297
1337
  });
1298
1338
  }
1299
- let parsed;
1300
- try {
1301
- parsed = JSON.parse(output);
1339
+ const profileFilesToCheck = [...changes.profiles.new, ...changes.profiles.changed];
1340
+ for (const file of profileFilesToCheck) try {
1341
+ const profile = await readPortalFile$1(portalDir, file);
1342
+ if (profile.navigation && !validNavSlugs.has(profile.navigation)) errors.push({
1343
+ file,
1344
+ message: `References navigation "${profile.navigation}" which does not exist`
1345
+ });
1346
+ if (profile.mobile_navigation && !validNavSlugs.has(profile.mobile_navigation)) errors.push({
1347
+ file,
1348
+ message: `References mobile_navigation "${profile.mobile_navigation}" which does not exist`
1349
+ });
1350
+ for (const themeSlug of profile.themes) if (!validThemeSlugs.has(themeSlug)) errors.push({
1351
+ file,
1352
+ message: `References theme "${themeSlug}" which does not exist`
1353
+ });
1302
1354
  } catch {
1303
- return failure({
1304
- code: "INVALID_FORMAT",
1305
- message: "Failed to parse widget package output file as JSON",
1306
- details: `Output was: ${output.slice(0, 200)}`
1355
+ errors.push({
1356
+ file,
1357
+ message: "Failed to read profile file"
1307
1358
  });
1308
1359
  }
1309
- if (!isRecord$5(parsed) || parsed.sentinel !== SOURCE_PACKAGE_OUTPUT_SENTINEL) return failure({
1310
- code: "INVALID_FORMAT",
1311
- message: "Widget package extractor output file had an invalid sentinel"
1312
- });
1313
- if (!Array.isArray(parsed.data)) return failure({
1314
- code: "INVALID_FORMAT",
1315
- message: "Extracted widget package output is not an array",
1316
- details: `Expected an array, got: ${typeof parsed.data}`
1317
- });
1318
- return success(parsed.data);
1360
+ return errors;
1319
1361
  }
1320
- function createSourcePackageExtractorScript(options) {
1321
- return `
1322
- import fs from "node:fs/promises";
1323
- import path from "node:path";
1324
- import { createServer, normalizePath } from "vite";
1325
-
1326
- const SOURCE_PACKAGE_MARKER = "__fluidSourceWidgetPackage";
1327
- const OUTPUT_SENTINEL = ${JSON.stringify(SOURCE_PACKAGE_OUTPUT_SENTINEL)};
1328
- const projectRoot = ${JSON.stringify(options.projectDir)};
1329
- const widgetConfigPath = ${JSON.stringify(options.configPath)};
1330
- const outputPath = process.argv[2];
1331
- if (!outputPath) throw new Error("Missing widget package extractor output path.");
1332
-
1333
- function toViteModuleId(modulePath) {
1334
- const relativePath = path.relative(projectRoot, modulePath);
1335
- if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
1336
- return "/" + normalizePath(relativePath);
1337
- }
1338
- return normalizePath(modulePath);
1339
- }
1340
-
1341
- const server = await createServer({
1342
- root: projectRoot,
1343
- mode: "production",
1344
- server: { middlewareMode: true },
1345
- appType: "custom",
1346
- logLevel: "error",
1347
- clearScreen: false,
1348
- resolve: {
1349
- conditions: ["fluid-widget-authoring", "module", "browser", "development|production"],
1350
- },
1351
- ssr: {
1352
- noExternal: ["@fluid-app/portal-sdk"],
1353
- resolve: {
1354
- conditions: ["fluid-widget-authoring", "module", "node", "development|production"],
1355
- },
1356
- },
1357
- });
1358
-
1359
- try {
1360
- const widgetConfig = await server.ssrLoadModule(toViteModuleId(widgetConfigPath));
1361
-
1362
- function isSourceWidgetPackage(value) {
1363
- return Boolean(value && typeof value === "object" && value[SOURCE_PACKAGE_MARKER] === true);
1364
- }
1365
-
1366
- function collectSourceWidgetPackages(mod) {
1367
- const candidates = [
1368
- mod.widgetPackage,
1369
- ...(Array.isArray(mod.widgetPackages) ? mod.widgetPackages : []),
1370
- mod.default,
1371
- ];
1372
- const byPackageId = new Map();
1373
- for (const candidate of candidates) {
1374
- if (!isSourceWidgetPackage(candidate)) continue;
1375
- if (!byPackageId.has(candidate.packageId)) byPackageId.set(candidate.packageId, candidate);
1376
- }
1377
- return Array.from(byPackageId.values());
1378
- }
1379
-
1380
- function serializeWidget(widget) {
1381
- if (!widget || typeof widget !== "object" || Array.isArray(widget)) {
1382
- return widget;
1383
- }
1384
- const { component, ...metadata } = widget;
1385
- return metadata;
1386
- }
1387
-
1388
- function serializePackage(sourcePackage) {
1389
- return {
1390
- manifestVersion: sourcePackage.manifestVersion,
1391
- scope: sourcePackage.scope,
1392
- packageStableId: sourcePackage.packageStableId,
1393
- packageId: sourcePackage.packageId,
1394
- packageType: sourcePackage.packageType,
1395
- version: sourcePackage.version,
1396
- cssUrls: sourcePackage.cssUrls,
1397
- widgets: Array.isArray(sourcePackage.widgets)
1398
- ? sourcePackage.widgets.map(serializeWidget)
1399
- : sourcePackage.widgets,
1400
- };
1401
- }
1402
-
1403
- await fs.writeFile(
1404
- outputPath,
1405
- JSON.stringify({
1406
- sentinel: OUTPUT_SENTINEL,
1407
- data: collectSourceWidgetPackages(widgetConfig).map(serializePackage),
1408
- }),
1409
- "utf-8",
1410
- );
1411
- } finally {
1412
- await server.close();
1413
- }
1414
- `;
1415
- }
1416
- function isRecord$5(value) {
1417
- return typeof value === "object" && value !== null && !Array.isArray(value);
1418
- }
1419
- //#endregion
1420
- //#region src/utils/extract-manifests.ts
1421
- /**
1422
- * Manifest extraction utility
1423
- *
1424
- * Extracts serializable widget manifest metadata from portal.config.ts
1425
- * by writing a minimal wrapper script that imports customWidgets plus
1426
- * source widget package exports and serializes the result to a temp output
1427
- * file, then running it with tsx.
1428
- *
1429
- * Strips the `component` field (not serializable) from each manifest.
1430
- *
1431
- * Writes a temp script, runs it with tsx, and parses JSON output from the
1432
- * output file. The wrapper loads config modules through Vite SSR so project
1433
- * aliases and Vite-compatible module resolution are honored.
1434
- */
1435
- const EXTRACT_FILENAME = "extract-manifests.ts";
1436
- const EXTRACT_OUTPUT_FILENAME = "manifests.json";
1437
- const EXTRACT_OUTPUT_SENTINEL = "fluid-widget-manifests:v1";
1438
- const TSX_CLI_PATH = createRequire(import.meta.url).resolve("tsx/cli");
1439
- /**
1440
- * Extract serializable widget manifests from a project's portal.config.ts.
1441
- *
1442
- * Writes a temp wrapper script, runs it with tsx, and parses the temp JSON
1443
- * output file. The temp files are always cleaned up.
1444
- *
1445
- * Returns an empty array if no customWidgets or source package exports exist.
1446
- *
1447
- * Supported static export shapes are intentionally simple so the CLI can avoid
1448
- * executing unrelated portal configs: named `export const|let|var customWidgets`,
1449
- * `widgetPackage`, or `widgetPackages`; direct `export default
1450
- * defineWidgetPackage(...)`; or `export default <identifier>` where that
1451
- * identifier is initialized with `defineWidgetPackage(...)` in the same file.
1452
- * Re-export-only and computed export shapes are not detected by this layer.
1453
- *
1454
- * @param projectDir - The project root directory containing src/portal.config.ts
1455
- */
1456
- async function extractManifests(projectDir) {
1457
- let tempDir;
1458
- try {
1459
- const config = await resolvePortalWidgetSourceConfig(projectDir);
1460
- if (!config) return success([]);
1461
- const configExports = readStaticWidgetExports(await fs.readFile(config.path, "utf-8"));
1462
- const legacyPortalConfigPath = path$1.join(projectDir, "src", "portal.config.ts");
1463
- const legacyCustomWidgetsConfigPath = (path$1.resolve(config.path) !== path$1.resolve(legacyPortalConfigPath) && await fs.pathExists(legacyPortalConfigPath) ? readStaticWidgetExports(await fs.readFile(legacyPortalConfigPath, "utf-8")) : void 0)?.hasCustomWidgets ? legacyPortalConfigPath : void 0;
1464
- if (!configExports.hasCustomWidgets && !configExports.hasSourceWidgetPackages && !legacyCustomWidgetsConfigPath) return success([]);
1465
- tempDir = await createTempDirectory(projectDir);
1466
- const extractFile = path$1.join(tempDir, EXTRACT_FILENAME);
1467
- const outputFile = path$1.join(tempDir, EXTRACT_OUTPUT_FILENAME);
1468
- const wrapperScript = createManifestExtractorScript({
1469
- projectDir,
1470
- widgetConfigPath: config.path,
1471
- legacyCustomWidgetsConfigPath
1472
- });
1473
- await fs.writeFile(extractFile, wrapperScript, {
1474
- encoding: "utf-8",
1475
- flag: "wx"
1476
- });
1477
- await execa(process.execPath, [
1478
- TSX_CLI_PATH,
1479
- extractFile,
1480
- outputFile
1481
- ], {
1482
- cwd: projectDir,
1483
- stdio: "pipe",
1484
- env: {
1485
- ...process.env,
1486
- NODE_ENV: "production",
1487
- NODE_OPTIONS: withFluidWidgetAuthoringNodeCondition(process.env.NODE_OPTIONS)
1488
- }
1489
- });
1490
- const outputResult = await readManifestExtractorOutput(outputFile);
1491
- if (!outputResult.success) return outputResult;
1492
- const parsed = outputResult.value;
1493
- if (parsed.length === 0) return success([]);
1494
- return success(parsed.filter((m) => typeof m === "object" && m !== null && typeof m.type === "string" && typeof m.displayName === "string"));
1495
- } catch (err) {
1496
- const error = err;
1497
- return failure({
1498
- code: "EXTRACTION_FAILED",
1499
- message: "Failed to extract widget manifests from portal widget source config",
1500
- details: error.stderr ?? error.message ?? String(err)
1501
- });
1502
- } finally {
1503
- if (tempDir) await fs.remove(tempDir).catch(() => {});
1504
- }
1505
- }
1506
- function readStaticWidgetExports(source) {
1507
- const strippedSource = source.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
1508
- const defaultExportMatch = /export\s+default\s+([A-Za-z_$][\w$]*)\b/.exec(strippedSource);
1509
- const widgetPackageDefinitionNames = new Set(Array.from(strippedSource.matchAll(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)(?:\s*:[^=]+)?\s*=\s*defineWidgetPackage\s*\(/g), (match) => match[1]));
1510
- const hasDefaultWidgetPackageExport = /export\s+default\s+defineWidgetPackage\s*\(/.test(strippedSource) || defaultExportMatch !== null && widgetPackageDefinitionNames.has(defaultExportMatch[1]);
1511
- return {
1512
- hasCustomWidgets: /export\s+(?:const|let|var)\s+customWidgets\b/.test(strippedSource),
1513
- hasSourceWidgetPackages: /export\s+(?:const|let|var)\s+widgetPackage\b/.test(strippedSource) || /export\s+(?:const|let|var)\s+widgetPackages\b/.test(strippedSource) || hasDefaultWidgetPackageExport
1514
- };
1515
- }
1516
- async function createTempDirectory(projectDir) {
1517
- const projectTmpDir = path$1.join(projectDir, ".fluid", "tmp");
1518
- try {
1519
- await fs.ensureDir(projectTmpDir);
1520
- return await fs.mkdtemp(path$1.join(projectTmpDir, "manifests-"));
1521
- } catch (err) {
1522
- const message = err instanceof Error ? err.message : String(err);
1523
- throw new Error(`Unable to create project-local temporary directory at ${projectTmpDir}: ${message}`);
1524
- }
1525
- }
1526
- async function readManifestExtractorOutput(outputFile) {
1527
- let output;
1528
- try {
1529
- output = await fs.readFile(outputFile, "utf-8");
1530
- } catch (err) {
1531
- return failure({
1532
- code: "EXTRACTION_FAILED",
1533
- message: "Manifest extractor did not write an output file",
1534
- details: err instanceof Error ? err.message : String(err)
1535
- });
1536
- }
1537
- let parsed;
1538
- try {
1539
- parsed = JSON.parse(output);
1540
- } catch {
1541
- return failure({
1542
- code: "INVALID_FORMAT",
1543
- message: "Failed to parse manifest output file as JSON",
1544
- details: `Output was: ${output.slice(0, 200)}`
1545
- });
1546
- }
1547
- if (!isRecord$4(parsed) || parsed.sentinel !== EXTRACT_OUTPUT_SENTINEL) return failure({
1548
- code: "INVALID_FORMAT",
1549
- message: "Manifest extractor output file had an invalid sentinel"
1550
- });
1551
- if (!Array.isArray(parsed.data)) return failure({
1552
- code: "INVALID_FORMAT",
1553
- message: "extracted widget manifests output is not an array",
1554
- details: `Expected an array, got: ${typeof parsed.data}`
1555
- });
1556
- return success(parsed.data);
1557
- }
1558
- function isRecord$4(value) {
1559
- return typeof value === "object" && value !== null && !Array.isArray(value);
1560
- }
1561
- function createManifestExtractorScript(options) {
1562
- return `
1563
- import fs from "node:fs/promises";
1564
- import path from "node:path";
1565
- import { createServer, normalizePath } from "vite";
1566
- import {
1567
- isSourceWidgetPackage,
1568
- sourceWidgetPackagesToManifests,
1569
- } from "@fluid-app/portal-sdk";
1570
-
1571
- const OUTPUT_SENTINEL = ${JSON.stringify(EXTRACT_OUTPUT_SENTINEL)};
1572
- const projectRoot = ${JSON.stringify(options.projectDir)};
1573
- const widgetConfigPath = ${JSON.stringify(options.widgetConfigPath)};
1574
- const legacyCustomWidgetsConfigPath = ${JSON.stringify(options.legacyCustomWidgetsConfigPath)};
1575
- const outputPath = process.argv[2];
1576
- if (!outputPath) throw new Error("Missing manifest extractor output path.");
1577
-
1578
- function toViteModuleId(modulePath) {
1579
- const relativePath = path.relative(projectRoot, modulePath);
1580
- if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
1581
- return "/" + normalizePath(relativePath);
1582
- }
1583
- return normalizePath(modulePath);
1584
- }
1585
-
1586
- const server = await createServer({
1587
- root: projectRoot,
1588
- mode: "production",
1589
- server: { middlewareMode: true },
1590
- appType: "custom",
1591
- logLevel: "error",
1592
- clearScreen: false,
1593
- resolve: {
1594
- conditions: ["fluid-widget-authoring", "module", "browser", "development|production"],
1595
- },
1596
- ssr: {
1597
- noExternal: ["@fluid-app/portal-sdk"],
1598
- resolve: {
1599
- conditions: ["fluid-widget-authoring", "module", "node", "development|production"],
1600
- },
1601
- },
1602
- });
1603
-
1604
- try {
1605
- const portalConfig = await server.ssrLoadModule(toViteModuleId(widgetConfigPath));
1606
- const legacyPortalConfig = legacyCustomWidgetsConfigPath
1607
- ? await server.ssrLoadModule(toViteModuleId(legacyCustomWidgetsConfigPath))
1608
- : {};
1609
-
1610
- const sourcePackages = [];
1611
- const seenSourcePackageIds = new Set();
1612
- for (const candidate of [
1613
- portalConfig.widgetPackage,
1614
- ...(Array.isArray(portalConfig.widgetPackages) ? portalConfig.widgetPackages : []),
1615
- portalConfig.default,
1616
- ]) {
1617
- if (!isSourceWidgetPackage(candidate)) continue;
1618
- if (seenSourcePackageIds.has(candidate.packageId)) continue;
1619
- seenSourcePackageIds.add(candidate.packageId);
1620
- sourcePackages.push(candidate);
1621
- }
1622
- const manifests = [
1623
- ...(Array.isArray(portalConfig.customWidgets) ? portalConfig.customWidgets : []),
1624
- ...(Array.isArray(legacyPortalConfig.customWidgets) ? legacyPortalConfig.customWidgets : []),
1625
- ...sourceWidgetPackagesToManifests(sourcePackages),
1626
- ];
1627
- const serializable = manifests.map(({ component, ...rest }) => rest);
1628
- await fs.writeFile(
1629
- outputPath,
1630
- JSON.stringify({ sentinel: OUTPUT_SENTINEL, data: serializable }),
1631
- "utf-8",
1632
- );
1633
- } finally {
1634
- await server.close();
1635
- }
1636
- `;
1637
- }
1638
- //#endregion
1639
- //#region src/commands/build.ts
1640
- const buildCommand = new Command("build").description("Build the application for production").option("-o, --out-dir <dir>", "Output directory", "dist").action(async (options) => {
1641
- const cwd = process.cwd();
1642
- if (!existsSync(join(cwd, "package.json"))) {
1643
- console.error(chalk.red("Error: No package.json found in current directory"));
1644
- console.error(chalk.yellow("Make sure you're in a Fluid project directory"));
1645
- process.exit(1);
1646
- }
1647
- if (!existsSync(join(cwd, "vite.config.ts"))) {
1648
- console.error(chalk.red("Error: No vite.config.ts found"));
1649
- console.error(chalk.yellow("This command must be run from a Fluid project directory"));
1650
- process.exit(1);
1651
- }
1652
- console.log();
1653
- console.log(chalk.bold("Building for production..."));
1654
- console.log();
1655
- const spinner = ora("Building...").start();
1656
- try {
1657
- await execa("pnpm", ["run", "build"], {
1658
- cwd,
1659
- stdio: "pipe"
1660
- });
1661
- spinner.succeed("Build completed");
1662
- const manifestSpinner = ora("Extracting widget manifests...").start();
1663
- const outDir = options.outDir ?? "dist";
1664
- const manifestResult = await extractManifests(cwd);
1665
- const manifestPath = [join(cwd, outDir, "__manifests__.json"), join(cwd, outDir, "public", "__manifests__.json")].find((p) => existsSync(p));
1666
- if (manifestResult.success) if (manifestResult.value.length === 0) manifestSpinner.info("No custom widgets found");
1667
- else if (!manifestPath) manifestSpinner.warn(`__manifests__.json not found in build output — skipping manifest write`);
1668
- else {
1669
- writeFileSync(manifestPath, JSON.stringify(manifestResult.value));
1670
- manifestSpinner.succeed(`Extracted ${manifestResult.value.length} widget manifest(s)`);
1671
- }
1672
- else manifestSpinner.warn(`Manifest extraction failed: ${manifestResult.error.message}`);
1673
- console.log();
1674
- console.log(`Output written to ${chalk.cyan(outDir)}/`);
1675
- console.log();
1676
- console.log("To preview the build locally:");
1677
- console.log(chalk.cyan(" pnpm vite preview"));
1678
- console.log();
1679
- } catch (error) {
1680
- spinner.fail("Build failed");
1681
- const execaError = error;
1682
- if (execaError.stderr) console.error(execaError.stderr);
1683
- process.exit(1);
1684
- }
1685
- });
1686
- //#endregion
1687
- //#region src/utils/push-validation.ts
1688
- /**
1689
- * Cross-reference validation and change categorization utilities for the push command.
1690
- *
1691
- * Extracted into a standalone utility so that pure logic can be tested
1692
- * without pulling in CLI dependencies (ora, chalk, prompts, etc.).
1693
- */
1694
- /**
1695
- * Extract the slug from a file path (e.g., "screens/home.json" -> "home").
1696
- */
1697
- function slugFromPath(filePath) {
1698
- return basename(filePath, ".json");
1699
- }
1700
- /**
1701
- * Extract the resource type directory from a file path (e.g., "screens/home.json" -> "screens").
1702
- */
1703
- function resourceTypeFromPath(filePath) {
1704
- const dir = filePath.split("/")[0];
1705
- if (dir === "screens" || dir === "themes" || dir === "navigations" || dir === "profiles") return dir;
1706
- return null;
1707
- }
1708
- /**
1709
- * Read and parse a JSON file from the portal directory.
1710
- */
1711
- async function readPortalFile$1(portalDir, relativePath) {
1712
- const content = await readFile(join(portalDir, relativePath), "utf-8");
1713
- return JSON.parse(content);
1714
- }
1715
- /**
1716
- * Categorize a snapshot diff into resource-type-specific change lists.
1717
- */
1718
- function categorizeChanges(diff) {
1719
- const result = {
1720
- screens: {
1721
- new: [],
1722
- changed: [],
1723
- deleted: []
1724
- },
1725
- themes: {
1726
- new: [],
1727
- changed: [],
1728
- deleted: []
1729
- },
1730
- navigations: {
1731
- new: [],
1732
- changed: [],
1733
- deleted: []
1734
- },
1735
- profiles: {
1736
- new: [],
1737
- changed: [],
1738
- deleted: []
1739
- }
1740
- };
1741
- for (const file of diff.new) {
1742
- const type = resourceTypeFromPath(file);
1743
- if (type) result[type].new.push(file);
1744
- }
1745
- for (const file of diff.changed) {
1746
- const type = resourceTypeFromPath(file);
1747
- if (type) result[type].changed.push(file);
1748
- }
1749
- for (const file of diff.deleted) {
1750
- const type = resourceTypeFromPath(file);
1751
- if (type) result[type].deleted.push(file);
1752
- }
1753
- return result;
1754
- }
1755
- /**
1756
- * Validate that all cross-references between local portal files are valid.
1757
- *
1758
- * Checks:
1759
- * - Navigation items' "screen" slugs reference existing screen files or mappings
1760
- * - Profile "navigation" and "mobile_navigation" slugs reference existing nav files or mappings
1761
- * - Profile "themes" slugs reference existing theme files or mappings
1762
- */
1763
- async function validateCrossReferences(portalDir, mappings, changes) {
1764
- const errors = [];
1765
- const validScreenSlugs = buildValidSlugsSet(portalDir, "screens", mappings);
1766
- const validNavSlugs = buildValidSlugsSet(portalDir, "navigations", mappings);
1767
- const validThemeSlugs = buildValidSlugsSet(portalDir, "themes", mappings);
1768
- for (const file of changes.screens.deleted) validScreenSlugs.delete(slugFromPath(file));
1769
- for (const file of changes.navigations.deleted) validNavSlugs.delete(slugFromPath(file));
1770
- for (const file of changes.themes.deleted) validThemeSlugs.delete(slugFromPath(file));
1771
- const navFilesToCheck = [...changes.navigations.new, ...changes.navigations.changed];
1772
- for (const file of navFilesToCheck) try {
1773
- validateNavigationItems((await readPortalFile$1(portalDir, file)).navigation_items, file, validScreenSlugs, errors);
1774
- } catch {
1775
- errors.push({
1776
- file,
1777
- message: "Failed to read navigation file"
1778
- });
1779
- }
1780
- const profileFilesToCheck = [...changes.profiles.new, ...changes.profiles.changed];
1781
- for (const file of profileFilesToCheck) try {
1782
- const profile = await readPortalFile$1(portalDir, file);
1783
- if (profile.navigation && !validNavSlugs.has(profile.navigation)) errors.push({
1784
- file,
1785
- message: `References navigation "${profile.navigation}" which does not exist`
1786
- });
1787
- if (profile.mobile_navigation && !validNavSlugs.has(profile.mobile_navigation)) errors.push({
1788
- file,
1789
- message: `References mobile_navigation "${profile.mobile_navigation}" which does not exist`
1790
- });
1791
- for (const themeSlug of profile.themes) if (!validThemeSlugs.has(themeSlug)) errors.push({
1792
- file,
1793
- message: `References theme "${themeSlug}" which does not exist`
1794
- });
1795
- } catch {
1796
- errors.push({
1797
- file,
1798
- message: "Failed to read profile file"
1799
- });
1800
- }
1801
- return errors;
1802
- }
1803
- /**
1804
- * Build a set of valid slugs for a resource type by combining
1805
- * existing mapping slugs with local file slugs on disk.
1806
- */
1807
- function buildValidSlugsSet(portalDir, resourceType, mappings) {
1808
- const slugs = /* @__PURE__ */ new Set();
1809
- for (const slug of Object.keys(mappings[resourceType])) slugs.add(slug);
1810
- const dir = join(portalDir, resourceType);
1811
- if (existsSync(dir)) try {
1812
- const entries = readdirSync(dir);
1813
- for (const entry of entries) if (entry.endsWith(".json")) slugs.add(basename(entry, ".json"));
1814
- } catch {}
1815
- return slugs;
1362
+ /**
1363
+ * Build a set of valid slugs for a resource type by combining
1364
+ * existing mapping slugs with local file slugs on disk.
1365
+ */
1366
+ function buildValidSlugsSet(portalDir, resourceType, mappings) {
1367
+ const slugs = /* @__PURE__ */ new Set();
1368
+ for (const slug of Object.keys(mappings[resourceType])) slugs.add(slug);
1369
+ const dir = join(portalDir, resourceType);
1370
+ if (existsSync(dir)) try {
1371
+ const entries = readdirSync(dir);
1372
+ for (const entry of entries) if (entry.endsWith(".json")) slugs.add(basename(entry, ".json"));
1373
+ } catch {}
1374
+ return slugs;
1816
1375
  }
1817
1376
  /**
1818
1377
  * Recursively validate navigation item screen references.
@@ -2094,7 +1653,7 @@ async function pushScreens(client, defId, portalDir, changes, mappings) {
2094
1653
  const slug = slugFromPath(file);
2095
1654
  try {
2096
1655
  const local = await readPortalFile(portalDir, file);
2097
- const newId = (await fluid_os_v0_create_fluid_osscreen(client, defId, { screen: {
1656
+ const newId = (await fluidOs.fluid_os_v0_create_fluid_osscreen(client, defId, { screen: {
2098
1657
  name: local.name,
2099
1658
  slug,
2100
1659
  component_tree: toApiComponentTree(local.component_tree)
@@ -2135,7 +1694,7 @@ async function pushScreens(client, defId, portalDir, changes, mappings) {
2135
1694
  }
2136
1695
  try {
2137
1696
  const local = await readPortalFile(portalDir, file);
2138
- await fluid_os_v0_update_fluid_osscreen(client, defId, screenId, { screen: {
1697
+ await fluidOs.fluid_os_v0_update_fluid_osscreen(client, defId, screenId, { screen: {
2139
1698
  name: local.name,
2140
1699
  slug,
2141
1700
  component_tree: toApiComponentTree(local.component_tree)
@@ -2174,7 +1733,7 @@ async function pushScreens(client, defId, portalDir, changes, mappings) {
2174
1733
  };
2175
1734
  }
2176
1735
  try {
2177
- await fluid_os_v0_delete_fluid_osscreen(client, defId, screenId);
1736
+ await fluidOs.fluid_os_v0_delete_fluid_osscreen(client, defId, screenId);
2178
1737
  currentMappings = removeMapping(currentMappings, "screens", slug);
2179
1738
  results.push({
2180
1739
  file,
@@ -2209,7 +1768,7 @@ async function pushThemes(client, defId, portalDir, changes, mappings) {
2209
1768
  const slug = slugFromPath(file);
2210
1769
  try {
2211
1770
  const local = await readPortalFile(portalDir, file);
2212
- const newId = (await fluid_os_v0_create_fluid_ostheme(client, defId, { theme: {
1771
+ const newId = (await fluidOs.fluid_os_v0_create_fluid_ostheme(client, defId, { theme: {
2213
1772
  name: local.name,
2214
1773
  active: local.active,
2215
1774
  config: local.config
@@ -2250,7 +1809,7 @@ async function pushThemes(client, defId, portalDir, changes, mappings) {
2250
1809
  }
2251
1810
  try {
2252
1811
  const local = await readPortalFile(portalDir, file);
2253
- await fluid_os_v0_update_fluid_ostheme(client, defId, themeId, { theme: {
1812
+ await fluidOs.fluid_os_v0_update_fluid_ostheme(client, defId, themeId, { theme: {
2254
1813
  name: local.name,
2255
1814
  active: local.active,
2256
1815
  config: local.config
@@ -2289,7 +1848,7 @@ async function pushThemes(client, defId, portalDir, changes, mappings) {
2289
1848
  };
2290
1849
  }
2291
1850
  try {
2292
- await fluid_os_v0_delete_fluid_ostheme(client, defId, themeId);
1851
+ await fluidOs.fluid_os_v0_delete_fluid_ostheme(client, defId, themeId);
2293
1852
  currentMappings = removeMapping(currentMappings, "themes", slug);
2294
1853
  results.push({
2295
1854
  file,
@@ -2359,7 +1918,7 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
2359
1918
  const slug = slugFromPath(file);
2360
1919
  try {
2361
1920
  const local = await readPortalFile(portalDir, file);
2362
- const newId = (await fluid_os_v0_create_fluid_osnavigation(client, defId, { navigation: {
1921
+ const newId = (await fluidOs.fluid_os_v0_create_fluid_osnavigation(client, defId, { navigation: {
2363
1922
  name: local.name,
2364
1923
  platform: local.platform
2365
1924
  } })).navigation?.id;
@@ -2369,7 +1928,7 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
2369
1928
  const localToServerId = /* @__PURE__ */ new Map();
2370
1929
  for (const item of resolvedItems) {
2371
1930
  const resolvedParentId = item.parent_id != null ? localToServerId.get(item.parent_id) ?? item.parent_id : void 0;
2372
- const created = await fluid_os_v0_create_fluid_osnavigation_item(client, defId, newId, { navigation_item: {
1931
+ const created = await fluidOs.fluid_os_v0_create_fluid_osnavigation_item(client, defId, newId, { navigation_item: {
2373
1932
  label: item.label ?? "",
2374
1933
  position: item.position ?? 0,
2375
1934
  icon: item.icon ?? void 0,
@@ -2416,15 +1975,15 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
2416
1975
  }
2417
1976
  try {
2418
1977
  const local = await readPortalFile(portalDir, file);
2419
- await fluid_os_v0_update_fluid_osnavigation(client, defId, navId, { navigation: {
1978
+ await fluidOs.fluid_os_v0_update_fluid_osnavigation(client, defId, navId, { navigation: {
2420
1979
  name: local.name,
2421
1980
  platform: local.platform
2422
1981
  } });
2423
1982
  const resolvedItems = flattenNavigationItems(resolveNavigationItemScreenIds(local.navigation_items, currentMappings));
2424
- const serverItems = (await fluid_os_v0_list_fluid_osnavigation_items(client, defId, navId)).navigation_items ?? [];
1983
+ const serverItems = (await fluidOs.fluid_os_v0_list_fluid_osnavigation_items(client, defId, navId)).navigation_items ?? [];
2425
1984
  const serverById = new Map(serverItems.map((s) => [s.id, s]));
2426
1985
  const localIds = new Set(resolvedItems.filter((i) => i.id).map((i) => i.id));
2427
- for (const serverItem of serverItems) if (!localIds.has(serverItem.id)) await fluid_os_v0_delete_fluid_osnavigation_item(client, defId, navId, serverItem.id);
1986
+ for (const serverItem of serverItems) if (!localIds.has(serverItem.id)) await fluidOs.fluid_os_v0_delete_fluid_osnavigation_item(client, defId, navId, serverItem.id);
2428
1987
  const localToServerId = /* @__PURE__ */ new Map();
2429
1988
  for (const item of resolvedItems) {
2430
1989
  const resolvedParentId = item.parent_id != null ? localToServerId.get(item.parent_id) ?? item.parent_id : void 0;
@@ -2437,9 +1996,9 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
2437
1996
  source: item.source ?? void 0,
2438
1997
  parent_id: resolvedParentId
2439
1998
  };
2440
- if (item.id && serverById.has(item.id)) await fluid_os_v0_update_fluid_osnavigation_item(client, defId, navId, item.id, { navigation_item: body });
1999
+ if (item.id && serverById.has(item.id)) await fluidOs.fluid_os_v0_update_fluid_osnavigation_item(client, defId, navId, item.id, { navigation_item: body });
2441
2000
  else {
2442
- const created = await fluid_os_v0_create_fluid_osnavigation_item(client, defId, navId, { navigation_item: {
2001
+ const created = await fluidOs.fluid_os_v0_create_fluid_osnavigation_item(client, defId, navId, { navigation_item: {
2443
2002
  ...body,
2444
2003
  label: body.label ?? "",
2445
2004
  position: body.position ?? 0
@@ -2481,7 +2040,7 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
2481
2040
  };
2482
2041
  }
2483
2042
  try {
2484
- await fluid_os_v0_delete_fluid_osnavigation(client, defId, navId);
2043
+ await fluidOs.fluid_os_v0_delete_fluid_osnavigation(client, defId, navId);
2485
2044
  currentMappings = removeMapping(currentMappings, "navigations", slug);
2486
2045
  results.push({
2487
2046
  file,
@@ -2515,7 +2074,8 @@ async function pushProfiles(client, defId, portalDir, changes, mappings) {
2515
2074
  for (const file of changes.new) {
2516
2075
  const slug = slugFromPath(file);
2517
2076
  try {
2518
- const newId = (await fluid_os_v0_create_fluid_osprofile(client, defId, { profile: resolveProfileBody(await readPortalFile(portalDir, file), currentMappings) })).profile?.id;
2077
+ const body = resolveProfileBody(await readPortalFile(portalDir, file), currentMappings);
2078
+ const newId = (await fluidOs.fluid_os_v0_create_fluid_osprofile(client, defId, { profile: body })).profile?.id;
2519
2079
  if (newId != null) currentMappings = updateMapping(currentMappings, "profiles", slug, newId);
2520
2080
  results.push({
2521
2081
  file,
@@ -2551,7 +2111,8 @@ async function pushProfiles(client, defId, portalDir, changes, mappings) {
2551
2111
  };
2552
2112
  }
2553
2113
  try {
2554
- await fluid_os_v0_update_fluid_osprofile(client, defId, profileId, { profile: resolveProfileBody(await readPortalFile(portalDir, file), currentMappings) });
2114
+ const body = resolveProfileBody(await readPortalFile(portalDir, file), currentMappings);
2115
+ await fluidOs.fluid_os_v0_update_fluid_osprofile(client, defId, profileId, { profile: body });
2555
2116
  results.push({
2556
2117
  file,
2557
2118
  action: "updated",
@@ -2586,7 +2147,7 @@ async function pushProfiles(client, defId, portalDir, changes, mappings) {
2586
2147
  };
2587
2148
  }
2588
2149
  try {
2589
- await fluid_os_v0_delete_fluid_osprofile(client, defId, profileId);
2150
+ await fluidOs.fluid_os_v0_delete_fluid_osprofile(client, defId, profileId);
2590
2151
  currentMappings = removeMapping(currentMappings, "profiles", slug);
2591
2152
  results.push({
2592
2153
  file,
@@ -2843,7 +2404,7 @@ async function selectDefinition(client, appName) {
2843
2404
  const definitions = [];
2844
2405
  let page = 1;
2845
2406
  while (true) {
2846
- const response = await fluid_os_v0_list_fluid_osdefinitions(client, {
2407
+ const response = await fluidOs.fluid_os_v0_list_fluid_osdefinitions(client, {
2847
2408
  page,
2848
2409
  per_page: 500
2849
2410
  });
@@ -2988,138 +2549,340 @@ function insertImport(source, importLine) {
2988
2549
  if (lineEnd === -1) return source + "\n" + importLine + "\n";
2989
2550
  return source.slice(0, lineEnd + 1) + importLine + "\n" + source.slice(lineEnd + 1);
2990
2551
  }
2991
- /**
2992
- * Insert a manifest reference into the customWidgets array.
2993
- * Preserves developer comments. Skips if already present.
2994
- * Returns null if the array pattern isn't found.
2995
- *
2996
- * Searches for the LAST `export const customWidgets` declaration to skip
2997
- * matches inside JSDoc @example blocks.
2998
- */
2999
- function insertIntoCustomWidgets(source, camelName) {
3000
- const pattern = /(export const customWidgets(?::[\s\S]*?)?\s*=\s*)\[([^\]]*)\]/g;
2552
+ /** Insert a widget definition into a defineWidgetPackage widgets array. */
2553
+ function insertIntoWidgetPackage(source, exportName) {
2554
+ const packageStart = source.lastIndexOf("defineWidgetPackage(");
2555
+ if (packageStart === -1) return null;
2556
+ const packageSource = source.slice(packageStart);
2557
+ const pattern = /(widgets\s*:\s*)\[([^\]]*)\]/g;
3001
2558
  let lastMatch = null;
3002
2559
  let m;
3003
- while ((m = pattern.exec(source)) !== null) lastMatch = m;
2560
+ while ((m = pattern.exec(packageSource)) !== null) lastMatch = m;
3004
2561
  if (!lastMatch) return null;
3005
2562
  const fullMatch = lastMatch[0];
3006
2563
  const declaration = lastMatch[1];
3007
2564
  const inner = lastMatch[2];
3008
- const matchStart = lastMatch.index;
2565
+ const matchStart = packageStart + lastMatch.index;
3009
2566
  const lines = inner.split("\n");
3010
2567
  const existingEntries = [];
3011
2568
  for (const line of lines) {
3012
2569
  const trimmed = line.trim();
3013
2570
  if (trimmed && !trimmed.startsWith("//")) existingEntries.push(trimmed.replace(/,$/, ""));
3014
2571
  }
3015
- if (existingEntries.includes(camelName)) return source;
3016
- const commentLines = lines.filter((line) => line.trim().startsWith("//")).map((line) => line.trimEnd());
3017
- const replacement = `${declaration}[${commentLines.length > 0 ? "\n" + commentLines.join("\n") : ""}\n${[...existingEntries, camelName].map((e) => ` ${e},`).join("\n")}\n]`;
3018
- return source.slice(0, matchStart) + replacement + source.slice(matchStart + fullMatch.length);
2572
+ if (existingEntries.includes(exportName)) return source;
2573
+ const commentLines = lines.filter((line) => line.trim().startsWith("//")).map((line) => line.trimEnd());
2574
+ const replacement = `${declaration}[${commentLines.length > 0 ? "\n" + commentLines.join("\n") : ""}\n${[...existingEntries, exportName].map((e) => ` ${e},`).join("\n")}\n]`;
2575
+ return source.slice(0, matchStart) + replacement + source.slice(matchStart + fullMatch.length);
2576
+ }
2577
+ //#endregion
2578
+ //#region src/commands/widget-create.ts
2579
+ const WIDGET_CONFIG_PATH$1 = path.join("src", "widgets.config.ts");
2580
+ const createSubcommand = new Command("create").description("Scaffold a new Remote DOM widget").argument("<name>", "Widget name in kebab-case (e.g., stock-ticker)").option("-c, --category <category>", "Widget category for palette grouping", "components").action(async (name, options) => {
2581
+ const cwd = process.cwd();
2582
+ const packageJsonPath = path.join(cwd, "package.json");
2583
+ const configPath = path.join(cwd, WIDGET_CONFIG_PATH$1);
2584
+ const widgetDir = path.join(cwd, "src", "widgets", name);
2585
+ const validationError = validateWidgetName(name);
2586
+ if (validationError) {
2587
+ console.error(chalk.red(`Error: ${validationError}`));
2588
+ process.exit(1);
2589
+ }
2590
+ if (!await fs.pathExists(packageJsonPath)) {
2591
+ console.error(chalk.red("Error: No package.json found."));
2592
+ console.error(chalk.yellow("Run this command from a Fluid portal project root."));
2593
+ process.exit(1);
2594
+ }
2595
+ if (await fs.pathExists(widgetDir)) {
2596
+ console.error(chalk.red(`Error: Widget directory already exists: src/widgets/${name}`));
2597
+ process.exit(1);
2598
+ }
2599
+ const widgetType = toWidgetType(name);
2600
+ const componentName = toComponentName(widgetType);
2601
+ const camelName = toCamelCase(name);
2602
+ const exportName = camelName.endsWith("Widget") ? camelName : `${camelName}Widget`;
2603
+ const displayName = toDisplayName(name);
2604
+ const category = options.category ?? "components";
2605
+ const importLine = `import { ${exportName} } from "./widgets/${name}";`;
2606
+ try {
2607
+ await fs.ensureDir(widgetDir);
2608
+ await fs.writeFile(path.join(widgetDir, "index.tsx"), createWidgetSource({
2609
+ name,
2610
+ widgetType,
2611
+ componentName,
2612
+ exportName,
2613
+ displayName,
2614
+ category
2615
+ }), "utf-8");
2616
+ if (await fs.pathExists(configPath)) {
2617
+ const configSource = await fs.readFile(configPath, "utf-8");
2618
+ if (isEmptyWidgetConfig(configSource)) await fs.writeFile(configPath, createWidgetConfigSource(importLine, exportName), "utf-8");
2619
+ else {
2620
+ const withImport = insertImport(configSource, importLine);
2621
+ const updated = withImport ? insertIntoWidgetPackage(withImport, exportName) : null;
2622
+ if (!updated) throw new Error(`${WIDGET_CONFIG_PATH$1} must export the starter widgetPackages placeholder or contain a defineWidgetPackage({ widgets: [...] }) declaration.`);
2623
+ await fs.writeFile(configPath, updated, "utf-8");
2624
+ }
2625
+ } else await fs.writeFile(configPath, createWidgetConfigSource(importLine, exportName), "utf-8");
2626
+ } catch (error) {
2627
+ await fs.remove(widgetDir).catch(() => {});
2628
+ console.error(chalk.red("Error: Failed to scaffold Remote DOM widget."));
2629
+ console.error(error);
2630
+ process.exit(1);
2631
+ }
2632
+ console.log();
2633
+ console.log(chalk.green("Created widget:") + ` src/widgets/${name}/index.tsx`);
2634
+ console.log(chalk.green("Registered") + ` in ${chalk.cyan(WIDGET_CONFIG_PATH$1)}`);
2635
+ console.log();
2636
+ console.log(chalk.yellow("Next steps:"));
2637
+ console.log(` 1. Edit ${chalk.cyan(`src/widgets/${name}/index.tsx`)}`);
2638
+ console.log(` 2. Run ${chalk.cyan("fluid portal deploy --dry-run")} to verify the package`);
2639
+ });
2640
+ function createWidgetSource(options) {
2641
+ return `import { defineWidget } from "@fluid-app/portal-sdk/widgets/worker";
2642
+
2643
+ interface ${options.widgetType}Props {
2644
+ readonly title?: string;
2645
+ }
2646
+
2647
+ function ${options.componentName}({ title = ${JSON.stringify(options.displayName)} }: ${options.widgetType}Props) {
2648
+ return (
2649
+ <section>
2650
+ <h3>{title}</h3>
2651
+ <p>Edit this widget in src/widgets/${options.name}/index.tsx</p>
2652
+ </section>
2653
+ );
2654
+ }
2655
+
2656
+ export const ${options.exportName} = defineWidget({
2657
+ name: ${JSON.stringify(options.componentName)},
2658
+ component: ${options.componentName},
2659
+ displayName: ${JSON.stringify(options.displayName)},
2660
+ description: ${JSON.stringify(`A custom ${options.displayName.toLowerCase()} widget`)},
2661
+ icon: "puzzle-piece",
2662
+ category: ${JSON.stringify(options.category)},
2663
+ propertySchema: {
2664
+ fields: [{ key: "title", label: "Title", type: "text" }],
2665
+ },
2666
+ defaultProps: {
2667
+ title: ${JSON.stringify(options.displayName)},
2668
+ },
2669
+ });
2670
+ `;
2671
+ }
2672
+ function isEmptyWidgetConfig(source) {
2673
+ return /export\s+const\s+widgetPackages\s*=\s*\[\]\s+as\s+const\s*;/u.test(source);
2674
+ }
2675
+ function createWidgetConfigSource(importLine, exportName) {
2676
+ return `import { defineWidgetPackage } from "@fluid-app/portal-sdk/widgets/worker";
2677
+ ${importLine}
2678
+
2679
+ export const widgetPackage = defineWidgetPackage({
2680
+ scope: "company",
2681
+ packageStableId: "portal-widgets",
2682
+ version: "0.0.0-dev",
2683
+ widgets: [${exportName}],
2684
+ });
2685
+ `;
2686
+ }
2687
+ const widgetCommand = new Command("widget").description("Manage Remote DOM portal widgets").addCommand(createSubcommand);
2688
+ //#endregion
2689
+ //#region src/utils/widget-package-config.ts
2690
+ const WIDGET_CONFIG_PATH = "src/widgets.config.ts";
2691
+ const SOURCE_PACKAGE_EXTRACT_FILENAME = "extract-widget-packages.ts";
2692
+ const SOURCE_PACKAGE_OUTPUT_FILENAME = "source-widget-packages.json";
2693
+ const SOURCE_PACKAGE_OUTPUT_SENTINEL = "fluid-widget-source-packages:v1";
2694
+ const TSX_CLI_PATH = createRequire(import.meta.url).resolve("tsx/cli");
2695
+ async function resolvePortalWidgetSourceConfig(projectDir) {
2696
+ const candidate = path.join(projectDir, WIDGET_CONFIG_PATH);
2697
+ return await fs.pathExists(candidate) ? {
2698
+ path: candidate,
2699
+ relativePath: WIDGET_CONFIG_PATH
2700
+ } : void 0;
2701
+ }
2702
+ async function loadSourceWidgetPackages(projectDir) {
2703
+ const config = await resolvePortalWidgetSourceConfig(projectDir);
2704
+ if (!config) return success([]);
2705
+ let tempDir;
2706
+ try {
2707
+ tempDir = await createTempDirectory(projectDir);
2708
+ const extractFile = path.join(tempDir, SOURCE_PACKAGE_EXTRACT_FILENAME);
2709
+ const outputFile = path.join(tempDir, SOURCE_PACKAGE_OUTPUT_FILENAME);
2710
+ await fs.writeFile(extractFile, createSourcePackageExtractorScript({
2711
+ projectDir,
2712
+ configPath: config.path
2713
+ }), {
2714
+ encoding: "utf-8",
2715
+ flag: "wx"
2716
+ });
2717
+ await execa(process.execPath, [
2718
+ TSX_CLI_PATH,
2719
+ extractFile,
2720
+ outputFile
2721
+ ], {
2722
+ cwd: projectDir,
2723
+ stdio: "pipe",
2724
+ env: {
2725
+ ...process.env,
2726
+ NODE_ENV: "production"
2727
+ }
2728
+ });
2729
+ const outputResult = await readSourcePackageExtractorOutput(outputFile);
2730
+ if (!outputResult.success) return outputResult;
2731
+ const parsed = outputResult.value;
2732
+ if (parsed.length === 0) return success([]);
2733
+ return success(parsed);
2734
+ } catch (err) {
2735
+ const error = err;
2736
+ return failure({
2737
+ code: "CONFIG_LOAD_FAILED",
2738
+ message: `Failed to load widget packages from ${config.relativePath}`,
2739
+ details: error.stderr ?? error.message ?? String(err)
2740
+ });
2741
+ } finally {
2742
+ if (tempDir) await fs.remove(tempDir).catch(() => {});
2743
+ }
3019
2744
  }
3020
- //#endregion
3021
- //#region src/commands/widget-create.ts
3022
- const createSubcommand = new Command("create").description("Scaffold a new custom widget").argument("<name>", "Widget name in kebab-case (e.g., stock-ticker)").option("-c, --category <category>", "Widget category for palette grouping", "components").action(async (name, options) => {
3023
- const cwd = process.cwd();
3024
- const widgetDir = path.join(cwd, "src", "widgets", name);
3025
- const validationError = validateWidgetName(name);
3026
- if (validationError) {
3027
- console.error(chalk.red(`Error: ${validationError}`));
3028
- process.exit(1);
3029
- }
3030
- if (!fs.existsSync(path.join(cwd, "src", "portal.config.ts"))) {
3031
- console.error(chalk.red("Error: No src/portal.config.ts found."));
3032
- console.error(chalk.yellow("Run this command from a Fluid portal project root."));
3033
- process.exit(1);
2745
+ async function createTempDirectory(projectDir) {
2746
+ const projectTmpDir = path.join(projectDir, ".fluid", "tmp");
2747
+ try {
2748
+ await fs.ensureDir(projectTmpDir);
2749
+ return await fs.mkdtemp(path.join(projectTmpDir, "widget-packages-"));
2750
+ } catch (err) {
2751
+ const message = err instanceof Error ? err.message : String(err);
2752
+ throw new Error(`Unable to create project-local temporary directory at ${projectTmpDir}: ${message}`);
3034
2753
  }
3035
- if (fs.existsSync(widgetDir)) {
3036
- console.error(chalk.red(`Error: Widget directory already exists: src/widgets/${name}`));
3037
- process.exit(1);
2754
+ }
2755
+ async function readSourcePackageExtractorOutput(outputFile) {
2756
+ let output;
2757
+ try {
2758
+ output = await fs.readFile(outputFile, "utf-8");
2759
+ } catch (err) {
2760
+ return failure({
2761
+ code: "CONFIG_LOAD_FAILED",
2762
+ message: "Widget package extractor did not write an output file",
2763
+ details: err instanceof Error ? err.message : String(err)
2764
+ });
3038
2765
  }
3039
- const widgetType = toWidgetType(name);
3040
- const componentName = toComponentName(widgetType);
3041
- const displayName = toDisplayName(name);
3042
- const category = options.category ?? "components";
2766
+ let parsed;
3043
2767
  try {
3044
- await fs.ensureDir(widgetDir);
3045
- await fs.writeFile(path.join(widgetDir, "component.tsx"), `interface ${widgetType}Props {
3046
- title?: string;
2768
+ parsed = JSON.parse(output);
2769
+ } catch {
2770
+ return failure({
2771
+ code: "INVALID_FORMAT",
2772
+ message: "Failed to parse widget package output file as JSON",
2773
+ details: `Output was: ${output.slice(0, 200)}`
2774
+ });
2775
+ }
2776
+ if (!isRecord$4(parsed) || parsed.sentinel !== SOURCE_PACKAGE_OUTPUT_SENTINEL) return failure({
2777
+ code: "INVALID_FORMAT",
2778
+ message: "Widget package extractor output file had an invalid sentinel"
2779
+ });
2780
+ if (!Array.isArray(parsed.data)) return failure({
2781
+ code: "INVALID_FORMAT",
2782
+ message: "Extracted widget package output is not an array",
2783
+ details: `Expected an array, got: ${typeof parsed.data}`
2784
+ });
2785
+ return success(parsed.data);
3047
2786
  }
2787
+ function createSourcePackageExtractorScript(options) {
2788
+ return `
2789
+ import fs from "node:fs/promises";
2790
+ import path from "node:path";
2791
+ import { createServer, normalizePath } from "vite";
3048
2792
 
3049
- export function ${componentName}({ title = "${displayName}" }: ${widgetType}Props) {
3050
- return (
3051
- <div className="p-4">
3052
- <h3 className="text-lg font-semibold">{title}</h3>
3053
- <p className="text-sm text-gray-500">
3054
- Edit this widget in src/widgets/${name}/component.tsx
3055
- </p>
3056
- </div>
3057
- );
2793
+ const SOURCE_PACKAGE_MARKER = "__fluidSourceWidgetPackage";
2794
+ const OUTPUT_SENTINEL = ${JSON.stringify(SOURCE_PACKAGE_OUTPUT_SENTINEL)};
2795
+ const projectRoot = ${JSON.stringify(options.projectDir)};
2796
+ const widgetConfigPath = ${JSON.stringify(options.configPath)};
2797
+ const outputPath = process.argv[2];
2798
+ if (!outputPath) throw new Error("Missing widget package extractor output path.");
2799
+
2800
+ function toViteModuleId(modulePath) {
2801
+ const relativePath = path.relative(projectRoot, modulePath);
2802
+ if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
2803
+ return "/" + normalizePath(relativePath);
2804
+ }
2805
+ return normalizePath(modulePath);
3058
2806
  }
3059
- `);
3060
- await fs.writeFile(path.join(widgetDir, "manifest.ts"), `import type { WidgetManifest } from "@fluid-app/portal-sdk";
3061
- import { ${componentName} } from "./component";
3062
2807
 
3063
- export const manifest: WidgetManifest = {
3064
- manifestVersion: 1,
3065
- type: "${widgetType}",
3066
- component: ${componentName},
3067
- displayName: "${displayName}",
3068
- description: "A custom ${displayName.toLowerCase()} widget",
3069
- icon: "puzzle-piece",
3070
- category: "${category}",
3071
- propertySchema: {
3072
- widgetType: "${widgetType}",
3073
- displayName: "${displayName}",
3074
- fields: [{ key: "title", label: "Title", type: "text" }],
2808
+ const server = await createServer({
2809
+ root: projectRoot,
2810
+ mode: "production",
2811
+ server: { middlewareMode: true },
2812
+ appType: "custom",
2813
+ logLevel: "error",
2814
+ clearScreen: false,
2815
+ resolve: {
2816
+ conditions: ["module", "browser", "development|production"],
3075
2817
  },
3076
- defaultProps: {
3077
- title: "${displayName}",
2818
+ ssr: {
2819
+ noExternal: ["@fluid-app/portal-sdk"],
2820
+ resolve: {
2821
+ conditions: ["module", "node", "development|production"],
2822
+ },
3078
2823
  },
3079
- };
3080
- `);
3081
- await fs.writeFile(path.join(widgetDir, "index.ts"), `export { ${componentName} } from "./component";
3082
- export { manifest } from "./manifest";
3083
- `);
3084
- } catch (err) {
3085
- await fs.remove(widgetDir).catch(() => {});
3086
- console.error(chalk.red("Error: Failed to scaffold widget files."));
3087
- console.error(err);
3088
- process.exit(1);
3089
- }
3090
- const configPath = path.join(cwd, "src", "portal.config.ts");
3091
- const camelName = toCamelCase(name);
3092
- const importLine = `import { manifest as ${camelName} } from "./widgets/${name}";`;
3093
- const configSource = await fs.readFile(configPath, "utf-8");
3094
- const withImport = insertImport(configSource, importLine);
3095
- if (withImport === null) {
3096
- console.warn(chalk.yellow("Warning: Could not find import statements in portal.config.ts. Add the import manually:"));
3097
- console.warn(chalk.cyan(` ${importLine}`));
3098
- }
3099
- let updated = insertIntoCustomWidgets(withImport ?? configSource, camelName);
3100
- if (updated === null) if (withImport === null) {
3101
- console.warn(chalk.yellow("Warning: Could not register widget in portal.config.ts. Add manually:"));
3102
- console.warn(chalk.cyan(` ${importLine}`));
3103
- console.warn(chalk.cyan(` customWidgets: [..., ${camelName}]`));
3104
- } else updated = withImport.trimEnd() + `\n\nexport const customWidgets = [${camelName}];\n`;
3105
- if (updated !== null) await fs.writeFile(configPath, updated, "utf-8");
3106
- else if (withImport !== null) await fs.writeFile(configPath, withImport, "utf-8");
3107
- console.log();
3108
- console.log(chalk.green("Created widget:") + ` src/widgets/${name}/`);
3109
- console.log(chalk.gray(" component.tsx — React component"));
3110
- console.log(chalk.gray(" manifest.ts — WidgetManifest"));
3111
- console.log(chalk.gray(" index.ts — Re-exports"));
3112
- console.log();
3113
- if (updated !== null) {
3114
- console.log(chalk.green("Registered") + ` in ${chalk.cyan("src/portal.config.ts")}`);
3115
- console.log();
3116
- }
3117
- console.log(chalk.yellow("Next steps:"));
3118
- console.log(` 1. Edit the component in ${chalk.cyan(`src/widgets/${name}/component.tsx`)}`);
3119
- console.log(` 2. Customize the manifest fields in ${chalk.cyan(`src/widgets/${name}/manifest.ts`)}`);
3120
- console.log(` 3. Run ${chalk.cyan("fluid portal dev")} to preview in the builder`);
3121
2824
  });
3122
- const widgetCommand = new Command("widget").description("Manage custom portal widgets").addCommand(createSubcommand);
2825
+
2826
+ try {
2827
+ const widgetConfig = await server.ssrLoadModule(toViteModuleId(widgetConfigPath));
2828
+
2829
+ function isSourceWidgetPackage(value) {
2830
+ return Boolean(value && typeof value === "object" && value[SOURCE_PACKAGE_MARKER] === true);
2831
+ }
2832
+
2833
+ function collectSourceWidgetPackages(mod) {
2834
+ const candidates = [
2835
+ mod.widgetPackage,
2836
+ ...(Array.isArray(mod.widgetPackages) ? mod.widgetPackages : []),
2837
+ mod.default,
2838
+ ];
2839
+ const byPackageId = new Map();
2840
+ for (const candidate of candidates) {
2841
+ if (!isSourceWidgetPackage(candidate)) continue;
2842
+ if (!byPackageId.has(candidate.packageId)) byPackageId.set(candidate.packageId, candidate);
2843
+ }
2844
+ return Array.from(byPackageId.values());
2845
+ }
2846
+
2847
+ function serializeWidget(widget) {
2848
+ if (!widget || typeof widget !== "object" || Array.isArray(widget)) {
2849
+ return widget;
2850
+ }
2851
+ const { component, ...metadata } = widget;
2852
+ return metadata;
2853
+ }
2854
+
2855
+ function serializePackage(sourcePackage) {
2856
+ return {
2857
+ manifestVersion: sourcePackage.manifestVersion,
2858
+ scope: sourcePackage.scope,
2859
+ packageStableId: sourcePackage.packageStableId,
2860
+ packageId: sourcePackage.packageId,
2861
+ packageType: sourcePackage.packageType,
2862
+ version: sourcePackage.version,
2863
+ cssUrls: sourcePackage.cssUrls,
2864
+ widgets: Array.isArray(sourcePackage.widgets)
2865
+ ? sourcePackage.widgets.map(serializeWidget)
2866
+ : sourcePackage.widgets,
2867
+ };
2868
+ }
2869
+
2870
+ await fs.writeFile(
2871
+ outputPath,
2872
+ JSON.stringify({
2873
+ sentinel: OUTPUT_SENTINEL,
2874
+ data: collectSourceWidgetPackages(widgetConfig).map(serializePackage),
2875
+ }),
2876
+ "utf-8",
2877
+ );
2878
+ } finally {
2879
+ await server.close();
2880
+ }
2881
+ `;
2882
+ }
2883
+ function isRecord$4(value) {
2884
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2885
+ }
3123
2886
  //#endregion
3124
2887
  //#region src/utils/widget-package-artifacts.ts
3125
2888
  const WIDGET_PACKAGE_BUILDER_VERSION = "1";
@@ -3240,7 +3003,7 @@ const DEFAULT_MIN_SDK_VERSION = "0.0.0";
3240
3003
  function validateSingleSourceWidgetPackage(sourcePackages, options = {}) {
3241
3004
  if (sourcePackages.length === 0) return validationFailure({
3242
3005
  code: "NO_SOURCE_PACKAGE",
3243
- message: "No defineWidgetPackage source package was found. Export widgetPackage, widgetPackages, or a default widget package from src/widgets.config.ts, src/portal.config.ts, or portal.config.ts."
3006
+ message: "No defineWidgetPackage source package was found. Export widgetPackage, widgetPackages, or a default widget package from src/widgets.config.ts."
3244
3007
  });
3245
3008
  if (sourcePackages.length > 1) return validationFailure({
3246
3009
  code: "MULTIPLE_SOURCE_PACKAGES",
@@ -3342,22 +3105,19 @@ function validateSingleSourceWidgetPackage(sourcePackages, options = {}) {
3342
3105
  errors: []
3343
3106
  };
3344
3107
  }
3345
- function buildWidgetPackageDescriptor(validated, options = {}) {
3346
- const remoteEntryUrl = options.remoteEntryUrl ?? "widget.js";
3347
- validateRuntimeDescriptorUrl(remoteEntryUrl, "remoteEntryUrl");
3108
+ function buildWidgetPackagePublishManifest(validated, options = {}) {
3348
3109
  return {
3349
3110
  manifestVersion: WIDGET_PACKAGE_MANIFEST_VERSION,
3350
- packageId: getWidgetPackageDescriptorPackageId(validated),
3351
- packageType: validated.owner,
3111
+ runtime: "remote-dom",
3112
+ packageId: validated.packageId,
3113
+ source: validated.owner,
3352
3114
  version: validated.version,
3353
- remoteEntryUrl,
3115
+ workerEntryUrl: toWidgetPackageArtifactPath("widget.js"),
3354
3116
  cssUrls: mergeCssUrls(validated.sourcePackage.cssUrls, options.cssUrls),
3355
- widgets: validated.widgets
3117
+ assetUrls: [],
3118
+ widgets: validated.widgets.map(({ name: _name, ...widget }) => widget)
3356
3119
  };
3357
3120
  }
3358
- function getWidgetPackageDescriptorPackageId(validated) {
3359
- return validated.owner === "droplet" ? validated.packageKey : validated.packageId;
3360
- }
3361
3121
  function sourceWidgetToDescriptor(widget, type) {
3362
3122
  const displayName = widget.displayName ?? widget.name;
3363
3123
  return {
@@ -3371,9 +3131,16 @@ function sourceWidgetToDescriptor(widget, type) {
3371
3131
  defaultProps: widget.defaultProps ?? {},
3372
3132
  container: normalizeContainer(widget.container),
3373
3133
  minSdkVersion: widget.minSdkVersion ?? DEFAULT_MIN_SDK_VERSION,
3374
- resizable: normalizeResizable(widget.resizable)
3134
+ resizable: normalizeResizable(widget.resizable),
3135
+ capabilities: normalizeCapabilities(widget.capabilities ?? [])
3375
3136
  };
3376
3137
  }
3138
+ function normalizeCapabilities(capabilities) {
3139
+ return capabilities.map((capability) => ({
3140
+ name: capability.name,
3141
+ version: capability.version ?? "1"
3142
+ }));
3143
+ }
3377
3144
  function validationFailure(error) {
3378
3145
  return {
3379
3146
  success: false,
@@ -3426,8 +3193,9 @@ function validateSourcePackageShape(value) {
3426
3193
  validateOptionalStringField(widget, "icon", `${widgetPath}.icon`, errors, { requireNonEmpty: true });
3427
3194
  validateOptionalStringField(widget, "category", `${widgetPath}.category`, errors, { requireNonEmpty: true });
3428
3195
  validateOptionalStringField(widget, "minSdkVersion", `${widgetPath}.minSdkVersion`, errors, { requireNonEmpty: true });
3429
- validateOptionalRecordField(widget, "propertySchema", `${widgetPath}.propertySchema`, errors);
3430
- validateOptionalRecordField(widget, "defaultProps", `${widgetPath}.defaultProps`, errors);
3196
+ validateOptionalJsonRecordField(widget, "propertySchema", `${widgetPath}.propertySchema`, errors);
3197
+ validateOptionalJsonRecordField(widget, "defaultProps", `${widgetPath}.defaultProps`, errors);
3198
+ validateOptionalCapabilities(widget, "capabilities", `${widgetPath}.capabilities`, errors);
3431
3199
  });
3432
3200
  return errors;
3433
3201
  }
@@ -3473,85 +3241,67 @@ function validateCssUrl(value, cssUrlPath, errors) {
3473
3241
  message: "Widget package cssUrls entries must be top-level generated CSS artifact filenames matching [A-Za-z0-9._~-]+.css. Self-host external CSS or fonts via @font-face in widget CSS so the build can isolate them."
3474
3242
  });
3475
3243
  }
3476
- function validateRuntimeDescriptorUrl(value, urlPath) {
3477
- if (value.trim().length === 0) throw new Error(`${urlPath} must be a non-empty string.`);
3478
- if (!isValidRuntimeDescriptorUrl(value)) throw new Error(`${urlPath} must be an https URL, trusted local http URL, or relative URL.`);
3479
- }
3480
- function isValidRuntimeDescriptorUrl(value) {
3481
- if (value.trim() !== value || containsUnsafeUrlCharacter(value)) return false;
3482
- if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.exec(value)) {
3483
- let parsedUrl;
3484
- try {
3485
- parsedUrl = new URL(value);
3486
- } catch {
3487
- return false;
3488
- }
3489
- return parsedUrl.protocol === "https:" || isTrustedLocalHttpUrl(parsedUrl, value);
3490
- }
3491
- if (value.slice(0, 2) === "//") return false;
3492
- try {
3493
- new URL(value, "http://localhost");
3494
- return true;
3495
- } catch {
3496
- return false;
3497
- }
3498
- }
3499
- function isTrustedLocalHttpUrl(url, rawUrl) {
3500
- if (url.protocol !== "http:") return false;
3501
- const hostname = extractRawHostname(rawUrl).toLowerCase();
3502
- return hostname === "localhost" || hostname === "[::1]" || hostname === "::1" || isLoopbackIpv4Hostname(hostname);
3503
- }
3504
- function extractRawHostname(rawUrl) {
3505
- const authorityStart = rawUrl.indexOf("://") + 3;
3506
- const authorityEnd = findAuthorityEnd(rawUrl, authorityStart);
3507
- const authority = rawUrl.slice(authorityStart, authorityEnd);
3508
- const hostAndPort = authority.slice(authority.lastIndexOf("@") + 1);
3509
- if (hostAndPort.charAt(0) === "[") {
3510
- const bracketEnd = hostAndPort.indexOf("]");
3511
- return bracketEnd === -1 ? hostAndPort : hostAndPort.slice(0, bracketEnd + 1);
3512
- }
3513
- const portStart = hostAndPort.indexOf(":");
3514
- return portStart === -1 ? hostAndPort : hostAndPort.slice(0, portStart);
3515
- }
3516
- function findAuthorityEnd(rawUrl, authorityStart) {
3517
- let authorityEnd = rawUrl.length;
3518
- for (const delimiter of [
3519
- "/",
3520
- "?",
3521
- "#"
3522
- ]) {
3523
- const delimiterIndex = rawUrl.indexOf(delimiter, authorityStart);
3524
- if (delimiterIndex !== -1 && delimiterIndex < authorityEnd) authorityEnd = delimiterIndex;
3525
- }
3526
- return authorityEnd;
3527
- }
3528
- function isLoopbackIpv4Hostname(hostname) {
3529
- const octets = hostname.split(".");
3530
- if (octets.length !== 4 || octets[0] !== "127") return false;
3531
- return octets.every(isBoundedIpv4Octet);
3532
- }
3533
- function isBoundedIpv4Octet(octet) {
3534
- if (!/^\d{1,3}$/.test(octet)) return false;
3535
- if (octet.length > 1 && octet.charAt(0) === "0") return false;
3536
- const value = Number(octet);
3537
- return value >= 0 && value <= 255;
3538
- }
3539
- function containsUnsafeUrlCharacter(value) {
3540
- for (let index = 0; index < value.length; index += 1) {
3541
- const charCode = value.charCodeAt(index);
3542
- if (charCode <= 31 || charCode === 127 || charCode === 92) return true;
3543
- }
3544
- return false;
3545
- }
3546
- function validateOptionalRecordField(record, key, path, errors) {
3244
+ function validateOptionalJsonRecordField(record, key, path, errors) {
3547
3245
  const value = record[key];
3548
- if (value === void 0 || isRecord$3(value)) return;
3246
+ if (value === void 0) return;
3247
+ if (isRecord$3(value) && isJsonValue(value)) return;
3549
3248
  errors.push({
3550
3249
  code: "INVALID_WIDGET_METADATA",
3551
3250
  path,
3552
3251
  message: `${path} must be a JSON object when present.`
3553
3252
  });
3554
3253
  }
3254
+ function validateOptionalCapabilities(record, key, path, errors) {
3255
+ const value = record[key];
3256
+ if (value === void 0) return;
3257
+ if (!Array.isArray(value)) {
3258
+ errors.push({
3259
+ code: "INVALID_WIDGET_METADATA",
3260
+ path,
3261
+ message: `${path} must be an array when present.`
3262
+ });
3263
+ return;
3264
+ }
3265
+ const names = /* @__PURE__ */ new Set();
3266
+ value.forEach((candidate, index) => {
3267
+ const capabilityPath = `${path}[${index}]`;
3268
+ if (!isRecord$3(candidate)) {
3269
+ errors.push({
3270
+ code: "INVALID_WIDGET_METADATA",
3271
+ path: capabilityPath,
3272
+ message: `${capabilityPath} must be a JSON object.`
3273
+ });
3274
+ return;
3275
+ }
3276
+ const name = candidate.name;
3277
+ if (!isCanonicalMetadataString(name)) {
3278
+ errors.push({
3279
+ code: "INVALID_WIDGET_METADATA",
3280
+ path: `${capabilityPath}.name`,
3281
+ message: `${capabilityPath}.name must be a non-empty string without surrounding whitespace or control characters.`
3282
+ });
3283
+ return;
3284
+ }
3285
+ const version = candidate.version;
3286
+ if (version !== void 0 && !isCanonicalMetadataString(version)) errors.push({
3287
+ code: "INVALID_WIDGET_METADATA",
3288
+ path: `${capabilityPath}.version`,
3289
+ message: `${capabilityPath}.version must be a non-empty string without surrounding whitespace or control characters when present.`
3290
+ });
3291
+ if (names.has(name)) {
3292
+ errors.push({
3293
+ code: "INVALID_WIDGET_METADATA",
3294
+ path: `${capabilityPath}.name`,
3295
+ message: `${path} must not contain duplicate capability name ${JSON.stringify(name)}.`
3296
+ });
3297
+ return;
3298
+ }
3299
+ names.add(name);
3300
+ });
3301
+ }
3302
+ function isCanonicalMetadataString(value) {
3303
+ return typeof value === "string" && value.length > 0 && value === value.trim() && !/\p{Cc}/u.test(value);
3304
+ }
3555
3305
  function mergeCssUrls(sourceCssUrls, generatedCssUrls) {
3556
3306
  const generatedCssUrlSet = new Set(generatedCssUrls ?? []);
3557
3307
  generatedCssUrls?.forEach((cssUrl, index) => {
@@ -3562,7 +3312,10 @@ function mergeCssUrls(sourceCssUrls, generatedCssUrls) {
3562
3312
  if (generatedCssUrlSet.has(cssUrl)) return;
3563
3313
  throw new Error(`cssUrls[${index}] URL ${JSON.stringify(cssUrl)} must match a generated top-level CSS artifact. Import the CSS from the widget package bundle or remove it from cssUrls.`);
3564
3314
  });
3565
- return Array.from(new Set([...sourceCssUrls ?? [], ...generatedCssUrls ?? []]));
3315
+ return Array.from(new Set([...sourceCssUrls ?? [], ...generatedCssUrls ?? []])).map(toWidgetPackageArtifactPath);
3316
+ }
3317
+ function toWidgetPackageArtifactPath(value) {
3318
+ return value;
3566
3319
  }
3567
3320
  function validateGeneratedCssArtifactUrl(value, cssUrlPath) {
3568
3321
  if (value.trim().length === 0) throw new Error(`${cssUrlPath} must be a non-empty string.`);
@@ -3639,15 +3392,7 @@ const PROJECT_VITE_CONFIG_CANDIDATES = [
3639
3392
  "vite.config.js",
3640
3393
  "vite.config.mjs"
3641
3394
  ];
3642
- const BLOCKED_FLUID_PROJECT_PLUGIN_NAMES = [
3643
- "fluid-manifest-plugin",
3644
- "fluid-preview-plugin",
3645
- "fluid-builder-preview",
3646
- "fluid-builder-preview-standalone",
3647
- "fluid-portal-dev",
3648
- "fluid-backend-dev"
3649
- ];
3650
- const WIDGET_PACKAGE_QUEUE_GLOBAL = "FluidWidgetPackageQueue";
3395
+ const BLOCKED_FLUID_PROJECT_PLUGIN_NAMES = ["fluid-portal-dev", "fluid-backend-dev"];
3651
3396
  const require = createRequire(import.meta.url);
3652
3397
  function validateSharedWidgetPackagePublishDir(projectDir, publishDir = DEFAULT_PUBLISH_DIR, options = {}) {
3653
3398
  const label = options.optionName ?? "publishDir";
@@ -3686,9 +3431,11 @@ async function buildSharedWidgetPackage(options) {
3686
3431
  try {
3687
3432
  await validateWidgetPackageOutputPaths(options.projectDir, publishDirInput);
3688
3433
  const publishDir = path.resolve(options.projectDir, publishDirInput);
3434
+ const widgetScriptPath = path.join(publishDir, "widget.js");
3689
3435
  await fs.emptyDir(publishDir);
3690
3436
  const tempDir = path.resolve(options.projectDir, TEMP_BUILD_DIR);
3691
3437
  await fs.emptyDir(tempDir);
3438
+ let buildFailure;
3692
3439
  try {
3693
3440
  await buildWidgetScriptBundle({
3694
3441
  projectDir: options.projectDir,
@@ -3696,15 +3443,25 @@ async function buildSharedWidgetPackage(options) {
3696
3443
  publishDir,
3697
3444
  validated: validation.value
3698
3445
  });
3699
- } finally {
3700
- await fs.remove(tempDir).catch(() => {});
3446
+ } catch (error) {
3447
+ buildFailure = { error };
3448
+ }
3449
+ try {
3450
+ await fs.remove(tempDir);
3451
+ } catch {
3452
+ const cleanupFailure = /* @__PURE__ */ new Error("Failed to remove reserved temporary build directory.");
3453
+ if (buildFailure) {
3454
+ const primaryError = buildFailure.error instanceof Error ? buildFailure.error : new Error(String(buildFailure.error));
3455
+ throw new AggregateError([primaryError, cleanupFailure], `${primaryError.message}\n${cleanupFailure.message}`);
3456
+ }
3457
+ throw cleanupFailure;
3701
3458
  }
3459
+ if (buildFailure) throw buildFailure.error;
3702
3460
  const cssUrls = await collectCssUrls(publishDir);
3703
- const descriptor = buildWidgetPackageDescriptor(validation.value, { cssUrls });
3461
+ const descriptor = buildWidgetPackagePublishManifest(validation.value, { cssUrls });
3704
3462
  const manifestContent = stableStringify(descriptor);
3705
3463
  const publishPackageId = descriptor.packageId;
3706
3464
  const manifestPath = path.join(publishDir, "manifest.json");
3707
- const widgetScriptPath = path.join(publishDir, "widget.js");
3708
3465
  const publishManifestPath = path.join(publishDir, "publish-manifest.json");
3709
3466
  await fs.writeFile(manifestPath, manifestContent, "utf-8");
3710
3467
  const artifacts = await collectWidgetPackageArtifacts(publishDir);
@@ -3719,7 +3476,7 @@ async function buildSharedWidgetPackage(options) {
3719
3476
  await fs.writeFile(publishManifestPath, stableStringify(publishManifest), "utf-8");
3720
3477
  return success({
3721
3478
  publishDir,
3722
- packageId: getWidgetPackageDescriptorPackageId(validation.value),
3479
+ packageId: validation.value.packageId,
3723
3480
  version: validation.value.version,
3724
3481
  manifestPath,
3725
3482
  publishManifestPath,
@@ -3736,13 +3493,12 @@ async function buildSharedWidgetPackage(options) {
3736
3493
  }
3737
3494
  }
3738
3495
  function createWidgetPackageEntrySource(options) {
3739
- const runtimePackageId = getWidgetPackageDescriptorPackageId(options.validated);
3740
- return `import * as widgetConfig from ${JSON.stringify(options.configImportPath)};
3741
- import { createElement } from "react";
3496
+ return `import { createElement } from "react";
3497
+ import { startWidgetPackage } from "@fluid-app/portal-sdk/widgets/worker";
3498
+ ${options.cssImportPath ? `import ${JSON.stringify(options.cssImportPath)};` : ""}
3742
3499
 
3500
+ const widgetConfig = await import(${JSON.stringify(options.configImportPath)});
3743
3501
  const SOURCE_PACKAGE_MARKER = "__fluidSourceWidgetPackage";
3744
- const WIDGET_PACKAGE_ID = ${JSON.stringify(runtimePackageId)};
3745
- const WIDGET_PACKAGE_VERSION = ${JSON.stringify(options.validated.version)};
3746
3502
  const descriptors = ${JSON.stringify(options.validated.widgets, null, 2)};
3747
3503
 
3748
3504
  function isSourceWidgetPackage(value) {
@@ -3759,50 +3515,25 @@ function collectSourceWidgetPackages(mod) {
3759
3515
 
3760
3516
  const sourcePackage = collectSourceWidgetPackages(widgetConfig)[0];
3761
3517
  if (!sourcePackage) {
3762
- throw new Error("No Fluid source widget package found while loading widget package bundle.");
3518
+ throw new Error("No Fluid source widget package found while loading widget package worker bundle.");
3763
3519
  }
3764
3520
 
3765
3521
  const componentsByName = new Map(
3766
3522
  sourcePackage.widgets.map((widget) => [widget.name, widget.component]),
3767
3523
  );
3768
3524
 
3769
- function createScopedWidgetComponent(component, descriptor) {
3770
- const wrapperTagName = descriptor.container === "inline" ? "span" : "div";
3771
- const WrappedFluidWidgetComponent = (props) => createElement(
3772
- wrapperTagName,
3773
- {
3774
- "data-fluid-widget-package": WIDGET_PACKAGE_ID,
3775
- "data-fluid-widget-version": WIDGET_PACKAGE_VERSION,
3776
- "data-fluid-widget-name": descriptor.name,
3777
- },
3778
- // PortalContainerProvider is not imported here because standalone widget packages
3779
- // only depend on @fluid-app/portal-sdk, which does not currently expose a
3780
- // public portal-container provider. Radix/ui-primitives portals remain a
3781
- // residual isolation risk until a public widget-safe provider is available.
3782
- createElement(component, props),
3783
- );
3784
- WrappedFluidWidgetComponent.displayName = "FluidWidgetPackage(" + WIDGET_PACKAGE_ID + "/" + descriptor.name + ")";
3785
- return WrappedFluidWidgetComponent;
3786
- }
3787
-
3788
3525
  const widgets = descriptors.map((descriptor) => {
3789
3526
  const component = componentsByName.get(descriptor.name);
3790
- if (typeof component !== "function" && typeof component !== "object") {
3791
- throw new Error("Widget package is missing component for " + descriptor.name + ".");
3527
+ if (typeof component !== "function") {
3528
+ throw new Error("Widget package is missing Remote DOM component for " + descriptor.name + ".");
3792
3529
  }
3793
- return { ...descriptor, component: createScopedWidgetComponent(component, descriptor) };
3530
+ return {
3531
+ ...descriptor,
3532
+ component: (props) => createElement(component, props),
3533
+ };
3794
3534
  });
3795
3535
 
3796
- var _fluidWidgets = globalObject.FluidWidgets;
3797
- if (!_fluidWidgets || typeof _fluidWidgets.registerPackage !== "function") {
3798
- throw new Error("FluidWidgets registry is not installed.");
3799
- }
3800
-
3801
- _fluidWidgets.registerPackage({
3802
- packageId: ${JSON.stringify(runtimePackageId)},
3803
- version: ${JSON.stringify(options.validated.version)},
3804
- widgets,
3805
- });
3536
+ startWidgetPackage({ widgets });
3806
3537
  `;
3807
3538
  }
3808
3539
  async function buildWidgetScriptBundle(options) {
@@ -3810,14 +3541,14 @@ async function buildWidgetScriptBundle(options) {
3810
3541
  if (!sourceConfig) throw new Error("No portal widget source config found.");
3811
3542
  const entryPath = path.join(options.tempDir, "widget-entry.ts");
3812
3543
  const viteConfigPath = path.join(options.tempDir, "vite.config.mjs");
3544
+ const rootStylesPath = path.join(options.projectDir, "styles.css");
3813
3545
  await fs.writeFile(entryPath, createWidgetPackageEntrySource({
3814
3546
  configImportPath: pathToFileURL(sourceConfig.path).href,
3547
+ cssImportPath: await fs.pathExists(rootStylesPath) ? pathToFileURL(rootStylesPath).href : void 0,
3815
3548
  validated: options.validated
3816
3549
  }), "utf-8");
3817
3550
  await fs.writeFile(viteConfigPath, createViteConfigSource({
3818
3551
  entryPath,
3819
- packageId: getWidgetPackageDescriptorPackageId(options.validated),
3820
- packageVersion: options.validated.version,
3821
3552
  publishDir: options.publishDir,
3822
3553
  projectConfigPath: await resolveProjectViteConfigPath(options.projectDir)
3823
3554
  }), "utf-8");
@@ -3832,43 +3563,9 @@ async function buildWidgetScriptBundle(options) {
3832
3563
  stdio: "pipe",
3833
3564
  env: {
3834
3565
  ...process.env,
3835
- NODE_ENV: "production",
3836
- NODE_OPTIONS: withFluidWidgetAuthoringNodeCondition(process.env.NODE_OPTIONS)
3566
+ NODE_ENV: "production"
3837
3567
  }
3838
3568
  });
3839
- const widgetScriptPath = path.join(options.publishDir, "widget.js");
3840
- const widgetScriptContent = await fs.readFile(widgetScriptPath, "utf-8");
3841
- await fs.writeFile(widgetScriptPath, wrapWidgetPackageScript(widgetScriptContent), "utf-8");
3842
- }
3843
- function wrapWidgetPackageScript(widgetScriptContent) {
3844
- return `;(function () {
3845
- var globalObject = window;
3846
- var runWidgetPackage = function fluidWidgetPackageFactory() {
3847
- // Intentionally shadow Rollup's expected FluidShared global with the wrapper-scoped host globals.
3848
- var FluidShared = globalObject.FluidShared;
3849
- if (!FluidShared || !FluidShared.PortalSdk) {
3850
- throw new Error("window.FluidShared.PortalSdk is not installed.");
3851
- }
3852
- ${indentScript(widgetScriptContent, " ")}
3853
- };
3854
- runWidgetPackage.__fluidRequiresPortalSdk = true;
3855
-
3856
- if (globalObject.FluidShared && globalObject.FluidShared.PortalSdk) {
3857
- runWidgetPackage();
3858
- return;
3859
- }
3860
-
3861
- var queue = globalObject.${WIDGET_PACKAGE_QUEUE_GLOBAL};
3862
- if (!Array.isArray(queue)) {
3863
- queue = [];
3864
- globalObject.${WIDGET_PACKAGE_QUEUE_GLOBAL} = queue;
3865
- }
3866
- queue.push(runWidgetPackage);
3867
- })();
3868
- `;
3869
- }
3870
- function indentScript(source, indent) {
3871
- return source.split("\n").map((line) => line.length > 0 ? `${indent}${line}` : line).join("\n");
3872
3569
  }
3873
3570
  function resolveViteCliPath(projectDir) {
3874
3571
  const projectViteCliPath = resolveViteCliPathFromRequire(createRequire(path.join(projectDir, "package.json")));
@@ -3896,6 +3593,9 @@ export default defineConfig(${widgetConfigSource});
3896
3593
 
3897
3594
  const projectConfigPath = ${JSON.stringify(options.projectConfigPath)};
3898
3595
  const widgetConfig = ${widgetConfigSource};
3596
+ const fluidWidgetDefineInvariants = {
3597
+ "process.env.NODE_ENV": JSON.stringify("production"),
3598
+ };
3899
3599
  const blockedFluidPluginNames = new Set(${JSON.stringify(BLOCKED_FLUID_PROJECT_PLUGIN_NAMES)});
3900
3600
 
3901
3601
  function isAllowedProjectPlugin(plugin) {
@@ -3936,30 +3636,35 @@ function sanitizeProjectConfig(config) {
3936
3636
  return sanitized;
3937
3637
  }
3938
3638
 
3639
+ function copyConfigDefineValue(value) {
3640
+ return value && typeof value === "object" && !Array.isArray(value)
3641
+ ? { ...value }
3642
+ : {};
3643
+ }
3644
+
3645
+ function mergeFluidWidgetDefineInvariants(mergedConfig, safeProjectConfig, widgetConfig) {
3646
+ mergedConfig.define = {
3647
+ ...copyConfigDefineValue(safeProjectConfig.define),
3648
+ ...copyConfigDefineValue(mergedConfig.define),
3649
+ ...copyConfigDefineValue(widgetConfig.define),
3650
+ ...fluidWidgetDefineInvariants,
3651
+ };
3652
+ return mergedConfig;
3653
+ }
3654
+
3939
3655
  export default defineConfig(async (configEnv) => {
3940
3656
  const projectConfig = await loadConfigFromFile(configEnv, projectConfigPath);
3941
3657
  const safeProjectConfig = sanitizeProjectConfig(projectConfig?.config ?? {});
3942
- return mergeConfig(safeProjectConfig, widgetConfig);
3658
+ return mergeFluidWidgetDefineInvariants(mergeConfig(safeProjectConfig, widgetConfig), safeProjectConfig, widgetConfig);
3943
3659
  });
3944
3660
  `;
3945
3661
  }
3946
3662
  function createWidgetViteConfigObjectSource(options) {
3947
3663
  const configFileSource = options.disableConfigFileLookup ? " configFile: false,\n" : "";
3948
3664
  return `(() => {
3949
- const widgetPackageId = ${JSON.stringify(options.packageId)};
3950
- const widgetPackageVersion = ${JSON.stringify(options.packageVersion)};
3951
- const widgetPackageScopeSelector = '[data-fluid-widget-package="' + widgetPackageId + '"][data-fluid-widget-version="' + widgetPackageVersion + '"]';
3952
- const widgetPackageGeneratedCssFileName = "widget.css";
3953
- const widgetPackageKeyframePrefix = "fluid-widget-" + encodeCssIdentifier(widgetPackageId) + "-" + encodeCssIdentifier(widgetPackageVersion) + "-";
3954
- const widgetPackageLayerPrefix = widgetPackageKeyframePrefix + "layer-";
3955
- const widgetPackageHostDefaultLayerName = widgetPackageLayerPrefix + "generated-host-defaults";
3956
- const widgetPackageHostDefaultRule = "@layer " + widgetPackageHostDefaultLayerName + " { " + widgetPackageScopeSelector + " { display: contents; } }";
3957
- const widgetPackageContainerPrefix = widgetPackageKeyframePrefix + "container-";
3958
- const widgetPackageCssGlobalNamePrefix = widgetPackageKeyframePrefix + "css-name-";
3959
- const groupingAtRules = new Set(["media", "supports", "container", "document", "starting-style"]);
3960
- const unsafeSelectorPattern = /(^|[\\s>+~,])(:root|html|body)(?=$|[\\s.#:[>+~,)])/i;
3961
- const universalSelectorPattern = /(^|[\\s>+~,])\\*(?=$|[\\s.#:[>+~,)])/g;
3962
- const pseudoElementOnlySelectorPattern = /^::[A-Za-z-]+(?:\\([^)]*\\))?$/;
3665
+ const fluidWidgetDefineInvariants = {
3666
+ "process.env.NODE_ENV": JSON.stringify("production"),
3667
+ };
3963
3668
 
3964
3669
  function createFluidWidgetBuildInvariants() {
3965
3670
  return {
@@ -3967,26 +3672,18 @@ function createWidgetViteConfigObjectSource(options) {
3967
3672
  emptyOutDir: false,
3968
3673
  outDir: ${JSON.stringify(options.publishDir)},
3969
3674
  sourcemap: false,
3675
+ target: "esnext",
3676
+ cssCodeSplit: false,
3970
3677
  lib: {
3971
3678
  entry: ${JSON.stringify(options.entryPath)},
3972
- formats: ["iife"],
3973
- name: "FluidWidgetPackage",
3679
+ formats: ["es"],
3974
3680
  fileName: () => "widget.js",
3681
+ cssFileName: "widget",
3975
3682
  },
3976
3683
  rollupOptions: {
3977
- external: [
3978
- "react",
3979
- "react-dom",
3980
- "react/jsx-runtime",
3981
- "@fluid-app/portal-sdk",
3982
- ],
3983
3684
  output: {
3984
- globals: {
3985
- react: "FluidShared.React",
3986
- "react-dom": "FluidShared.ReactDOM",
3987
- "react/jsx-runtime": "FluidShared.ReactJsxRuntime",
3988
- "@fluid-app/portal-sdk": "FluidShared.PortalSdk",
3989
- },
3685
+ inlineDynamicImports: true,
3686
+ hoistTransitiveImports: false,
3990
3687
  },
3991
3688
  },
3992
3689
  };
@@ -4000,1179 +3697,26 @@ function createWidgetViteConfigObjectSource(options) {
4000
3697
  return { build: createFluidWidgetBuildInvariants() };
4001
3698
  },
4002
3699
  configResolved(config) {
3700
+ config.define = {
3701
+ ...(config.define ?? {}),
3702
+ ...fluidWidgetDefineInvariants,
3703
+ };
4003
3704
  Object.assign(config.build, createFluidWidgetBuildInvariants());
4004
3705
  },
4005
3706
  };
4006
3707
  }
4007
3708
 
4008
- function fluidWidgetCssIsolationPlugin() {
4009
- return {
4010
- name: "fluid-widget-css-isolation",
4011
- enforce: "post",
4012
- generateBundle(_outputOptions, bundle) {
4013
- let hasCssAsset = false;
4014
- for (const asset of Object.values(bundle)) {
4015
- if (!asset || asset.type !== "asset" || !asset.fileName.endsWith(".css")) continue;
4016
- hasCssAsset = true;
4017
- const source = typeof asset.source === "string" ? asset.source : new TextDecoder().decode(asset.source);
4018
- asset.source = isolateWidgetCssAsset(source, asset.fileName);
4019
- }
4020
- if (!hasCssAsset) {
4021
- this.emitFile({
4022
- type: "asset",
4023
- fileName: widgetPackageGeneratedCssFileName,
4024
- source: widgetPackageHostDefaultRule + "\\n",
4025
- });
4026
- }
4027
- },
4028
- };
4029
- }
4030
-
4031
- function isolateWidgetCssAsset(css, assetName) {
4032
- const keyframeNames = collectKeyframeNames(css);
4033
- const fontFamilyNames = collectFontFamilyNames(css);
4034
- const customPropertyNames = collectRegisteredCustomPropertyNames(css);
4035
- const placementSensitivePrefix = readPlacementSensitiveCssPrefix(css, assetName);
4036
- const isolatedCss = isolateCssRange(css, assetName, keyframeNames, fontFamilyNames, customPropertyNames, placementSensitivePrefix.endIndex, css.length).css;
4037
- return placementSensitivePrefix.css + widgetPackageHostDefaultRule + "\\n" + isolatedCss;
4038
- }
4039
-
4040
- function collectKeyframeNames(css) {
4041
- const names = new Map();
4042
- visitCssAtRules(css, (atRuleName, atRule) => {
4043
- if (!isKeyframesAtRule(atRuleName)) return false;
4044
- const match = /^@(?:-[A-Za-z]+-)?keyframes\\s+([^\\s{]+)/i.exec(atRule.prelude);
4045
- const name = match?.[1];
4046
- if (name && !names.has(name)) names.set(name, widgetPackageKeyframePrefix + name);
4047
- return atRule.blockStart !== -1;
4048
- });
4049
- return names;
4050
- }
4051
-
4052
- function collectFontFamilyNames(css) {
4053
- const names = new Map();
4054
- visitCssAtRules(css, (atRuleName, atRule) => {
4055
- if (atRuleName !== "font-face" || atRule.blockStart === -1 || atRule.blockEnd === -1) return false;
4056
- let familyName = "";
4057
- rewriteDeclarationBlock(css.slice(atRule.blockStart + 1, atRule.blockEnd), (propertyName, rawProperty, value) => {
4058
- if (propertyName === "font-family" && !familyName) {
4059
- familyName = normalizeFontFamilyName(value);
4060
- }
4061
- return { property: rawProperty, value };
4062
- });
4063
- if (familyName && !names.has(familyName)) names.set(familyName, widgetPackageKeyframePrefix + familyName);
4064
- return true;
4065
- });
4066
- return names;
4067
- }
4068
-
4069
- function collectRegisteredCustomPropertyNames(css) {
4070
- const names = new Map();
4071
- visitCssAtRules(css, (atRuleName, atRule) => {
4072
- if (atRuleName !== "property") return false;
4073
- const match = /^@property\\s+(--[A-Za-z0-9_-]+)/i.exec(atRule.prelude);
4074
- const name = match?.[1];
4075
- if (name && !names.has(name)) names.set(name, "--" + widgetPackageKeyframePrefix + name.slice(2));
4076
- return atRule.blockStart !== -1;
4077
- });
4078
- return names;
4079
- }
4080
-
4081
- function visitCssAtRules(css, visitAtRule) {
4082
- let index = 0;
4083
- while (index < css.length) {
4084
- if (cssStartsWithComment(css, index)) {
4085
- const commentEnd = css.indexOf("*/", index + 2);
4086
- index = commentEnd === -1 ? css.length : commentEnd + 2;
4087
- continue;
4088
- }
4089
- const char = css[index];
4090
- if (char === '"' || char === "'") {
4091
- index = findCssStringEnd(css, index);
4092
- continue;
4093
- }
4094
- if (char !== "@") {
4095
- index += 1;
4096
- continue;
4097
- }
4098
- const atRule = readRulePrelude(css, index, css.length);
4099
- const blockEnd = atRule.blockStart === -1 ? -1 : findMatchingBrace(css, atRule.blockStart, css.length);
4100
- const shouldSkipBlock = visitAtRule(readAtRuleName(atRule.prelude), { ...atRule, blockEnd });
4101
- index = shouldSkipBlock && blockEnd !== -1 ? blockEnd + 1 : index + 1;
4102
- }
4103
- }
4104
-
4105
- function isolateCssRange(css, assetName, keyframeNames, fontFamilyNames, customPropertyNames, startIndex, endIndex) {
4106
- let output = "";
4107
- let index = startIndex;
4108
- while (index < endIndex) {
4109
- if (css.startsWith("/*", index)) {
4110
- const commentEnd = css.indexOf("*/", index + 2);
4111
- const nextIndex = commentEnd === -1 ? endIndex : commentEnd + 2;
4112
- output += css.slice(index, nextIndex);
4113
- index = nextIndex;
4114
- continue;
4115
- }
4116
-
4117
- if (/\\s/.test(css[index] ?? "")) {
4118
- output += css[index];
4119
- index += 1;
4120
- continue;
4121
- }
4122
-
4123
- if (css[index] === "@") {
4124
- const atRule = readRulePrelude(css, index, endIndex);
4125
- const atRuleName = readAtRuleName(atRule.prelude);
4126
- if (atRule.blockStart === -1) {
4127
- if (atRuleName === "import") {
4128
- throw new Error('Unsupported @import rule in CSS asset "' + assetName + '" cannot be isolated safely. Self-host external CSS or fonts via @font-face in widget CSS so the build can isolate them.');
4129
- }
4130
- if (atRuleName === "charset" || atRuleName === "namespace") {
4131
- throw new Error('Unsupported @' + atRuleName + ' rule in CSS asset "' + assetName + '" cannot be isolated safely after generated host defaults. Move @' + atRuleName + ' before all non-@charset/@namespace rules.');
4132
- }
4133
- if (atRuleName === "layer") {
4134
- output += rewriteLayerPrelude(atRule.prelude) + css.slice(index + atRule.prelude.length, atRule.end);
4135
- index = atRule.end;
4136
- continue;
4137
- }
4138
- output += css.slice(index, atRule.end);
4139
- index = atRule.end;
4140
- continue;
4141
- }
4142
-
4143
- const blockEnd = findMatchingBrace(css, atRule.blockStart, endIndex);
4144
- if (blockEnd === -1) {
4145
- output += css.slice(index, endIndex);
4146
- index = endIndex;
4147
- continue;
4148
- }
4149
-
4150
- const ruleHeader = css.slice(index, atRule.blockStart + 1);
4151
- if (isKeyframesAtRule(atRuleName)) {
4152
- output += rewriteKeyframesHeader(ruleHeader, keyframeNames) + namespaceKeyframesBlock(css.slice(atRule.blockStart + 1, blockEnd), keyframeNames, fontFamilyNames, customPropertyNames, assetName) + "}";
4153
- } else if (atRuleName === "font-face") {
4154
- output += ruleHeader + namespaceFontFaceBlock(css.slice(atRule.blockStart + 1, blockEnd), fontFamilyNames, customPropertyNames) + "}";
4155
- } else if (atRuleName === "property") {
4156
- output += rewritePropertyHeader(ruleHeader, customPropertyNames) + namespaceCustomPropertyReferences(css.slice(atRule.blockStart + 1, blockEnd), customPropertyNames) + "}";
4157
- } else if (atRuleName === "layer") {
4158
- output += rewriteLayerPrelude(atRule.prelude) + css.slice(index + atRule.prelude.length, atRule.blockStart + 1) + isolateCssRange(css, assetName, keyframeNames, fontFamilyNames, customPropertyNames, atRule.blockStart + 1, blockEnd).css + "}";
4159
- } else if (groupingAtRules.has(atRuleName)) {
4160
- const groupingRuleHeader = atRuleName === "container" ? rewriteContainerAtRuleHeader(ruleHeader) : ruleHeader;
4161
- output += groupingRuleHeader + isolateCssRange(css, assetName, keyframeNames, fontFamilyNames, customPropertyNames, atRule.blockStart + 1, blockEnd).css + "}";
4162
- } else {
4163
- throw new Error('Unsupported @' + atRuleName + ' rule in CSS asset "' + assetName + '" cannot be isolated safely.');
4164
- }
4165
- index = blockEnd + 1;
4166
- continue;
4167
- }
4168
-
4169
- const styleRule = readRulePrelude(css, index, endIndex);
4170
- if (styleRule.blockStart === -1) {
4171
- output += css.slice(index, styleRule.end);
4172
- index = styleRule.end;
4173
- continue;
4174
- }
4175
-
4176
- const styleBlockEnd = findMatchingBrace(css, styleRule.blockStart, endIndex);
4177
- if (styleBlockEnd === -1) {
4178
- output += css.slice(index, endIndex);
4179
- index = endIndex;
4180
- continue;
4181
- }
4182
-
4183
- output += isolateSelectorList(styleRule.prelude, assetName) + "{" + namespaceStyleBlock(css.slice(styleRule.blockStart + 1, styleBlockEnd), keyframeNames, fontFamilyNames, customPropertyNames, assetName) + "}";
4184
- index = styleBlockEnd + 1;
4185
- }
4186
-
4187
- return { css: output, index };
4188
- }
4189
-
4190
- function readPlacementSensitiveCssPrefix(css, assetName) {
4191
- let output = "";
4192
- let index = 0;
4193
- let hasNamespace = false;
4194
- while (index < css.length) {
4195
- const leadingStart = index;
4196
- index = skipCssWhitespaceAndComments(css, index);
4197
- const leading = css.slice(leadingStart, index);
4198
- if (css[index] !== "@") {
4199
- return { css: output + leading, endIndex: index };
4200
- }
4201
-
4202
- const atRule = readRulePrelude(css, index, css.length);
4203
- const atRuleName = readAtRuleName(atRule.prelude);
4204
- if (atRuleName !== "charset" && atRuleName !== "namespace") {
4205
- return { css: output + leading, endIndex: index };
4206
- }
4207
- if (atRule.blockStart !== -1) {
4208
- throw new Error('Unsupported @' + atRuleName + ' rule in CSS asset "' + assetName + '" cannot be isolated safely.');
4209
- }
4210
- if (atRuleName === "charset" && hasNamespace) {
4211
- throw new Error('Unsupported @charset rule in CSS asset "' + assetName + '" cannot be isolated safely after @namespace. Move @charset before @namespace.');
4212
- }
4213
- if (atRuleName === "namespace") hasNamespace = true;
4214
- output += leading + css.slice(index, atRule.end);
4215
- index = atRule.end;
4216
- }
4217
- return { css: output, endIndex: index };
4218
- }
4219
-
4220
- function readRulePrelude(css, startIndex, endIndex) {
4221
- let index = startIndex;
4222
- let quote = "";
4223
- let bracketDepth = 0;
4224
- let parenDepth = 0;
4225
- while (index < endIndex) {
4226
- const char = css[index];
4227
- if (quote) {
4228
- if (char === "\\\\") index += 2;
4229
- else {
4230
- if (char === quote) quote = "";
4231
- index += 1;
4232
- }
4233
- continue;
4234
- }
4235
- if (cssStartsWithComment(css, index)) {
4236
- const commentEnd = css.indexOf("*/", index + 2);
4237
- index = commentEnd === -1 ? endIndex : Math.min(commentEnd + 2, endIndex);
4238
- continue;
4239
- }
4240
- if (char === '"' || char === "'") {
4241
- quote = char;
4242
- index += 1;
4243
- continue;
4244
- }
4245
- if (char === "[") bracketDepth += 1;
4246
- else if (char === "]") bracketDepth = Math.max(0, bracketDepth - 1);
4247
- else if (char === "(") parenDepth += 1;
4248
- else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
4249
- else if (bracketDepth === 0 && parenDepth === 0 && char === "{") {
4250
- return { prelude: css.slice(startIndex, index), blockStart: index, end: index + 1 };
4251
- } else if (bracketDepth === 0 && parenDepth === 0 && char === ";") {
4252
- return { prelude: css.slice(startIndex, index), blockStart: -1, end: index + 1 };
4253
- }
4254
- index += 1;
4255
- }
4256
- return { prelude: css.slice(startIndex, endIndex), blockStart: -1, end: endIndex };
4257
- }
4258
-
4259
- function findMatchingBrace(css, openBraceIndex, endIndex) {
4260
- let depth = 1;
4261
- let index = openBraceIndex + 1;
4262
- let quote = "";
4263
- while (index < endIndex) {
4264
- const char = css[index];
4265
- if (quote) {
4266
- if (char === "\\\\") index += 2;
4267
- else {
4268
- if (char === quote) quote = "";
4269
- index += 1;
4270
- }
4271
- continue;
4272
- }
4273
- if (char === '"' || char === "'") {
4274
- quote = char;
4275
- index += 1;
4276
- continue;
4277
- }
4278
- if (css.startsWith("/*", index)) {
4279
- const commentEnd = css.indexOf("*/", index + 2);
4280
- index = commentEnd === -1 ? endIndex : commentEnd + 2;
4281
- continue;
4282
- }
4283
- if (char === "{") depth += 1;
4284
- else if (char === "}") {
4285
- depth -= 1;
4286
- if (depth === 0) return index;
4287
- }
4288
- index += 1;
4289
- }
4290
- return -1;
4291
- }
4292
-
4293
- function readAtRuleName(prelude) {
4294
- const match = /^@([A-Za-z-]+)/.exec(prelude.trim());
4295
- return match?.[1]?.toLowerCase() ?? "";
4296
- }
4297
-
4298
- function isKeyframesAtRule(atRuleName) {
4299
- return atRuleName === "keyframes" || atRuleName.endsWith("-keyframes");
4300
- }
4301
-
4302
- function rewriteKeyframesHeader(header, keyframeNames) {
4303
- return header.replace(/(@(?:-[A-Za-z]+-)?keyframes\\s+)([^\\s{]+)/, (_match, prefix, name) => prefix + (keyframeNames.get(name) ?? name));
4304
- }
4305
-
4306
- function splitSelectorList(selectorList) {
4307
- const selectors = [];
4308
- let current = "";
4309
- let quote = "";
4310
- let bracketDepth = 0;
4311
- let parenDepth = 0;
4312
- for (let index = 0; index < selectorList.length; index += 1) {
4313
- const char = selectorList[index];
4314
- if (quote) {
4315
- current += char;
4316
- if (char === "\\\\") {
4317
- index += 1;
4318
- current += selectorList[index] ?? "";
4319
- } else if (char === quote) quote = "";
4320
- continue;
4321
- }
4322
- if (char === '"' || char === "'") {
4323
- quote = char;
4324
- current += char;
4325
- continue;
4326
- }
4327
- if (cssStartsWithComment(selectorList, index)) {
4328
- const commentEnd = selectorList.indexOf("*/", index + 2);
4329
- const nextIndex = commentEnd === -1 ? selectorList.length : commentEnd + 2;
4330
- current += selectorList.slice(index, nextIndex);
4331
- index = nextIndex - 1;
4332
- continue;
4333
- }
4334
- if (char === "[") bracketDepth += 1;
4335
- else if (char === "]") bracketDepth = Math.max(0, bracketDepth - 1);
4336
- else if (char === "(") parenDepth += 1;
4337
- else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
4338
- if (char === "," && bracketDepth === 0 && parenDepth === 0) {
4339
- selectors.push(current);
4340
- current = "";
4341
- } else current += char;
4342
- }
4343
- selectors.push(current);
4344
- return selectors;
4345
- }
4346
-
4347
- function isolateSelectorList(selectorList, assetName) {
4348
- return splitSelectorList(selectorList).map((selector) => isolateSelector(selector, assetName)).join(",");
4349
- }
4350
-
4351
- function isolateSelector(selector, assetName) {
4352
- const leadingWhitespace = selector.match(/^\\s*/)?.[0] ?? "";
4353
- const trailingWhitespace = selector.match(/\\s*$/)?.[0] ?? "";
4354
- const trimmed = selector.trim();
4355
- assertSafeSelector(trimmed, assetName);
4356
- if (trimmed.includes(":host")) {
4357
- return leadingWhitespace + isolateHostSelector(trimmed, assetName) + trailingWhitespace;
4358
- }
4359
- return leadingWhitespace + widgetPackageScopeSelector + " " + trimmed + trailingWhitespace;
4360
- }
4361
-
4362
- function isolateHostSelector(selector, assetName) {
4363
- if (!selector.startsWith(":host")) {
4364
- throw new Error('Unsupported :host selector "' + selector + '" in CSS asset "' + assetName + '" cannot be isolated safely.');
4365
- }
4366
- const match = /^:host(?:\\(([^()]*)\\))?($|[\\s>+~].*)/.exec(selector);
4367
- if (!match) {
4368
- throw new Error('Unsupported :host selector "' + selector + '" in CSS asset "' + assetName + '" cannot be isolated safely.');
4369
- }
4370
- const hostSelector = (match[1] ?? "").trim();
4371
- const selectorAfterHost = match[2] ?? "";
4372
- const firstSelectorAfterHostIndex = skipCssWhitespaceAndComments(selectorAfterHost, 0);
4373
- const firstSelectorAfterHostChar = selectorAfterHost[firstSelectorAfterHostIndex] ?? "";
4374
- if (hostSelector.includes(",") || hasTopLevelHostArgumentCombinator(hostSelector) || firstSelectorAfterHostChar === "+" || firstSelectorAfterHostChar === "~" || (hostSelector && (unsafeSelectorPattern.test(hostSelector) || hasUnsafeUniversalSelector(hostSelector)))) {
4375
- throw new Error('Unsupported :host selector "' + selector + '" in CSS asset "' + assetName + '" cannot be isolated safely.');
4376
- }
4377
- return widgetPackageScopeSelector + hostSelector + selectorAfterHost;
4378
- }
4379
-
4380
- function hasTopLevelHostArgumentCombinator(value) {
4381
- let quote = "";
4382
- let bracketDepth = 0;
4383
- let hasSelectorBeforeWhitespace = false;
4384
- let index = 0;
4385
- while (index < value.length) {
4386
- const char = value[index];
4387
- if (quote) {
4388
- if (char === "\\\\") index += 2;
4389
- else {
4390
- if (char === quote) quote = "";
4391
- index += 1;
4392
- }
4393
- continue;
4394
- }
4395
- if (char === '"' || char === "'") {
4396
- quote = char;
4397
- index += 1;
4398
- continue;
4399
- }
4400
- if (char === "[") {
4401
- bracketDepth += 1;
4402
- hasSelectorBeforeWhitespace = true;
4403
- } else if (char === "]") {
4404
- bracketDepth = Math.max(0, bracketDepth - 1);
4405
- } else if (bracketDepth === 0 && (char === "+" || char === "~" || char === ">")) {
4406
- return true;
4407
- } else if (bracketDepth === 0 && /\\s/.test(char)) {
4408
- const nextIndex = skipCssWhitespaceAndComments(value, index);
4409
- if (hasSelectorBeforeWhitespace && nextIndex < value.length) return true;
4410
- index = nextIndex;
4411
- continue;
4412
- } else if (bracketDepth === 0 && cssStartsWithComment(value, index)) {
4413
- const nextIndex = skipCssWhitespaceAndComments(value, index);
4414
- if (hasSelectorBeforeWhitespace && nextIndex < value.length) return true;
4415
- index = nextIndex;
4416
- continue;
4417
- } else if (bracketDepth === 0 && !/\\s/.test(char)) {
4418
- hasSelectorBeforeWhitespace = true;
4419
- }
4420
- index += 1;
4421
- }
4422
- return false;
4423
- }
4424
-
4425
- function assertSafeSelector(selector, assetName) {
4426
- if (!selector || unsafeSelectorPattern.test(selector) || hasUnsafeUniversalSelector(selector) || pseudoElementOnlySelectorPattern.test(selector)) {
4427
- throw new Error('Unsafe selector "' + selector + '" in CSS asset "' + assetName + '" cannot be isolated safely.');
4428
- }
4429
- }
4430
-
4431
- function hasUnsafeUniversalSelector(selector) {
4432
- universalSelectorPattern.lastIndex = 0;
4433
- let match;
4434
- while ((match = universalSelectorPattern.exec(selector)) !== null) {
4435
- const universalStart = match.index + match[1].length;
4436
- if (!hasAnchoringSelectorBefore(selector.slice(0, universalStart))) return true;
4437
- }
4438
- return false;
4439
- }
4440
-
4441
- function hasAnchoringSelectorBefore(value) {
4442
- return stripCssComments(value).replace(/[\\s>+~]+/g, "").length > 0;
4443
- }
4444
-
4445
- function namespaceKeyframesBlock(block, keyframeNames, fontFamilyNames, customPropertyNames, assetName) {
4446
- let output = "";
4447
- let index = 0;
4448
- while (index < block.length) {
4449
- if (cssStartsWithComment(block, index)) {
4450
- const commentEnd = block.indexOf("*/", index + 2);
4451
- const nextIndex = commentEnd === -1 ? block.length : commentEnd + 2;
4452
- output += block.slice(index, nextIndex);
4453
- index = nextIndex;
4454
- continue;
4455
- }
4456
- if (/\\s/.test(block[index] ?? "")) {
4457
- output += block[index];
4458
- index += 1;
4459
- continue;
4460
- }
4461
- const keyframeRule = readRulePrelude(block, index, block.length);
4462
- if (keyframeRule.blockStart === -1) {
4463
- output += block.slice(index, keyframeRule.end);
4464
- index = keyframeRule.end;
4465
- continue;
4466
- }
4467
- const keyframeBlockEnd = findMatchingBrace(block, keyframeRule.blockStart, block.length);
4468
- if (keyframeBlockEnd === -1) {
4469
- output += block.slice(index);
4470
- index = block.length;
4471
- continue;
4472
- }
4473
- output += keyframeRule.prelude + "{" + namespaceStyleBlock(block.slice(keyframeRule.blockStart + 1, keyframeBlockEnd), keyframeNames, fontFamilyNames, customPropertyNames, assetName) + "}";
4474
- index = keyframeBlockEnd + 1;
4475
- }
4476
- return output;
4477
- }
4478
-
4479
- function namespaceStyleBlock(block, keyframeNames, fontFamilyNames, customPropertyNames, assetName) {
4480
- assertNoNestedCssBlock(block, assetName);
4481
- return rewriteDeclarationBlock(block, (propertyName, rawProperty, value) => {
4482
- const nextProperty = namespaceCustomPropertyName(rawProperty, propertyName, customPropertyNames);
4483
- let nextValue = value;
4484
- if (propertyName === "animation" || propertyName === "-webkit-animation") {
4485
- nextValue = namespaceAnimationShorthandValue(nextValue, keyframeNames);
4486
- } else if (propertyName === "animation-name" || propertyName === "-webkit-animation-name") {
4487
- nextValue = namespaceAnimationNameValue(nextValue, keyframeNames);
4488
- } else if (propertyName === "font-family") {
4489
- nextValue = namespaceFontFamilyValue(nextValue, fontFamilyNames);
4490
- } else if (propertyName === "font") {
4491
- nextValue = namespaceFontShorthandValue(nextValue, fontFamilyNames, assetName);
4492
- }
4493
- if (propertyName === "container-name") {
4494
- nextValue = namespaceContainerNameValue(nextValue);
4495
- } else if (propertyName === "container") {
4496
- nextValue = namespaceContainerShorthandValue(nextValue);
4497
- }
4498
- if (propertyName === "transition" || propertyName === "transition-property" || propertyName === "will-change") {
4499
- nextValue = namespaceCustomPropertyIdentifierValue(nextValue, customPropertyNames);
4500
- }
4501
- if (propertyName === "view-transition-name" || propertyName === "scroll-timeline-name" || propertyName === "animation-timeline") {
4502
- nextValue = namespaceCssGlobalNameValue(nextValue);
4503
- }
4504
- nextValue = namespaceCustomPropertyReferencesValue(nextValue, customPropertyNames);
4505
- return { property: nextProperty, value: nextValue };
4506
- });
4507
- }
4508
-
4509
- function namespaceFontFaceBlock(block, fontFamilyNames, customPropertyNames) {
4510
- return rewriteDeclarationBlock(block, (propertyName, rawProperty, value) => {
4511
- const nextValue = propertyName === "font-family"
4512
- ? namespaceFontFamilyValue(value, fontFamilyNames)
4513
- : namespaceCustomPropertyReferencesValue(value, customPropertyNames);
4514
- return { property: rawProperty, value: nextValue };
4515
- });
4516
- }
4517
-
4518
- function namespaceAnimationNameValue(value, keyframeNames) {
4519
- return replaceCssValueOutsideProtectedRanges(value, (chunk) => {
4520
- let nextChunk = chunk;
4521
- for (const [name, scopedName] of keyframeNames) {
4522
- nextChunk = nextChunk.replace(new RegExp("(^|[^A-Za-z0-9_-])" + escapeRegExp(name) + "(?=$|[^A-Za-z0-9_-])", "g"), (_match, prefix) => prefix + scopedName);
4523
- }
4524
- return nextChunk;
4525
- });
4526
- }
4527
-
4528
- function namespaceAnimationShorthandValue(value, keyframeNames) {
4529
- if (keyframeNames.size === 0) return value;
4530
- return splitCssCommaList(value).map((item) => rewriteAnimationShorthandItem(item, keyframeNames)).join(",");
4531
- }
4532
-
4533
- function rewriteAnimationShorthandItem(item, keyframeNames) {
4534
- const tokens = readCssValueTokens(item);
4535
- const reservedState = createAnimationShorthandReservedState();
4536
- let output = "";
4537
- let lastIndex = 0;
4538
- for (const token of tokens) {
4539
- const scopedName = keyframeNames.get(token.text);
4540
- const isReservedToken = consumeAnimationShorthandReservedToken(token.text, reservedState);
4541
- if (!scopedName || isReservedToken) continue;
4542
- output += item.slice(lastIndex, token.start) + scopedName;
4543
- lastIndex = token.end;
4544
- }
4545
- return output + item.slice(lastIndex);
4546
- }
4547
-
4548
- function createAnimationShorthandReservedState() {
4549
- return {
4550
- duration: false,
4551
- delay: false,
4552
- timingFunction: false,
4553
- iterationCount: false,
4554
- direction: false,
4555
- fillMode: false,
4556
- playState: false,
4557
- };
4558
- }
4559
-
4560
- function consumeAnimationShorthandReservedToken(token, state) {
4561
- const lower = token.toLowerCase();
4562
- if (isAnimationTimeToken(token)) {
4563
- if (!state.duration) {
4564
- state.duration = true;
4565
- return true;
4566
- }
4567
- if (!state.delay) {
4568
- state.delay = true;
4569
- return true;
4570
- }
4571
- return false;
4572
- }
4573
- if (!state.timingFunction && isAnimationTimingFunctionToken(lower)) {
4574
- state.timingFunction = true;
4575
- return true;
4576
- }
4577
- if (!state.iterationCount && isAnimationIterationCountToken(token, lower)) {
4578
- state.iterationCount = true;
4579
- return true;
4580
- }
4581
- if (!state.direction && animationDirectionKeywords.has(lower)) {
4582
- state.direction = true;
4583
- return true;
4584
- }
4585
- if (!state.fillMode && animationFillModeKeywords.has(lower)) {
4586
- state.fillMode = true;
4587
- return true;
4588
- }
4589
- if (!state.playState && animationPlayStateKeywords.has(lower)) {
4590
- state.playState = true;
4591
- return true;
4592
- }
4593
- if (animationGlobalKeywords.has(lower)) return true;
4594
- return false;
4595
- }
4596
-
4597
- function isAnimationTimeToken(token) {
4598
- return /^-?(?:\\d+|\\d*\\.\\d+)(?:ms|s)$/i.test(token);
4599
- }
4600
-
4601
- function isAnimationTimingFunctionToken(lowerToken) {
4602
- return animationTimingFunctionKeywords.has(lowerToken) || lowerToken.includes("(");
4603
- }
4604
-
4605
- function isAnimationIterationCountToken(token, lowerToken) {
4606
- return lowerToken === "infinite" || /^-?(?:\\d+|\\d*\\.\\d+)$/.test(token);
4607
- }
4608
-
4609
- const animationTimingFunctionKeywords = new Set([
4610
- "ease",
4611
- "ease-in",
4612
- "ease-in-out",
4613
- "ease-out",
4614
- "linear",
4615
- "step-end",
4616
- "step-start",
4617
- ]);
4618
- const animationDirectionKeywords = new Set(["normal", "reverse", "alternate", "alternate-reverse"]);
4619
- const animationFillModeKeywords = new Set(["none", "forwards", "backwards", "both"]);
4620
- const animationPlayStateKeywords = new Set(["running", "paused"]);
4621
- const animationGlobalKeywords = new Set(["inherit", "initial", "revert", "revert-layer", "unset"]);
4622
-
4623
- function namespaceFontFamilyValue(value, fontFamilyNames) {
4624
- if (fontFamilyNames.size === 0) return value;
4625
- const important = splitCssImportantSuffix(value);
4626
- return splitCssCommaList(important.value).map((family) => namespaceSingleFontFamily(family, fontFamilyNames)).join(",") + important.suffix;
4627
- }
4628
-
4629
- function namespaceFontShorthandValue(value, fontFamilyNames, assetName) {
4630
- if (fontFamilyNames.size === 0) return value;
4631
- const familyStart = findFontShorthandFamilyStart(value);
4632
- if (familyStart === -1) {
4633
- if (fontShorthandReferencesScopedFontFamily(value, fontFamilyNames)) {
4634
- throw new Error('Unsupported font shorthand in CSS asset "' + assetName + '" cannot be isolated safely. Use font-family with font-style/font-weight/font-size longhands, or use a supported font shorthand with an explicit font-size before the family list.');
4635
- }
4636
- return value;
4637
- }
4638
- return value.slice(0, familyStart) + namespaceFontFamilyValue(value.slice(familyStart), fontFamilyNames);
4639
- }
4640
-
4641
- function fontShorthandReferencesScopedFontFamily(value, fontFamilyNames) {
4642
- const tokens = readCssValueTokens(value);
4643
- return tokens.some((token) => fontFamilyNames.has(normalizeFontFamilyName(token.text)));
4644
- }
4645
-
4646
- function splitCssImportantSuffix(value) {
4647
- const match = /!\\s*important\\s*$/i.exec(value);
4648
- if (!match) return { value, suffix: "" };
4649
- return {
4650
- value: value.slice(0, match.index),
4651
- suffix: value.slice(match.index),
4652
- };
4653
- }
4654
-
4655
- function namespaceSingleFontFamily(family, fontFamilyNames) {
4656
- const leadingWhitespace = family.match(/^\\s*/)?.[0] ?? "";
4657
- const trailingWhitespace = family.match(/\\s*$/)?.[0] ?? "";
4658
- const trimmed = family.trim();
4659
- const normalized = normalizeFontFamilyName(trimmed);
4660
- const scopedName = fontFamilyNames.get(normalized);
4661
- if (!scopedName) return family;
4662
- const quote = trimmed[0] === '"' || trimmed[0] === "'" ? trimmed[0] : "";
4663
- return leadingWhitespace + (quote ? quote + scopedName + quote : scopedName) + trailingWhitespace;
4664
- }
4665
-
4666
- function normalizeFontFamilyName(value) {
4667
- const trimmed = value.trim();
4668
- if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
4669
- return trimmed.slice(1, -1);
4670
- }
4671
- return trimmed;
4672
- }
4673
-
4674
- function rewritePropertyHeader(header, customPropertyNames) {
4675
- let nextHeader = header;
4676
- for (const [name, scopedName] of customPropertyNames) {
4677
- nextHeader = nextHeader.replace(new RegExp("(@property\\\\s+)" + escapeRegExp(name) + "(?=$|[\\\\s{])", "g"), (_match, prefix) => prefix + scopedName);
4678
- }
4679
- return nextHeader;
4680
- }
4681
-
4682
- function rewriteLayerPrelude(prelude) {
4683
- const match = /^(@layer\\b)([\\s\\S]*)$/i.exec(prelude);
4684
- if (!match) return prelude;
4685
- const layerList = match[2] ?? "";
4686
- if (!layerList.trim()) return prelude;
4687
- return match[1] + splitCssCommaList(layerList).map(namespaceLayerName).join(",");
4688
- }
4689
-
4690
- function namespaceLayerName(layerName) {
4691
- const leadingWhitespace = layerName.match(/^\\s*/)?.[0] ?? "";
4692
- const trailingWhitespace = layerName.match(/\\s*$/)?.[0] ?? "";
4693
- const trimmed = layerName.trim();
4694
- return trimmed ? leadingWhitespace + widgetPackageLayerPrefix + trimmed + trailingWhitespace : layerName;
4695
- }
4696
-
4697
- function rewriteContainerAtRuleHeader(header) {
4698
- if (!header.endsWith("{")) return header;
4699
- return rewriteContainerAtRulePrelude(header.slice(0, -1)) + "{";
4700
- }
4701
-
4702
- function rewriteContainerAtRulePrelude(prelude) {
4703
- const match = /^(@container\\b)([\\s\\S]*)$/i.exec(prelude);
4704
- if (!match) return prelude;
4705
- const condition = match[2] ?? "";
4706
- const leadingWhitespace = condition.match(/^\\s*/)?.[0] ?? "";
4707
- const conditionBody = condition.slice(leadingWhitespace.length);
4708
- if (!conditionBody || conditionBody.startsWith("(") || /^(?:style|scroll-state)\\s*\\(/.test(conditionBody)) {
4709
- return prelude;
4710
- }
4711
-
4712
- const tokens = readCssValueTokens(conditionBody);
4713
- const nameToken = tokens[0];
4714
- if (!nameToken || nameToken.start !== 0 || isContainerQueryConditionKeyword(nameToken.text) || isReservedContainerName(nameToken.text) || nameToken.text.includes("(")) {
4715
- return prelude;
4716
- }
4717
- return match[1] + leadingWhitespace + namespaceContainerName(nameToken.text) + conditionBody.slice(nameToken.end);
4718
- }
4719
-
4720
- function namespaceContainerNameValue(value) {
4721
- return replaceCssValueOutsideProtectedRanges(value, (chunk) => namespaceContainerNamesInChunk(chunk));
4722
- }
4723
-
4724
- function namespaceContainerShorthandValue(value) {
4725
- const slashIndex = findTopLevelSlash(value);
4726
- if (slashIndex !== -1) {
4727
- return namespaceContainerNameValue(value.slice(0, slashIndex)) + value.slice(slashIndex);
4728
- }
4729
-
4730
- const trimmed = stripCssComments(value).trim().toLowerCase();
4731
- if (isReservedContainerName(trimmed) || containerTypeKeywords.has(trimmed)) return value;
4732
- return namespaceContainerNameValue(value);
4733
- }
4734
-
4735
- function namespaceContainerNamesInChunk(chunk) {
4736
- return chunk.replace(/(^|[^A-Za-z0-9_-])([A-Za-z_][A-Za-z0-9_-]*|-[A-Za-z_][A-Za-z0-9_-]*)(?=$|[^A-Za-z0-9_-])/g, (_match, prefix, name) => {
4737
- if (isReservedContainerName(name)) return prefix + name;
4738
- return prefix + namespaceContainerName(name);
4739
- });
4740
- }
4741
-
4742
- function namespaceContainerName(name) {
4743
- return widgetPackageContainerPrefix + name;
4744
- }
4745
-
4746
- function isReservedContainerName(name) {
4747
- return containerNameReservedKeywords.has(name.toLowerCase());
4748
- }
4749
-
4750
- function isContainerQueryConditionKeyword(name) {
4751
- return containerQueryConditionKeywords.has(name.toLowerCase());
4752
- }
4753
-
4754
- const containerTypeKeywords = new Set(["normal", "size", "inline-size", "scroll-state"]);
4755
- const containerQueryConditionKeywords = new Set(["not", "and", "or"]);
4756
- const containerNameReservedKeywords = new Set(["none", "inherit", "initial", "revert", "revert-layer", "unset"]);
4757
-
4758
- function namespaceCustomPropertyName(rawProperty, propertyName, customPropertyNames) {
4759
- const scopedName = customPropertyNames.get(propertyName);
4760
- if (!scopedName) return rawProperty;
4761
- return rawProperty.replace(new RegExp(escapeRegExp(propertyName) + "(?=\\\\s*$)"), scopedName);
4762
- }
4763
-
4764
- function namespaceCustomPropertyReferences(block, customPropertyNames) {
4765
- return rewriteDeclarationBlock(block, (propertyName, rawProperty, value) => ({
4766
- property: rawProperty,
4767
- value: namespaceCustomPropertyReferencesValue(value, customPropertyNames),
4768
- }));
4769
- }
4770
-
4771
- function namespaceCustomPropertyReferencesValue(value, customPropertyNames) {
4772
- if (customPropertyNames.size === 0) return value;
4773
- return replaceCssValueOutsideProtectedRanges(value, (chunk) => {
4774
- let nextChunk = chunk;
4775
- for (const [name, scopedName] of customPropertyNames) {
4776
- nextChunk = nextChunk.replace(new RegExp("(\\\\bvar\\\\(\\\\s*)" + escapeRegExp(name) + "(?=\\\\s*[,)])", "g"), (_match, prefix) => prefix + scopedName);
4777
- }
4778
- return nextChunk;
4779
- });
4780
- }
4781
-
4782
- function namespaceCustomPropertyIdentifierValue(value, customPropertyNames) {
4783
- if (customPropertyNames.size === 0) return value;
4784
- return replaceCssValueOutsideProtectedRanges(value, (chunk) => {
4785
- let nextChunk = chunk;
4786
- for (const [name, scopedName] of customPropertyNames) {
4787
- nextChunk = nextChunk.replace(new RegExp("(^|[^A-Za-z0-9_-])" + escapeRegExp(name) + "(?=$|[^A-Za-z0-9_-])", "g"), (_match, prefix) => prefix + scopedName);
4788
- }
4789
- return nextChunk;
4790
- });
4791
- }
4792
-
4793
- function namespaceCssGlobalNameValue(value) {
4794
- return replaceCssValueOutsideProtectedRanges(value, (chunk) => {
4795
- return chunk.replace(/(^|[^A-Za-z0-9_-])(--[A-Za-z0-9_-]+|[A-Za-z_][A-Za-z0-9_-]*|-[A-Za-z_][A-Za-z0-9_-]*)(?=$|[^A-Za-z0-9_-])/g, (_match, prefix, name, offset, source) => {
4796
- const nextChar = source[offset + prefix.length + name.length] ?? "";
4797
- const beforeName = source.slice(0, offset + prefix.length);
4798
- if (nextChar === "(" || /(?:^|[^A-Za-z0-9_-])var\\s*\\($/i.test(beforeName) || cssGlobalNameReservedKeywords.has(name.toLowerCase())) return prefix + name;
4799
- return prefix + widgetPackageCssGlobalNamePrefix + name;
4800
- });
4801
- });
4802
- }
4803
-
4804
- const cssGlobalNameReservedKeywords = new Set(["auto", "none", "inherit", "initial", "revert", "revert-layer", "unset"]);
4805
-
4806
- function assertNoNestedCssBlock(block, assetName) {
4807
- let index = 0;
4808
- let quote = "";
4809
- let parenDepth = 0;
4810
- while (index < block.length) {
4811
- const char = block[index];
4812
- if (quote) {
4813
- if (char === "\\\\") index += 2;
4814
- else {
4815
- if (char === quote) quote = "";
4816
- index += 1;
4817
- }
4818
- continue;
4819
- }
4820
- if (cssStartsWithComment(block, index)) {
4821
- const commentEnd = block.indexOf("*/", index + 2);
4822
- index = commentEnd === -1 ? block.length : commentEnd + 2;
4823
- continue;
4824
- }
4825
- if (char === '"' || char === "'") {
4826
- quote = char;
4827
- index += 1;
4828
- continue;
4829
- }
4830
- if (char === "(") parenDepth += 1;
4831
- else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
4832
- else if (char === "{" && parenDepth === 0) {
4833
- throw new Error('Unsupported nested CSS rule in CSS asset "' + assetName + '" cannot be isolated safely. Compile nested CSS before building the widget package.');
4834
- }
4835
- index += 1;
4836
- }
4837
- }
4838
-
4839
- function rewriteDeclarationBlock(block, rewriteDeclaration) {
4840
- let output = "";
4841
- let declarationStart = 0;
4842
- let index = 0;
4843
- let quote = "";
4844
- let parenDepth = 0;
4845
- while (index < block.length) {
4846
- const char = block[index];
4847
- if (quote) {
4848
- if (char === "\\\\") index += 2;
4849
- else {
4850
- if (char === quote) quote = "";
4851
- index += 1;
4852
- }
4853
- continue;
4854
- }
4855
- if (cssStartsWithComment(block, index)) {
4856
- const commentEnd = block.indexOf("*/", index + 2);
4857
- index = commentEnd === -1 ? block.length : commentEnd + 2;
4858
- continue;
4859
- }
4860
- if (char === '"' || char === "'") {
4861
- quote = char;
4862
- index += 1;
4863
- continue;
4864
- }
4865
- if (char === "(") parenDepth += 1;
4866
- else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
4867
- else if (char === ";" && parenDepth === 0) {
4868
- output += rewriteDeclarationSegment(block.slice(declarationStart, index), rewriteDeclaration) + ";";
4869
- declarationStart = index + 1;
4870
- }
4871
- index += 1;
4872
- }
4873
- return output + rewriteDeclarationSegment(block.slice(declarationStart), rewriteDeclaration);
4874
- }
4875
-
4876
- function rewriteDeclarationSegment(segment, rewriteDeclaration) {
4877
- const colonIndex = findDeclarationColon(segment);
4878
- if (colonIndex === -1) return segment;
4879
- const rawProperty = segment.slice(0, colonIndex);
4880
- const rawPropertyName = stripCssComments(rawProperty).trim();
4881
- const propertyName = rawPropertyName.startsWith("--") ? rawPropertyName : rawPropertyName.toLowerCase();
4882
- if (!propertyName) return segment;
4883
- const rewritten = rewriteDeclaration(propertyName, rawProperty, segment.slice(colonIndex + 1));
4884
- return rewritten.property + ":" + rewritten.value;
4885
- }
4886
-
4887
- function findDeclarationColon(segment) {
4888
- let index = 0;
4889
- let quote = "";
4890
- let parenDepth = 0;
4891
- while (index < segment.length) {
4892
- const char = segment[index];
4893
- if (quote) {
4894
- if (char === "\\\\") index += 2;
4895
- else {
4896
- if (char === quote) quote = "";
4897
- index += 1;
4898
- }
4899
- continue;
4900
- }
4901
- if (cssStartsWithComment(segment, index)) {
4902
- const commentEnd = segment.indexOf("*/", index + 2);
4903
- index = commentEnd === -1 ? segment.length : commentEnd + 2;
4904
- continue;
4905
- }
4906
- if (char === '"' || char === "'") {
4907
- quote = char;
4908
- index += 1;
4909
- continue;
4910
- }
4911
- if (char === "(") parenDepth += 1;
4912
- else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
4913
- else if (char === ":" && parenDepth === 0) return index;
4914
- index += 1;
4915
- }
4916
- return -1;
4917
- }
4918
-
4919
- function replaceCssValueOutsideProtectedRanges(value, replaceChunk) {
4920
- let output = "";
4921
- let chunkStart = 0;
4922
- let index = 0;
4923
- while (index < value.length) {
4924
- if (cssStartsWithComment(value, index)) {
4925
- output += replaceChunk(value.slice(chunkStart, index));
4926
- const commentEnd = value.indexOf("*/", index + 2);
4927
- const nextIndex = commentEnd === -1 ? value.length : commentEnd + 2;
4928
- output += value.slice(index, nextIndex);
4929
- index = nextIndex;
4930
- chunkStart = index;
4931
- continue;
4932
- }
4933
- const char = value[index];
4934
- if (char === '"' || char === "'") {
4935
- output += replaceChunk(value.slice(chunkStart, index));
4936
- const stringEnd = findCssStringEnd(value, index);
4937
- output += value.slice(index, stringEnd);
4938
- index = stringEnd;
4939
- chunkStart = index;
4940
- continue;
4941
- }
4942
- if (isCssUrlFunctionStart(value, index)) {
4943
- output += replaceChunk(value.slice(chunkStart, index));
4944
- const urlEnd = findCssFunctionEnd(value, value.indexOf("(", index));
4945
- output += value.slice(index, urlEnd);
4946
- index = urlEnd;
4947
- chunkStart = index;
4948
- continue;
4949
- }
4950
- index += 1;
4951
- }
4952
- return output + replaceChunk(value.slice(chunkStart));
4953
- }
4954
-
4955
- function findCssStringEnd(value, quoteIndex) {
4956
- const quote = value[quoteIndex];
4957
- let index = quoteIndex + 1;
4958
- while (index < value.length) {
4959
- const char = value[index];
4960
- if (char === "\\\\") index += 2;
4961
- else {
4962
- index += 1;
4963
- if (char === quote) return index;
4964
- }
4965
- }
4966
- return value.length;
4967
- }
4968
-
4969
- function findCssFunctionEnd(value, openParenIndex) {
4970
- let index = openParenIndex + 1;
4971
- let depth = 1;
4972
- let quote = "";
4973
- while (index < value.length) {
4974
- const char = value[index];
4975
- if (quote) {
4976
- if (char === "\\\\") index += 2;
4977
- else {
4978
- if (char === quote) quote = "";
4979
- index += 1;
4980
- }
4981
- continue;
4982
- }
4983
- if (char === '"' || char === "'") {
4984
- quote = char;
4985
- index += 1;
4986
- continue;
4987
- }
4988
- if (cssStartsWithComment(value, index)) {
4989
- const commentEnd = value.indexOf("*/", index + 2);
4990
- index = commentEnd === -1 ? value.length : commentEnd + 2;
4991
- continue;
4992
- }
4993
- if (char === "(") depth += 1;
4994
- else if (char === ")") {
4995
- depth -= 1;
4996
- if (depth === 0) return index + 1;
4997
- }
4998
- index += 1;
4999
- }
5000
- return value.length;
5001
- }
5002
-
5003
- function findFontShorthandFamilyStart(value) {
5004
- const tokens = readCssValueTokens(value);
5005
- for (let index = 0; index < tokens.length; index += 1) {
5006
- const token = tokens[index];
5007
- if (!token || !isFontSizeToken(token.text)) continue;
5008
- if (findTopLevelSlash(token.text) !== -1) return token.end;
5009
- const slashIndex = skipCssWhitespaceAndComments(value, token.end);
5010
- if (value[slashIndex] !== "/") return token.end;
5011
- const lineHeightStart = skipCssWhitespaceAndComments(value, slashIndex + 1);
5012
- const lineHeightToken = tokens.find((candidate) => candidate.start >= lineHeightStart);
5013
- return lineHeightToken ? lineHeightToken.end : token.end;
5014
- }
5015
- return -1;
5016
- }
5017
-
5018
- function readCssValueTokens(value) {
5019
- const tokens = [];
5020
- let index = 0;
5021
- while (index < value.length) {
5022
- if (/\\s/.test(value[index] ?? "")) {
5023
- index += 1;
5024
- continue;
5025
- }
5026
- if (cssStartsWithComment(value, index)) {
5027
- const commentEnd = value.indexOf("*/", index + 2);
5028
- index = commentEnd === -1 ? value.length : commentEnd + 2;
5029
- continue;
5030
- }
5031
- const start = index;
5032
- const char = value[index];
5033
- if (char === '"' || char === "'") {
5034
- index = findCssStringEnd(value, index);
5035
- } else {
5036
- let parenDepth = 0;
5037
- while (index < value.length) {
5038
- if (cssStartsWithComment(value, index)) {
5039
- if (parenDepth === 0) break;
5040
- const commentEnd = value.indexOf("*/", index + 2);
5041
- index = commentEnd === -1 ? value.length : commentEnd + 2;
5042
- continue;
5043
- }
5044
- const tokenChar = value[index];
5045
- if ((tokenChar === '"' || tokenChar === "'") && parenDepth > 0) {
5046
- index = findCssStringEnd(value, index);
5047
- continue;
5048
- }
5049
- if (tokenChar === "(") parenDepth += 1;
5050
- else if (tokenChar === ")") parenDepth = Math.max(0, parenDepth - 1);
5051
- if (parenDepth === 0 && /\\s/.test(tokenChar ?? "")) break;
5052
- index += 1;
5053
- }
5054
- }
5055
- tokens.push({ start, end: index, text: value.slice(start, index) });
5056
- }
5057
- return tokens;
5058
- }
5059
-
5060
- function isFontSizeToken(token) {
5061
- const slashIndex = findTopLevelSlash(token);
5062
- const sizeToken = slashIndex === -1 ? token : token.slice(0, slashIndex);
5063
- return /^(?:xx-small|x-small|small|medium|large|x-large|xx-large|xxx-large|smaller|larger|math|(?:\\d+|\\d*\\.\\d+)(?:px|em|rem|%|vh|vw|vmin|vmax|ch|ex|cap|ic|lh|rlh|cm|mm|q|in|pc|pt))$/i.test(sizeToken) || isFontSizeFunctionToken(sizeToken);
5064
- }
5065
-
5066
- function isFontSizeFunctionToken(token) {
5067
- return /^(?:calc|clamp|min|max|var|env|abs|sign|round|mod|rem|sin|cos|tan|asin|acos|atan|atan2|pow|sqrt|hypot|log|exp)\\(/i.test(token);
5068
- }
5069
-
5070
- function findTopLevelSlash(value) {
5071
- let index = 0;
5072
- let quote = "";
5073
- let parenDepth = 0;
5074
- while (index < value.length) {
5075
- const char = value[index];
5076
- if (quote) {
5077
- if (char === "\\\\") index += 2;
5078
- else {
5079
- if (char === quote) quote = "";
5080
- index += 1;
5081
- }
5082
- continue;
5083
- }
5084
- if (char === '"' || char === "'") {
5085
- quote = char;
5086
- index += 1;
5087
- continue;
5088
- }
5089
- if (char === "(") parenDepth += 1;
5090
- else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
5091
- else if (char === "/" && parenDepth === 0) return index;
5092
- index += 1;
5093
- }
5094
- return -1;
5095
- }
5096
-
5097
- function skipCssWhitespaceAndComments(value, startIndex) {
5098
- let index = startIndex;
5099
- while (index < value.length) {
5100
- if (/\\s/.test(value[index] ?? "")) {
5101
- index += 1;
5102
- continue;
5103
- }
5104
- if (cssStartsWithComment(value, index)) {
5105
- const commentEnd = value.indexOf("*/", index + 2);
5106
- index = commentEnd === -1 ? value.length : commentEnd + 2;
5107
- continue;
5108
- }
5109
- return index;
5110
- }
5111
- return index;
5112
- }
5113
-
5114
- function cssStartsWithComment(value, index) {
5115
- return value[index] === "/" && value[index + 1] === "*";
5116
- }
5117
-
5118
- function isCssUrlFunctionStart(value, index) {
5119
- return /^url\\s*\\(/i.test(value.slice(index)) && !/[A-Za-z0-9_-]/.test(value[index - 1] ?? "");
5120
- }
5121
-
5122
- function stripCssComments(value) {
5123
- return value.replace(/\\/\\*[\\s\\S]*?\\*\\//g, "");
5124
- }
5125
-
5126
- function splitCssCommaList(value) {
5127
- const parts = [];
5128
- let current = "";
5129
- let quote = "";
5130
- let parenDepth = 0;
5131
- for (let index = 0; index < value.length; index += 1) {
5132
- const char = value[index];
5133
- if (quote) {
5134
- current += char;
5135
- if (char === "\\\\") {
5136
- index += 1;
5137
- current += value[index] ?? "";
5138
- } else if (char === quote) quote = "";
5139
- continue;
5140
- }
5141
- if (char === '"' || char === "'") {
5142
- quote = char;
5143
- current += char;
5144
- continue;
5145
- }
5146
- if (char === "(") parenDepth += 1;
5147
- else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
5148
- if (char === "," && parenDepth === 0) {
5149
- parts.push(current);
5150
- current = "";
5151
- } else current += char;
5152
- }
5153
- parts.push(current);
5154
- return parts;
5155
- }
5156
-
5157
- function escapeRegExp(value) {
5158
- return value.replace(/[.*+?^\${}()|[\\]\\\\]/g, "\\\\$&");
5159
- }
5160
-
5161
- function encodeCssIdentifier(value) {
5162
- return Array.from(value, (char) => char.codePointAt(0)?.toString(16).padStart(2, "0") ?? "00").join("_");
5163
- }
5164
-
5165
3709
  return {
5166
3710
  ${configFileSource} publicDir: false,
3711
+ define: fluidWidgetDefineInvariants,
5167
3712
  resolve: {
5168
3713
  conditions: [
5169
- "fluid-widget-authoring",
5170
3714
  "module",
5171
3715
  "browser",
5172
3716
  "development|production",
5173
3717
  ],
5174
3718
  },
5175
- plugins: [fluidWidgetBuildInvariantsPlugin(), fluidWidgetCssIsolationPlugin()],
3719
+ plugins: [fluidWidgetBuildInvariantsPlugin()],
5176
3720
  build: createFluidWidgetBuildInvariants(),
5177
3721
  };
5178
3722
  })()`;
@@ -5223,12 +3767,22 @@ function stableStringify(value) {
5223
3767
  return `${JSON.stringify(value, null, 2)}\n`;
5224
3768
  }
5225
3769
  function readCliVersion() {
5226
- const startDir = path.dirname(fileURLToPath(import.meta.url));
5227
- for (const packageJsonPath of candidatePackageJsonPaths(startDir)) try {
5228
- const packageJson = require(packageJsonPath);
5229
- if (packageJson.name === "@fluid-app/fluid-cli-portal" && typeof packageJson.version === "string") return packageJson.version;
5230
- } catch {}
5231
- return "0.0.0";
3770
+ return resolveCliVersionFromPackageMetadata(path.dirname(fileURLToPath(import.meta.url)), require);
3771
+ }
3772
+ function resolveCliVersionFromPackageMetadata(startDir, loadPackageMetadata) {
3773
+ for (const packageJsonPath of candidatePackageJsonPaths(startDir)) {
3774
+ let packageJson;
3775
+ try {
3776
+ packageJson = loadPackageMetadata(packageJsonPath);
3777
+ } catch (error) {
3778
+ if (isModuleResolutionError(error)) continue;
3779
+ throw new Error("Failed to read Fluid CLI package metadata.", { cause: error });
3780
+ }
3781
+ if (!isRecord$2(packageJson) || packageJson.name !== "@fluid-app/fluid-cli-portal") continue;
3782
+ if (typeof packageJson.version !== "string" || packageJson.version.length === 0) throw new Error("Fluid CLI package metadata does not declare a valid version.");
3783
+ return packageJson.version;
3784
+ }
3785
+ throw new Error("Unable to find Fluid CLI package metadata.");
5232
3786
  }
5233
3787
  function resolveViteCliPathFromRequire(moduleRequire) {
5234
3788
  try {
@@ -5276,22 +3830,6 @@ var WidgetPackageUploadError = class extends Error {
5276
3830
  this.code = code;
5277
3831
  }
5278
3832
  };
5279
- const SESSION_ARRAY_KEYS = [
5280
- "uploads",
5281
- "artifacts",
5282
- "signedUploads",
5283
- "signed_uploads",
5284
- "signedUrls",
5285
- "signed_urls",
5286
- "uploadUrls",
5287
- "upload_urls"
5288
- ];
5289
- const SESSION_RECORD_KEYS = [
5290
- "signedUrls",
5291
- "signed_urls",
5292
- "uploadUrls",
5293
- "upload_urls"
5294
- ];
5295
3833
  function buildWidgetPackageArtifactPayload(artifact) {
5296
3834
  return {
5297
3835
  path: artifact.path,
@@ -5333,7 +3871,7 @@ function getWidgetPackageCompleteUploadRoute(owner, version) {
5333
3871
  async function requestWidgetPackageUploadSession(client, owner, request) {
5334
3872
  const payload = buildWidgetPackageUploadSessionPayload(request);
5335
3873
  try {
5336
- return normalizeWidgetPackageUploadSession(owner.kind === "company" ? await fluid_os_v0_create_widget_package_version(client, payload) : await client.post(getWidgetPackageUploadSessionRoute(owner), payload), request.version, request.artifacts);
3874
+ return normalizeWidgetPackageUploadSession(owner.kind === "company" ? await fluidOs.fluid_os_v0_create_widget_package_version(client, payload) : await client.post(getWidgetPackageUploadSessionRoute(owner), payload), request.version, request.artifacts);
5337
3875
  } catch (err) {
5338
3876
  if (err instanceof WidgetPackageUploadError) throw err;
5339
3877
  throw new WidgetPackageUploadError("SESSION_REQUEST_FAILED", `Failed to create widget package upload session: ${formatUnknownError(err)}`);
@@ -5372,7 +3910,7 @@ async function uploadWidgetPackageArtifacts(session, artifacts, fetchImpl = fetc
5372
3910
  async function completeWidgetPackageUpload(client, owner, version, packageKey, artifacts) {
5373
3911
  const payload = buildWidgetPackageCompleteUploadPayload(packageKey, artifacts);
5374
3912
  try {
5375
- return normalizeWidgetPackageCompleteResponse(owner.kind === "company" ? await fluid_os_v0_complete_widget_package_version_upload(client, version, payload) : await client.post(getWidgetPackageCompleteUploadRoute(owner, version), payload), version);
3913
+ return normalizeWidgetPackageCompleteResponse(owner.kind === "company" ? await fluidOs.fluid_os_v0_complete_widget_package_version_upload(client, version, payload) : await client.post(getWidgetPackageCompleteUploadRoute(owner, version), payload), version);
5376
3914
  } catch (err) {
5377
3915
  if (err instanceof WidgetPackageUploadError) throw err;
5378
3916
  throw new WidgetPackageUploadError("COMPLETE_UPLOAD_FAILED", `Failed to complete widget package upload: ${formatUnknownError(err)}`);
@@ -5395,128 +3933,53 @@ async function publishWidgetPackageVersion(options) {
5395
3933
  };
5396
3934
  }
5397
3935
  function normalizeWidgetPackageUploadSession(response, requestedVersion, requestedArtifacts) {
5398
- const record = requireRecord(response, "Upload session response must be a JSON object.");
5399
- const responseVersion = getResponseVersion(record);
5400
- if (responseVersion && responseVersion !== requestedVersion) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response version "${responseVersion}" did not match requested version "${requestedVersion}".`);
5401
- const version = responseVersion ?? requestedVersion;
5402
- const nestedRecord = getNestedVersionRecord(record);
5403
- const uploads = getSignedUploads(nestedRecord ? [record, nestedRecord] : [record], requestedArtifacts);
5404
- const missingUploadPaths = requestedArtifacts.map((artifact) => artifact.path).filter((artifactPath) => !uploads.some((upload) => upload.path === artifactPath));
3936
+ const record = requireRecord(response, "INVALID_SESSION_RESPONSE", "Upload session response must be a JSON object.");
3937
+ requireFiniteNumberProperty(record, "version_id", "INVALID_SESSION_RESPONSE");
3938
+ requireStringProperty(record, "artifact_base_url", "INVALID_SESSION_RESPONSE");
3939
+ const uploadValues = record["uploads"];
3940
+ if (!Array.isArray(uploadValues)) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", "Upload session response must contain an \"uploads\" array.");
3941
+ const requestedArtifactsByPath = new Map(requestedArtifacts.map((artifact) => [artifact.path, artifact]));
3942
+ const seenUploadPaths = /* @__PURE__ */ new Set();
3943
+ const uploads = uploadValues.map((value, index) => {
3944
+ const upload = requireRecord(value, "INVALID_SESSION_RESPONSE", `Upload session uploads[${index}] must be a JSON object.`);
3945
+ const path = requireStringProperty(upload, "path", "INVALID_SESSION_RESPONSE");
3946
+ const url = requireStringProperty(upload, "upload_url", "INVALID_SESSION_RESPONSE");
3947
+ requireStringProperty(upload, "public_url", "INVALID_SESSION_RESPONSE");
3948
+ const requestedArtifact = requestedArtifactsByPath.get(path);
3949
+ if (!requestedArtifact) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response included unexpected artifact "${path}".`);
3950
+ if (seenUploadPaths.has(path)) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response included duplicate artifact "${path}".`);
3951
+ seenUploadPaths.add(path);
3952
+ return {
3953
+ path,
3954
+ url,
3955
+ contentType: requestedArtifact.contentType,
3956
+ headers: {}
3957
+ };
3958
+ });
3959
+ const missingUploadPaths = requestedArtifacts.map((artifact) => artifact.path).filter((artifactPath) => !seenUploadPaths.has(artifactPath));
5405
3960
  if (missingUploadPaths.length > 0) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response is missing signed URLs for artifact(s): ${missingUploadPaths.join(", ")}.`);
5406
3961
  return {
5407
- version,
3962
+ version: requestedVersion,
5408
3963
  uploads,
5409
3964
  raw: response
5410
3965
  };
5411
3966
  }
5412
3967
  function normalizeWidgetPackageCompleteResponse(response, requestedVersion) {
5413
- if (response == null) return {
5414
- version: requestedVersion,
5415
- raw: response
5416
- };
5417
- const record = requireRecord(response, "Complete upload response must be a JSON object or empty response.");
5418
- const nested = getNestedVersionRecord(record) ?? record;
5419
- const responseVersion = getResponseVersion(nested);
5420
- if (responseVersion && responseVersion !== requestedVersion) throw new WidgetPackageUploadError("INVALID_COMPLETE_RESPONSE", `Complete upload response version "${responseVersion}" did not match requested version "${requestedVersion}".`);
5421
- const packageKey = getStringProperty(nested, "package_key") ?? getStringProperty(nested, "packageKey") ?? getStringProperty(nested, "packageId");
5422
- const status = getStringProperty(nested, "status") ?? getStringProperty(nested, "state");
3968
+ const record = requireRecord(response, "INVALID_COMPLETE_RESPONSE", "Complete upload response must be a JSON object.");
3969
+ const responseVersion = requireStringProperty(record, "version", "INVALID_COMPLETE_RESPONSE");
3970
+ if (responseVersion !== requestedVersion) throw new WidgetPackageUploadError("INVALID_COMPLETE_RESPONSE", `Complete upload response version "${responseVersion}" did not match requested version "${requestedVersion}".`);
3971
+ const packageKey = requireStringProperty(record, "packageId", "INVALID_COMPLETE_RESPONSE");
3972
+ const status = requireStringProperty(record, "status", "INVALID_COMPLETE_RESPONSE");
3973
+ requireStringProperty(record, "scriptUrl", "INVALID_COMPLETE_RESPONSE");
3974
+ requireStringProperty(record, "manifestUrl", "INVALID_COMPLETE_RESPONSE");
3975
+ requireStringArrayProperty(record, "cssUrls", "INVALID_COMPLETE_RESPONSE");
5423
3976
  return {
5424
- version: responseVersion ?? requestedVersion,
5425
- ...status ? { status } : {},
5426
- ...packageKey ? { packageKey } : {},
3977
+ version: responseVersion,
3978
+ status,
3979
+ packageKey,
5427
3980
  raw: response
5428
3981
  };
5429
3982
  }
5430
- function getSignedUploads(records, requestedArtifacts) {
5431
- const uploads = [];
5432
- for (const record of records) {
5433
- for (const key of SESSION_ARRAY_KEYS) {
5434
- const value = record[key];
5435
- if (!Array.isArray(value)) continue;
5436
- for (const item of value) {
5437
- const upload = signedUploadFromRecord(item, requestedArtifacts);
5438
- if (upload) uploads.push(upload);
5439
- }
5440
- }
5441
- for (const key of SESSION_RECORD_KEYS) {
5442
- const value = record[key];
5443
- if (!isRecord$1(value) || Array.isArray(value)) continue;
5444
- for (const [artifactPath, uploadValue] of Object.entries(value)) {
5445
- const upload = signedUploadFromKeyedValue(artifactPath, uploadValue, requestedArtifacts);
5446
- if (upload) uploads.push(upload);
5447
- }
5448
- }
5449
- }
5450
- return dedupeUploads(uploads);
5451
- }
5452
- function signedUploadFromRecord(value, requestedArtifacts) {
5453
- if (!isRecord$1(value)) return null;
5454
- const artifactPath = readFirstString(value, [
5455
- "path",
5456
- "key",
5457
- "artifactPath",
5458
- "artifact_path",
5459
- "artifactKey",
5460
- "artifact_key",
5461
- "name"
5462
- ]);
5463
- const url = readFirstString(value, [
5464
- "url",
5465
- "signedUrl",
5466
- "signed_url",
5467
- "uploadUrl",
5468
- "upload_url"
5469
- ]);
5470
- if (!artifactPath || !url) return null;
5471
- const requestedArtifact = findRequestedArtifact(requestedArtifacts, artifactPath);
5472
- const contentType = readFirstString(value, [
5473
- "contentType",
5474
- "content_type",
5475
- "expectedContentType",
5476
- "expected_content_type"
5477
- ]) ?? requestedArtifact?.contentType;
5478
- if (!contentType) return null;
5479
- return {
5480
- path: artifactPath,
5481
- url,
5482
- contentType,
5483
- headers: getHeaders(value["headers"])
5484
- };
5485
- }
5486
- function signedUploadFromKeyedValue(artifactPath, value, requestedArtifacts) {
5487
- const requestedArtifact = findRequestedArtifact(requestedArtifacts, artifactPath);
5488
- if (typeof value === "string") {
5489
- if (!requestedArtifact) return null;
5490
- return {
5491
- path: artifactPath,
5492
- url: value,
5493
- contentType: requestedArtifact.contentType,
5494
- headers: {}
5495
- };
5496
- }
5497
- if (!isRecord$1(value)) return null;
5498
- const url = readFirstString(value, [
5499
- "url",
5500
- "signedUrl",
5501
- "signed_url",
5502
- "uploadUrl",
5503
- "upload_url"
5504
- ]);
5505
- if (!url) return null;
5506
- const contentType = readFirstString(value, [
5507
- "contentType",
5508
- "content_type",
5509
- "expectedContentType",
5510
- "expected_content_type"
5511
- ]) ?? requestedArtifact?.contentType;
5512
- if (!contentType) return null;
5513
- return {
5514
- path: artifactPath,
5515
- url,
5516
- contentType,
5517
- headers: getHeaders(value["headers"])
5518
- };
5519
- }
5520
3983
  function normalizeHeaderName(name) {
5521
3984
  return name.toLowerCase();
5522
3985
  }
@@ -5529,57 +3992,47 @@ function mergeUploadHeaders(headers, contentType) {
5529
3992
  merged["Content-Type"] = contentType;
5530
3993
  return merged;
5531
3994
  }
5532
- function dedupeUploads(uploads) {
5533
- const deduped = /* @__PURE__ */ new Map();
5534
- for (const upload of uploads) deduped.set(upload.path, upload);
5535
- return Array.from(deduped.values());
5536
- }
5537
- function findRequestedArtifact(requestedArtifacts, artifactPath) {
5538
- return requestedArtifacts.find((artifact) => artifact.path === artifactPath);
5539
- }
5540
- function requireRecord(value, message) {
5541
- if (!isRecord$1(value) || Array.isArray(value)) throw new WidgetPackageUploadError("INVALID_RESPONSE", message);
3995
+ function requireRecord(value, code, message) {
3996
+ if (!isRecord$1(value) || Array.isArray(value)) throw new WidgetPackageUploadError(code, message);
5542
3997
  return value;
5543
3998
  }
5544
3999
  function isRecord$1(value) {
5545
4000
  return typeof value === "object" && value !== null;
5546
4001
  }
5547
- function getStringProperty(record, key) {
4002
+ function requireStringProperty(record, key, code) {
5548
4003
  const value = record[key];
5549
4004
  if (typeof value === "string" && value.trim().length > 0) return value;
5550
- if (typeof value === "number" && Number.isFinite(value)) return String(value);
5551
- }
5552
- function readFirstString(record, keys) {
5553
- for (const key of keys) {
5554
- const value = getStringProperty(record, key);
5555
- if (value) return value;
5556
- }
5557
- }
5558
- function getHeaders(value) {
5559
- if (!isRecord$1(value) || Array.isArray(value)) return {};
5560
- const headers = {};
5561
- for (const [key, headerValue] of Object.entries(value)) if (typeof headerValue === "string") headers[key] = headerValue;
5562
- return headers;
5563
- }
5564
- function getResponseVersion(record) {
5565
- const directVersion = getStringProperty(record, "version");
5566
- if (directVersion) return directVersion;
5567
- const nested = getNestedVersionRecord(record);
5568
- return nested ? getStringProperty(nested, "version") : void 0;
5569
- }
5570
- function getNestedVersionRecord(record) {
5571
- const candidates = [
5572
- record["portal_widget_package_version"],
5573
- record["widget_package_version"],
5574
- record["package_version"]
5575
- ];
5576
- for (const candidate of candidates) if (isRecord$1(candidate) && !Array.isArray(candidate)) return candidate;
4005
+ throw new WidgetPackageUploadError(code, `Response field "${key}" must be a non-empty string.`);
4006
+ }
4007
+ function requireFiniteNumberProperty(record, key, code) {
4008
+ const value = record[key];
4009
+ if (typeof value === "number" && Number.isFinite(value)) return value;
4010
+ throw new WidgetPackageUploadError(code, `Response field "${key}" must be a finite number.`);
4011
+ }
4012
+ function requireStringArrayProperty(record, key, code) {
4013
+ const value = record[key];
4014
+ if (Array.isArray(value) && value.every((entry) => typeof entry === "string" && entry.trim().length > 0)) return value;
4015
+ throw new WidgetPackageUploadError(code, `Response field "${key}" must be an array of non-empty strings.`);
5577
4016
  }
5578
4017
  function formatUnknownError(err) {
5579
4018
  const base = err instanceof Error ? err.message : String(err);
5580
- if (isRecord$1(err) && "data" in err) return `${base} — ${JSON.stringify(err.data)}`;
4019
+ if (isRecord$1(err) && "data" in err) return `${base} — ${safeStringify(err.data)}`;
5581
4020
  return base;
5582
4021
  }
4022
+ function safeStringify(value) {
4023
+ const seen = /* @__PURE__ */ new WeakSet();
4024
+ try {
4025
+ return JSON.stringify(value, (_key, item) => {
4026
+ if (typeof item === "bigint") return `${item}n`;
4027
+ if (typeof item !== "object" || item === null) return item;
4028
+ if (seen.has(item)) return "[Circular]";
4029
+ seen.add(item);
4030
+ return item;
4031
+ }) ?? String(value);
4032
+ } catch {
4033
+ return "[unserializable error data]";
4034
+ }
4035
+ }
5583
4036
  //#endregion
5584
4037
  //#region src/commands/widget-package-publish.ts
5585
4038
  async function publishWidgetRuntimeArtifacts(options, dependencies = {}) {
@@ -6074,7 +4527,7 @@ const createVersionCommand = new Command("create").description("Create a new ver
6074
4527
  console.log(chalk.bold("Creating version..."));
6075
4528
  let result;
6076
4529
  try {
6077
- result = await fluid_os_v0_create_fluid_osversion(client, definitionId);
4530
+ result = await fluidOs.fluid_os_v0_create_fluid_osversion(client, definitionId);
6078
4531
  } catch (err) {
6079
4532
  console.error(chalk.red("Error:") + " Failed to create version — " + (err instanceof Error ? err.message : String(err)));
6080
4533
  process.exit(1);
@@ -6092,7 +4545,7 @@ const createVersionCommand = new Command("create").description("Create a new ver
6092
4545
  if (options.activate) {
6093
4546
  console.log("Activating version...");
6094
4547
  try {
6095
- await fluid_os_v0_update_fluid_osversion(client, definitionId, version.id, { version: { active: true } });
4548
+ await fluidOs.fluid_os_v0_update_fluid_osversion(client, definitionId, version.id, { version: { active: true } });
6096
4549
  } catch (err) {
6097
4550
  console.error(chalk.red("Error:") + " Failed to activate version — " + (err instanceof Error ? err.message : String(err)));
6098
4551
  process.exit(1);
@@ -6107,7 +4560,7 @@ const listVersionCommand = new Command("list").description("List all versions of
6107
4560
  const definitionId = await requireDefinitionId();
6108
4561
  let result;
6109
4562
  try {
6110
- result = await fluid_os_v0_list_fluid_osversions(client, definitionId);
4563
+ result = await fluidOs.fluid_os_v0_list_fluid_osversions(client, definitionId);
6111
4564
  } catch (err) {
6112
4565
  console.error(chalk.red("Error:") + " Failed to list versions — " + (err instanceof Error ? err.message : String(err)));
6113
4566
  process.exit(1);
@@ -6150,7 +4603,7 @@ const activateVersionCommand = new Command("activate").description("Activate a s
6150
4603
  console.log();
6151
4604
  console.log(chalk.bold("Activating version..."));
6152
4605
  try {
6153
- await fluid_os_v0_update_fluid_osversion(client, definitionId, versionId, { version: { active: true } });
4606
+ await fluidOs.fluid_os_v0_update_fluid_osversion(client, definitionId, versionId, { version: { active: true } });
6154
4607
  } catch (err) {
6155
4608
  console.error(chalk.red("Error:") + " Failed to activate version — " + (err instanceof Error ? err.message : String(err)));
6156
4609
  process.exit(1);
@@ -6233,6 +4686,6 @@ const plugin = {
6233
4686
  }
6234
4687
  };
6235
4688
  //#endregion
6236
- export { CANONICAL_PROJECT_NAME, FILE_SYSTEM_ERRORS, GitError, TEMPLATES, TEMPLATE_REPO_URL, WIDGET_PACKAGE_BUILDER_VERSION, WidgetPackageUploadError, buildIdToSlugMap, buildNavigationIdToSlugMap, buildSharedWidgetPackage, buildSnapshot, buildThemeIdToSlugMap, buildWidgetPackageCompleteUploadPayload, buildWidgetPackageDescriptor, buildWidgetPackageUploadSessionPayload, categorizeChanges, cloneCommand, collectWidgetPackageArtifacts, completeWidgetPackageUpload, computeArtifactMetadata, computeFileHash, copyTemplate, copyTemplateSafe, createAuthenticatedWidgetPackageClient, createCommand, createDirectory, createDirectorySafe, createPortalGitCredential, createPublishManifestMetadata, createWidgetPackageEntrySource, createWidgetPackagePublishRequest, plugin as default, deployCommand, deriveScreenSlug, deriveSlug, diffAgainstSnapshot, directoryExists, doctorCommand, ensureGitAvailable, fileExists, getArtifactContentType, getInstallCommand, getOriginUrl, getRunCommand, getSdkVersion, getSdkVersionSafe, getTemplatePaths, getWidgetPackageCompleteUploadRoute, getWidgetPackageUploadSessionRoute, gitAheadBehindVsFetchHead, gitAutoCommit, gitClone, gitFetch, gitInit, gitMergeFastForward, gitMissingError, gitPush, hasUncommittedChanges, installDependencies, isGitAvailable, isGitRepo, loadSourceWidgetPackages, normalizeWidgetPackageCompleteResponse, normalizeWidgetPackageUploadSession, pathExists, portalGitCredentialPath, printDryRunSessionPayload, printRuntimeOnlyNotice, printWidgetPublishSummary, promptProjectConfig, publishWidgetPackageVersion, publishWidgetRuntimeArtifacts, pullCommand, pushCommand, readFileSafe, readMappings, readSnapshot, removeMapping, requestWidgetPackageUploadSession, resolveIdToSlug, resolvePortalWidgetSourceConfig, resolveSlugToId, rewriteProjectName, runPackageManager, runPortalGitSync, scaffoldFromGitHub, sha256, slugFromPath, topLevelWidgetCommand, transformNavigation, transformNavigationItems, transformProfile, transformScreen, transformTheme, updateMapping, uploadWidgetPackageArtifacts, validateCrossReferences, validateSingleSourceWidgetPackage, validateWidgetPublishOutDir, versionCommand, widgetCommand, writeFileSafe, writeFluidrcProfile, writeMappings, writeSnapshot };
4689
+ export { CANONICAL_PROJECT_NAME, FILE_SYSTEM_ERRORS, GitError, TEMPLATES, TEMPLATE_REPO_URL, WIDGET_PACKAGE_BUILDER_VERSION, WidgetPackageUploadError, buildIdToSlugMap, buildNavigationIdToSlugMap, buildSharedWidgetPackage, buildSnapshot, buildThemeIdToSlugMap, buildWidgetPackageCompleteUploadPayload, buildWidgetPackagePublishManifest, buildWidgetPackageUploadSessionPayload, categorizeChanges, cloneCommand, collectWidgetPackageArtifacts, completeWidgetPackageUpload, computeArtifactMetadata, computeFileHash, copyTemplate, copyTemplateSafe, createAuthenticatedWidgetPackageClient, createCommand, createDirectory, createDirectorySafe, createPortalGitCredential, createPublishManifestMetadata, createWidgetPackageEntrySource, createWidgetPackagePublishRequest, plugin as default, deployCommand, deriveScreenSlug, deriveSlug, diffAgainstSnapshot, directoryExists, doctorCommand, ensureGitAvailable, fileExists, getArtifactContentType, getInstallCommand, getOriginUrl, getRunCommand, getSdkVersion, getSdkVersionSafe, getTemplatePaths, getWidgetPackageCompleteUploadRoute, getWidgetPackageUploadSessionRoute, gitAheadBehindVsFetchHead, gitAutoCommit, gitClone, gitFetch, gitInit, gitMergeFastForward, gitMissingError, gitPush, hasUncommittedChanges, installDependencies, isGitAvailable, isGitRepo, loadSourceWidgetPackages, normalizeWidgetPackageCompleteResponse, normalizeWidgetPackageUploadSession, pathExists, portalGitCredentialPath, printDryRunSessionPayload, printRuntimeOnlyNotice, printWidgetPublishSummary, promptProjectConfig, publishWidgetPackageVersion, publishWidgetRuntimeArtifacts, pullCommand, pushCommand, readFileSafe, readMappings, readSnapshot, removeMapping, requestWidgetPackageUploadSession, resolveIdToSlug, resolvePortalWidgetSourceConfig, resolveSlugToId, rewriteProjectName, runPackageManager, runPortalGitSync, scaffoldFromGitHub, sha256, slugFromPath, topLevelWidgetCommand, transformNavigation, transformNavigationItems, transformProfile, transformScreen, transformTheme, updateMapping, uploadWidgetPackageArtifacts, validateCrossReferences, validateSingleSourceWidgetPackage, validateWidgetPublishOutDir, versionCommand, widgetCommand, writeFileSafe, writeFluidrcProfile, writeMappings, writeSnapshot };
6237
4690
 
6238
4691
  //# sourceMappingURL=index.mjs.map