@ethisyscore/vite-plugin 1.6.3 → 1.7.1
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 +327 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +135 -2
- package/dist/index.d.ts +135 -2
- package/dist/index.js +326 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -320,7 +320,20 @@ var CONTRACT_B_SEMANTIC_PRIMITIVES = [
|
|
|
320
320
|
"Drawer",
|
|
321
321
|
"Modal",
|
|
322
322
|
"CanvasSurface",
|
|
323
|
-
"WebGLSurface"
|
|
323
|
+
"WebGLSurface",
|
|
324
|
+
// Phase 1 additions (per Contract B + PlatformReact plan §Q6, frequency
|
|
325
|
+
// survey of coreconnect-web Timesheets feature). Card / Tabs / Select /
|
|
326
|
+
// Alert close the four most-common gaps in the v1 primitive set:
|
|
327
|
+
// - Card: universal layout container, ~20 imports across surveyed pages.
|
|
328
|
+
// - Tabs: settings / configuration surfaces (6 tabs in TimesheetSettings).
|
|
329
|
+
// - Select: filter + form input, ~10 imports; Form's text input doesn't
|
|
330
|
+
// cover dropdowns.
|
|
331
|
+
// - Alert: lock/unlock warnings, validation banners, non-modal system
|
|
332
|
+
// messages; Card doesn't carry severity semantics.
|
|
333
|
+
"Card",
|
|
334
|
+
"Tabs",
|
|
335
|
+
"Select",
|
|
336
|
+
"Alert"
|
|
324
337
|
];
|
|
325
338
|
var CONTRACT_B_RUNTIME_IMPORTS = [
|
|
326
339
|
"react",
|
|
@@ -579,9 +592,313 @@ function ethisysContractBPlugin(options = {}) {
|
|
|
579
592
|
}
|
|
580
593
|
};
|
|
581
594
|
}
|
|
595
|
+
var ID_REGEX = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/i;
|
|
596
|
+
function assertHostOriginRelativePath2(value, label) {
|
|
597
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
|
|
598
|
+
throw new Error(
|
|
599
|
+
`[ethisys-platform-react] ${label} "${value}" must be a host-origin relative path, not a remote URL.`
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
if (/^(data|blob|javascript):/i.test(value)) {
|
|
603
|
+
throw new Error(
|
|
604
|
+
`[ethisys-platform-react] ${label} "${value}" must be a host-origin relative path, not a ${value.split(":")[0]}: URL.`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
const normalized = path.normalize(value);
|
|
608
|
+
if (path.isAbsolute(value) || path.isAbsolute(normalized)) {
|
|
609
|
+
throw new Error(
|
|
610
|
+
`[ethisys-platform-react] ${label} "${value}" must be relative, not absolute.`
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
if (normalized.split(/[\\/]/).includes("..")) {
|
|
614
|
+
throw new Error(
|
|
615
|
+
`[ethisys-platform-react] ${label} "${value}" must not contain path traversal.`
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function slash2(p) {
|
|
620
|
+
return path.sep === "\\" ? p.replace(/\\/g, "/") : p;
|
|
621
|
+
}
|
|
622
|
+
function ethisysPlatformReactPlugin(options = {}) {
|
|
623
|
+
const explicitRoot = options.root;
|
|
624
|
+
const manifestRel = options.manifestPath ?? "feature.manifest.json";
|
|
625
|
+
const outputPrefix = (options.outputPrefix ?? "platform-react/").replace(/\/+$/, "/").replace(/^\/+/, "");
|
|
626
|
+
let resolvedRoot;
|
|
627
|
+
let manifestAbsPath;
|
|
628
|
+
let pages = [];
|
|
629
|
+
function resolveRoot() {
|
|
630
|
+
if (resolvedRoot) {
|
|
631
|
+
return resolvedRoot;
|
|
632
|
+
}
|
|
633
|
+
resolvedRoot = explicitRoot ?? process.cwd();
|
|
634
|
+
manifestAbsPath = path.isAbsolute(manifestRel) ? manifestRel : path.resolve(resolvedRoot, manifestRel);
|
|
635
|
+
return resolvedRoot;
|
|
636
|
+
}
|
|
637
|
+
function readManifest() {
|
|
638
|
+
resolveRoot();
|
|
639
|
+
if (!fs.existsSync(manifestAbsPath)) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
const raw = fs.readFileSync(manifestAbsPath, "utf-8");
|
|
643
|
+
try {
|
|
644
|
+
return JSON.parse(raw);
|
|
645
|
+
} catch (e) {
|
|
646
|
+
throw new Error(
|
|
647
|
+
`[ethisys-platform-react] Failed to parse manifest at "${manifestAbsPath}": ${e.message}`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function validate() {
|
|
652
|
+
pages = [];
|
|
653
|
+
const manifest = readManifest();
|
|
654
|
+
if (manifest === null) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const declared = manifest.ui?.platformReactPages;
|
|
658
|
+
if (!Array.isArray(declared) || declared.length === 0) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
662
|
+
for (let i = 0; i < declared.length; i++) {
|
|
663
|
+
const decl = declared[i];
|
|
664
|
+
const where = `ui.platformReactPages[${i}]`;
|
|
665
|
+
if (typeof decl?.id !== "string" || decl.id.length === 0) {
|
|
666
|
+
throw new Error(
|
|
667
|
+
`[ethisys-platform-react] ${where}.id is required (non-empty string).`
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
if (!ID_REGEX.test(decl.id)) {
|
|
671
|
+
throw new Error(
|
|
672
|
+
`[ethisys-platform-react] ${where}.id "${decl.id}" must match [a-z0-9_-]+ (URL-safe; case-insensitive).`
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
if (seenIds.has(decl.id)) {
|
|
676
|
+
throw new Error(
|
|
677
|
+
`[ethisys-platform-react] Duplicate page id "${decl.id}".`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
seenIds.add(decl.id);
|
|
681
|
+
if (typeof decl.moduleSpecifier !== "string" || decl.moduleSpecifier.length === 0) {
|
|
682
|
+
throw new Error(
|
|
683
|
+
`[ethisys-platform-react] ${where}.moduleSpecifier is required (non-empty relative path).`
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
assertHostOriginRelativePath2(decl.moduleSpecifier, `${where}.moduleSpecifier`);
|
|
687
|
+
const absPath = path.resolve(resolvedRoot, decl.moduleSpecifier);
|
|
688
|
+
if (!fs.existsSync(absPath)) {
|
|
689
|
+
throw new Error(
|
|
690
|
+
`[ethisys-platform-react] ${where}.moduleSpecifier "${decl.moduleSpecifier}" does not exist on disk (resolved: ${absPath}).`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
const exportName = typeof decl.exportName === "string" && decl.exportName.length > 0 ? decl.exportName : "default";
|
|
694
|
+
pages.push({
|
|
695
|
+
id: decl.id,
|
|
696
|
+
exportName,
|
|
697
|
+
title: typeof decl.title === "string" && decl.title.length > 0 ? decl.title : null,
|
|
698
|
+
moduleSpecifierRel: decl.moduleSpecifier,
|
|
699
|
+
moduleSpecifierAbs: absPath
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
let isBuild = false;
|
|
704
|
+
return {
|
|
705
|
+
name: "ethisys-platform-react",
|
|
706
|
+
// `pre` ordering: validation should fire before user-supplied plugins.
|
|
707
|
+
enforce: "pre",
|
|
708
|
+
/**
|
|
709
|
+
* Force ESM single-module output per page. Output options don't depend on
|
|
710
|
+
* the resolved root, so they're safe to declare here — `config()` runs
|
|
711
|
+
* before `configResolved()`. The Rollup input table is added later in
|
|
712
|
+
* `configResolved()` once Vite has populated `resolvedRoot` correctly.
|
|
713
|
+
*/
|
|
714
|
+
config(_config, { command }) {
|
|
715
|
+
if (command !== "build") {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
build: {
|
|
720
|
+
rollupOptions: {
|
|
721
|
+
// CRITICAL: single-file ESM output per page. The host loads each
|
|
722
|
+
// page via `import(url)` at mount time and reads the declared
|
|
723
|
+
// `exportName` from the resulting module namespace. Chunk-split
|
|
724
|
+
// output would break that contract because the host has no
|
|
725
|
+
// import-map surface to resolve sibling chunks.
|
|
726
|
+
output: {
|
|
727
|
+
format: "es",
|
|
728
|
+
inlineDynamicImports: true,
|
|
729
|
+
entryFileNames: `${outputPrefix}[name].js`,
|
|
730
|
+
chunkFileNames: `${outputPrefix}[name]-[hash].js`,
|
|
731
|
+
assetFileNames: `${outputPrefix}[name][extname]`
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
},
|
|
737
|
+
/**
|
|
738
|
+
* Resolve the actual root Vite is using (which may differ from
|
|
739
|
+
* `process.cwd()`), validate the manifest against it, and mutate the
|
|
740
|
+
* resolved config's Rollup input table so each declared page becomes a
|
|
741
|
+
* build entry. Mutation in `configResolved` is the standard Vite-plugin
|
|
742
|
+
* pattern for input contributions that depend on the resolved root —
|
|
743
|
+
* `config()` runs too early to know the real value.
|
|
744
|
+
*/
|
|
745
|
+
configResolved(config) {
|
|
746
|
+
isBuild = config.command === "build";
|
|
747
|
+
if (!explicitRoot) {
|
|
748
|
+
resolvedRoot = config.root;
|
|
749
|
+
manifestAbsPath = path.isAbsolute(manifestRel) ? manifestRel : path.resolve(resolvedRoot, manifestRel);
|
|
750
|
+
}
|
|
751
|
+
if (!isBuild) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
validate();
|
|
755
|
+
if (pages.length === 0) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
const input = {};
|
|
759
|
+
for (const page of pages) {
|
|
760
|
+
input[page.id] = slash2(page.moduleSpecifierAbs);
|
|
761
|
+
}
|
|
762
|
+
const writable = config;
|
|
763
|
+
writable.build ??= {};
|
|
764
|
+
writable.build.rollupOptions ??= {};
|
|
765
|
+
const rollupOptions = writable.build.rollupOptions;
|
|
766
|
+
const existing = rollupOptions.input;
|
|
767
|
+
if (existing && typeof existing === "object" && !Array.isArray(existing)) {
|
|
768
|
+
rollupOptions.input = { ...existing, ...input };
|
|
769
|
+
} else if (typeof existing === "string") {
|
|
770
|
+
rollupOptions.input = { _entry: existing, ...input };
|
|
771
|
+
} else if (Array.isArray(existing)) {
|
|
772
|
+
const indexed = {};
|
|
773
|
+
existing.forEach((entry, idx) => {
|
|
774
|
+
indexed[`_entry${idx}`] = entry;
|
|
775
|
+
});
|
|
776
|
+
rollupOptions.input = { ...indexed, ...input };
|
|
777
|
+
} else {
|
|
778
|
+
rollupOptions.input = input;
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
buildStart() {
|
|
782
|
+
validate();
|
|
783
|
+
},
|
|
784
|
+
/**
|
|
785
|
+
* Emit the companion `platform-react-pages.json` listing every declared
|
|
786
|
+
* page. The `.ccpkg` packaging step (and the host's runtime loader) read
|
|
787
|
+
* this file to discover which page modules ship in the bundle without
|
|
788
|
+
* having to re-parse the manifest.
|
|
789
|
+
*/
|
|
790
|
+
generateBundle() {
|
|
791
|
+
if (pages.length === 0) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const summary = {
|
|
795
|
+
pages: pages.map((p) => ({
|
|
796
|
+
id: p.id,
|
|
797
|
+
exportName: p.exportName,
|
|
798
|
+
title: p.title,
|
|
799
|
+
bundlePath: `${outputPrefix}${p.id}.js`,
|
|
800
|
+
source: p.moduleSpecifierRel
|
|
801
|
+
}))
|
|
802
|
+
};
|
|
803
|
+
this.emitFile({
|
|
804
|
+
type: "asset",
|
|
805
|
+
fileName: `${outputPrefix}platform-react-pages.json`,
|
|
806
|
+
source: JSON.stringify(summary, null, 2)
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
var ID_REGEX2 = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/i;
|
|
812
|
+
function assertHostOriginRelativePath3(value, label) {
|
|
813
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
|
|
814
|
+
throw new Error(
|
|
815
|
+
`[ethisys-platform-react] ${label} "${value}" must be a host-origin relative path, not a remote URL.`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
if (/^(data|blob|javascript):/i.test(value)) {
|
|
819
|
+
throw new Error(
|
|
820
|
+
`[ethisys-platform-react] ${label} "${value}" must be a host-origin relative path, not a ${value.split(":")[0]}: URL.`
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
const normalized = path.normalize(value);
|
|
824
|
+
if (path.isAbsolute(value) || path.isAbsolute(normalized)) {
|
|
825
|
+
throw new Error(
|
|
826
|
+
`[ethisys-platform-react] ${label} "${value}" must be relative, not absolute.`
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
if (normalized.split(/[\\/]/).includes("..")) {
|
|
830
|
+
throw new Error(
|
|
831
|
+
`[ethisys-platform-react] ${label} "${value}" must not contain path traversal.`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
function parsePlatformReactPages(manifestPath, options = {}) {
|
|
836
|
+
const root = options.root ?? process.cwd();
|
|
837
|
+
const verifyOnDisk = options.verifyOnDisk ?? false;
|
|
838
|
+
const manifestAbsPath = path.isAbsolute(manifestPath) ? manifestPath : path.resolve(root, manifestPath);
|
|
839
|
+
if (!fs.existsSync(manifestAbsPath)) {
|
|
840
|
+
return [];
|
|
841
|
+
}
|
|
842
|
+
let manifest;
|
|
843
|
+
try {
|
|
844
|
+
const raw = fs.readFileSync(manifestAbsPath, "utf-8");
|
|
845
|
+
manifest = JSON.parse(raw);
|
|
846
|
+
} catch (e) {
|
|
847
|
+
throw new Error(
|
|
848
|
+
`[ethisys-platform-react] Failed to parse manifest at "${manifestAbsPath}": ${e.message}`
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
const declared = manifest.ui?.platformReactPages;
|
|
852
|
+
if (!Array.isArray(declared) || declared.length === 0) {
|
|
853
|
+
return [];
|
|
854
|
+
}
|
|
855
|
+
const result = [];
|
|
856
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
857
|
+
for (let i = 0; i < declared.length; i++) {
|
|
858
|
+
const decl = declared[i];
|
|
859
|
+
const where = `ui.platformReactPages[${i}]`;
|
|
860
|
+
if (typeof decl?.id !== "string" || decl.id.length === 0) {
|
|
861
|
+
throw new Error(
|
|
862
|
+
`[ethisys-platform-react] ${where}.id is required (non-empty string).`
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
if (!ID_REGEX2.test(decl.id)) {
|
|
866
|
+
throw new Error(
|
|
867
|
+
`[ethisys-platform-react] ${where}.id "${decl.id}" must match [a-z0-9_-]+ (URL-safe; case-insensitive).`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
if (seenIds.has(decl.id)) {
|
|
871
|
+
throw new Error(
|
|
872
|
+
`[ethisys-platform-react] Duplicate page id "${decl.id}".`
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
seenIds.add(decl.id);
|
|
876
|
+
if (typeof decl.moduleSpecifier !== "string" || decl.moduleSpecifier.length === 0) {
|
|
877
|
+
throw new Error(
|
|
878
|
+
`[ethisys-platform-react] ${where}.moduleSpecifier is required (non-empty relative path).`
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
assertHostOriginRelativePath3(decl.moduleSpecifier, `${where}.moduleSpecifier`);
|
|
882
|
+
if (verifyOnDisk) {
|
|
883
|
+
const absPath = path.resolve(root, decl.moduleSpecifier);
|
|
884
|
+
if (!fs.existsSync(absPath)) {
|
|
885
|
+
throw new Error(
|
|
886
|
+
`[ethisys-platform-react] ${where}.moduleSpecifier "${decl.moduleSpecifier}" does not exist on disk (resolved: ${absPath}).`
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
result.push({
|
|
891
|
+
id: decl.id,
|
|
892
|
+
moduleSpecifier: decl.moduleSpecifier,
|
|
893
|
+
exportName: typeof decl.exportName === "string" && decl.exportName.length > 0 ? decl.exportName : "default",
|
|
894
|
+
title: typeof decl.title === "string" && decl.title.length > 0 ? decl.title : null
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
return result;
|
|
898
|
+
}
|
|
582
899
|
|
|
583
900
|
// src/index.ts
|
|
584
|
-
function
|
|
901
|
+
function slash3(p) {
|
|
585
902
|
return path.sep === "\\" ? p.replace(/\\/g, "/") : p;
|
|
586
903
|
}
|
|
587
904
|
function escapeHtml(str) {
|
|
@@ -706,7 +1023,7 @@ function ethisysManifestPlugin(options = {}) {
|
|
|
706
1023
|
}
|
|
707
1024
|
seen.add(entry.entrypoint);
|
|
708
1025
|
const name = entry.entrypoint.replace(/\.html$/, "");
|
|
709
|
-
input[name] =
|
|
1026
|
+
input[name] = slash3(path.resolve(rootDir, entry.entrypoint));
|
|
710
1027
|
}
|
|
711
1028
|
return {
|
|
712
1029
|
build: {
|
|
@@ -771,7 +1088,7 @@ function ethisysManifestPlugin(options = {}) {
|
|
|
771
1088
|
// Entire handler is wrapped in try/catch so JSON syntax errors in the
|
|
772
1089
|
// manifest don't crash the dev server (common during active editing).
|
|
773
1090
|
handleHotUpdate({ file, server }) {
|
|
774
|
-
if (
|
|
1091
|
+
if (slash3(file) === slash3(manifestAbsPath)) {
|
|
775
1092
|
try {
|
|
776
1093
|
entries = readManifest();
|
|
777
1094
|
validateEntries();
|
|
@@ -789,9 +1106,9 @@ function ethisysManifestPlugin(options = {}) {
|
|
|
789
1106
|
},
|
|
790
1107
|
// Build: resolve virtual HTML module IDs (no physical files exist on disk)
|
|
791
1108
|
resolveId(id) {
|
|
792
|
-
const normalizedId =
|
|
1109
|
+
const normalizedId = slash3(id);
|
|
793
1110
|
const match = entries.find(
|
|
794
|
-
(e) =>
|
|
1111
|
+
(e) => slash3(path.resolve(rootDir, e.entrypoint)) === normalizedId
|
|
795
1112
|
);
|
|
796
1113
|
if (match) {
|
|
797
1114
|
return id;
|
|
@@ -799,9 +1116,9 @@ function ethisysManifestPlugin(options = {}) {
|
|
|
799
1116
|
},
|
|
800
1117
|
// Build: provide virtual HTML content for Rollup
|
|
801
1118
|
load(id) {
|
|
802
|
-
const normalizedId =
|
|
1119
|
+
const normalizedId = slash3(id);
|
|
803
1120
|
const match = entries.find(
|
|
804
|
-
(e) =>
|
|
1121
|
+
(e) => slash3(path.resolve(rootDir, e.entrypoint)) === normalizedId
|
|
805
1122
|
);
|
|
806
1123
|
if (match) {
|
|
807
1124
|
return generateHtml(match);
|
|
@@ -816,6 +1133,8 @@ exports.CONTRACT_B_SEMANTIC_PRIMITIVES = CONTRACT_B_SEMANTIC_PRIMITIVES;
|
|
|
816
1133
|
exports.ethisysContractAPlugin = ethisysContractAPlugin;
|
|
817
1134
|
exports.ethisysContractBPlugin = ethisysContractBPlugin;
|
|
818
1135
|
exports.ethisysManifestPlugin = ethisysManifestPlugin;
|
|
1136
|
+
exports.ethisysPlatformReactPlugin = ethisysPlatformReactPlugin;
|
|
1137
|
+
exports.parsePlatformReactPages = parsePlatformReactPages;
|
|
819
1138
|
exports.validateDeclarativeResource = validateDeclarativeResource;
|
|
820
1139
|
exports.validateReactiveRule = validateReactiveRule;
|
|
821
1140
|
//# sourceMappingURL=index.cjs.map
|