@fluid-app/fluid-cli-portal 0.1.27 → 0.1.28

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,17 +1,19 @@
1
- import { A as deleteFluidOSNavigation, B as updateFluidOSScreen, C as writeMappings, D as createFluidOSScreen, E as createFluidOSProfile, F as listFluidOSNavigationItems, H as updateFluidOSVersion, I as listFluidOSVersions, L as updateFluidOSNavigation, M as deleteFluidOSProfile, N as deleteFluidOSScreen, O as createFluidOSTheme, P as deleteFluidOSTheme, R as updateFluidOSNavigationItem, S as updateMapping, T as createFluidOSNavigationItem, U as createFetchClient, V as updateFluidOSTheme, _ 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 deleteFluidOSNavigationItem, k as createFluidOSVersion, 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 createFluidOSNavigation, x as resolveSlugToId, y as removeMapping, z as updateFluidOSProfile } from "./pull-hAdXOpgb.mjs";
1
+ import { A as fluid_os_v0_create_fluid_osversion, B as fluid_os_v0_update_fluid_osnavigation_item, 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 createFetchClient, H as fluid_os_v0_update_fluid_osscreen, I as fluid_os_v0_delete_fluid_ostheme, L as fluid_os_v0_list_fluid_osnavigation_items, 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_osversions, S as updateMapping, T as fluid_os_v0_create_fluid_osnavigation, U as fluid_os_v0_update_fluid_ostheme, V as fluid_os_v0_update_fluid_osprofile, W as fluid_os_v0_update_fluid_osversion, _ 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_update_fluid_osnavigation } from "./pull-zrTaJuSb.mjs";
2
+ import { createRequire } from "node:module";
2
3
  import { Command } from "commander";
3
4
  import chalk from "chalk";
4
5
  import ora from "ora";
5
6
  import path, { basename, dirname, join, relative, resolve } from "node:path";
6
- import { fileURLToPath } from "node:url";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
8
  import { copyFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
8
- import { failure, getActiveProfile, getAuthToken, listProfileNames, success } from "@fluid-app/fluid-cli";
9
+ import { failure, findProjectConfig, getActiveProfile, getAuthToken, listProfileNames, readConfig, success } from "@fluid-app/fluid-cli";
9
10
  import prompts from "prompts";
10
11
  import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
11
12
  import Handlebars from "handlebars";
12
13
  import { execa } from "execa";
13
14
  import fs from "fs-extra";
14
15
  import path$1 from "path";
16
+ import { createHash } from "node:crypto";
15
17
  //#region src/types.ts
16
18
  /**
17
19
  * Available project templates
@@ -510,7 +512,7 @@ async function autoPull(cwd) {
510
512
  console.log(chalk.yellow("No portal/ directory found.") + " Attempting to pull content...");
511
513
  console.log();
512
514
  try {
513
- const { pullCommand } = await import("./pull-hAdXOpgb.mjs").then((n) => n.n);
515
+ const { pullCommand } = await import("./pull-zrTaJuSb.mjs").then((n) => n.n);
514
516
  await pullCommand.parseAsync([], { from: "user" });
515
517
  return hasPortalContent(cwd);
516
518
  } catch (err) {
@@ -565,83 +567,417 @@ const devCommand = new Command("dev").description("Start the development server
565
567
  }
566
568
  });
567
569
  //#endregion
570
+ //#region src/utils/widget-package-config.ts
571
+ const CONFIG_CANDIDATES = [
572
+ "src/widgets.config.ts",
573
+ "src/portal.config.ts",
574
+ "portal.config.ts"
575
+ ];
576
+ const SOURCE_PACKAGE_EXTRACT_FILENAME = "extract-widget-packages.ts";
577
+ const SOURCE_PACKAGE_OUTPUT_FILENAME = "source-widget-packages.json";
578
+ const SOURCE_PACKAGE_OUTPUT_SENTINEL = "fluid-widget-source-packages:v1";
579
+ const TSX_CLI_PATH$1 = createRequire(import.meta.url).resolve("tsx/cli");
580
+ async function resolvePortalWidgetSourceConfig(projectDir) {
581
+ for (const relativePath of CONFIG_CANDIDATES) {
582
+ const candidate = path.join(projectDir, relativePath);
583
+ if (await fs.pathExists(candidate)) return {
584
+ path: candidate,
585
+ relativePath
586
+ };
587
+ }
588
+ }
589
+ async function loadSourceWidgetPackages(projectDir) {
590
+ const config = await resolvePortalWidgetSourceConfig(projectDir);
591
+ if (!config) return success([]);
592
+ let tempDir;
593
+ try {
594
+ tempDir = await createTempDirectory$1(projectDir);
595
+ const extractFile = path.join(tempDir, SOURCE_PACKAGE_EXTRACT_FILENAME);
596
+ const outputFile = path.join(tempDir, SOURCE_PACKAGE_OUTPUT_FILENAME);
597
+ await fs.writeFile(extractFile, createSourcePackageExtractorScript({
598
+ projectDir,
599
+ configPath: config.path
600
+ }), {
601
+ encoding: "utf-8",
602
+ flag: "wx"
603
+ });
604
+ await execa(process.execPath, [
605
+ TSX_CLI_PATH$1,
606
+ extractFile,
607
+ outputFile
608
+ ], {
609
+ cwd: projectDir,
610
+ stdio: "pipe",
611
+ env: {
612
+ ...process.env,
613
+ NODE_ENV: "production"
614
+ }
615
+ });
616
+ const outputResult = await readSourcePackageExtractorOutput(outputFile);
617
+ if (!outputResult.success) return outputResult;
618
+ const parsed = outputResult.value;
619
+ if (parsed.length === 0) return success([]);
620
+ return success(parsed);
621
+ } catch (err) {
622
+ const error = err;
623
+ return failure({
624
+ code: "CONFIG_LOAD_FAILED",
625
+ message: `Failed to load widget packages from ${config.relativePath}`,
626
+ details: error.stderr ?? error.message ?? String(err)
627
+ });
628
+ } finally {
629
+ if (tempDir) await fs.remove(tempDir).catch(() => {});
630
+ }
631
+ }
632
+ async function createTempDirectory$1(projectDir) {
633
+ const projectTmpDir = path.join(projectDir, ".fluid", "tmp");
634
+ try {
635
+ await fs.ensureDir(projectTmpDir);
636
+ return await fs.mkdtemp(path.join(projectTmpDir, "widget-packages-"));
637
+ } catch (err) {
638
+ const message = err instanceof Error ? err.message : String(err);
639
+ throw new Error(`Unable to create project-local temporary directory at ${projectTmpDir}: ${message}`);
640
+ }
641
+ }
642
+ async function readSourcePackageExtractorOutput(outputFile) {
643
+ let output;
644
+ try {
645
+ output = await fs.readFile(outputFile, "utf-8");
646
+ } catch (err) {
647
+ return failure({
648
+ code: "CONFIG_LOAD_FAILED",
649
+ message: "Widget package extractor did not write an output file",
650
+ details: err instanceof Error ? err.message : String(err)
651
+ });
652
+ }
653
+ let parsed;
654
+ try {
655
+ parsed = JSON.parse(output);
656
+ } catch {
657
+ return failure({
658
+ code: "INVALID_FORMAT",
659
+ message: "Failed to parse widget package output file as JSON",
660
+ details: `Output was: ${output.slice(0, 200)}`
661
+ });
662
+ }
663
+ if (!isRecord$5(parsed) || parsed.sentinel !== SOURCE_PACKAGE_OUTPUT_SENTINEL) return failure({
664
+ code: "INVALID_FORMAT",
665
+ message: "Widget package extractor output file had an invalid sentinel"
666
+ });
667
+ if (!Array.isArray(parsed.data)) return failure({
668
+ code: "INVALID_FORMAT",
669
+ message: "Extracted widget package output is not an array",
670
+ details: `Expected an array, got: ${typeof parsed.data}`
671
+ });
672
+ return success(parsed.data);
673
+ }
674
+ function createSourcePackageExtractorScript(options) {
675
+ return `
676
+ import fs from "node:fs/promises";
677
+ import path from "node:path";
678
+ import { createServer, normalizePath } from "vite";
679
+
680
+ const SOURCE_PACKAGE_MARKER = "__fluidSourceWidgetPackage";
681
+ const OUTPUT_SENTINEL = ${JSON.stringify(SOURCE_PACKAGE_OUTPUT_SENTINEL)};
682
+ const projectRoot = ${JSON.stringify(options.projectDir)};
683
+ const widgetConfigPath = ${JSON.stringify(options.configPath)};
684
+ const outputPath = process.argv[2];
685
+ if (!outputPath) throw new Error("Missing widget package extractor output path.");
686
+
687
+ function toViteModuleId(modulePath) {
688
+ const relativePath = path.relative(projectRoot, modulePath);
689
+ if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
690
+ return "/" + normalizePath(relativePath);
691
+ }
692
+ return normalizePath(modulePath);
693
+ }
694
+
695
+ const server = await createServer({
696
+ root: projectRoot,
697
+ mode: "production",
698
+ server: { middlewareMode: true },
699
+ appType: "custom",
700
+ logLevel: "error",
701
+ clearScreen: false,
702
+ resolve: {
703
+ conditions: ["fluid-widget-authoring"],
704
+ },
705
+ ssr: {
706
+ resolve: {
707
+ conditions: ["fluid-widget-authoring"],
708
+ },
709
+ },
710
+ });
711
+
712
+ try {
713
+ const widgetConfig = await server.ssrLoadModule(toViteModuleId(widgetConfigPath));
714
+
715
+ function isSourceWidgetPackage(value) {
716
+ return Boolean(value && typeof value === "object" && value[SOURCE_PACKAGE_MARKER] === true);
717
+ }
718
+
719
+ function collectSourceWidgetPackages(mod) {
720
+ const candidates = [
721
+ mod.widgetPackage,
722
+ ...(Array.isArray(mod.widgetPackages) ? mod.widgetPackages : []),
723
+ mod.default,
724
+ ];
725
+ const byPackageId = new Map();
726
+ for (const candidate of candidates) {
727
+ if (!isSourceWidgetPackage(candidate)) continue;
728
+ if (!byPackageId.has(candidate.packageId)) byPackageId.set(candidate.packageId, candidate);
729
+ }
730
+ return Array.from(byPackageId.values());
731
+ }
732
+
733
+ function serializeWidget(widget) {
734
+ if (!widget || typeof widget !== "object" || Array.isArray(widget)) {
735
+ return widget;
736
+ }
737
+ const { component, ...metadata } = widget;
738
+ return metadata;
739
+ }
740
+
741
+ function serializePackage(sourcePackage) {
742
+ return {
743
+ manifestVersion: sourcePackage.manifestVersion,
744
+ scope: sourcePackage.scope,
745
+ packageStableId: sourcePackage.packageStableId,
746
+ packageId: sourcePackage.packageId,
747
+ packageType: sourcePackage.packageType,
748
+ version: sourcePackage.version,
749
+ cssUrls: sourcePackage.cssUrls,
750
+ widgets: Array.isArray(sourcePackage.widgets)
751
+ ? sourcePackage.widgets.map(serializeWidget)
752
+ : sourcePackage.widgets,
753
+ };
754
+ }
755
+
756
+ await fs.writeFile(
757
+ outputPath,
758
+ JSON.stringify({
759
+ sentinel: OUTPUT_SENTINEL,
760
+ data: collectSourceWidgetPackages(widgetConfig).map(serializePackage),
761
+ }),
762
+ "utf-8",
763
+ );
764
+ } finally {
765
+ await server.close();
766
+ }
767
+ `;
768
+ }
769
+ function isRecord$5(value) {
770
+ return typeof value === "object" && value !== null && !Array.isArray(value);
771
+ }
772
+ //#endregion
568
773
  //#region src/utils/extract-manifests.ts
569
774
  /**
570
775
  * Manifest extraction utility
571
776
  *
572
777
  * Extracts serializable widget manifest metadata from portal.config.ts
573
- * by writing a minimal wrapper script that imports customWidgets and
574
- * serializes the result to stdout, then running it with tsx.
778
+ * by writing a minimal wrapper script that imports customWidgets plus
779
+ * source widget package exports and serializes the result to a temp output
780
+ * file, then running it with tsx.
575
781
  *
576
782
  * Strips the `component` field (not serializable) from each manifest.
577
783
  *
578
- * Writes a temp script, runs it with tsx, and parses JSON output. This
579
- * avoids needing Vite's module resolution — portal.config.ts must use
580
- * relative imports only.
581
- *
582
- * Used by `fluid build`. The dev server uses Vite's ssrLoadModule
583
- * instead (see manifest-plugin.ts in portal-sdk).
784
+ * Writes a temp script, runs it with tsx, and parses JSON output from the
785
+ * output file. The wrapper loads config modules through Vite SSR so project
786
+ * aliases and Vite-compatible module resolution are honored.
584
787
  */
585
- const EXTRACT_FILENAME = "__fluid_extract_manifests.ts";
788
+ const EXTRACT_FILENAME = "extract-manifests.ts";
789
+ const EXTRACT_OUTPUT_FILENAME = "manifests.json";
790
+ const EXTRACT_OUTPUT_SENTINEL = "fluid-widget-manifests:v1";
791
+ const TSX_CLI_PATH = createRequire(import.meta.url).resolve("tsx/cli");
586
792
  /**
587
793
  * Extract serializable widget manifests from a project's portal.config.ts.
588
794
  *
589
- * Writes a temp wrapper script, runs it with tsx, and parses JSON output.
590
- * The temp file is always cleaned up.
795
+ * Writes a temp wrapper script, runs it with tsx, and parses the temp JSON
796
+ * output file. The temp files are always cleaned up.
591
797
  *
592
- * Returns an empty array if no customWidgets export exists.
798
+ * Returns an empty array if no customWidgets or source package exports exist.
799
+ *
800
+ * Supported static export shapes are intentionally simple so the CLI can avoid
801
+ * executing unrelated portal configs: named `export const|let|var customWidgets`,
802
+ * `widgetPackage`, or `widgetPackages`; direct `export default
803
+ * defineWidgetPackage(...)`; or `export default <identifier>` where that
804
+ * identifier is initialized with `defineWidgetPackage(...)` in the same file.
805
+ * Re-export-only and computed export shapes are not detected by this layer.
593
806
  *
594
807
  * @param projectDir - The project root directory containing src/portal.config.ts
595
808
  */
596
809
  async function extractManifests(projectDir) {
597
- const configPath = path$1.join(projectDir, "src", "portal.config.ts");
598
- const extractFile = path$1.join(projectDir, EXTRACT_FILENAME);
810
+ let tempDir;
599
811
  try {
600
- if (!await fs.pathExists(configPath)) return success([]);
601
- const strippedSource = (await fs.readFile(configPath, "utf-8")).replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
602
- if (!/export\s+(const|let)\s+customWidgets[\s:=]/.test(strippedSource)) return success([]);
603
- await fs.writeFile(extractFile, `
604
- import { customWidgets } from "./src/portal.config.ts";
605
-
606
- const serializable = customWidgets.map(({ component, ...rest }) => rest);
607
- console.log(JSON.stringify(serializable));
608
- `, "utf-8");
609
- const output = (await execa("npx", ["tsx", EXTRACT_FILENAME], {
812
+ const config = await resolvePortalWidgetSourceConfig(projectDir);
813
+ if (!config) return success([]);
814
+ const configExports = readStaticWidgetExports(await fs.readFile(config.path, "utf-8"));
815
+ const legacyPortalConfigPath = path$1.join(projectDir, "src", "portal.config.ts");
816
+ 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;
817
+ if (!configExports.hasCustomWidgets && !configExports.hasSourceWidgetPackages && !legacyCustomWidgetsConfigPath) return success([]);
818
+ tempDir = await createTempDirectory(projectDir);
819
+ const extractFile = path$1.join(tempDir, EXTRACT_FILENAME);
820
+ const outputFile = path$1.join(tempDir, EXTRACT_OUTPUT_FILENAME);
821
+ const wrapperScript = createManifestExtractorScript({
822
+ projectDir,
823
+ widgetConfigPath: config.path,
824
+ legacyCustomWidgetsConfigPath
825
+ });
826
+ await fs.writeFile(extractFile, wrapperScript, {
827
+ encoding: "utf-8",
828
+ flag: "wx"
829
+ });
830
+ await execa(process.execPath, [
831
+ TSX_CLI_PATH,
832
+ extractFile,
833
+ outputFile
834
+ ], {
610
835
  cwd: projectDir,
611
836
  stdio: "pipe",
612
837
  env: {
613
838
  ...process.env,
614
839
  NODE_ENV: "production"
615
840
  }
616
- })).stdout.trim();
617
- if (!output || output === "null" || output === "[]") return success([]);
618
- let parsed;
619
- try {
620
- parsed = JSON.parse(output);
621
- } catch {
622
- return failure({
623
- code: "INVALID_FORMAT",
624
- message: "Failed to parse manifest output as JSON",
625
- details: `Output was: ${output.slice(0, 200)}`
626
- });
627
- }
628
- if (!Array.isArray(parsed)) return failure({
629
- code: "INVALID_FORMAT",
630
- message: "customWidgets export is not an array",
631
- details: `Expected an array, got: ${typeof parsed}`
632
841
  });
842
+ const outputResult = await readManifestExtractorOutput(outputFile);
843
+ if (!outputResult.success) return outputResult;
844
+ const parsed = outputResult.value;
845
+ if (parsed.length === 0) return success([]);
633
846
  return success(parsed.filter((m) => typeof m === "object" && m !== null && typeof m.type === "string" && typeof m.displayName === "string"));
634
847
  } catch (err) {
635
848
  const error = err;
636
849
  return failure({
637
850
  code: "EXTRACTION_FAILED",
638
- message: "Failed to extract widget manifests from portal.config.ts",
851
+ message: "Failed to extract widget manifests from portal widget source config",
639
852
  details: error.stderr ?? error.message ?? String(err)
640
853
  });
641
854
  } finally {
642
- await fs.remove(extractFile).catch(() => {});
855
+ if (tempDir) await fs.remove(tempDir).catch(() => {});
856
+ }
857
+ }
858
+ function readStaticWidgetExports(source) {
859
+ const strippedSource = source.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
860
+ const defaultExportMatch = /export\s+default\s+([A-Za-z_$][\w$]*)\b/.exec(strippedSource);
861
+ 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]));
862
+ const hasDefaultWidgetPackageExport = /export\s+default\s+defineWidgetPackage\s*\(/.test(strippedSource) || defaultExportMatch !== null && widgetPackageDefinitionNames.has(defaultExportMatch[1]);
863
+ return {
864
+ hasCustomWidgets: /export\s+(?:const|let|var)\s+customWidgets\b/.test(strippedSource),
865
+ hasSourceWidgetPackages: /export\s+(?:const|let|var)\s+widgetPackage\b/.test(strippedSource) || /export\s+(?:const|let|var)\s+widgetPackages\b/.test(strippedSource) || hasDefaultWidgetPackageExport
866
+ };
867
+ }
868
+ async function createTempDirectory(projectDir) {
869
+ const projectTmpDir = path$1.join(projectDir, ".fluid", "tmp");
870
+ try {
871
+ await fs.ensureDir(projectTmpDir);
872
+ return await fs.mkdtemp(path$1.join(projectTmpDir, "manifests-"));
873
+ } catch (err) {
874
+ const message = err instanceof Error ? err.message : String(err);
875
+ throw new Error(`Unable to create project-local temporary directory at ${projectTmpDir}: ${message}`);
643
876
  }
644
877
  }
878
+ async function readManifestExtractorOutput(outputFile) {
879
+ let output;
880
+ try {
881
+ output = await fs.readFile(outputFile, "utf-8");
882
+ } catch (err) {
883
+ return failure({
884
+ code: "EXTRACTION_FAILED",
885
+ message: "Manifest extractor did not write an output file",
886
+ details: err instanceof Error ? err.message : String(err)
887
+ });
888
+ }
889
+ let parsed;
890
+ try {
891
+ parsed = JSON.parse(output);
892
+ } catch {
893
+ return failure({
894
+ code: "INVALID_FORMAT",
895
+ message: "Failed to parse manifest output file as JSON",
896
+ details: `Output was: ${output.slice(0, 200)}`
897
+ });
898
+ }
899
+ if (!isRecord$4(parsed) || parsed.sentinel !== EXTRACT_OUTPUT_SENTINEL) return failure({
900
+ code: "INVALID_FORMAT",
901
+ message: "Manifest extractor output file had an invalid sentinel"
902
+ });
903
+ if (!Array.isArray(parsed.data)) return failure({
904
+ code: "INVALID_FORMAT",
905
+ message: "extracted widget manifests output is not an array",
906
+ details: `Expected an array, got: ${typeof parsed.data}`
907
+ });
908
+ return success(parsed.data);
909
+ }
910
+ function isRecord$4(value) {
911
+ return typeof value === "object" && value !== null && !Array.isArray(value);
912
+ }
913
+ function createManifestExtractorScript(options) {
914
+ return `
915
+ import fs from "node:fs/promises";
916
+ import path from "node:path";
917
+ import { createServer, normalizePath } from "vite";
918
+ import {
919
+ isSourceWidgetPackage,
920
+ sourceWidgetPackagesToManifests,
921
+ } from "@fluid-app/portal-sdk";
922
+
923
+ const OUTPUT_SENTINEL = ${JSON.stringify(EXTRACT_OUTPUT_SENTINEL)};
924
+ const projectRoot = ${JSON.stringify(options.projectDir)};
925
+ const widgetConfigPath = ${JSON.stringify(options.widgetConfigPath)};
926
+ const legacyCustomWidgetsConfigPath = ${JSON.stringify(options.legacyCustomWidgetsConfigPath)};
927
+ const outputPath = process.argv[2];
928
+ if (!outputPath) throw new Error("Missing manifest extractor output path.");
929
+
930
+ function toViteModuleId(modulePath) {
931
+ const relativePath = path.relative(projectRoot, modulePath);
932
+ if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
933
+ return "/" + normalizePath(relativePath);
934
+ }
935
+ return normalizePath(modulePath);
936
+ }
937
+
938
+ const server = await createServer({
939
+ root: projectRoot,
940
+ mode: "production",
941
+ server: { middlewareMode: true },
942
+ appType: "custom",
943
+ logLevel: "error",
944
+ clearScreen: false,
945
+ });
946
+
947
+ try {
948
+ const portalConfig = await server.ssrLoadModule(toViteModuleId(widgetConfigPath));
949
+ const legacyPortalConfig = legacyCustomWidgetsConfigPath
950
+ ? await server.ssrLoadModule(toViteModuleId(legacyCustomWidgetsConfigPath))
951
+ : {};
952
+
953
+ const sourcePackages = [];
954
+ const seenSourcePackageIds = new Set();
955
+ for (const candidate of [
956
+ portalConfig.widgetPackage,
957
+ ...(Array.isArray(portalConfig.widgetPackages) ? portalConfig.widgetPackages : []),
958
+ portalConfig.default,
959
+ ]) {
960
+ if (!isSourceWidgetPackage(candidate)) continue;
961
+ if (seenSourcePackageIds.has(candidate.packageId)) continue;
962
+ seenSourcePackageIds.add(candidate.packageId);
963
+ sourcePackages.push(candidate);
964
+ }
965
+ const manifests = [
966
+ ...(Array.isArray(portalConfig.customWidgets) ? portalConfig.customWidgets : []),
967
+ ...(Array.isArray(legacyPortalConfig.customWidgets) ? legacyPortalConfig.customWidgets : []),
968
+ ...sourceWidgetPackagesToManifests(sourcePackages),
969
+ ];
970
+ const serializable = manifests.map(({ component, ...rest }) => rest);
971
+ await fs.writeFile(
972
+ outputPath,
973
+ JSON.stringify({ sentinel: OUTPUT_SENTINEL, data: serializable }),
974
+ "utf-8",
975
+ );
976
+ } finally {
977
+ await server.close();
978
+ }
979
+ `;
980
+ }
645
981
  //#endregion
646
982
  //#region src/commands/build.ts
647
983
  const buildCommand = new Command("build").description("Build the application for production").option("-o, --out-dir <dir>", "Output directory", "dist").action(async (options) => {
@@ -895,7 +1231,7 @@ async function pushScreens(client, defId, portalDir, changes, mappings) {
895
1231
  const slug = slugFromPath(file);
896
1232
  try {
897
1233
  const local = await readPortalFile(portalDir, file);
898
- const newId = (await createFluidOSScreen(client, defId, { screen: {
1234
+ const newId = (await fluid_os_v0_create_fluid_osscreen(client, defId, { screen: {
899
1235
  name: local.name,
900
1236
  slug,
901
1237
  component_tree: toApiComponentTree(local.component_tree)
@@ -936,7 +1272,7 @@ async function pushScreens(client, defId, portalDir, changes, mappings) {
936
1272
  }
937
1273
  try {
938
1274
  const local = await readPortalFile(portalDir, file);
939
- await updateFluidOSScreen(client, defId, screenId, { screen: {
1275
+ await fluid_os_v0_update_fluid_osscreen(client, defId, screenId, { screen: {
940
1276
  name: local.name,
941
1277
  slug,
942
1278
  component_tree: toApiComponentTree(local.component_tree)
@@ -975,7 +1311,7 @@ async function pushScreens(client, defId, portalDir, changes, mappings) {
975
1311
  };
976
1312
  }
977
1313
  try {
978
- await deleteFluidOSScreen(client, defId, screenId);
1314
+ await fluid_os_v0_delete_fluid_osscreen(client, defId, screenId);
979
1315
  currentMappings = removeMapping(currentMappings, "screens", slug);
980
1316
  results.push({
981
1317
  file,
@@ -1010,7 +1346,7 @@ async function pushThemes(client, defId, portalDir, changes, mappings) {
1010
1346
  const slug = slugFromPath(file);
1011
1347
  try {
1012
1348
  const local = await readPortalFile(portalDir, file);
1013
- const newId = (await createFluidOSTheme(client, defId, { theme: {
1349
+ const newId = (await fluid_os_v0_create_fluid_ostheme(client, defId, { theme: {
1014
1350
  name: local.name,
1015
1351
  active: local.active,
1016
1352
  config: local.config
@@ -1051,7 +1387,7 @@ async function pushThemes(client, defId, portalDir, changes, mappings) {
1051
1387
  }
1052
1388
  try {
1053
1389
  const local = await readPortalFile(portalDir, file);
1054
- await updateFluidOSTheme(client, defId, themeId, { theme: {
1390
+ await fluid_os_v0_update_fluid_ostheme(client, defId, themeId, { theme: {
1055
1391
  name: local.name,
1056
1392
  active: local.active,
1057
1393
  config: local.config
@@ -1090,7 +1426,7 @@ async function pushThemes(client, defId, portalDir, changes, mappings) {
1090
1426
  };
1091
1427
  }
1092
1428
  try {
1093
- await deleteFluidOSTheme(client, defId, themeId);
1429
+ await fluid_os_v0_delete_fluid_ostheme(client, defId, themeId);
1094
1430
  currentMappings = removeMapping(currentMappings, "themes", slug);
1095
1431
  results.push({
1096
1432
  file,
@@ -1160,7 +1496,7 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
1160
1496
  const slug = slugFromPath(file);
1161
1497
  try {
1162
1498
  const local = await readPortalFile(portalDir, file);
1163
- const newId = (await createFluidOSNavigation(client, defId, { navigation: {
1499
+ const newId = (await fluid_os_v0_create_fluid_osnavigation(client, defId, { navigation: {
1164
1500
  name: local.name,
1165
1501
  platform: local.platform
1166
1502
  } })).navigation?.id;
@@ -1170,7 +1506,7 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
1170
1506
  const localToServerId = /* @__PURE__ */ new Map();
1171
1507
  for (const item of resolvedItems) {
1172
1508
  const resolvedParentId = item.parent_id != null ? localToServerId.get(item.parent_id) ?? item.parent_id : void 0;
1173
- const created = await createFluidOSNavigationItem(client, defId, newId, { navigation_item: {
1509
+ const created = await fluid_os_v0_create_fluid_osnavigation_item(client, defId, newId, { navigation_item: {
1174
1510
  label: item.label ?? "",
1175
1511
  position: item.position ?? 0,
1176
1512
  icon: item.icon ?? void 0,
@@ -1217,15 +1553,15 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
1217
1553
  }
1218
1554
  try {
1219
1555
  const local = await readPortalFile(portalDir, file);
1220
- await updateFluidOSNavigation(client, defId, navId, { navigation: {
1556
+ await fluid_os_v0_update_fluid_osnavigation(client, defId, navId, { navigation: {
1221
1557
  name: local.name,
1222
1558
  platform: local.platform
1223
1559
  } });
1224
1560
  const resolvedItems = flattenNavigationItems(resolveNavigationItemScreenIds(local.navigation_items, currentMappings));
1225
- const serverItems = (await listFluidOSNavigationItems(client, defId, navId)).navigation_items ?? [];
1561
+ const serverItems = (await fluid_os_v0_list_fluid_osnavigation_items(client, defId, navId)).navigation_items ?? [];
1226
1562
  const serverById = new Map(serverItems.map((s) => [s.id, s]));
1227
1563
  const localIds = new Set(resolvedItems.filter((i) => i.id).map((i) => i.id));
1228
- for (const serverItem of serverItems) if (!localIds.has(serverItem.id)) await deleteFluidOSNavigationItem(client, defId, navId, serverItem.id);
1564
+ for (const serverItem of serverItems) if (!localIds.has(serverItem.id)) await fluid_os_v0_delete_fluid_osnavigation_item(client, defId, navId, serverItem.id);
1229
1565
  const localToServerId = /* @__PURE__ */ new Map();
1230
1566
  for (const item of resolvedItems) {
1231
1567
  const resolvedParentId = item.parent_id != null ? localToServerId.get(item.parent_id) ?? item.parent_id : void 0;
@@ -1238,9 +1574,9 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
1238
1574
  source: item.source ?? void 0,
1239
1575
  parent_id: resolvedParentId
1240
1576
  };
1241
- if (item.id && serverById.has(item.id)) await updateFluidOSNavigationItem(client, defId, navId, item.id, { navigation_item: body });
1577
+ if (item.id && serverById.has(item.id)) await fluid_os_v0_update_fluid_osnavigation_item(client, defId, navId, item.id, { navigation_item: body });
1242
1578
  else {
1243
- const created = await createFluidOSNavigationItem(client, defId, navId, { navigation_item: {
1579
+ const created = await fluid_os_v0_create_fluid_osnavigation_item(client, defId, navId, { navigation_item: {
1244
1580
  ...body,
1245
1581
  label: body.label ?? "",
1246
1582
  position: body.position ?? 0
@@ -1282,7 +1618,7 @@ async function pushNavigations(client, defId, portalDir, changes, mappings) {
1282
1618
  };
1283
1619
  }
1284
1620
  try {
1285
- await deleteFluidOSNavigation(client, defId, navId);
1621
+ await fluid_os_v0_delete_fluid_osnavigation(client, defId, navId);
1286
1622
  currentMappings = removeMapping(currentMappings, "navigations", slug);
1287
1623
  results.push({
1288
1624
  file,
@@ -1316,7 +1652,7 @@ async function pushProfiles(client, defId, portalDir, changes, mappings) {
1316
1652
  for (const file of changes.new) {
1317
1653
  const slug = slugFromPath(file);
1318
1654
  try {
1319
- const newId = (await createFluidOSProfile(client, defId, { profile: resolveProfileBody(await readPortalFile(portalDir, file), currentMappings) })).profile?.id;
1655
+ const newId = (await fluid_os_v0_create_fluid_osprofile(client, defId, { profile: resolveProfileBody(await readPortalFile(portalDir, file), currentMappings) })).profile?.id;
1320
1656
  if (newId != null) currentMappings = updateMapping(currentMappings, "profiles", slug, newId);
1321
1657
  results.push({
1322
1658
  file,
@@ -1352,7 +1688,7 @@ async function pushProfiles(client, defId, portalDir, changes, mappings) {
1352
1688
  };
1353
1689
  }
1354
1690
  try {
1355
- await updateFluidOSProfile(client, defId, profileId, { profile: resolveProfileBody(await readPortalFile(portalDir, file), currentMappings) });
1691
+ await fluid_os_v0_update_fluid_osprofile(client, defId, profileId, { profile: resolveProfileBody(await readPortalFile(portalDir, file), currentMappings) });
1356
1692
  results.push({
1357
1693
  file,
1358
1694
  action: "updated",
@@ -1387,7 +1723,7 @@ async function pushProfiles(client, defId, portalDir, changes, mappings) {
1387
1723
  };
1388
1724
  }
1389
1725
  try {
1390
- await deleteFluidOSProfile(client, defId, profileId);
1726
+ await fluid_os_v0_delete_fluid_osprofile(client, defId, profileId);
1391
1727
  currentMappings = removeMapping(currentMappings, "profiles", slug);
1392
1728
  results.push({
1393
1729
  file,
@@ -1810,6 +2146,1529 @@ export { manifest } from "./manifest";
1810
2146
  });
1811
2147
  const widgetCommand = new Command("widget").description("Manage custom portal widgets").addCommand(createSubcommand);
1812
2148
  //#endregion
2149
+ //#region src/utils/widget-package-artifacts.ts
2150
+ const WIDGET_PACKAGE_BUILDER_VERSION = "1";
2151
+ const CONTENT_TYPES = {
2152
+ ".css": "text/css",
2153
+ ".js": "application/javascript",
2154
+ ".json": "application/json",
2155
+ ".map": "application/json"
2156
+ };
2157
+ const CSS_ARTIFACT_PATH_PATTERN = /^[A-Za-z0-9._~-]+\.css$/;
2158
+ const JS_MAP_ARTIFACT_PATH_PATTERN = /^[A-Za-z0-9._~-]+\.js\.map$/;
2159
+ async function computeArtifactMetadata(filePath, baseDir) {
2160
+ const relativePath = toPosixPath$1(path.relative(baseDir, filePath));
2161
+ assertAllowedArtifactPath(relativePath);
2162
+ await assertRegularArtifactFile(filePath, relativePath);
2163
+ const buffer = await fs.readFile(filePath);
2164
+ return {
2165
+ path: relativePath,
2166
+ sha256: sha256(buffer),
2167
+ bytes: buffer.byteLength,
2168
+ contentType: getArtifactContentType(filePath)
2169
+ };
2170
+ }
2171
+ async function collectWidgetPackageArtifacts(publishDir) {
2172
+ const artifactPaths = await findArtifactPaths(publishDir);
2173
+ const artifacts = await Promise.all(artifactPaths.map((artifactPath) => computeArtifactMetadata(artifactPath, publishDir)));
2174
+ assertRequiredUploadArtifacts(artifacts);
2175
+ return artifacts.sort((a, b) => a.path.localeCompare(b.path));
2176
+ }
2177
+ function createPublishManifestMetadata(options) {
2178
+ return {
2179
+ packageId: options.packageId,
2180
+ version: options.version,
2181
+ builderVersion: options.builderVersion ?? "1",
2182
+ cliVersion: options.cliVersion,
2183
+ runtimeVersion: options.runtimeVersion,
2184
+ manifestHash: sha256(options.manifestContent),
2185
+ artifacts: options.artifacts
2186
+ };
2187
+ }
2188
+ function getArtifactContentType(filePath) {
2189
+ if (path.basename(filePath).endsWith(".js.map")) return "application/json";
2190
+ return CONTENT_TYPES[path.extname(filePath)] ?? "application/octet-stream";
2191
+ }
2192
+ function sha256(content) {
2193
+ return createHash("sha256").update(content).digest("hex");
2194
+ }
2195
+ function isWidgetPackageCssArtifactPath(relativePath) {
2196
+ return CSS_ARTIFACT_PATH_PATTERN.test(relativePath);
2197
+ }
2198
+ function isWidgetPackageJsMapArtifactPath(relativePath) {
2199
+ return JS_MAP_ARTIFACT_PATH_PATTERN.test(relativePath);
2200
+ }
2201
+ async function assertRegularArtifactFile(filePath, relativePath) {
2202
+ const stat = await fs.lstat(filePath);
2203
+ if (stat.isSymbolicLink()) throw new Error(`Widget publish artifact ${JSON.stringify(relativePath)} must be a regular file and must not be a symbolic link.`);
2204
+ if (!stat.isFile()) throw new Error(`Widget publish artifact ${JSON.stringify(relativePath)} must be a regular file.`);
2205
+ }
2206
+ async function findArtifactPaths(dir, baseDir = dir) {
2207
+ if (!await fs.pathExists(dir)) return [];
2208
+ const entries = await fs.readdir(dir, { withFileTypes: true });
2209
+ return (await Promise.all(entries.map(async (entry) => {
2210
+ const entryPath = path.join(dir, entry.name);
2211
+ const relativePath = toPosixPath$1(path.relative(baseDir, entryPath));
2212
+ if (entry.isSymbolicLink()) throw new Error(`Widget publish artifact ${JSON.stringify(relativePath)} must be a regular file and must not be a symbolic link.`);
2213
+ if (isUploadArtifact(relativePath)) {
2214
+ if (!entry.isFile()) throw new Error(`Widget publish artifact ${JSON.stringify(relativePath)} must be a regular file.`);
2215
+ return [entryPath];
2216
+ }
2217
+ if (entry.isDirectory()) {
2218
+ const nestedPaths = await findArtifactPaths(entryPath, baseDir);
2219
+ if (nestedPaths.length === 0) throwUnsupportedArtifactPath(`${relativePath}/`);
2220
+ return nestedPaths;
2221
+ }
2222
+ if (isAllowedGeneratedFile(relativePath)) return [];
2223
+ throwUnsupportedArtifactPath(relativePath);
2224
+ }))).flat();
2225
+ }
2226
+ function assertRequiredUploadArtifacts(artifacts) {
2227
+ const artifactPaths = new Set(artifacts.map((artifact) => artifact.path));
2228
+ for (const requiredPath of ["widget.js", "manifest.json"]) {
2229
+ if (artifactPaths.has(requiredPath)) continue;
2230
+ throw new Error(`Widget package publish output must include regular file ${JSON.stringify(requiredPath)}.`);
2231
+ }
2232
+ }
2233
+ function assertAllowedArtifactPath(relativePath) {
2234
+ if (isUploadArtifact(relativePath) || isAllowedGeneratedFile(relativePath)) return;
2235
+ throwUnsupportedArtifactPath(relativePath);
2236
+ }
2237
+ function isUploadArtifact(relativePath) {
2238
+ return relativePath === "widget.js" || relativePath === "manifest.json" || isWidgetPackageCssArtifactPath(relativePath) || isWidgetPackageJsMapArtifactPath(relativePath);
2239
+ }
2240
+ function isAllowedGeneratedFile(relativePath) {
2241
+ return isFlatArtifactPath(relativePath) && relativePath === "publish-manifest.json";
2242
+ }
2243
+ function isFlatArtifactPath(relativePath) {
2244
+ return relativePath.length > 0 && !relativePath.includes("/") && !relativePath.includes("\\");
2245
+ }
2246
+ function throwUnsupportedArtifactPath(relativePath) {
2247
+ throw new Error(`Unsupported widget publish artifact ${JSON.stringify(relativePath)}. Allowed files are widget.js, manifest.json, top-level [A-Za-z0-9._~-]+.css, top-level [A-Za-z0-9._~-]+.js.map, and publish-manifest.json.`);
2248
+ }
2249
+ function toPosixPath$1(value) {
2250
+ return value.split(path.sep).join("/");
2251
+ }
2252
+ //#endregion
2253
+ //#region src/utils/widget-package-validation.ts
2254
+ const URL_SAFE_SEGMENT_SOURCE = "[A-Za-z0-9][A-Za-z0-9_~-]*";
2255
+ const URL_SAFE_WIDGET_NAME_PATTERN = new RegExp(`^${URL_SAFE_SEGMENT_SOURCE}$`);
2256
+ const URL_SAFE_DOTTED_IDENTIFIER_PATTERN = new RegExp(`^${URL_SAFE_SEGMENT_SOURCE}(?:\\.${URL_SAFE_SEGMENT_SOURCE})*$`);
2257
+ const SEMVER_CORE_PATTERN = "(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)";
2258
+ const SEMVER_PRERELEASE_IDENTIFIER_PATTERN = "(?:0|[1-9]\\d*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)";
2259
+ const SEMVER_URL_SAFE_PATTERN = new RegExp(`^${SEMVER_CORE_PATTERN}(?:-${SEMVER_PRERELEASE_IDENTIFIER_PATTERN}(?:\\.${SEMVER_PRERELEASE_IDENTIFIER_PATTERN})*)?$`);
2260
+ const WIDGET_PACKAGE_MANIFEST_VERSION = 1;
2261
+ const DEFAULT_WIDGET_ICON = "box";
2262
+ const DEFAULT_WIDGET_CATEGORY = "components";
2263
+ const DEFAULT_WIDGET_CONTAINER = "block";
2264
+ const DEFAULT_MIN_SDK_VERSION = "0.0.0";
2265
+ function validateSingleSourceWidgetPackage(sourcePackages, options = {}) {
2266
+ if (sourcePackages.length === 0) return validationFailure({
2267
+ code: "NO_SOURCE_PACKAGE",
2268
+ 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."
2269
+ });
2270
+ if (sourcePackages.length > 1) return validationFailure({
2271
+ code: "MULTIPLE_SOURCE_PACKAGES",
2272
+ message: "Publish/deploy supports exactly one defineWidgetPackage source package. Export one package or split packages into separate builds."
2273
+ });
2274
+ const sourcePackageValue = sourcePackages[0];
2275
+ if (!sourcePackageValue) return validationFailure({
2276
+ code: "NO_SOURCE_PACKAGE",
2277
+ message: "No defineWidgetPackage source package was found."
2278
+ });
2279
+ const shapeErrors = validateSourcePackageShape(sourcePackageValue);
2280
+ if (shapeErrors.length > 0) return {
2281
+ success: false,
2282
+ errors: shapeErrors
2283
+ };
2284
+ const sourcePackage = sourcePackageValue;
2285
+ const errors = [];
2286
+ const owner = options.owner ?? sourcePackage.packageType;
2287
+ if (owner !== "company" && owner !== "droplet") errors.push({
2288
+ code: "UNSUPPORTED_OWNER",
2289
+ path: "packageType",
2290
+ message: "Widget package publish/deploy currently supports company and droplet owners. Set packageType to \"company\" or \"droplet\"."
2291
+ });
2292
+ const resolvedOwner = owner === "droplet" ? "droplet" : "company";
2293
+ const packageKey = resolvePackageKey(sourcePackage, resolvedOwner);
2294
+ const packageKeyPath = readNonEmptyTrimmedString(sourcePackage.packageStableId) ? "packageStableId" : "packageId";
2295
+ if (!isUrlSafeDottedIdentifier(packageKey)) errors.push({
2296
+ code: "INVALID_PACKAGE_KEY",
2297
+ path: packageKeyPath,
2298
+ message: "Widget package key must be URL-safe dot-separated text (letters, numbers, '.', '_', '~', and '-' only)."
2299
+ });
2300
+ if (!isUrlSafeSemver(sourcePackage.version)) errors.push({
2301
+ code: "INVALID_VERSION",
2302
+ path: "version",
2303
+ message: "Widget package version must be a URL-safe SemVer version without build metadata (for example 1.2.3 or 1.2.3-beta.1)."
2304
+ });
2305
+ if (sourcePackage.widgets.length === 0) errors.push({
2306
+ code: "NO_WIDGETS",
2307
+ path: "widgets",
2308
+ message: "Widget package must include at least one widget."
2309
+ });
2310
+ const packageId = `${resolvedOwner}.${packageKey}`;
2311
+ const seenNames = /* @__PURE__ */ new Set();
2312
+ const seenTypes = /* @__PURE__ */ new Set();
2313
+ const widgets = [];
2314
+ sourcePackage.widgets.forEach((widget, index) => {
2315
+ const path = `widgets[${index}]`;
2316
+ if (!isUrlSafeWidgetName(widget.name)) {
2317
+ errors.push({
2318
+ code: "INVALID_WIDGET_NAME",
2319
+ path: `${path}.name`,
2320
+ message: "Widget name must be URL-safe text (letters, numbers, '_', '~', and '-' only) and cannot be empty."
2321
+ });
2322
+ return;
2323
+ }
2324
+ if (seenNames.has(widget.name)) {
2325
+ errors.push({
2326
+ code: "DUPLICATE_WIDGET",
2327
+ path: `${path}.name`,
2328
+ message: `Duplicate widget name "${widget.name}" found in package ${packageKey}.`
2329
+ });
2330
+ return;
2331
+ }
2332
+ seenNames.add(widget.name);
2333
+ const type = `${packageId}.${widget.name}`;
2334
+ if (!isUrlSafeDottedIdentifier(type)) {
2335
+ errors.push({
2336
+ code: "INVALID_WIDGET_TYPE",
2337
+ path: `${path}.type`,
2338
+ message: `Generated widget type "${type}" is not URL-safe.`
2339
+ });
2340
+ return;
2341
+ }
2342
+ if (seenTypes.has(type)) {
2343
+ errors.push({
2344
+ code: "DUPLICATE_WIDGET",
2345
+ path: `${path}.type`,
2346
+ message: `Duplicate generated widget type "${type}" found.`
2347
+ });
2348
+ return;
2349
+ }
2350
+ seenTypes.add(type);
2351
+ widgets.push(sourceWidgetToDescriptor(widget, type));
2352
+ });
2353
+ if (errors.length > 0) return {
2354
+ success: false,
2355
+ errors
2356
+ };
2357
+ return {
2358
+ success: true,
2359
+ value: {
2360
+ sourcePackage,
2361
+ packageKey,
2362
+ packageId,
2363
+ owner: resolvedOwner,
2364
+ version: sourcePackage.version,
2365
+ widgets
2366
+ },
2367
+ errors: []
2368
+ };
2369
+ }
2370
+ function buildWidgetPackageDescriptor(validated, options = {}) {
2371
+ const remoteEntryUrl = options.remoteEntryUrl ?? "widget.js";
2372
+ validateRuntimeDescriptorUrl(remoteEntryUrl, "remoteEntryUrl");
2373
+ return {
2374
+ manifestVersion: WIDGET_PACKAGE_MANIFEST_VERSION,
2375
+ packageId: getWidgetPackageDescriptorPackageId(validated),
2376
+ packageType: validated.owner,
2377
+ version: validated.version,
2378
+ remoteEntryUrl,
2379
+ cssUrls: mergeCssUrls(validated.sourcePackage.cssUrls, options.cssUrls),
2380
+ widgets: validated.widgets
2381
+ };
2382
+ }
2383
+ function getWidgetPackageDescriptorPackageId(validated) {
2384
+ return validated.owner === "droplet" ? validated.packageKey : validated.packageId;
2385
+ }
2386
+ function sourceWidgetToDescriptor(widget, type) {
2387
+ const displayName = widget.displayName ?? widget.name;
2388
+ return {
2389
+ type,
2390
+ name: widget.name,
2391
+ displayName,
2392
+ description: widget.description ?? `Custom widget ${displayName}`,
2393
+ icon: widget.icon ?? DEFAULT_WIDGET_ICON,
2394
+ category: widget.category ?? DEFAULT_WIDGET_CATEGORY,
2395
+ propertySchema: normalizePropertySchema(widget.propertySchema, type),
2396
+ defaultProps: widget.defaultProps ?? {},
2397
+ container: normalizeContainer(widget.container),
2398
+ minSdkVersion: widget.minSdkVersion ?? DEFAULT_MIN_SDK_VERSION,
2399
+ resizable: normalizeResizable(widget.resizable)
2400
+ };
2401
+ }
2402
+ function validationFailure(error) {
2403
+ return {
2404
+ success: false,
2405
+ errors: [error]
2406
+ };
2407
+ }
2408
+ function validateSourcePackageShape(value) {
2409
+ if (!isRecord$3(value)) return [{
2410
+ code: "INVALID_SOURCE_PACKAGE",
2411
+ message: "Widget source package must be a JSON object."
2412
+ }];
2413
+ const errors = [];
2414
+ requireStringField(value, "packageId", "packageId", errors);
2415
+ requireStringField(value, "version", "version", errors);
2416
+ validateOptionalStringField(value, "scope", "scope", errors);
2417
+ validateOptionalStringField(value, "packageStableId", "packageStableId", errors);
2418
+ validateOptionalStringField(value, "packageType", "packageType", errors);
2419
+ if (value.cssUrls !== void 0) if (!Array.isArray(value.cssUrls)) errors.push({
2420
+ code: "INVALID_SOURCE_PACKAGE",
2421
+ path: "cssUrls",
2422
+ message: "Widget package cssUrls must be an array of strings when present."
2423
+ });
2424
+ else value.cssUrls.forEach((cssUrl, index) => {
2425
+ validateCssUrl(cssUrl, `cssUrls[${index}]`, errors);
2426
+ });
2427
+ if (!Array.isArray(value.widgets)) {
2428
+ errors.push({
2429
+ code: "INVALID_SOURCE_PACKAGE",
2430
+ path: "widgets",
2431
+ message: "Widget package widgets must be an array."
2432
+ });
2433
+ return errors;
2434
+ }
2435
+ value.widgets.forEach((widget, index) => {
2436
+ const widgetPath = `widgets[${index}]`;
2437
+ if (!isRecord$3(widget)) {
2438
+ errors.push({
2439
+ code: "INVALID_WIDGET_METADATA",
2440
+ path: widgetPath,
2441
+ message: "Widget metadata must be a JSON object."
2442
+ });
2443
+ return;
2444
+ }
2445
+ requireStringField(widget, "name", `${widgetPath}.name`, errors, {
2446
+ code: "INVALID_WIDGET_NAME",
2447
+ message: "Widget name must be a string and cannot be empty."
2448
+ });
2449
+ validateOptionalStringField(widget, "displayName", `${widgetPath}.displayName`, errors, { requireNonEmpty: true });
2450
+ validateOptionalStringField(widget, "description", `${widgetPath}.description`, errors, { requireNonEmpty: true });
2451
+ validateOptionalStringField(widget, "icon", `${widgetPath}.icon`, errors, { requireNonEmpty: true });
2452
+ validateOptionalStringField(widget, "category", `${widgetPath}.category`, errors, { requireNonEmpty: true });
2453
+ validateOptionalStringField(widget, "minSdkVersion", `${widgetPath}.minSdkVersion`, errors, { requireNonEmpty: true });
2454
+ validateOptionalRecordField(widget, "propertySchema", `${widgetPath}.propertySchema`, errors);
2455
+ validateOptionalRecordField(widget, "defaultProps", `${widgetPath}.defaultProps`, errors);
2456
+ });
2457
+ return errors;
2458
+ }
2459
+ function requireStringField(record, key, path, errors, override) {
2460
+ const value = record[key];
2461
+ if (typeof value === "string" && value.trim().length > 0) return;
2462
+ errors.push({
2463
+ code: override?.code ?? "INVALID_SOURCE_PACKAGE",
2464
+ path,
2465
+ message: override?.message ?? `${path} must be a string and cannot be empty.`
2466
+ });
2467
+ }
2468
+ function validateOptionalStringField(record, key, path, errors, options = {}) {
2469
+ const value = record[key];
2470
+ if (value === void 0) return;
2471
+ if (typeof value === "string") {
2472
+ if (!options.requireNonEmpty || value.trim().length > 0) return;
2473
+ errors.push({
2474
+ code: "INVALID_SOURCE_PACKAGE",
2475
+ path,
2476
+ message: `${path} must be a non-empty string when present.`
2477
+ });
2478
+ return;
2479
+ }
2480
+ errors.push({
2481
+ code: "INVALID_SOURCE_PACKAGE",
2482
+ path,
2483
+ message: `${path} must be a string when present.`
2484
+ });
2485
+ }
2486
+ function validateCssUrl(value, cssUrlPath, errors) {
2487
+ if (typeof value !== "string" || value.trim().length === 0) {
2488
+ errors.push({
2489
+ code: "INVALID_SOURCE_PACKAGE",
2490
+ path: cssUrlPath,
2491
+ message: "Widget package cssUrls entries must be non-empty strings."
2492
+ });
2493
+ return;
2494
+ }
2495
+ if (!isValidRuntimeDescriptorUrl(value)) {
2496
+ errors.push({
2497
+ code: "INVALID_SOURCE_PACKAGE",
2498
+ path: cssUrlPath,
2499
+ message: "Widget package cssUrls entries must be https URLs, trusted local http URLs, or relative URLs."
2500
+ });
2501
+ return;
2502
+ }
2503
+ if (!isAbsoluteRuntimeUrl(value) && !isWidgetPackageCssArtifactPath(value)) errors.push({
2504
+ code: "INVALID_SOURCE_PACKAGE",
2505
+ path: cssUrlPath,
2506
+ message: "Widget package relative cssUrls entries must be top-level CSS artifact filenames matching [A-Za-z0-9._~-]+.css."
2507
+ });
2508
+ }
2509
+ function validateRuntimeDescriptorUrl(value, urlPath) {
2510
+ if (value.trim().length === 0) throw new Error(`${urlPath} must be a non-empty string.`);
2511
+ if (!isValidRuntimeDescriptorUrl(value)) throw new Error(`${urlPath} must be an https URL, trusted local http URL, or relative URL.`);
2512
+ }
2513
+ function isValidRuntimeDescriptorUrl(value) {
2514
+ if (value.trim() !== value || containsUnsafeUrlCharacter(value)) return false;
2515
+ if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.exec(value)) {
2516
+ let parsedUrl;
2517
+ try {
2518
+ parsedUrl = new URL(value);
2519
+ } catch {
2520
+ return false;
2521
+ }
2522
+ return parsedUrl.protocol === "https:" || isTrustedLocalHttpUrl(parsedUrl, value);
2523
+ }
2524
+ if (value.slice(0, 2) === "//") return false;
2525
+ try {
2526
+ new URL(value, "http://localhost");
2527
+ return true;
2528
+ } catch {
2529
+ return false;
2530
+ }
2531
+ }
2532
+ function isTrustedLocalHttpUrl(url, rawUrl) {
2533
+ if (url.protocol !== "http:") return false;
2534
+ const hostname = extractRawHostname(rawUrl).toLowerCase();
2535
+ return hostname === "localhost" || hostname === "[::1]" || hostname === "::1" || isLoopbackIpv4Hostname(hostname);
2536
+ }
2537
+ function extractRawHostname(rawUrl) {
2538
+ const authorityStart = rawUrl.indexOf("://") + 3;
2539
+ const authorityEnd = findAuthorityEnd(rawUrl, authorityStart);
2540
+ const authority = rawUrl.slice(authorityStart, authorityEnd);
2541
+ const hostAndPort = authority.slice(authority.lastIndexOf("@") + 1);
2542
+ if (hostAndPort.charAt(0) === "[") {
2543
+ const bracketEnd = hostAndPort.indexOf("]");
2544
+ return bracketEnd === -1 ? hostAndPort : hostAndPort.slice(0, bracketEnd + 1);
2545
+ }
2546
+ const portStart = hostAndPort.indexOf(":");
2547
+ return portStart === -1 ? hostAndPort : hostAndPort.slice(0, portStart);
2548
+ }
2549
+ function findAuthorityEnd(rawUrl, authorityStart) {
2550
+ let authorityEnd = rawUrl.length;
2551
+ for (const delimiter of [
2552
+ "/",
2553
+ "?",
2554
+ "#"
2555
+ ]) {
2556
+ const delimiterIndex = rawUrl.indexOf(delimiter, authorityStart);
2557
+ if (delimiterIndex !== -1 && delimiterIndex < authorityEnd) authorityEnd = delimiterIndex;
2558
+ }
2559
+ return authorityEnd;
2560
+ }
2561
+ function isLoopbackIpv4Hostname(hostname) {
2562
+ const octets = hostname.split(".");
2563
+ if (octets.length !== 4 || octets[0] !== "127") return false;
2564
+ return octets.every(isBoundedIpv4Octet);
2565
+ }
2566
+ function isBoundedIpv4Octet(octet) {
2567
+ if (!/^\d{1,3}$/.test(octet)) return false;
2568
+ if (octet.length > 1 && octet.charAt(0) === "0") return false;
2569
+ const value = Number(octet);
2570
+ return value >= 0 && value <= 255;
2571
+ }
2572
+ function containsUnsafeUrlCharacter(value) {
2573
+ for (let index = 0; index < value.length; index += 1) {
2574
+ const charCode = value.charCodeAt(index);
2575
+ if (charCode <= 31 || charCode === 127 || charCode === 92) return true;
2576
+ }
2577
+ return false;
2578
+ }
2579
+ function validateOptionalRecordField(record, key, path, errors) {
2580
+ const value = record[key];
2581
+ if (value === void 0 || isRecord$3(value)) return;
2582
+ errors.push({
2583
+ code: "INVALID_WIDGET_METADATA",
2584
+ path,
2585
+ message: `${path} must be a JSON object when present.`
2586
+ });
2587
+ }
2588
+ function mergeCssUrls(sourceCssUrls, generatedCssUrls) {
2589
+ const generatedCssUrlSet = new Set(generatedCssUrls ?? []);
2590
+ sourceCssUrls?.forEach((cssUrl, index) => {
2591
+ validateRuntimeDescriptorUrl(cssUrl, `cssUrls[${index}]`);
2592
+ if (isAbsoluteRuntimeUrl(cssUrl)) return;
2593
+ if (!isWidgetPackageCssArtifactPath(cssUrl)) throw new Error(`cssUrls[${index}] relative URL ${JSON.stringify(cssUrl)} must be a top-level CSS artifact filename matching [A-Za-z0-9._~-]+.css.`);
2594
+ if (generatedCssUrlSet.has(cssUrl)) return;
2595
+ throw new Error(`cssUrls[${index}] relative 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.`);
2596
+ });
2597
+ const cssUrls = Array.from(new Set([...sourceCssUrls ?? [], ...generatedCssUrls ?? []]));
2598
+ cssUrls.forEach((cssUrl, index) => {
2599
+ validateRuntimeDescriptorUrl(cssUrl, `cssUrls[${index}]`);
2600
+ });
2601
+ return cssUrls;
2602
+ }
2603
+ function isAbsoluteRuntimeUrl(value) {
2604
+ return /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(value);
2605
+ }
2606
+ function normalizePropertySchema(value, widgetType) {
2607
+ return {
2608
+ ...value ?? {},
2609
+ widgetType
2610
+ };
2611
+ }
2612
+ function normalizeContainer(value) {
2613
+ if (value === "inline" || value === "block" || value === "card" || value === "fullscreen") return value;
2614
+ return DEFAULT_WIDGET_CONTAINER;
2615
+ }
2616
+ function normalizeResizable(value) {
2617
+ if (typeof value === "boolean") return value;
2618
+ if (value === "horizontal" || value === "vertical" || value === "both") return value;
2619
+ if (!isRecord$3(value)) return false;
2620
+ const horizontal = value.horizontal === true;
2621
+ const vertical = value.vertical === true;
2622
+ if (horizontal && vertical) return "both";
2623
+ if (horizontal) return "horizontal";
2624
+ if (vertical) return "vertical";
2625
+ return false;
2626
+ }
2627
+ function resolvePackageKey(sourcePackage, owner) {
2628
+ const packageStableId = readNonEmptyTrimmedString(sourcePackage.packageStableId);
2629
+ if (packageStableId) return prefixPackageStableIdWithNonOwnerScope(packageStableId, sourcePackage.scope);
2630
+ return stripOwnerScopeFromPackageId(sourcePackage.packageId, sourcePackage.scope, owner);
2631
+ }
2632
+ function prefixPackageStableIdWithNonOwnerScope(packageStableId, sourceScope) {
2633
+ const sourceScopeText = readNonEmptyTrimmedString(sourceScope);
2634
+ if (!sourceScopeText || isWidgetPackageOwnerKind(sourceScopeText)) return packageStableId;
2635
+ const sourceScopePrefix = `${sourceScopeText}.`;
2636
+ if (packageStableId.startsWith(sourceScopePrefix)) return packageStableId;
2637
+ return `${sourceScopeText}.${packageStableId}`;
2638
+ }
2639
+ function stripOwnerScopeFromPackageId(packageId, sourceScope, owner) {
2640
+ const ownerPrefix = `${owner}.`;
2641
+ if (packageId.startsWith(ownerPrefix) && packageId.length > ownerPrefix.length) return packageId.slice(ownerPrefix.length);
2642
+ const sourceScopeText = readNonEmptyTrimmedString(sourceScope);
2643
+ if (isWidgetPackageOwnerKind(sourceScopeText) && packageId.startsWith(`${sourceScopeText}.`) && packageId.length > sourceScopeText.length + 1) return packageId.slice(sourceScopeText.length + 1);
2644
+ return packageId;
2645
+ }
2646
+ function readNonEmptyTrimmedString(value) {
2647
+ const trimmed = value?.trim();
2648
+ return trimmed ? trimmed : void 0;
2649
+ }
2650
+ function isWidgetPackageOwnerKind(value) {
2651
+ return value === "company" || value === "droplet";
2652
+ }
2653
+ function isUrlSafeDottedIdentifier(value) {
2654
+ return URL_SAFE_DOTTED_IDENTIFIER_PATTERN.test(value);
2655
+ }
2656
+ function isUrlSafeWidgetName(value) {
2657
+ return URL_SAFE_WIDGET_NAME_PATTERN.test(value);
2658
+ }
2659
+ function isUrlSafeSemver(value) {
2660
+ return SEMVER_URL_SAFE_PATTERN.test(value);
2661
+ }
2662
+ function isRecord$3(value) {
2663
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2664
+ }
2665
+ //#endregion
2666
+ //#region src/utils/widget-package-builder.ts
2667
+ const DEFAULT_PUBLISH_DIR = ".fluid/widget-dist";
2668
+ const DEFAULT_RUNTIME_VERSION = "1";
2669
+ const TEMP_BUILD_DIR = ".fluid/widget-build";
2670
+ const TEMP_CLI_DIR = ".fluid/tmp";
2671
+ const PROJECT_VITE_CONFIG_CANDIDATES = [
2672
+ "vite.config.ts",
2673
+ "vite.config.mts",
2674
+ "vite.config.js",
2675
+ "vite.config.mjs"
2676
+ ];
2677
+ const BLOCKED_FLUID_PROJECT_PLUGIN_NAMES = [
2678
+ "fluid-manifest-plugin",
2679
+ "fluid-preview-plugin",
2680
+ "fluid-builder-preview",
2681
+ "fluid-builder-preview-standalone",
2682
+ "fluid-portal-dev",
2683
+ "fluid-backend-dev"
2684
+ ];
2685
+ const require = createRequire(import.meta.url);
2686
+ function validateSharedWidgetPackagePublishDir(projectDir, publishDir = DEFAULT_PUBLISH_DIR, options = {}) {
2687
+ const label = options.optionName ?? "publishDir";
2688
+ if (path.isAbsolute(publishDir)) throw new Error(`${label} must be relative to the project directory.`);
2689
+ const normalizedPublishDir = normalizePublishDirForValidation(publishDir);
2690
+ if (normalizedPublishDir === ".fluid" || !normalizedPublishDir.startsWith(".fluid/")) throw new Error(`${label} must be a relative path under .fluid/.`);
2691
+ if (isPathWithinReservedDir(normalizedPublishDir, TEMP_BUILD_DIR)) throw new Error(`${label} must not be ${TEMP_BUILD_DIR} because it is reserved for temporary builder output.`);
2692
+ if (isPathWithinReservedDir(normalizedPublishDir, TEMP_CLI_DIR)) throw new Error(`${label} must not be ${TEMP_CLI_DIR} because it is reserved for Fluid CLI temporary files.`);
2693
+ const resolvedProjectDir = path.resolve(projectDir);
2694
+ const resolvedPublishDir = path.resolve(resolvedProjectDir, publishDir);
2695
+ const relative = path.relative(resolvedProjectDir, resolvedPublishDir);
2696
+ if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`${label} must stay inside the project directory.`);
2697
+ }
2698
+ async function buildSharedWidgetPackage(options) {
2699
+ const publishDirInput = options.publishDir ?? DEFAULT_PUBLISH_DIR;
2700
+ try {
2701
+ await validateWidgetPackageOutputPaths(options.projectDir, publishDirInput);
2702
+ } catch (err) {
2703
+ return failure({
2704
+ code: "INVALID_PUBLISH_DIR",
2705
+ message: "Unsafe widget package publish directory",
2706
+ details: (err instanceof Error ? err : new Error(String(err))).message
2707
+ });
2708
+ }
2709
+ const sourcePackageResult = await loadSourceWidgetPackages(options.projectDir);
2710
+ if (!sourcePackageResult.success) return failure({
2711
+ code: "CONFIG_LOAD_FAILED",
2712
+ message: sourcePackageResult.error.message,
2713
+ details: sourcePackageResult.error.details
2714
+ });
2715
+ const validation = validateSingleSourceWidgetPackage(sourcePackageResult.value, { owner: options.owner });
2716
+ if (!validation.success || !validation.value) return failure({
2717
+ code: "VALIDATION_FAILED",
2718
+ message: validation.errors.map((error) => error.message).join("\n")
2719
+ });
2720
+ try {
2721
+ await validateWidgetPackageOutputPaths(options.projectDir, publishDirInput);
2722
+ const publishDir = path.resolve(options.projectDir, publishDirInput);
2723
+ await fs.emptyDir(publishDir);
2724
+ const tempDir = path.resolve(options.projectDir, TEMP_BUILD_DIR);
2725
+ await fs.emptyDir(tempDir);
2726
+ try {
2727
+ await buildWidgetScriptBundle({
2728
+ projectDir: options.projectDir,
2729
+ tempDir,
2730
+ publishDir,
2731
+ validated: validation.value
2732
+ });
2733
+ } finally {
2734
+ await fs.remove(tempDir).catch(() => {});
2735
+ }
2736
+ const cssUrls = await collectCssUrls(publishDir);
2737
+ const manifestContent = stableStringify(buildWidgetPackageDescriptor(validation.value, { cssUrls }));
2738
+ const manifestPath = path.join(publishDir, "manifest.json");
2739
+ const widgetScriptPath = path.join(publishDir, "widget.js");
2740
+ const publishManifestPath = path.join(publishDir, "publish-manifest.json");
2741
+ await fs.writeFile(manifestPath, manifestContent, "utf-8");
2742
+ const artifacts = await collectWidgetPackageArtifacts(publishDir);
2743
+ const publishManifest = createPublishManifestMetadata({
2744
+ packageId: getWidgetPackageDescriptorPackageId(validation.value),
2745
+ version: validation.value.version,
2746
+ cliVersion: readCliVersion(),
2747
+ runtimeVersion: DEFAULT_RUNTIME_VERSION,
2748
+ manifestContent,
2749
+ artifacts
2750
+ });
2751
+ await fs.writeFile(publishManifestPath, stableStringify(publishManifest), "utf-8");
2752
+ return success({
2753
+ publishDir,
2754
+ packageId: getWidgetPackageDescriptorPackageId(validation.value),
2755
+ version: validation.value.version,
2756
+ manifestPath,
2757
+ publishManifestPath,
2758
+ widgetScriptPath
2759
+ });
2760
+ } catch (err) {
2761
+ const error = err instanceof Error ? err : new Error(String(err));
2762
+ const isPublishDirValidationError = /^(publishDir|reserved temporary build directory) /.test(error.message);
2763
+ return failure({
2764
+ code: isPublishDirValidationError ? "INVALID_PUBLISH_DIR" : "WRITE_FAILED",
2765
+ message: isPublishDirValidationError ? "Unsafe widget package publish directory" : "Failed to build shared widget package artifacts",
2766
+ details: error.message
2767
+ });
2768
+ }
2769
+ }
2770
+ function createWidgetPackageEntrySource(options) {
2771
+ return `import * as widgetConfig from ${JSON.stringify(options.configImportPath)};
2772
+
2773
+ const SOURCE_PACKAGE_MARKER = "__fluidSourceWidgetPackage";
2774
+ const descriptors = ${JSON.stringify(options.validated.widgets, null, 2)};
2775
+
2776
+ function isSourceWidgetPackage(value) {
2777
+ return Boolean(value && typeof value === "object" && value[SOURCE_PACKAGE_MARKER] === true);
2778
+ }
2779
+
2780
+ function collectSourceWidgetPackages(mod) {
2781
+ return [
2782
+ mod.widgetPackage,
2783
+ ...(Array.isArray(mod.widgetPackages) ? mod.widgetPackages : []),
2784
+ mod.default,
2785
+ ].filter(isSourceWidgetPackage);
2786
+ }
2787
+
2788
+ const sourcePackage = collectSourceWidgetPackages(widgetConfig)[0];
2789
+ if (!sourcePackage) {
2790
+ throw new Error("No Fluid source widget package found while loading widget package bundle.");
2791
+ }
2792
+
2793
+ const componentsByName = new Map(
2794
+ sourcePackage.widgets.map((widget) => [widget.name, widget.component]),
2795
+ );
2796
+
2797
+ const widgets = descriptors.map((descriptor) => {
2798
+ const component = componentsByName.get(descriptor.name);
2799
+ if (typeof component !== "function" && typeof component !== "object") {
2800
+ throw new Error("Widget package is missing component for " + descriptor.name + ".");
2801
+ }
2802
+ return { ...descriptor, component };
2803
+ });
2804
+
2805
+ if (!window.FluidWidgets || typeof window.FluidWidgets.registerPackage !== "function") {
2806
+ throw new Error("FluidWidgets registry is not installed.");
2807
+ }
2808
+
2809
+ window.FluidWidgets.registerPackage({
2810
+ packageId: ${JSON.stringify(options.validated.packageId)},
2811
+ version: ${JSON.stringify(options.validated.version)},
2812
+ widgets,
2813
+ });
2814
+ `;
2815
+ }
2816
+ async function buildWidgetScriptBundle(options) {
2817
+ const sourceConfig = await resolvePortalWidgetSourceConfig(options.projectDir);
2818
+ if (!sourceConfig) throw new Error("No portal widget source config found.");
2819
+ const entryPath = path.join(options.tempDir, "widget-entry.ts");
2820
+ const viteConfigPath = path.join(options.tempDir, "vite.config.mjs");
2821
+ await fs.writeFile(entryPath, createWidgetPackageEntrySource({
2822
+ configImportPath: pathToFileURL(sourceConfig.path).href,
2823
+ validated: options.validated
2824
+ }), "utf-8");
2825
+ await fs.writeFile(viteConfigPath, createViteConfigSource({
2826
+ entryPath,
2827
+ publishDir: options.publishDir,
2828
+ projectConfigPath: await resolveProjectViteConfigPath(options.projectDir)
2829
+ }), "utf-8");
2830
+ const viteCliPath = resolveViteCliPath(options.projectDir);
2831
+ await execa(process.execPath, [
2832
+ viteCliPath,
2833
+ "build",
2834
+ "--config",
2835
+ viteConfigPath
2836
+ ], {
2837
+ cwd: options.projectDir,
2838
+ stdio: "pipe",
2839
+ env: {
2840
+ ...process.env,
2841
+ NODE_ENV: "production"
2842
+ }
2843
+ });
2844
+ }
2845
+ function resolveViteCliPath(projectDir) {
2846
+ const projectViteCliPath = resolveViteCliPathFromRequire(createRequire(path.join(projectDir, "package.json")));
2847
+ if (projectViteCliPath) return projectViteCliPath;
2848
+ const cliViteCliPath = resolveViteCliPathFromRequire(require);
2849
+ if (cliViteCliPath) return cliViteCliPath;
2850
+ throw new Error("Unable to resolve Vite for the portal project. Install the project dependencies and ensure vite is available before building widget packages.");
2851
+ }
2852
+ async function resolveProjectViteConfigPath(projectDir) {
2853
+ for (const fileName of PROJECT_VITE_CONFIG_CANDIDATES) {
2854
+ const candidate = path.join(projectDir, fileName);
2855
+ if (await fs.pathExists(candidate)) return candidate;
2856
+ }
2857
+ }
2858
+ function createViteConfigSource(options) {
2859
+ const widgetConfigSource = createWidgetViteConfigObjectSource({
2860
+ ...options,
2861
+ disableConfigFileLookup: !options.projectConfigPath
2862
+ });
2863
+ if (!options.projectConfigPath) return `import { defineConfig } from "vite";
2864
+
2865
+ export default defineConfig(${widgetConfigSource});
2866
+ `;
2867
+ return `import { defineConfig, loadConfigFromFile, mergeConfig } from "vite";
2868
+
2869
+ const projectConfigPath = ${JSON.stringify(options.projectConfigPath)};
2870
+ const widgetConfig = ${widgetConfigSource};
2871
+ const blockedFluidPluginNames = new Set(${JSON.stringify(BLOCKED_FLUID_PROJECT_PLUGIN_NAMES)});
2872
+
2873
+ function isAllowedProjectPlugin(plugin) {
2874
+ return !plugin || typeof plugin !== "object" || !blockedFluidPluginNames.has(plugin.name);
2875
+ }
2876
+
2877
+ function sanitizeProjectPlugins(pluginOption) {
2878
+ if (!Array.isArray(pluginOption)) {
2879
+ return isAllowedProjectPlugin(pluginOption) ? pluginOption : [];
2880
+ }
2881
+
2882
+ return pluginOption
2883
+ .map((plugin) => Array.isArray(plugin) ? sanitizeProjectPlugins(plugin) : plugin)
2884
+ .filter(isAllowedProjectPlugin);
2885
+ }
2886
+
2887
+ function copyProjectConfigValue(source, target, key) {
2888
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
2889
+ target[key] = source[key];
2890
+ }
2891
+ }
2892
+
2893
+ function sanitizeProjectConfig(config) {
2894
+ if (!config || typeof config !== "object") return {};
2895
+
2896
+ const sanitized = {};
2897
+ if (Object.prototype.hasOwnProperty.call(config, "plugins")) {
2898
+ sanitized.plugins = sanitizeProjectPlugins(config.plugins);
2899
+ }
2900
+ copyProjectConfigValue(config, sanitized, "resolve");
2901
+ copyProjectConfigValue(config, sanitized, "define");
2902
+ copyProjectConfigValue(config, sanitized, "css");
2903
+ copyProjectConfigValue(config, sanitized, "esbuild");
2904
+ copyProjectConfigValue(config, sanitized, "json");
2905
+ copyProjectConfigValue(config, sanitized, "assetsInclude");
2906
+ copyProjectConfigValue(config, sanitized, "envDir");
2907
+ copyProjectConfigValue(config, sanitized, "envPrefix");
2908
+ return sanitized;
2909
+ }
2910
+
2911
+ export default defineConfig(async (configEnv) => {
2912
+ const projectConfig = await loadConfigFromFile(configEnv, projectConfigPath);
2913
+ const safeProjectConfig = sanitizeProjectConfig(projectConfig?.config ?? {});
2914
+ return mergeConfig(safeProjectConfig, widgetConfig);
2915
+ });
2916
+ `;
2917
+ }
2918
+ function createWidgetViteConfigObjectSource(options) {
2919
+ const configFileSource = options.disableConfigFileLookup ? " configFile: false,\n" : "";
2920
+ return `(() => {
2921
+ function createFluidWidgetBuildInvariants() {
2922
+ return {
2923
+ copyPublicDir: false,
2924
+ emptyOutDir: false,
2925
+ outDir: ${JSON.stringify(options.publishDir)},
2926
+ sourcemap: false,
2927
+ lib: {
2928
+ entry: ${JSON.stringify(options.entryPath)},
2929
+ formats: ["iife"],
2930
+ name: "FluidWidgetPackage",
2931
+ fileName: () => "widget.js",
2932
+ },
2933
+ rollupOptions: {
2934
+ external: ["react", "react-dom", "react/jsx-runtime"],
2935
+ output: {
2936
+ globals: {
2937
+ react: "FluidShared.React",
2938
+ "react-dom": "FluidShared.ReactDOM",
2939
+ "react/jsx-runtime": "FluidShared.ReactJsxRuntime",
2940
+ },
2941
+ },
2942
+ },
2943
+ };
2944
+ }
2945
+
2946
+ function fluidWidgetBuildInvariantsPlugin() {
2947
+ return {
2948
+ name: "fluid-widget-build-invariants",
2949
+ enforce: "post",
2950
+ config() {
2951
+ return { build: createFluidWidgetBuildInvariants() };
2952
+ },
2953
+ configResolved(config) {
2954
+ Object.assign(config.build, createFluidWidgetBuildInvariants());
2955
+ },
2956
+ };
2957
+ }
2958
+
2959
+ return {
2960
+ ${configFileSource} publicDir: false,
2961
+ resolve: {
2962
+ conditions: ["fluid-widget-authoring"],
2963
+ },
2964
+ plugins: [fluidWidgetBuildInvariantsPlugin()],
2965
+ build: createFluidWidgetBuildInvariants(),
2966
+ };
2967
+ })()`;
2968
+ }
2969
+ async function validateWidgetPackageOutputPaths(projectDir, publishDir) {
2970
+ validateSharedWidgetPackagePublishDir(projectDir, publishDir);
2971
+ await rejectSymlinkedPathSegments({
2972
+ projectDir,
2973
+ relativePath: publishDir,
2974
+ label: "publishDir"
2975
+ });
2976
+ await rejectSymlinkedPathSegments({
2977
+ projectDir,
2978
+ relativePath: TEMP_BUILD_DIR,
2979
+ label: "reserved temporary build directory"
2980
+ });
2981
+ }
2982
+ async function collectCssUrls(publishDir) {
2983
+ return (await fs.readdir(publishDir, { withFileTypes: true })).filter((entry) => entry.isFile() && isWidgetPackageCssArtifactPath(entry.name)).map((entry) => entry.name).sort();
2984
+ }
2985
+ async function rejectSymlinkedPathSegments(options) {
2986
+ const resolvedProjectDir = path.resolve(options.projectDir);
2987
+ const resolvedTargetPath = path.resolve(resolvedProjectDir, options.relativePath);
2988
+ const segments = path.relative(resolvedProjectDir, resolvedTargetPath).split(path.sep).filter(Boolean);
2989
+ let currentPath = resolvedProjectDir;
2990
+ for (const segment of segments) {
2991
+ currentPath = path.join(currentPath, segment);
2992
+ let stat;
2993
+ try {
2994
+ stat = await fs.lstat(currentPath);
2995
+ } catch (err) {
2996
+ if (isNodeError(err) && err.code === "ENOENT") return;
2997
+ throw err;
2998
+ }
2999
+ if (stat.isSymbolicLink()) throw new Error(`${options.label} must not include symbolic links.`);
3000
+ }
3001
+ }
3002
+ function normalizePublishDirForValidation(value) {
3003
+ return toPosixPath(path.normalize(value)).replace(/\/+$/, "") || ".";
3004
+ }
3005
+ function isPathWithinReservedDir(value, reservedDir) {
3006
+ return value === reservedDir || value.startsWith(`${reservedDir}/`);
3007
+ }
3008
+ function toPosixPath(value) {
3009
+ return value.split(path.sep).join("/");
3010
+ }
3011
+ function stableStringify(value) {
3012
+ return `${JSON.stringify(value, null, 2)}\n`;
3013
+ }
3014
+ function readCliVersion() {
3015
+ const startDir = path.dirname(fileURLToPath(import.meta.url));
3016
+ for (const packageJsonPath of candidatePackageJsonPaths(startDir)) try {
3017
+ const packageJson = require(packageJsonPath);
3018
+ if (packageJson.name === "@fluid-app/fluid-cli-portal" && typeof packageJson.version === "string") return packageJson.version;
3019
+ } catch {}
3020
+ return "0.0.0";
3021
+ }
3022
+ function resolveViteCliPathFromRequire(moduleRequire) {
3023
+ try {
3024
+ const packageJsonPath = moduleRequire.resolve("vite/package.json");
3025
+ const binPath = getViteBinPath(moduleRequire(packageJsonPath).bin);
3026
+ if (!binPath) return void 0;
3027
+ return path.resolve(path.dirname(packageJsonPath), binPath);
3028
+ } catch (err) {
3029
+ if (isModuleResolutionError(err)) return void 0;
3030
+ throw err;
3031
+ }
3032
+ }
3033
+ function getViteBinPath(bin) {
3034
+ if (typeof bin === "string") return bin;
3035
+ if (!isRecord$2(bin)) return void 0;
3036
+ const viteBin = bin.vite;
3037
+ return typeof viteBin === "string" ? viteBin : void 0;
3038
+ }
3039
+ function isRecord$2(value) {
3040
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3041
+ }
3042
+ function isNodeError(value) {
3043
+ return typeof value === "object" && value !== null && "code" in value && typeof value.code === "string";
3044
+ }
3045
+ function isModuleResolutionError(value) {
3046
+ return isNodeError(value) && (value.code === "MODULE_NOT_FOUND" || value.code === "ERR_MODULE_NOT_FOUND");
3047
+ }
3048
+ function candidatePackageJsonPaths(startDir) {
3049
+ const paths = [];
3050
+ let current = startDir;
3051
+ while (true) {
3052
+ paths.push(path.join(current, "package.json"));
3053
+ const parent = path.dirname(current);
3054
+ if (parent === current) return paths;
3055
+ current = parent;
3056
+ }
3057
+ }
3058
+ //#endregion
3059
+ //#region src/utils/widget-package-upload.ts
3060
+ var WidgetPackageUploadError = class extends Error {
3061
+ code;
3062
+ constructor(code, message) {
3063
+ super(message);
3064
+ this.name = "WidgetPackageUploadError";
3065
+ this.code = code;
3066
+ }
3067
+ };
3068
+ const SESSION_ARRAY_KEYS = [
3069
+ "uploads",
3070
+ "artifacts",
3071
+ "signedUploads",
3072
+ "signed_uploads",
3073
+ "signedUrls",
3074
+ "signed_urls",
3075
+ "uploadUrls",
3076
+ "upload_urls"
3077
+ ];
3078
+ const SESSION_RECORD_KEYS = [
3079
+ "signedUrls",
3080
+ "signed_urls",
3081
+ "uploadUrls",
3082
+ "upload_urls"
3083
+ ];
3084
+ function buildWidgetPackageArtifactPayload(artifact) {
3085
+ return {
3086
+ path: artifact.path,
3087
+ sha256: artifact.sha256,
3088
+ bytes: artifact.bytes,
3089
+ contentType: artifact.contentType
3090
+ };
3091
+ }
3092
+ function buildWidgetPackageUploadSessionPayload(request) {
3093
+ return {
3094
+ version: request.version,
3095
+ package_key: request.packageKey,
3096
+ builder_version: request.builderVersion,
3097
+ cli_version: request.cliVersion,
3098
+ runtime_version: request.runtimeVersion,
3099
+ manifest_hash: request.manifestHash,
3100
+ manifest: request.manifest,
3101
+ artifacts: request.artifacts.map(buildWidgetPackageArtifactPayload)
3102
+ };
3103
+ }
3104
+ function buildWidgetPackageCompleteUploadPayload(packageKey, artifacts) {
3105
+ return {
3106
+ package_key: packageKey,
3107
+ artifacts: artifacts.map(buildWidgetPackageArtifactPayload)
3108
+ };
3109
+ }
3110
+ function getWidgetPackageUploadSessionRoute(owner) {
3111
+ switch (owner.kind) {
3112
+ case "droplet": return `/api/droplets/${encodeURIComponent(owner.uuid)}/widget_package_versions`;
3113
+ case "company": return "/api/company/fluid_os/widget_package_versions";
3114
+ }
3115
+ }
3116
+ function getWidgetPackageCompleteUploadRoute(owner, version) {
3117
+ switch (owner.kind) {
3118
+ case "droplet": return `/api/droplets/${encodeURIComponent(owner.uuid)}/widget_package_versions/${encodeURIComponent(version)}/complete_upload`;
3119
+ case "company": return `/api/company/fluid_os/widget_package_versions/${encodeURIComponent(version)}/complete_upload`;
3120
+ }
3121
+ }
3122
+ async function requestWidgetPackageUploadSession(client, owner, request) {
3123
+ const payload = buildWidgetPackageUploadSessionPayload(request);
3124
+ try {
3125
+ 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);
3126
+ } catch (err) {
3127
+ if (err instanceof WidgetPackageUploadError) throw err;
3128
+ throw new WidgetPackageUploadError("SESSION_REQUEST_FAILED", `Failed to create widget package upload session: ${formatUnknownError(err)}`);
3129
+ }
3130
+ }
3131
+ async function uploadWidgetPackageArtifacts(session, artifacts, fetchImpl = fetch) {
3132
+ const uploadsByPath = new Map(session.uploads.map((upload) => [upload.path, upload]));
3133
+ const results = [];
3134
+ for (const artifact of artifacts) {
3135
+ const upload = uploadsByPath.get(artifact.path);
3136
+ if (!upload) throw new WidgetPackageUploadError("MISSING_SIGNED_URL", `Upload session did not include a signed URL for artifact "${artifact.path}".`);
3137
+ if (upload.contentType !== artifact.contentType) throw new WidgetPackageUploadError("CONTENT_TYPE_MISMATCH", `Upload session expected artifact "${artifact.path}" to use content type "${upload.contentType}", but local artifact uses "${artifact.contentType}".`);
3138
+ let response;
3139
+ try {
3140
+ response = await fetchImpl(upload.url, {
3141
+ method: "PUT",
3142
+ headers: mergeUploadHeaders(upload.headers, upload.contentType),
3143
+ body: artifact.body
3144
+ });
3145
+ } catch (err) {
3146
+ throw new WidgetPackageUploadError("ARTIFACT_UPLOAD_FAILED", `Failed to upload artifact "${artifact.path}": ${formatUnknownError(err)}`);
3147
+ }
3148
+ if (!response.ok) {
3149
+ const body = await response.text().catch(() => "");
3150
+ const details = body.trim() ? `: ${body.trim().slice(0, 500)}` : "";
3151
+ throw new WidgetPackageUploadError("ARTIFACT_UPLOAD_FAILED", `Failed to upload artifact "${artifact.path}": PUT returned HTTP ${response.status}${details}`);
3152
+ }
3153
+ results.push({
3154
+ path: artifact.path,
3155
+ status: response.status,
3156
+ contentType: upload.contentType
3157
+ });
3158
+ }
3159
+ return results;
3160
+ }
3161
+ async function completeWidgetPackageUpload(client, owner, version, packageKey, artifacts) {
3162
+ const payload = buildWidgetPackageCompleteUploadPayload(packageKey, artifacts);
3163
+ try {
3164
+ 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);
3165
+ } catch (err) {
3166
+ if (err instanceof WidgetPackageUploadError) throw err;
3167
+ throw new WidgetPackageUploadError("COMPLETE_UPLOAD_FAILED", `Failed to complete widget package upload: ${formatUnknownError(err)}`);
3168
+ }
3169
+ }
3170
+ async function publishWidgetPackageVersion(options) {
3171
+ const sessionPayload = buildWidgetPackageUploadSessionPayload(options.request);
3172
+ if (options.dryRun) return {
3173
+ dryRun: true,
3174
+ sessionPayload
3175
+ };
3176
+ if (!options.client) throw new Error("A FetchClient is required when dryRun is false.");
3177
+ const session = await requestWidgetPackageUploadSession(options.client, options.owner, options.request);
3178
+ return {
3179
+ dryRun: false,
3180
+ sessionPayload,
3181
+ session,
3182
+ uploadedArtifacts: await uploadWidgetPackageArtifacts(session, options.request.artifacts, options.fetchImpl),
3183
+ complete: await completeWidgetPackageUpload(options.client, options.owner, session.version, options.request.packageKey, options.request.artifacts)
3184
+ };
3185
+ }
3186
+ function normalizeWidgetPackageUploadSession(response, requestedVersion, requestedArtifacts) {
3187
+ const record = requireRecord(response, "Upload session response must be a JSON object.");
3188
+ const responseVersion = getResponseVersion(record);
3189
+ if (responseVersion && responseVersion !== requestedVersion) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response version "${responseVersion}" did not match requested version "${requestedVersion}".`);
3190
+ const version = responseVersion ?? requestedVersion;
3191
+ const nestedRecord = getNestedVersionRecord(record);
3192
+ const uploads = getSignedUploads(nestedRecord ? [record, nestedRecord] : [record], requestedArtifacts);
3193
+ const missingUploadPaths = requestedArtifacts.map((artifact) => artifact.path).filter((artifactPath) => !uploads.some((upload) => upload.path === artifactPath));
3194
+ if (missingUploadPaths.length > 0) throw new WidgetPackageUploadError("INVALID_SESSION_RESPONSE", `Upload session response is missing signed URLs for artifact(s): ${missingUploadPaths.join(", ")}.`);
3195
+ return {
3196
+ version,
3197
+ uploads,
3198
+ raw: response
3199
+ };
3200
+ }
3201
+ function normalizeWidgetPackageCompleteResponse(response, requestedVersion) {
3202
+ if (response == null) return {
3203
+ version: requestedVersion,
3204
+ raw: response
3205
+ };
3206
+ const record = requireRecord(response, "Complete upload response must be a JSON object or empty response.");
3207
+ const nested = getNestedVersionRecord(record) ?? record;
3208
+ const responseVersion = getResponseVersion(nested);
3209
+ if (responseVersion && responseVersion !== requestedVersion) throw new WidgetPackageUploadError("INVALID_COMPLETE_RESPONSE", `Complete upload response version "${responseVersion}" did not match requested version "${requestedVersion}".`);
3210
+ const packageKey = getStringProperty(nested, "package_key") ?? getStringProperty(nested, "packageKey") ?? getStringProperty(nested, "packageId");
3211
+ const status = getStringProperty(nested, "status") ?? getStringProperty(nested, "state");
3212
+ return {
3213
+ version: responseVersion ?? requestedVersion,
3214
+ ...status ? { status } : {},
3215
+ ...packageKey ? { packageKey } : {},
3216
+ raw: response
3217
+ };
3218
+ }
3219
+ function getSignedUploads(records, requestedArtifacts) {
3220
+ const uploads = [];
3221
+ for (const record of records) {
3222
+ for (const key of SESSION_ARRAY_KEYS) {
3223
+ const value = record[key];
3224
+ if (!Array.isArray(value)) continue;
3225
+ for (const item of value) {
3226
+ const upload = signedUploadFromRecord(item, requestedArtifacts);
3227
+ if (upload) uploads.push(upload);
3228
+ }
3229
+ }
3230
+ for (const key of SESSION_RECORD_KEYS) {
3231
+ const value = record[key];
3232
+ if (!isRecord$1(value) || Array.isArray(value)) continue;
3233
+ for (const [artifactPath, uploadValue] of Object.entries(value)) {
3234
+ const upload = signedUploadFromKeyedValue(artifactPath, uploadValue, requestedArtifacts);
3235
+ if (upload) uploads.push(upload);
3236
+ }
3237
+ }
3238
+ }
3239
+ return dedupeUploads(uploads);
3240
+ }
3241
+ function signedUploadFromRecord(value, requestedArtifacts) {
3242
+ if (!isRecord$1(value)) return null;
3243
+ const artifactPath = readFirstString(value, [
3244
+ "path",
3245
+ "key",
3246
+ "artifactPath",
3247
+ "artifact_path",
3248
+ "artifactKey",
3249
+ "artifact_key",
3250
+ "name"
3251
+ ]);
3252
+ const url = readFirstString(value, [
3253
+ "url",
3254
+ "signedUrl",
3255
+ "signed_url",
3256
+ "uploadUrl",
3257
+ "upload_url"
3258
+ ]);
3259
+ if (!artifactPath || !url) return null;
3260
+ const requestedArtifact = findRequestedArtifact(requestedArtifacts, artifactPath);
3261
+ const contentType = readFirstString(value, [
3262
+ "contentType",
3263
+ "content_type",
3264
+ "expectedContentType",
3265
+ "expected_content_type"
3266
+ ]) ?? requestedArtifact?.contentType;
3267
+ if (!contentType) return null;
3268
+ return {
3269
+ path: artifactPath,
3270
+ url,
3271
+ contentType,
3272
+ headers: getHeaders(value["headers"])
3273
+ };
3274
+ }
3275
+ function signedUploadFromKeyedValue(artifactPath, value, requestedArtifacts) {
3276
+ const requestedArtifact = findRequestedArtifact(requestedArtifacts, artifactPath);
3277
+ if (typeof value === "string") {
3278
+ if (!requestedArtifact) return null;
3279
+ return {
3280
+ path: artifactPath,
3281
+ url: value,
3282
+ contentType: requestedArtifact.contentType,
3283
+ headers: {}
3284
+ };
3285
+ }
3286
+ if (!isRecord$1(value)) return null;
3287
+ const url = readFirstString(value, [
3288
+ "url",
3289
+ "signedUrl",
3290
+ "signed_url",
3291
+ "uploadUrl",
3292
+ "upload_url"
3293
+ ]);
3294
+ if (!url) return null;
3295
+ const contentType = readFirstString(value, [
3296
+ "contentType",
3297
+ "content_type",
3298
+ "expectedContentType",
3299
+ "expected_content_type"
3300
+ ]) ?? requestedArtifact?.contentType;
3301
+ if (!contentType) return null;
3302
+ return {
3303
+ path: artifactPath,
3304
+ url,
3305
+ contentType,
3306
+ headers: getHeaders(value["headers"])
3307
+ };
3308
+ }
3309
+ function normalizeHeaderName(name) {
3310
+ return name.toLowerCase();
3311
+ }
3312
+ function mergeUploadHeaders(headers, contentType) {
3313
+ const merged = {};
3314
+ for (const [key, value] of Object.entries(headers)) {
3315
+ if (normalizeHeaderName(key) === "content-type") continue;
3316
+ merged[key] = value;
3317
+ }
3318
+ merged["Content-Type"] = contentType;
3319
+ return merged;
3320
+ }
3321
+ function dedupeUploads(uploads) {
3322
+ const deduped = /* @__PURE__ */ new Map();
3323
+ for (const upload of uploads) deduped.set(upload.path, upload);
3324
+ return Array.from(deduped.values());
3325
+ }
3326
+ function findRequestedArtifact(requestedArtifacts, artifactPath) {
3327
+ return requestedArtifacts.find((artifact) => artifact.path === artifactPath);
3328
+ }
3329
+ function requireRecord(value, message) {
3330
+ if (!isRecord$1(value) || Array.isArray(value)) throw new WidgetPackageUploadError("INVALID_RESPONSE", message);
3331
+ return value;
3332
+ }
3333
+ function isRecord$1(value) {
3334
+ return typeof value === "object" && value !== null;
3335
+ }
3336
+ function getStringProperty(record, key) {
3337
+ const value = record[key];
3338
+ if (typeof value === "string" && value.trim().length > 0) return value;
3339
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
3340
+ }
3341
+ function readFirstString(record, keys) {
3342
+ for (const key of keys) {
3343
+ const value = getStringProperty(record, key);
3344
+ if (value) return value;
3345
+ }
3346
+ }
3347
+ function getHeaders(value) {
3348
+ if (!isRecord$1(value) || Array.isArray(value)) return {};
3349
+ const headers = {};
3350
+ for (const [key, headerValue] of Object.entries(value)) if (typeof headerValue === "string") headers[key] = headerValue;
3351
+ return headers;
3352
+ }
3353
+ function getResponseVersion(record) {
3354
+ const directVersion = getStringProperty(record, "version");
3355
+ if (directVersion) return directVersion;
3356
+ const nested = getNestedVersionRecord(record);
3357
+ return nested ? getStringProperty(nested, "version") : void 0;
3358
+ }
3359
+ function getNestedVersionRecord(record) {
3360
+ const candidates = [
3361
+ record["portal_widget_package_version"],
3362
+ record["widget_package_version"],
3363
+ record["package_version"]
3364
+ ];
3365
+ for (const candidate of candidates) if (isRecord$1(candidate) && !Array.isArray(candidate)) return candidate;
3366
+ }
3367
+ function formatUnknownError(err) {
3368
+ const base = err instanceof Error ? err.message : String(err);
3369
+ if (isRecord$1(err) && "data" in err) return `${base} — ${JSON.stringify(err.data)}`;
3370
+ return base;
3371
+ }
3372
+ //#endregion
3373
+ //#region src/commands/widget-package-publish.ts
3374
+ async function publishWidgetRuntimeArtifacts(options, dependencies = {}) {
3375
+ const buildPackage = dependencies.buildSharedWidgetPackage ?? buildSharedWidgetPackage;
3376
+ const publishVersion = dependencies.publishWidgetPackageVersion ?? publishWidgetPackageVersion;
3377
+ const isDryRun = options.dryRun === true;
3378
+ const client = options.client;
3379
+ if (!isDryRun && !client) throw new Error("A FetchClient is required when dryRun is false.");
3380
+ const safeOutDir = validateWidgetPublishOutDir(options.projectDir, options.outDir);
3381
+ const buildResult = await buildPackage({
3382
+ projectDir: options.projectDir,
3383
+ publishDir: safeOutDir,
3384
+ owner: options.buildOwner
3385
+ });
3386
+ if (!buildResult.success) {
3387
+ const details = buildResult.error.details ? `\n${buildResult.error.details}` : "";
3388
+ throw new Error(`${buildResult.error.message}${details}`);
3389
+ }
3390
+ const request = await createWidgetPackagePublishRequest({
3391
+ publishDir: buildResult.value.publishDir,
3392
+ manifestPath: buildResult.value.manifestPath,
3393
+ publishManifestPath: buildResult.value.publishManifestPath,
3394
+ owner: options.buildOwner
3395
+ });
3396
+ let publish;
3397
+ if (isDryRun) publish = await publishVersion({
3398
+ owner: options.uploadOwner,
3399
+ request,
3400
+ dryRun: true
3401
+ });
3402
+ else {
3403
+ if (!client) throw new Error("A FetchClient is required when dryRun is false.");
3404
+ publish = await publishVersion({
3405
+ client,
3406
+ owner: options.uploadOwner,
3407
+ request
3408
+ });
3409
+ }
3410
+ return {
3411
+ build: buildResult.value,
3412
+ request,
3413
+ publish
3414
+ };
3415
+ }
3416
+ async function createWidgetPackagePublishRequest(options) {
3417
+ const manifestContent = await fs.readFile(options.manifestPath);
3418
+ const manifest = parseJsonRecord(manifestContent.toString("utf-8"), options.manifestPath);
3419
+ const publishManifest = await readPublishManifest(options.publishManifestPath);
3420
+ assertManifestIdentityMatches(manifest, publishManifest, options.manifestPath);
3421
+ assertMetadataMatches({
3422
+ label: "manifestHash",
3423
+ path: "manifest.json",
3424
+ expected: publishManifest.manifestHash,
3425
+ actual: sha256(manifestContent)
3426
+ });
3427
+ const collectedArtifacts = await collectWidgetPackageArtifacts(options.publishDir);
3428
+ assertArtifactInventoryMatches(publishManifest.artifacts, collectedArtifacts);
3429
+ const artifacts = await readUploadArtifacts(options.publishDir, publishManifest.artifacts);
3430
+ return {
3431
+ version: publishManifest.version,
3432
+ packageKey: stripOwnerPrefix(publishManifest.packageId, options.owner),
3433
+ builderVersion: publishManifest.builderVersion,
3434
+ cliVersion: publishManifest.cliVersion,
3435
+ runtimeVersion: publishManifest.runtimeVersion,
3436
+ manifestHash: publishManifest.manifestHash,
3437
+ manifest,
3438
+ artifacts
3439
+ };
3440
+ }
3441
+ function createAuthenticatedWidgetPackageClient() {
3442
+ const { profile, profileName, token } = resolveAuthenticatedProfile();
3443
+ if (!token) {
3444
+ if (!profileName) throw new Error("Not logged in. Run " + chalk.cyan("fluid login") + " first.");
3445
+ throw new Error("No auth token found for profile " + chalk.cyan(profileName) + ". Run " + chalk.cyan("fluid login") + " to re-authenticate.");
3446
+ }
3447
+ return createFetchClient({
3448
+ baseUrl: profile?.baseUrl ?? process.env["FLUID_API_BASE"] ?? "https://api.fluid.app",
3449
+ getAuthToken: () => token
3450
+ });
3451
+ }
3452
+ function resolveAuthenticatedProfile() {
3453
+ const config = readConfig();
3454
+ const projectConfig = findProjectConfig(process.cwd());
3455
+ if (projectConfig?.profile) {
3456
+ const profile = config.profiles[projectConfig.profile];
3457
+ if (profile) return {
3458
+ profile,
3459
+ profileName: projectConfig.profile,
3460
+ token: profile.token
3461
+ };
3462
+ }
3463
+ if (!config.activeProfile) return {};
3464
+ const profile = config.profiles[config.activeProfile];
3465
+ if (!profile) return {};
3466
+ return {
3467
+ profile,
3468
+ profileName: config.activeProfile,
3469
+ token: profile.token
3470
+ };
3471
+ }
3472
+ function printWidgetPublishSummary(options) {
3473
+ console.log();
3474
+ console.log(chalk.green.bold(`${options.title} complete`));
3475
+ console.log();
3476
+ console.log(chalk.gray("Owner: ") + chalk.white(options.ownerLabel));
3477
+ if (options.environment) console.log(chalk.gray("Environment: ") + chalk.white(options.environment));
3478
+ console.log(chalk.gray("Package: ") + chalk.white(options.result.build.packageId));
3479
+ console.log(chalk.gray("Version: ") + chalk.white(options.result.build.version));
3480
+ console.log(chalk.gray("Output: ") + chalk.cyan(options.outDir));
3481
+ console.log(chalk.gray("Artifacts: ") + chalk.white(String(options.result.request.artifacts.length)));
3482
+ console.log();
3483
+ }
3484
+ function printRuntimeOnlyNotice() {
3485
+ console.log(chalk.yellow("This publishes widget runtime artifacts only; it does not deploy the hosted portal shell."));
3486
+ console.log();
3487
+ }
3488
+ function printDryRunSessionPayload(result) {
3489
+ console.log(chalk.bold("Dry-run upload session payload:"));
3490
+ console.log(JSON.stringify(result.publish.sessionPayload, null, 2));
3491
+ console.log();
3492
+ }
3493
+ function validateWidgetPublishOutDir(projectDir, outDir) {
3494
+ validateSharedWidgetPackagePublishDir(projectDir, outDir, { optionName: "--out-dir" });
3495
+ return outDir;
3496
+ }
3497
+ async function readJsonRecord(filePath) {
3498
+ return parseJsonRecord(await fs.readFile(filePath, "utf-8"), filePath);
3499
+ }
3500
+ function parseJsonRecord(content, filePath) {
3501
+ const value = JSON.parse(content);
3502
+ if (!isRecord(value) || Array.isArray(value)) throw new Error(`${path.basename(filePath)} must contain a JSON object.`);
3503
+ return value;
3504
+ }
3505
+ async function readPublishManifest(filePath) {
3506
+ const record = await readJsonRecord(filePath);
3507
+ const artifactsValue = record["artifacts"];
3508
+ if (!Array.isArray(artifactsValue)) throw new Error("publish-manifest.json must include an artifacts array.");
3509
+ return {
3510
+ packageId: readRequiredString(record, "packageId", filePath),
3511
+ version: readRequiredString(record, "version", filePath),
3512
+ builderVersion: readRequiredString(record, "builderVersion", filePath),
3513
+ cliVersion: readRequiredString(record, "cliVersion", filePath),
3514
+ runtimeVersion: readRequiredString(record, "runtimeVersion", filePath),
3515
+ manifestHash: readRequiredString(record, "manifestHash", filePath),
3516
+ artifacts: artifactsValue.map((artifact, index) => readArtifactMetadata(artifact, index))
3517
+ };
3518
+ }
3519
+ function readArtifactMetadata(value, index) {
3520
+ if (!isRecord(value) || Array.isArray(value)) throw new Error(`publish-manifest.json artifacts[${index}] must be a JSON object.`);
3521
+ return {
3522
+ path: readRequiredString(value, "path", "publish-manifest.json"),
3523
+ sha256: readRequiredString(value, "sha256", "publish-manifest.json"),
3524
+ bytes: readRequiredNumber(value, "bytes", "publish-manifest.json"),
3525
+ contentType: readRequiredString(value, "contentType", "publish-manifest.json")
3526
+ };
3527
+ }
3528
+ async function readUploadArtifacts(publishDir, artifacts) {
3529
+ const resolvedPublishDir = path.resolve(publishDir);
3530
+ return Promise.all(artifacts.map(async (artifact) => {
3531
+ const artifactPath = resolveArtifactPath(resolvedPublishDir, artifact.path);
3532
+ const actual = await computeArtifactMetadata(artifactPath, resolvedPublishDir);
3533
+ assertArtifactMetadataMatches(artifact, actual);
3534
+ return {
3535
+ ...actual,
3536
+ body: await fs.readFile(artifactPath)
3537
+ };
3538
+ }));
3539
+ }
3540
+ function assertManifestIdentityMatches(manifest, publishManifest, manifestPath) {
3541
+ assertMetadataMatches({
3542
+ label: "packageId",
3543
+ path: "manifest.json",
3544
+ expected: publishManifest.packageId,
3545
+ actual: readRequiredString(manifest, "packageId", manifestPath)
3546
+ });
3547
+ assertMetadataMatches({
3548
+ label: "version",
3549
+ path: "manifest.json",
3550
+ expected: publishManifest.version,
3551
+ actual: readRequiredString(manifest, "version", manifestPath)
3552
+ });
3553
+ }
3554
+ function assertArtifactInventoryMatches(expected, actual) {
3555
+ const expectedByPath = /* @__PURE__ */ new Map();
3556
+ for (const artifact of expected) {
3557
+ if (expectedByPath.has(artifact.path)) throw new Error(`publish-manifest.json artifacts include duplicate path ${JSON.stringify(artifact.path)}. Rebuild the widget package before publishing.`);
3558
+ expectedByPath.set(artifact.path, artifact);
3559
+ }
3560
+ const actualPaths = /* @__PURE__ */ new Set();
3561
+ for (const actualArtifact of actual) {
3562
+ actualPaths.add(actualArtifact.path);
3563
+ const expectedArtifact = expectedByPath.get(actualArtifact.path);
3564
+ if (!expectedArtifact) throw new Error(`publish-manifest.json artifacts omit ${JSON.stringify(actualArtifact.path)}. Rebuild the widget package before publishing.`);
3565
+ assertArtifactMetadataMatches(expectedArtifact, actualArtifact);
3566
+ }
3567
+ for (const expectedArtifact of expected) {
3568
+ if (actualPaths.has(expectedArtifact.path)) continue;
3569
+ throw new Error(`publish-manifest.json artifacts include ${JSON.stringify(expectedArtifact.path)}, but that file is not present on disk. Rebuild the widget package before publishing.`);
3570
+ }
3571
+ }
3572
+ function assertArtifactMetadataMatches(expected, actual) {
3573
+ assertMetadataMatches({
3574
+ label: "path",
3575
+ path: expected.path,
3576
+ expected: expected.path,
3577
+ actual: actual.path
3578
+ });
3579
+ assertMetadataMatches({
3580
+ label: "sha256",
3581
+ path: expected.path,
3582
+ expected: expected.sha256,
3583
+ actual: actual.sha256
3584
+ });
3585
+ assertMetadataMatches({
3586
+ label: "bytes",
3587
+ path: expected.path,
3588
+ expected: expected.bytes,
3589
+ actual: actual.bytes
3590
+ });
3591
+ assertMetadataMatches({
3592
+ label: "contentType",
3593
+ path: expected.path,
3594
+ expected: expected.contentType,
3595
+ actual: actual.contentType
3596
+ });
3597
+ }
3598
+ function assertMetadataMatches(options) {
3599
+ if (options.expected === options.actual) return;
3600
+ throw new Error(`publish-manifest.json ${options.label} for ${JSON.stringify(options.path)} does not match the file on disk. Recorded ${JSON.stringify(options.expected)}, computed ${JSON.stringify(options.actual)}. Rebuild the widget package before publishing.`);
3601
+ }
3602
+ function resolveArtifactPath(publishDir, artifactPath) {
3603
+ const resolved = path.resolve(publishDir, artifactPath);
3604
+ const relative = path.relative(publishDir, resolved);
3605
+ if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Artifact path ${JSON.stringify(artifactPath)} escapes the publish directory.`);
3606
+ return resolved;
3607
+ }
3608
+ function stripOwnerPrefix(packageId, owner) {
3609
+ const prefix = `${owner}.`;
3610
+ if (packageId.startsWith(prefix) && packageId.length > prefix.length) return packageId.slice(prefix.length);
3611
+ return packageId;
3612
+ }
3613
+ function readRequiredString(record, key, filePath) {
3614
+ const value = record[key];
3615
+ if (typeof value === "string" && value.trim().length > 0) return value;
3616
+ throw new Error(`${path.basename(filePath)} must include string ${key}.`);
3617
+ }
3618
+ function readRequiredNumber(record, key, filePath) {
3619
+ const value = record[key];
3620
+ if (typeof value === "number" && Number.isFinite(value)) return value;
3621
+ throw new Error(`${path.basename(filePath)} must include number ${key}.`);
3622
+ }
3623
+ function isRecord(value) {
3624
+ return typeof value === "object" && value !== null;
3625
+ }
3626
+ //#endregion
3627
+ //#region src/commands/deploy.ts
3628
+ const DEFAULT_OUT_DIR$1 = ".fluid/widget-dist";
3629
+ const DEFAULT_ENVIRONMENT = "production";
3630
+ const deployCommand = new Command("deploy").description("Publish company-owned portal widget runtime artifacts").option("-e, --environment <name>", "Target environment label for output reporting", DEFAULT_ENVIRONMENT).option("-o, --out-dir <dir>", "Widget artifact output directory", DEFAULT_OUT_DIR$1).option("--dry-run", "Build and validate upload payload without publishing").action(async (options) => {
3631
+ const environment = options.environment ?? DEFAULT_ENVIRONMENT;
3632
+ const outDir = options.outDir ?? DEFAULT_OUT_DIR$1;
3633
+ const dryRun = options.dryRun === true;
3634
+ console.log();
3635
+ console.log(chalk.blue.bold("Fluid Portal Deploy"));
3636
+ console.log();
3637
+ printRuntimeOnlyNotice();
3638
+ console.log(chalk.gray("Environment: ") + chalk.white(environment));
3639
+ console.log(chalk.gray("Output: ") + chalk.cyan(outDir));
3640
+ if (dryRun) console.log(chalk.yellow("Dry run: no upload will be created."));
3641
+ console.log();
3642
+ const spinner = ora("Building company-owned widget package...").start();
3643
+ try {
3644
+ const result = await publishWidgetRuntimeArtifacts({
3645
+ projectDir: process.cwd(),
3646
+ outDir,
3647
+ buildOwner: "company",
3648
+ uploadOwner: { kind: "company" },
3649
+ dryRun,
3650
+ ...dryRun ? {} : { client: createAuthenticatedWidgetPackageClient() }
3651
+ });
3652
+ spinner.succeed("Built widget runtime artifacts");
3653
+ if (dryRun) {
3654
+ console.log(chalk.yellow("Dry run complete — upload session was not requested."));
3655
+ console.log();
3656
+ printDryRunSessionPayload(result);
3657
+ } else console.log(chalk.green("Published widget package version."));
3658
+ printWidgetPublishSummary({
3659
+ title: dryRun ? "Portal deploy dry run" : "Portal deploy",
3660
+ ownerLabel: "company",
3661
+ environment,
3662
+ outDir,
3663
+ result
3664
+ });
3665
+ } catch (err) {
3666
+ spinner.fail("Portal deploy failed");
3667
+ console.error(chalk.red("Error:") + " " + (err instanceof Error ? err.message : String(err)));
3668
+ process.exit(1);
3669
+ }
3670
+ });
3671
+ //#endregion
1813
3672
  //#region src/commands/doctor.ts
1814
3673
  /** Files that are managed by the SDK and should match the canonical template. */
1815
3674
  const INFRASTRUCTURE_FILES = [
@@ -2004,7 +3863,7 @@ const createVersionCommand = new Command("create").description("Create a new ver
2004
3863
  console.log(chalk.bold("Creating version..."));
2005
3864
  let result;
2006
3865
  try {
2007
- result = await createFluidOSVersion(client, definitionId);
3866
+ result = await fluid_os_v0_create_fluid_osversion(client, definitionId);
2008
3867
  } catch (err) {
2009
3868
  console.error(chalk.red("Error:") + " Failed to create version — " + (err instanceof Error ? err.message : String(err)));
2010
3869
  process.exit(1);
@@ -2022,7 +3881,7 @@ const createVersionCommand = new Command("create").description("Create a new ver
2022
3881
  if (options.activate) {
2023
3882
  console.log("Activating version...");
2024
3883
  try {
2025
- await updateFluidOSVersion(client, definitionId, version.id, { version: { active: true } });
3884
+ await fluid_os_v0_update_fluid_osversion(client, definitionId, version.id, { version: { active: true } });
2026
3885
  } catch (err) {
2027
3886
  console.error(chalk.red("Error:") + " Failed to activate version — " + (err instanceof Error ? err.message : String(err)));
2028
3887
  process.exit(1);
@@ -2037,7 +3896,7 @@ const listVersionCommand = new Command("list").description("List all versions of
2037
3896
  const definitionId = await requireDefinitionId();
2038
3897
  let result;
2039
3898
  try {
2040
- result = await listFluidOSVersions(client, definitionId);
3899
+ result = await fluid_os_v0_list_fluid_osversions(client, definitionId);
2041
3900
  } catch (err) {
2042
3901
  console.error(chalk.red("Error:") + " Failed to list versions — " + (err instanceof Error ? err.message : String(err)));
2043
3902
  process.exit(1);
@@ -2080,7 +3939,7 @@ const activateVersionCommand = new Command("activate").description("Activate a s
2080
3939
  console.log();
2081
3940
  console.log(chalk.bold("Activating version..."));
2082
3941
  try {
2083
- await updateFluidOSVersion(client, definitionId, versionId, { version: { active: true } });
3942
+ await fluid_os_v0_update_fluid_osversion(client, definitionId, versionId, { version: { active: true } });
2084
3943
  } catch (err) {
2085
3944
  console.error(chalk.red("Error:") + " Failed to activate version — " + (err instanceof Error ? err.message : String(err)));
2086
3945
  process.exit(1);
@@ -2091,6 +3950,52 @@ const activateVersionCommand = new Command("activate").description("Activate a s
2091
3950
  });
2092
3951
  const versionCommand = new Command("version").description("Manage portal definition versions").addCommand(createVersionCommand).addCommand(listVersionCommand).addCommand(activateVersionCommand);
2093
3952
  //#endregion
3953
+ //#region src/commands/widget-publish.ts
3954
+ const DEFAULT_OUT_DIR = ".fluid/widget-dist";
3955
+ const publishSubcommand = new Command("publish").description("Publish droplet-owned widget runtime artifacts").requiredOption("--droplet <uuid>", "Droplet UUID that owns the widget package").option("-o, --out-dir <dir>", "Widget artifact output directory", DEFAULT_OUT_DIR).option("--dry-run", "Build and validate upload payload without publishing").action(async (options) => {
3956
+ const outDir = options.outDir ?? DEFAULT_OUT_DIR;
3957
+ const dryRun = options.dryRun === true;
3958
+ console.log();
3959
+ console.log(chalk.blue.bold("Fluid Widget Publish"));
3960
+ console.log();
3961
+ printRuntimeOnlyNotice();
3962
+ console.log(chalk.gray("Droplet: ") + chalk.white(options.droplet));
3963
+ console.log(chalk.gray("Output: ") + chalk.cyan(outDir));
3964
+ if (dryRun) console.log(chalk.yellow("Dry run: no upload will be created."));
3965
+ console.log();
3966
+ const spinner = ora("Building droplet-owned widget package...").start();
3967
+ try {
3968
+ const result = await publishWidgetRuntimeArtifacts({
3969
+ projectDir: process.cwd(),
3970
+ outDir,
3971
+ buildOwner: "droplet",
3972
+ uploadOwner: {
3973
+ kind: "droplet",
3974
+ uuid: options.droplet
3975
+ },
3976
+ dryRun,
3977
+ ...dryRun ? {} : { client: createAuthenticatedWidgetPackageClient() }
3978
+ });
3979
+ spinner.succeed("Built widget runtime artifacts");
3980
+ if (dryRun) {
3981
+ console.log(chalk.yellow("Dry run complete — upload session was not requested."));
3982
+ console.log();
3983
+ printDryRunSessionPayload(result);
3984
+ } else console.log(chalk.green("Published widget package version."));
3985
+ printWidgetPublishSummary({
3986
+ title: dryRun ? "Widget publish dry run" : "Widget publish",
3987
+ ownerLabel: `droplet ${options.droplet}`,
3988
+ outDir,
3989
+ result
3990
+ });
3991
+ } catch (err) {
3992
+ spinner.fail("Widget publish failed");
3993
+ console.error(chalk.red("Error:") + " " + (err instanceof Error ? err.message : String(err)));
3994
+ process.exit(1);
3995
+ }
3996
+ });
3997
+ const topLevelWidgetCommand = new Command("widget").description("Publish Fluid widget packages").addCommand(publishSubcommand);
3998
+ //#endregion
2094
3999
  //#region src/index.ts
2095
4000
  /**
2096
4001
  * @fluid-app/fluid-cli-portal
@@ -2108,6 +4013,7 @@ const plugin = {
2108
4013
  portal.addCommand(buildCommand);
2109
4014
  portal.addCommand(pullCommand);
2110
4015
  portal.addCommand(pushCommand);
4016
+ portal.addCommand(deployCommand);
2111
4017
  portal.addCommand(widgetCommand);
2112
4018
  portal.addCommand(doctorCommand);
2113
4019
  portal.addCommand(versionCommand);
@@ -2115,6 +4021,6 @@ const plugin = {
2115
4021
  }
2116
4022
  };
2117
4023
  //#endregion
2118
- export { FILE_SYSTEM_ERRORS, TEMPLATES, buildIdToSlugMap, buildNavigationIdToSlugMap, buildSnapshot, buildThemeIdToSlugMap, categorizeChanges, computeFileHash, copyTemplate, copyTemplateSafe, createCommand, createDirectory, createDirectorySafe, plugin as default, deriveScreenSlug, deriveSlug, diffAgainstSnapshot, directoryExists, doctorCommand, fileExists, getCoreVersion, getInstallCommand, getRunCommand, getSdkVersion, getSdkVersionSafe, getTemplatePaths, installDependencies, pathExists, promptProjectConfig, pullCommand, pushCommand, readFileSafe, readMappings, readSnapshot, removeMapping, resolveIdToSlug, resolveSlugToId, runPackageManager, slugFromPath, transformNavigation, transformNavigationItems, transformProfile, transformScreen, transformTheme, updateMapping, validateCrossReferences, versionCommand, widgetCommand, writeFileSafe, writeMappings, writeSnapshot };
4024
+ export { FILE_SYSTEM_ERRORS, TEMPLATES, WIDGET_PACKAGE_BUILDER_VERSION, WidgetPackageUploadError, buildIdToSlugMap, buildNavigationIdToSlugMap, buildSharedWidgetPackage, buildSnapshot, buildThemeIdToSlugMap, buildWidgetPackageCompleteUploadPayload, buildWidgetPackageDescriptor, buildWidgetPackageUploadSessionPayload, categorizeChanges, collectWidgetPackageArtifacts, completeWidgetPackageUpload, computeArtifactMetadata, computeFileHash, copyTemplate, copyTemplateSafe, createAuthenticatedWidgetPackageClient, createCommand, createDirectory, createDirectorySafe, createPublishManifestMetadata, createWidgetPackageEntrySource, createWidgetPackagePublishRequest, plugin as default, deployCommand, deriveScreenSlug, deriveSlug, diffAgainstSnapshot, directoryExists, doctorCommand, fileExists, getArtifactContentType, getCoreVersion, getInstallCommand, getRunCommand, getSdkVersion, getSdkVersionSafe, getTemplatePaths, getWidgetPackageCompleteUploadRoute, getWidgetPackageUploadSessionRoute, installDependencies, loadSourceWidgetPackages, normalizeWidgetPackageCompleteResponse, normalizeWidgetPackageUploadSession, pathExists, printDryRunSessionPayload, printRuntimeOnlyNotice, printWidgetPublishSummary, promptProjectConfig, publishWidgetPackageVersion, publishWidgetRuntimeArtifacts, pullCommand, pushCommand, readFileSafe, readMappings, readSnapshot, removeMapping, requestWidgetPackageUploadSession, resolveIdToSlug, resolvePortalWidgetSourceConfig, resolveSlugToId, runPackageManager, sha256, slugFromPath, topLevelWidgetCommand, transformNavigation, transformNavigationItems, transformProfile, transformScreen, transformTheme, updateMapping, uploadWidgetPackageArtifacts, validateCrossReferences, validateSingleSourceWidgetPackage, validateWidgetPublishOutDir, versionCommand, widgetCommand, writeFileSafe, writeMappings, writeSnapshot };
2119
4025
 
2120
4026
  //# sourceMappingURL=index.mjs.map