@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.cjs +330 -247
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +332 -249
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isAbsolute, resolve, basename, relative, sep,
|
|
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 (
|
|
671
|
-
|
|
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
|
|
674
|
-
|
|
675
|
-
const
|
|
676
|
-
|
|
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(
|
|
938
|
+
if (!ID_REGEX.test(overlayDecl.id)) {
|
|
683
939
|
throw new Error(
|
|
684
|
-
`[ethisys-platform-react] ${where}.id "${
|
|
940
|
+
`[ethisys-platform-react] ${where}.id "${overlayDecl.id}" must match [a-z0-9_-]+ (URL-safe; case-insensitive).`
|
|
685
941
|
);
|
|
686
942
|
}
|
|
687
|
-
if (
|
|
943
|
+
if (typeof overlayDecl.moduleSpecifier !== "string" || overlayDecl.moduleSpecifier.length === 0) {
|
|
688
944
|
throw new Error(
|
|
689
|
-
`[ethisys-platform-react]
|
|
945
|
+
`[ethisys-platform-react] ${where}.moduleSpecifier is required (non-empty relative path).`
|
|
690
946
|
);
|
|
691
947
|
}
|
|
692
|
-
|
|
693
|
-
|
|
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
|
|
952
|
+
`[ethisys-platform-react] ${where}.moduleSpecifier "${overlayDecl.moduleSpecifier}" does not exist on disk (resolved: ${absPath}).`
|
|
696
953
|
);
|
|
697
954
|
}
|
|
698
|
-
|
|
699
|
-
|
|
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]
|
|
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
|
|
706
|
-
|
|
707
|
-
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
|
|
710
|
-
moduleSpecifierRel:
|
|
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
|
-
//
|
|
746
|
-
//
|
|
747
|
-
//
|
|
748
|
-
|
|
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: {
|
|
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);
|