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