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