@ethisyscore/vite-plugin 1.31.0 → 1.33.0

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.cjs CHANGED
@@ -607,6 +607,221 @@ function titleCase(s) {
607
607
  function escapeRegExp(s) {
608
608
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
609
609
  }
610
+ var PLATFORM_REACT_EXTERNALS = [
611
+ "react",
612
+ "react/jsx-runtime",
613
+ "react-dom",
614
+ "@mui/material",
615
+ "@mui/icons-material",
616
+ "@emotion/react",
617
+ "@emotion/styled",
618
+ "date-fns",
619
+ "@ethisyscore/components-react",
620
+ "@ethisyscore/extension-runtime",
621
+ "@ethisyscore/extension-runtime/plugin"
622
+ ];
623
+ var PLATFORM_REACT_DEDUPE = [
624
+ "@tanstack/react-query",
625
+ "react",
626
+ "react-dom",
627
+ "@ethisyscore/extension-runtime"
628
+ ];
629
+ var DEFAULT_OUT_DIR = "dist";
630
+ var DEFAULT_OUTPUT_PREFIX = "platform-react";
631
+ var DEFAULT_DEFINE_PLUGIN_PAGE = "@/app/definePluginPage";
632
+ var DEFAULT_SRC_DIR = "src";
633
+ var DEFAULT_ALIAS_PREFIX = "@";
634
+ var SUMMARY_FILENAME = "platform-react-pages.json";
635
+ var DIGEST_MAP_FILENAME = "platform-react-digest-map.json";
636
+ function rewriteAliasedExternalImports() {
637
+ return {
638
+ name: "rewrite-aliased-external-imports",
639
+ renderChunk(code) {
640
+ let counter = 0;
641
+ let changed = false;
642
+ const out = code.replace(
643
+ // Optional default binding (`import Foo, { … }`) then the named braces.
644
+ /\bimport\s+(?:([\w$]+)\s*,\s*)?\{([^{}]*)\}\s*from\s*("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\s*;?/g,
645
+ (full, def, specs, mod) => {
646
+ if (!/\sas\s/.test(specs)) return full;
647
+ changed = true;
648
+ const ns = `__ns_imp_${counter++}`;
649
+ const decls = specs.split(",").map((s) => s.trim()).filter(Boolean).map((s) => {
650
+ const [imported, local] = s.split(/\s+as\s+/).map((t) => t.trim());
651
+ return `${local ?? imported} = ${ns}.${imported}`;
652
+ }).join(", ");
653
+ const defPart = def ? `import ${def} from ${mod}; ` : "";
654
+ return `${defPart}import * as ${ns} from ${mod}; const ${decls};`;
655
+ }
656
+ );
657
+ return changed ? { code: out, map: null } : null;
658
+ }
659
+ };
660
+ }
661
+ function virtualPageEntry(page, config) {
662
+ const VIRTUAL_ID = `\0virtual:surface/${page.id}`;
663
+ const views = page.views ?? {};
664
+ const names = Object.keys(views);
665
+ const viewImports = names.map((n) => `import { ${n} } from ${JSON.stringify(config.toAlias(views[n]))};`).join("\n");
666
+ const src = `import Page from ${JSON.stringify(config.toAlias(page.moduleSpecifier))};
667
+ ${viewImports}
668
+ import { definePluginPage } from ${JSON.stringify(config.definePluginPageSpecifier)};
669
+ export default definePluginPage(Page, { ${names.join(", ")} });
670
+ `;
671
+ return {
672
+ id: VIRTUAL_ID,
673
+ plugin: {
674
+ name: `virtual-surface-${page.id}`,
675
+ resolveId: (id) => id === VIRTUAL_ID ? VIRTUAL_ID : null,
676
+ load: (id) => id === VIRTUAL_ID ? src : null
677
+ }
678
+ };
679
+ }
680
+ function defaultWriteFile(path$1, contents) {
681
+ fs.mkdirSync(path.dirname(path$1), { recursive: true });
682
+ fs.writeFileSync(path$1, contents);
683
+ }
684
+ function readManifest(options, root) {
685
+ if (options.manifest !== void 0) {
686
+ return options.manifest;
687
+ }
688
+ const rel = options.manifestPath ?? "feature.manifest.json";
689
+ const abs = path.isAbsolute(rel) ? rel : path.resolve(root, rel);
690
+ if (!fs.existsSync(abs)) {
691
+ return null;
692
+ }
693
+ const raw = fs.readFileSync(abs, "utf-8");
694
+ try {
695
+ return JSON.parse(raw);
696
+ } catch (e) {
697
+ throw new Error(
698
+ `[ethisys-platform-react] Failed to parse manifest at "${abs}": ${e.message}`
699
+ );
700
+ }
701
+ }
702
+ function makeDefaultBuild(viteBuild) {
703
+ return async (ctx) => {
704
+ const build = viteBuild ?? (await import('vite')).build;
705
+ await build({
706
+ root: ctx.root,
707
+ configFile: false,
708
+ plugins: [ctx.surfacePlugin, ...ctx.plugins, rewriteAliasedExternalImports()],
709
+ resolve: {
710
+ dedupe: [...ctx.dedupe],
711
+ alias: ctx.alias
712
+ },
713
+ build: {
714
+ target: ctx.target,
715
+ outDir: ctx.outDir,
716
+ emptyOutDir: false,
717
+ assetsDir: "",
718
+ minify: ctx.minify,
719
+ sourcemap: ctx.sourcemap,
720
+ rollupOptions: {
721
+ input: { [ctx.pageId]: ctx.entryId },
722
+ preserveEntrySignatures: "strict",
723
+ external: [...ctx.external],
724
+ output: {
725
+ format: "es",
726
+ inlineDynamicImports: true,
727
+ entryFileNames: `${ctx.outputPrefix}/[name].js`,
728
+ chunkFileNames: `${ctx.outputPrefix}/[name]-[hash].js`,
729
+ assetFileNames: `${ctx.outputPrefix}/[name][extname]`
730
+ }
731
+ }
732
+ },
733
+ logLevel: "warn"
734
+ });
735
+ };
736
+ }
737
+ async function buildPlatformReactPages(options = {}) {
738
+ const root = options.root ?? process.cwd();
739
+ const logger = options.logger ?? console;
740
+ const manifest = readManifest(options, root);
741
+ const declared = manifest?.ui?.platformReactPages;
742
+ if (!Array.isArray(declared) || declared.length === 0) {
743
+ logger.warn("[build-pages] no platformReactPages declared \u2014 nothing to build");
744
+ return { pages: [], digests: [], summaryPath: null, digestMapPath: null };
745
+ }
746
+ const seen = /* @__PURE__ */ new Set();
747
+ const pages = declared.map((raw, i) => {
748
+ const decl = raw;
749
+ if (typeof decl?.id !== "string" || decl.id.length === 0) {
750
+ throw new Error(`[build-pages] ui.platformReactPages[${i}].id is required (non-empty string).`);
751
+ }
752
+ if (seen.has(decl.id)) {
753
+ throw new Error(`[build-pages] Duplicate page id "${decl.id}".`);
754
+ }
755
+ seen.add(decl.id);
756
+ if (typeof decl.moduleSpecifier !== "string" || decl.moduleSpecifier.length === 0) {
757
+ throw new Error(`[build-pages] surface "${decl.id}" has no moduleSpecifier in the manifest.`);
758
+ }
759
+ return decl;
760
+ });
761
+ const outDir = options.outDir ?? DEFAULT_OUT_DIR;
762
+ const outDirAbs = path.isAbsolute(outDir) ? outDir : path.resolve(root, outDir);
763
+ const outputPrefix = (options.outputPrefix ?? DEFAULT_OUTPUT_PREFIX).replace(/^\/+|\/+$/g, "");
764
+ const outputPrefixDir = path.resolve(outDirAbs, outputPrefix);
765
+ const external = options.external ?? PLATFORM_REACT_EXTERNALS;
766
+ const dedupe = options.dedupe ?? PLATFORM_REACT_DEDUPE;
767
+ const alias = options.alias ?? [];
768
+ const plugins = options.plugins ?? [];
769
+ const definePluginPageSpecifier = options.definePluginPageSpecifier ?? DEFAULT_DEFINE_PLUGIN_PAGE;
770
+ const srcDir = options.srcDir ?? DEFAULT_SRC_DIR;
771
+ const aliasPrefix = options.aliasPrefix ?? DEFAULT_ALIAS_PREFIX;
772
+ const srcRe = new RegExp(`^${escapeRegExp(srcDir)}/`);
773
+ const toAlias = (p) => `${aliasPrefix}/${p.replace(srcRe, "")}`;
774
+ const write = options.writeFile ?? defaultWriteFile;
775
+ const read = options.readFile ?? ((p) => fs.readFileSync(p, "utf-8"));
776
+ const runBuild = options.build ?? makeDefaultBuild(options.viteBuild);
777
+ if (options.clean ?? true) {
778
+ if (fs.existsSync(outDirAbs)) {
779
+ fs.rmSync(outDirAbs, { recursive: true, force: true });
780
+ }
781
+ fs.mkdirSync(outputPrefixDir, { recursive: true });
782
+ }
783
+ for (const page of pages) {
784
+ const { id: entryId, plugin: surfacePlugin } = virtualPageEntry(page, {
785
+ definePluginPageSpecifier,
786
+ toAlias
787
+ });
788
+ logger.log(`[build-pages] ${page.id} \u2190 ${page.moduleSpecifier}`);
789
+ await runBuild({
790
+ pageId: page.id,
791
+ entryId,
792
+ surfacePlugin,
793
+ root,
794
+ outDir: outDirAbs,
795
+ outputPrefix,
796
+ external,
797
+ dedupe,
798
+ alias,
799
+ plugins,
800
+ target: options.target ?? "es2022",
801
+ minify: options.minify ?? false,
802
+ sourcemap: options.sourcemap ?? true
803
+ });
804
+ }
805
+ const summaryPages = pages.map((p) => ({
806
+ id: p.id,
807
+ exportName: p.exportName ?? "default",
808
+ title: p.title ?? null,
809
+ bundlePath: `${outputPrefix}/${p.id}.js`,
810
+ source: p.moduleSpecifier
811
+ }));
812
+ const summaryPath = path.resolve(outputPrefixDir, SUMMARY_FILENAME);
813
+ write(summaryPath, JSON.stringify({ pages: summaryPages }, null, 2));
814
+ const digests = pages.map((p) => `${outputPrefix}/${p.id}.js`).sort().map((relPath) => {
815
+ const code = read(path.resolve(outDirAbs, relPath));
816
+ return { path: relPath, sha256: crypto.createHash("sha256").update(code, "utf8").digest("hex") };
817
+ });
818
+ const digestMapPath = path.resolve(outputPrefixDir, DIGEST_MAP_FILENAME);
819
+ write(digestMapPath, JSON.stringify({ digests }, null, 2));
820
+ logger.log(
821
+ `[build-pages] emitted ${pages.length} page(s) + summary + digest map (${digests.length} digests)`
822
+ );
823
+ return { pages: summaryPages, digests, summaryPath, digestMapPath };
824
+ }
610
825
 
611
826
  // src/platform-react/page-bundles.ts
612
827
  var ID_REGEX = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/i;
@@ -640,6 +855,7 @@ function ethisysPlatformReactPlugin(options = {}) {
640
855
  let resolvedRoot;
641
856
  let manifestAbsPath;
642
857
  let pages = [];
858
+ let overlay = null;
643
859
  function resolveRoot() {
644
860
  if (resolvedRoot) {
645
861
  return resolvedRoot;
@@ -664,54 +880,94 @@ function ethisysPlatformReactPlugin(options = {}) {
664
880
  }
665
881
  function validate() {
666
882
  pages = [];
883
+ overlay = null;
667
884
  const manifest = readManifest3();
668
885
  if (manifest === null) {
669
886
  return;
670
887
  }
671
888
  const declared = manifest.ui?.platformReactPages;
672
- if (!Array.isArray(declared) || declared.length === 0) {
673
- return;
889
+ if (Array.isArray(declared) && declared.length > 0) {
890
+ const seenIds = /* @__PURE__ */ new Set();
891
+ for (let i = 0; i < declared.length; i++) {
892
+ const decl = declared[i];
893
+ const where = `ui.platformReactPages[${i}]`;
894
+ if (typeof decl?.id !== "string" || decl.id.length === 0) {
895
+ throw new Error(
896
+ `[ethisys-platform-react] ${where}.id is required (non-empty string).`
897
+ );
898
+ }
899
+ if (!ID_REGEX.test(decl.id)) {
900
+ throw new Error(
901
+ `[ethisys-platform-react] ${where}.id "${decl.id}" must match [a-z0-9_-]+ (URL-safe; case-insensitive).`
902
+ );
903
+ }
904
+ if (seenIds.has(decl.id)) {
905
+ throw new Error(
906
+ `[ethisys-platform-react] Duplicate page id "${decl.id}".`
907
+ );
908
+ }
909
+ seenIds.add(decl.id);
910
+ if (typeof decl.moduleSpecifier !== "string" || decl.moduleSpecifier.length === 0) {
911
+ throw new Error(
912
+ `[ethisys-platform-react] ${where}.moduleSpecifier is required (non-empty relative path).`
913
+ );
914
+ }
915
+ assertHostOriginRelativePath2(decl.moduleSpecifier, `${where}.moduleSpecifier`);
916
+ const absPath = path.resolve(resolvedRoot, decl.moduleSpecifier);
917
+ if (!fs.existsSync(absPath)) {
918
+ throw new Error(
919
+ `[ethisys-platform-react] ${where}.moduleSpecifier "${decl.moduleSpecifier}" does not exist on disk (resolved: ${absPath}).`
920
+ );
921
+ }
922
+ const exportName = typeof decl.exportName === "string" && decl.exportName.length > 0 ? decl.exportName : "default";
923
+ pages.push({
924
+ id: decl.id,
925
+ exportName,
926
+ title: typeof decl.title === "string" && decl.title.length > 0 ? decl.title : null,
927
+ moduleSpecifierRel: decl.moduleSpecifier,
928
+ moduleSpecifierAbs: absPath
929
+ });
930
+ }
674
931
  }
675
- const seenIds = /* @__PURE__ */ new Set();
676
- for (let i = 0; i < declared.length; i++) {
677
- const decl = declared[i];
678
- const where = `ui.platformReactPages[${i}]`;
679
- if (typeof decl?.id !== "string" || decl.id.length === 0) {
932
+ const overlayDecl = manifest.ui?.platformReactOverlay;
933
+ if (overlayDecl !== void 0 && overlayDecl !== null) {
934
+ const where = "ui.platformReactOverlay";
935
+ if (typeof overlayDecl.id !== "string" || overlayDecl.id.length === 0) {
680
936
  throw new Error(
681
937
  `[ethisys-platform-react] ${where}.id is required (non-empty string).`
682
938
  );
683
939
  }
684
- if (!ID_REGEX.test(decl.id)) {
940
+ if (!ID_REGEX.test(overlayDecl.id)) {
685
941
  throw new Error(
686
- `[ethisys-platform-react] ${where}.id "${decl.id}" must match [a-z0-9_-]+ (URL-safe; case-insensitive).`
942
+ `[ethisys-platform-react] ${where}.id "${overlayDecl.id}" must match [a-z0-9_-]+ (URL-safe; case-insensitive).`
687
943
  );
688
944
  }
689
- if (seenIds.has(decl.id)) {
945
+ if (typeof overlayDecl.moduleSpecifier !== "string" || overlayDecl.moduleSpecifier.length === 0) {
690
946
  throw new Error(
691
- `[ethisys-platform-react] Duplicate page id "${decl.id}".`
947
+ `[ethisys-platform-react] ${where}.moduleSpecifier is required (non-empty relative path).`
692
948
  );
693
949
  }
694
- seenIds.add(decl.id);
695
- if (typeof decl.moduleSpecifier !== "string" || decl.moduleSpecifier.length === 0) {
950
+ assertHostOriginRelativePath2(overlayDecl.moduleSpecifier, `${where}.moduleSpecifier`);
951
+ const absPath = path.resolve(resolvedRoot, overlayDecl.moduleSpecifier);
952
+ if (!fs.existsSync(absPath)) {
696
953
  throw new Error(
697
- `[ethisys-platform-react] ${where}.moduleSpecifier is required (non-empty relative path).`
954
+ `[ethisys-platform-react] ${where}.moduleSpecifier "${overlayDecl.moduleSpecifier}" does not exist on disk (resolved: ${absPath}).`
698
955
  );
699
956
  }
700
- assertHostOriginRelativePath2(decl.moduleSpecifier, `${where}.moduleSpecifier`);
701
- const absPath = path.resolve(resolvedRoot, decl.moduleSpecifier);
702
- if (!fs.existsSync(absPath)) {
957
+ const collidingPage = pages.find((p) => p.id === overlayDecl.id);
958
+ if (collidingPage !== void 0) {
703
959
  throw new Error(
704
- `[ethisys-platform-react] ${where}.moduleSpecifier "${decl.moduleSpecifier}" does not exist on disk (resolved: ${absPath}).`
960
+ `[ethisys-platform-react] ui.platformReactOverlay.id "${overlayDecl.id}" collides with a declared page id \u2014 ids must be unique across pages and the overlay.`
705
961
  );
706
962
  }
707
- const exportName = typeof decl.exportName === "string" && decl.exportName.length > 0 ? decl.exportName : "default";
708
- pages.push({
709
- id: decl.id,
963
+ const exportName = typeof overlayDecl.exportName === "string" && overlayDecl.exportName.length > 0 ? overlayDecl.exportName : "default";
964
+ overlay = {
965
+ id: overlayDecl.id,
710
966
  exportName,
711
- title: typeof decl.title === "string" && decl.title.length > 0 ? decl.title : null,
712
- moduleSpecifierRel: decl.moduleSpecifier,
967
+ title: typeof overlayDecl.title === "string" && overlayDecl.title.length > 0 ? overlayDecl.title : null,
968
+ moduleSpecifierRel: overlayDecl.moduleSpecifier,
713
969
  moduleSpecifierAbs: absPath
714
- });
970
+ };
715
971
  }
716
972
  }
717
973
  let isBuild = false;
@@ -731,6 +987,15 @@ function ethisysPlatformReactPlugin(options = {}) {
731
987
  }
732
988
  return {
733
989
  build: {
990
+ // CRITICAL: emit NON-MINIFIED ESM. The host loads each bundle by
991
+ // fetching it as text and REGEX-rewriting its bare-specifier imports
992
+ // (`import { x } from "react"` → `const { x } = registry["react"]`) —
993
+ // the host's rewriter (hostModuleRegistry) assumes non-minified ESM,
994
+ // so a minified bundle leaves `react`/`react-dom`/MUI unrewritten and
995
+ // they resolve to `undefined`/`null` at mount (the overlay/page then
996
+ // throws on the first hook). Vite minifies production builds by
997
+ // default, so this MUST be forced off for every PlatformReact surface.
998
+ minify: false,
734
999
  rollupOptions: {
735
1000
  // CRITICAL: single-file ESM output per page. The host loads each
736
1001
  // page via `import(url)` at mount time and reads the declared
@@ -744,10 +1009,20 @@ function ethisysPlatformReactPlugin(options = {}) {
744
1009
  chunkFileNames: `${outputPrefix}[name]-[hash].js`,
745
1010
  assetFileNames: `${outputPrefix}[name][extname]`
746
1011
  },
747
- // Auto-externalise the host-ui-externals specifier so the host's
748
- // module-registry rewriter resolves it to the live `@coreconnect/gogo-ui`
749
- // instance at runtime Tier T remotes never ship their own Gogo copy.
750
- external: ["@ethisyscore/host-ui-externals"]
1012
+ // Externalise every host-realm specifier so the host's module-registry
1013
+ // rewriter resolves them to the live host singletons at runtime — react,
1014
+ // react-dom, MUI, emotion, components-react, extension-runtime (the shared
1015
+ // PLATFORM_REACT_EXTERNALS contract) PLUS host-ui-externals. This is
1016
+ // CRITICAL: if react is NOT externalised it gets bundled, the surface then
1017
+ // runs against a SECOND React instance, and hooks throw (invalid-hook /
1018
+ // "reading useEffect of null"). Must match the programmatic build-pages
1019
+ // externals so config-hook plugins (cc-scaffolded) behave identically.
1020
+ external: [...PLATFORM_REACT_EXTERNALS, "@ethisyscore/host-ui-externals"],
1021
+ // Nothing inside the build imports the entry module, so the bundler's
1022
+ // default entry tree-shaking would drop the declared `exportName`
1023
+ // (e.g. the overlay's `Overlay`). Pin entry signatures so the host
1024
+ // loader can read `bundle[exportName]` at mount time.
1025
+ preserveEntrySignatures: "strict"
751
1026
  }
752
1027
  }
753
1028
  };
@@ -770,13 +1045,16 @@ function ethisysPlatformReactPlugin(options = {}) {
770
1045
  return;
771
1046
  }
772
1047
  validate();
773
- if (pages.length === 0) {
1048
+ if (pages.length === 0 && overlay === null) {
774
1049
  return;
775
1050
  }
776
1051
  const input = {};
777
1052
  for (const page of pages) {
778
1053
  input[page.id] = slash2(page.moduleSpecifierAbs);
779
1054
  }
1055
+ if (overlay !== null) {
1056
+ input[overlay.id] = slash2(overlay.moduleSpecifierAbs);
1057
+ }
780
1058
  const writable = config;
781
1059
  writable.build ??= {};
782
1060
  writable.build.rollupOptions ??= {};
@@ -806,7 +1084,7 @@ function ethisysPlatformReactPlugin(options = {}) {
806
1084
  * having to re-parse the manifest.
807
1085
  */
808
1086
  generateBundle(_options, bundle) {
809
- if (pages.length === 0) {
1087
+ if (pages.length === 0 && overlay === null) {
810
1088
  return;
811
1089
  }
812
1090
  const digests = [];
@@ -828,6 +1106,15 @@ function ethisysPlatformReactPlugin(options = {}) {
828
1106
  source: p.moduleSpecifierRel
829
1107
  }))
830
1108
  };
1109
+ if (overlay !== null) {
1110
+ summary.overlay = {
1111
+ id: overlay.id,
1112
+ exportName: overlay.exportName,
1113
+ title: overlay.title,
1114
+ bundlePath: `${outputPrefix}${overlay.id}.js`,
1115
+ source: overlay.moduleSpecifierRel
1116
+ };
1117
+ }
831
1118
  this.emitFile({
832
1119
  type: "asset",
833
1120
  fileName: `${outputPrefix}platform-react-pages.json`,
@@ -929,221 +1216,6 @@ function parsePlatformReactPages(manifestPath, options = {}) {
929
1216
  }
930
1217
  return result;
931
1218
  }
932
- var PLATFORM_REACT_EXTERNALS = [
933
- "react",
934
- "react/jsx-runtime",
935
- "react-dom",
936
- "@mui/material",
937
- "@mui/icons-material",
938
- "@emotion/react",
939
- "@emotion/styled",
940
- "date-fns",
941
- "@ethisyscore/components-react",
942
- "@ethisyscore/extension-runtime",
943
- "@ethisyscore/extension-runtime/plugin"
944
- ];
945
- var PLATFORM_REACT_DEDUPE = [
946
- "@tanstack/react-query",
947
- "react",
948
- "react-dom",
949
- "@ethisyscore/extension-runtime"
950
- ];
951
- var DEFAULT_OUT_DIR = "dist";
952
- var DEFAULT_OUTPUT_PREFIX = "platform-react";
953
- var DEFAULT_DEFINE_PLUGIN_PAGE = "@/app/definePluginPage";
954
- var DEFAULT_SRC_DIR = "src";
955
- var DEFAULT_ALIAS_PREFIX = "@";
956
- var SUMMARY_FILENAME = "platform-react-pages.json";
957
- var DIGEST_MAP_FILENAME = "platform-react-digest-map.json";
958
- function rewriteAliasedExternalImports() {
959
- return {
960
- name: "rewrite-aliased-external-imports",
961
- renderChunk(code) {
962
- let counter = 0;
963
- let changed = false;
964
- const out = code.replace(
965
- // Optional default binding (`import Foo, { … }`) then the named braces.
966
- /\bimport\s+(?:([\w$]+)\s*,\s*)?\{([^{}]*)\}\s*from\s*("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\s*;?/g,
967
- (full, def, specs, mod) => {
968
- if (!/\sas\s/.test(specs)) return full;
969
- changed = true;
970
- const ns = `__ns_imp_${counter++}`;
971
- const decls = specs.split(",").map((s) => s.trim()).filter(Boolean).map((s) => {
972
- const [imported, local] = s.split(/\s+as\s+/).map((t) => t.trim());
973
- return `${local ?? imported} = ${ns}.${imported}`;
974
- }).join(", ");
975
- const defPart = def ? `import ${def} from ${mod}; ` : "";
976
- return `${defPart}import * as ${ns} from ${mod}; const ${decls};`;
977
- }
978
- );
979
- return changed ? { code: out, map: null } : null;
980
- }
981
- };
982
- }
983
- function virtualPageEntry(page, config) {
984
- const VIRTUAL_ID = `\0virtual:surface/${page.id}`;
985
- const views = page.views ?? {};
986
- const names = Object.keys(views);
987
- const viewImports = names.map((n) => `import { ${n} } from ${JSON.stringify(config.toAlias(views[n]))};`).join("\n");
988
- const src = `import Page from ${JSON.stringify(config.toAlias(page.moduleSpecifier))};
989
- ${viewImports}
990
- import { definePluginPage } from ${JSON.stringify(config.definePluginPageSpecifier)};
991
- export default definePluginPage(Page, { ${names.join(", ")} });
992
- `;
993
- return {
994
- id: VIRTUAL_ID,
995
- plugin: {
996
- name: `virtual-surface-${page.id}`,
997
- resolveId: (id) => id === VIRTUAL_ID ? VIRTUAL_ID : null,
998
- load: (id) => id === VIRTUAL_ID ? src : null
999
- }
1000
- };
1001
- }
1002
- function defaultWriteFile(path$1, contents) {
1003
- fs.mkdirSync(path.dirname(path$1), { recursive: true });
1004
- fs.writeFileSync(path$1, contents);
1005
- }
1006
- function readManifest(options, root) {
1007
- if (options.manifest !== void 0) {
1008
- return options.manifest;
1009
- }
1010
- const rel = options.manifestPath ?? "feature.manifest.json";
1011
- const abs = path.isAbsolute(rel) ? rel : path.resolve(root, rel);
1012
- if (!fs.existsSync(abs)) {
1013
- return null;
1014
- }
1015
- const raw = fs.readFileSync(abs, "utf-8");
1016
- try {
1017
- return JSON.parse(raw);
1018
- } catch (e) {
1019
- throw new Error(
1020
- `[ethisys-platform-react] Failed to parse manifest at "${abs}": ${e.message}`
1021
- );
1022
- }
1023
- }
1024
- function makeDefaultBuild(viteBuild) {
1025
- return async (ctx) => {
1026
- const build = viteBuild ?? (await import('vite')).build;
1027
- await build({
1028
- root: ctx.root,
1029
- configFile: false,
1030
- plugins: [ctx.surfacePlugin, ...ctx.plugins, rewriteAliasedExternalImports()],
1031
- resolve: {
1032
- dedupe: [...ctx.dedupe],
1033
- alias: ctx.alias
1034
- },
1035
- build: {
1036
- target: ctx.target,
1037
- outDir: ctx.outDir,
1038
- emptyOutDir: false,
1039
- assetsDir: "",
1040
- minify: ctx.minify,
1041
- sourcemap: ctx.sourcemap,
1042
- rollupOptions: {
1043
- input: { [ctx.pageId]: ctx.entryId },
1044
- preserveEntrySignatures: "strict",
1045
- external: [...ctx.external],
1046
- output: {
1047
- format: "es",
1048
- inlineDynamicImports: true,
1049
- entryFileNames: `${ctx.outputPrefix}/[name].js`,
1050
- chunkFileNames: `${ctx.outputPrefix}/[name]-[hash].js`,
1051
- assetFileNames: `${ctx.outputPrefix}/[name][extname]`
1052
- }
1053
- }
1054
- },
1055
- logLevel: "warn"
1056
- });
1057
- };
1058
- }
1059
- async function buildPlatformReactPages(options = {}) {
1060
- const root = options.root ?? process.cwd();
1061
- const logger = options.logger ?? console;
1062
- const manifest = readManifest(options, root);
1063
- const declared = manifest?.ui?.platformReactPages;
1064
- if (!Array.isArray(declared) || declared.length === 0) {
1065
- logger.warn("[build-pages] no platformReactPages declared \u2014 nothing to build");
1066
- return { pages: [], digests: [], summaryPath: null, digestMapPath: null };
1067
- }
1068
- const seen = /* @__PURE__ */ new Set();
1069
- const pages = declared.map((raw, i) => {
1070
- const decl = raw;
1071
- if (typeof decl?.id !== "string" || decl.id.length === 0) {
1072
- throw new Error(`[build-pages] ui.platformReactPages[${i}].id is required (non-empty string).`);
1073
- }
1074
- if (seen.has(decl.id)) {
1075
- throw new Error(`[build-pages] Duplicate page id "${decl.id}".`);
1076
- }
1077
- seen.add(decl.id);
1078
- if (typeof decl.moduleSpecifier !== "string" || decl.moduleSpecifier.length === 0) {
1079
- throw new Error(`[build-pages] surface "${decl.id}" has no moduleSpecifier in the manifest.`);
1080
- }
1081
- return decl;
1082
- });
1083
- const outDir = options.outDir ?? DEFAULT_OUT_DIR;
1084
- const outDirAbs = path.isAbsolute(outDir) ? outDir : path.resolve(root, outDir);
1085
- const outputPrefix = (options.outputPrefix ?? DEFAULT_OUTPUT_PREFIX).replace(/^\/+|\/+$/g, "");
1086
- const outputPrefixDir = path.resolve(outDirAbs, outputPrefix);
1087
- const external = options.external ?? PLATFORM_REACT_EXTERNALS;
1088
- const dedupe = options.dedupe ?? PLATFORM_REACT_DEDUPE;
1089
- const alias = options.alias ?? [];
1090
- const plugins = options.plugins ?? [];
1091
- const definePluginPageSpecifier = options.definePluginPageSpecifier ?? DEFAULT_DEFINE_PLUGIN_PAGE;
1092
- const srcDir = options.srcDir ?? DEFAULT_SRC_DIR;
1093
- const aliasPrefix = options.aliasPrefix ?? DEFAULT_ALIAS_PREFIX;
1094
- const srcRe = new RegExp(`^${escapeRegExp(srcDir)}/`);
1095
- const toAlias = (p) => `${aliasPrefix}/${p.replace(srcRe, "")}`;
1096
- const write = options.writeFile ?? defaultWriteFile;
1097
- const read = options.readFile ?? ((p) => fs.readFileSync(p, "utf-8"));
1098
- const runBuild = options.build ?? makeDefaultBuild(options.viteBuild);
1099
- if (options.clean ?? true) {
1100
- if (fs.existsSync(outDirAbs)) {
1101
- fs.rmSync(outDirAbs, { recursive: true, force: true });
1102
- }
1103
- fs.mkdirSync(outputPrefixDir, { recursive: true });
1104
- }
1105
- for (const page of pages) {
1106
- const { id: entryId, plugin: surfacePlugin } = virtualPageEntry(page, {
1107
- definePluginPageSpecifier,
1108
- toAlias
1109
- });
1110
- logger.log(`[build-pages] ${page.id} \u2190 ${page.moduleSpecifier}`);
1111
- await runBuild({
1112
- pageId: page.id,
1113
- entryId,
1114
- surfacePlugin,
1115
- root,
1116
- outDir: outDirAbs,
1117
- outputPrefix,
1118
- external,
1119
- dedupe,
1120
- alias,
1121
- plugins,
1122
- target: options.target ?? "es2022",
1123
- minify: options.minify ?? false,
1124
- sourcemap: options.sourcemap ?? true
1125
- });
1126
- }
1127
- const summaryPages = pages.map((p) => ({
1128
- id: p.id,
1129
- exportName: p.exportName ?? "default",
1130
- title: p.title ?? null,
1131
- bundlePath: `${outputPrefix}/${p.id}.js`,
1132
- source: p.moduleSpecifier
1133
- }));
1134
- const summaryPath = path.resolve(outputPrefixDir, SUMMARY_FILENAME);
1135
- write(summaryPath, JSON.stringify({ pages: summaryPages }, null, 2));
1136
- const digests = pages.map((p) => `${outputPrefix}/${p.id}.js`).sort().map((relPath) => {
1137
- const code = read(path.resolve(outDirAbs, relPath));
1138
- return { path: relPath, sha256: crypto.createHash("sha256").update(code, "utf8").digest("hex") };
1139
- });
1140
- const digestMapPath = path.resolve(outputPrefixDir, DIGEST_MAP_FILENAME);
1141
- write(digestMapPath, JSON.stringify({ digests }, null, 2));
1142
- logger.log(
1143
- `[build-pages] emitted ${pages.length} page(s) + summary + digest map (${digests.length} digests)`
1144
- );
1145
- return { pages: summaryPages, digests, summaryPath, digestMapPath };
1146
- }
1147
1219
  var DEFAULT_SCHEMA = "https://ethisys.dev/schemas/feature-manifest.json";
1148
1220
  function computePlatformReactPages(input) {
1149
1221
  const routePrefix = `/extensions/${input.slug}/`;
@@ -1231,16 +1303,26 @@ function generatePlatformReactManifest(config) {
1231
1303
  });
1232
1304
  const schema = config.manifest.schema ?? DEFAULT_SCHEMA;
1233
1305
  const renderMode = config.manifest.renderMode ?? "platform-react";
1306
+ const overlayEntry = config.overlay;
1234
1307
  const featureManifest = {
1235
1308
  $schema: schema,
1236
1309
  id: config.manifest.id,
1237
1310
  name: config.manifest.name,
1238
1311
  version: config.manifest.version,
1239
1312
  renderMode,
1240
- ui: { platformReactPages: entries }
1313
+ ui: {
1314
+ platformReactPages: entries,
1315
+ ...overlayEntry !== void 0 ? { platformReactOverlay: overlayEntry } : {}
1316
+ }
1241
1317
  };
1242
1318
  const featureManifestPath = path.resolve(root, featureManifestRel);
1243
1319
  fs.writeFileSync(featureManifestPath, JSON.stringify(featureManifest, null, 2) + "\n");
1320
+ const overlayWireEntry = overlayEntry !== void 0 ? {
1321
+ id: overlayEntry.id,
1322
+ moduleSpecifier: overlayEntry.moduleSpecifier,
1323
+ exportName: overlayEntry.exportName,
1324
+ title: overlayEntry.title
1325
+ } : void 0;
1244
1326
  const overlay = {
1245
1327
  $schema: schema,
1246
1328
  ui: {
@@ -1250,7 +1332,8 @@ function generatePlatformReactManifest(config) {
1250
1332
  exportName,
1251
1333
  ...route !== void 0 ? { route } : {},
1252
1334
  title
1253
- }))
1335
+ })),
1336
+ ...overlayWireEntry !== void 0 ? { platformReactOverlay: overlayWireEntry } : {}
1254
1337
  }
1255
1338
  };
1256
1339
  const overlayPath = path.resolve(root, overlayRel);