@ethisyscore/vite-plugin 1.5.2 → 1.6.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 CHANGED
@@ -1,39 +1,595 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+ var protocol = require('@ethisyscore/protocol');
19
6
 
20
7
  // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- ethisysManifestPlugin: () => ethisysManifestPlugin
24
- });
25
- module.exports = __toCommonJS(index_exports);
26
- var import_node_fs = require("fs");
27
- var import_node_path = require("path");
8
+ var KNOWN_PRIMITIVE_SET = new Set(protocol.KNOWN_PRIMITIVES);
9
+ var KNOWN_OPERATOR_SET = new Set(protocol.KNOWN_OPERATORS);
10
+ var KNOWN_RULE_KIND_SET = new Set(protocol.KNOWN_RULE_KINDS);
11
+ function toJsonPointer(path) {
12
+ if (path.length === 0) {
13
+ return "";
14
+ }
15
+ return "/" + path.map((seg) => {
16
+ const s = String(seg);
17
+ return s.replace(/~/g, "~0").replace(/\//g, "~1");
18
+ }).join("/");
19
+ }
20
+ function extractOffender(doc, issue) {
21
+ if (issue.code === "invalid_value" || issue.code === "invalid_enum_value") {
22
+ const received = issue.received;
23
+ if (typeof received === "string") {
24
+ return received;
25
+ }
26
+ }
27
+ let cur = doc;
28
+ for (const seg of issue.path) {
29
+ if (cur === null || cur === void 0) {
30
+ break;
31
+ }
32
+ cur = cur[seg];
33
+ }
34
+ if (cur && typeof cur === "object" && !Array.isArray(cur)) {
35
+ const keys = Object.keys(cur);
36
+ if (keys.length === 1 && !KNOWN_OPERATOR_SET.has(keys[0])) {
37
+ return keys[0];
38
+ }
39
+ }
40
+ if (issue.path.length > 0) {
41
+ const last = issue.path[issue.path.length - 1];
42
+ if (typeof last === "string" && !KNOWN_RULE_KIND_SET.has(last) && !KNOWN_PRIMITIVE_SET.has(last)) {
43
+ return last;
44
+ }
45
+ }
46
+ return void 0;
47
+ }
48
+ function zodIssuesToFailures(doc, issues, filePath) {
49
+ return issues.map((issue) => {
50
+ const pointer = toJsonPointer(issue.path);
51
+ const offender = extractOffender(doc, issue);
52
+ const offenderSuffix = offender ? ` (offender: '${offender}')` : "";
53
+ return {
54
+ filePath,
55
+ pointer,
56
+ offender,
57
+ message: `${filePath}${pointer ? ` at ${pointer}` : ""}: ${issue.message}${offenderSuffix}`
58
+ };
59
+ });
60
+ }
61
+ function validateDeclarativeResource(doc, uri, filePath) {
62
+ const parsed = protocol.SduiNode.safeParse(doc);
63
+ if (parsed.success) {
64
+ return { ok: true };
65
+ }
66
+ const failures = zodIssuesToFailures(doc, parsed.error.issues, filePath);
67
+ return {
68
+ ok: false,
69
+ failures: failures.map((f) => ({
70
+ ...f,
71
+ message: `[resource ${uri}] ${f.message}`
72
+ }))
73
+ };
74
+ }
75
+ function validateReactiveRule(rule, filePath) {
76
+ const parsed = protocol.ReactiveRule.safeParse(rule);
77
+ if (parsed.success) {
78
+ return { ok: true };
79
+ }
80
+ return {
81
+ ok: false,
82
+ failures: zodIssuesToFailures(rule, parsed.error.issues, filePath)
83
+ };
84
+ }
85
+
86
+ // src/contract-a/emit.ts
87
+ function slugForUri(uri) {
88
+ return uri.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
89
+ }
90
+ function assertSafeRelativePath(value, label) {
91
+ const normalized = path.normalize(value);
92
+ if (path.isAbsolute(value) || path.isAbsolute(normalized)) {
93
+ throw new Error(
94
+ `[ethisys-contract-a] ${label} "${value}" must be relative, not absolute.`
95
+ );
96
+ }
97
+ if (normalized.split(/[\\/]/).includes("..")) {
98
+ throw new Error(
99
+ `[ethisys-contract-a] ${label} "${value}" must not contain path traversal.`
100
+ );
101
+ }
102
+ }
103
+ function formatFailures(failures) {
104
+ const lines = failures.map((f, i) => ` ${i + 1}. ${f.message}`);
105
+ return [
106
+ `[@ethisyscore/vite-plugin] Contract A schema validation failed:`,
107
+ ...lines
108
+ ].join("\n");
109
+ }
110
+ function ethisysContractAPlugin(options = {}) {
111
+ const outputPrefix = (options.outputPrefix ?? "sdui/").replace(/\/+$/, "/").replace(/^\/+/, "");
112
+ const explicitRoot = options.root;
113
+ const manifestRel = options.manifestPath ?? "feature.manifest.json";
114
+ let resolvedRoot;
115
+ let manifestAbsPath;
116
+ let validatedResources = [];
117
+ const resourcePayloads = /* @__PURE__ */ new Map();
118
+ function resolveRoot() {
119
+ if (resolvedRoot) {
120
+ return resolvedRoot;
121
+ }
122
+ resolvedRoot = explicitRoot ?? process.cwd();
123
+ manifestAbsPath = path.isAbsolute(manifestRel) ? manifestRel : path.resolve(resolvedRoot, manifestRel);
124
+ return resolvedRoot;
125
+ }
126
+ function readManifest() {
127
+ resolveRoot();
128
+ if (!fs.existsSync(manifestAbsPath)) {
129
+ return null;
130
+ }
131
+ const raw = fs.readFileSync(manifestAbsPath, "utf-8");
132
+ try {
133
+ return JSON.parse(raw);
134
+ } catch (e) {
135
+ throw new Error(
136
+ `[@ethisyscore/vite-plugin] Failed to parse manifest at "${manifestAbsPath}": ${e.message}`
137
+ );
138
+ }
139
+ }
140
+ function validate() {
141
+ validatedResources = [];
142
+ resourcePayloads.clear();
143
+ const manifest = readManifest();
144
+ if (manifest === null) {
145
+ return;
146
+ }
147
+ const failures = [];
148
+ for (const ref of manifest.resources ?? []) {
149
+ if (!ref.file) {
150
+ continue;
151
+ }
152
+ assertSafeRelativePath(ref.file, "Resource file path");
153
+ const filePath = path.resolve(resolvedRoot, ref.file);
154
+ if (!fs.existsSync(filePath)) {
155
+ failures.push({
156
+ filePath,
157
+ pointer: "",
158
+ message: `[resource ${ref.uri}] file "${ref.file}" referenced by the manifest does not exist on disk (resolved: ${filePath}).`
159
+ });
160
+ continue;
161
+ }
162
+ const rawJson = fs.readFileSync(filePath, "utf-8");
163
+ let doc;
164
+ try {
165
+ doc = JSON.parse(rawJson);
166
+ } catch (e) {
167
+ failures.push({
168
+ filePath,
169
+ pointer: "",
170
+ message: `[resource ${ref.uri}] failed to parse JSON: ${e.message}`
171
+ });
172
+ continue;
173
+ }
174
+ const result = validateDeclarativeResource(doc, ref.uri, filePath);
175
+ if (!result.ok) {
176
+ failures.push(...result.failures);
177
+ continue;
178
+ }
179
+ const fileName = `${outputPrefix}${slugForUri(ref.uri)}.json`;
180
+ const payload = JSON.stringify(doc);
181
+ resourcePayloads.set(fileName, payload);
182
+ validatedResources.push({ uri: ref.uri, fileName });
183
+ }
184
+ for (const ruleRef of manifest.reactiveRules ?? []) {
185
+ const result = validateReactiveRule(ruleRef.rule, manifestAbsPath);
186
+ if (!result.ok) {
187
+ failures.push(
188
+ ...result.failures.map((f) => ({
189
+ ...f,
190
+ message: `[reactiveRule ${ruleRef.id}] ${f.message}`
191
+ }))
192
+ );
193
+ }
194
+ }
195
+ if (failures.length > 0) {
196
+ throw new Error(formatFailures(failures));
197
+ }
198
+ }
199
+ return {
200
+ name: "ethisys-contract-a",
201
+ enforce: "pre",
202
+ configResolved(config) {
203
+ if (!explicitRoot) {
204
+ resolvedRoot = config.root;
205
+ manifestAbsPath = path.isAbsolute(manifestRel) ? manifestRel : path.resolve(resolvedRoot, manifestRel);
206
+ }
207
+ },
208
+ buildStart() {
209
+ validate();
210
+ },
211
+ generateBundle() {
212
+ if (validatedResources.length === 0) {
213
+ return;
214
+ }
215
+ for (const { fileName } of validatedResources) {
216
+ const source = resourcePayloads.get(fileName);
217
+ if (source === void 0) {
218
+ continue;
219
+ }
220
+ this.emitFile({ type: "asset", fileName, source });
221
+ }
222
+ const sorted = [...validatedResources].sort(
223
+ (a, b) => a.uri.localeCompare(b.uri)
224
+ );
225
+ const index = {
226
+ version: 1,
227
+ resources: sorted.map(({ uri, fileName }) => ({ uri, file: fileName }))
228
+ };
229
+ this.emitFile({
230
+ type: "asset",
231
+ fileName: `${outputPrefix}sdui-manifest.json`,
232
+ source: JSON.stringify(index)
233
+ });
234
+ }
235
+ };
236
+ }
237
+
238
+ // src/contract-b/rewrite-imports.ts
239
+ var STATIC_IMPORT_RE = /^([ \t]*)import[ \t]+(.*?)[ \t]*;?[ \t]*$/gm;
240
+ function rewriteSafeImports(code, allowlist) {
241
+ const allowSet = new Set(allowlist);
242
+ return code.replace(STATIC_IMPORT_RE, (match, indent, body) => {
243
+ const rewrite = tryRewriteImport(body, allowSet);
244
+ return rewrite === null ? match : `${indent}${rewrite}`;
245
+ });
246
+ }
247
+ function convertNamedBindings(namedClause) {
248
+ const inner = namedClause.slice(1, -1).trim();
249
+ if (inner.length === 0) {
250
+ return "";
251
+ }
252
+ const parts = inner.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
253
+ const converted = parts.map((part) => {
254
+ const asMatch = part.match(/^(\w[\w$]*)\s+as\s+(\w[\w$]*)$/);
255
+ return asMatch === null ? part : `${asMatch[1]}: ${asMatch[2]}`;
256
+ });
257
+ return converted.join(", ");
258
+ }
259
+ function tryRewriteImport(body, allowSet) {
260
+ const sideEffect = body.match(/^["']([^"']+)["']$/);
261
+ if (sideEffect !== null) {
262
+ const spec = sideEffect[1];
263
+ if (!allowSet.has(spec)) {
264
+ return null;
265
+ }
266
+ return `await globalThis.__ethisysSafeImport(${JSON.stringify(spec)});`;
267
+ }
268
+ const defaultPlusNamed = body.match(
269
+ /^(\w[\w$]*)\s*,\s*(\{[^}]*\})\s+from\s+["']([^"']+)["']$/
270
+ );
271
+ if (defaultPlusNamed !== null) {
272
+ const defaultLocal = defaultPlusNamed[1];
273
+ const namedClause = defaultPlusNamed[2];
274
+ const spec = defaultPlusNamed[3];
275
+ if (!allowSet.has(spec)) {
276
+ return null;
277
+ }
278
+ const bindings = convertNamedBindings(namedClause);
279
+ const inner = bindings.length === 0 ? `default: ${defaultLocal}` : `default: ${defaultLocal}, ${bindings}`;
280
+ return `const { ${inner} } = await globalThis.__ethisysSafeImport(${JSON.stringify(spec)});`;
281
+ }
282
+ const defaultOnly = body.match(/^(\w[\w$]*)\s+from\s+["']([^"']+)["']$/);
283
+ if (defaultOnly !== null) {
284
+ const local = defaultOnly[1];
285
+ const spec = defaultOnly[2];
286
+ if (!allowSet.has(spec)) {
287
+ return null;
288
+ }
289
+ return `const { default: ${local} } = await globalThis.__ethisysSafeImport(${JSON.stringify(spec)});`;
290
+ }
291
+ const namedOnly = body.match(/^(\{[^}]*\})\s+from\s+["']([^"']+)["']$/);
292
+ if (namedOnly !== null) {
293
+ const namedClause = namedOnly[1];
294
+ const spec = namedOnly[2];
295
+ if (!allowSet.has(spec)) {
296
+ return null;
297
+ }
298
+ const bindings = convertNamedBindings(namedClause);
299
+ return `const { ${bindings} } = await globalThis.__ethisysSafeImport(${JSON.stringify(spec)});`;
300
+ }
301
+ const namespaceOnly = body.match(/^\*\s+as\s+(\w[\w$]*)\s+from\s+["']([^"']+)["']$/);
302
+ if (namespaceOnly !== null) {
303
+ const local = namespaceOnly[1];
304
+ const spec = namespaceOnly[2];
305
+ if (!allowSet.has(spec)) {
306
+ return null;
307
+ }
308
+ return `const ${local} = await globalThis.__ethisysSafeImport(${JSON.stringify(spec)});`;
309
+ }
310
+ return null;
311
+ }
312
+
313
+ // src/contract-b/worker-bundle.ts
314
+ var CONTRACT_B_SEMANTIC_PRIMITIVES = [
315
+ "Button",
316
+ "DataTable",
317
+ "Form",
318
+ "EntityPicker",
319
+ "CommandBar",
320
+ "Drawer",
321
+ "Modal",
322
+ "CanvasSurface",
323
+ "WebGLSurface"
324
+ ];
325
+ var CONTRACT_B_RUNTIME_IMPORTS = [
326
+ "react",
327
+ "@ethisyscore/extension-runtime"
328
+ ];
329
+ var CONTRACT_B_IMPORT_MAP_ALLOWLIST = Object.freeze([
330
+ ...CONTRACT_B_RUNTIME_IMPORTS,
331
+ ...CONTRACT_B_SEMANTIC_PRIMITIVES
332
+ ]);
333
+ var ALLOWLIST_SET = new Set(CONTRACT_B_IMPORT_MAP_ALLOWLIST);
334
+ function assertHostOriginRelativePath(value, label) {
335
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
336
+ throw new Error(
337
+ `[ethisys-contract-b] ${label} "${value}" must be a host-origin relative path, not a remote URL.`
338
+ );
339
+ }
340
+ if (/^(data|blob|javascript):/i.test(value)) {
341
+ throw new Error(
342
+ `[ethisys-contract-b] ${label} "${value}" must be a host-origin relative path, not a ${value.split(":")[0]}: URL.`
343
+ );
344
+ }
345
+ const normalized = path.normalize(value);
346
+ if (path.isAbsolute(value) || path.isAbsolute(normalized)) {
347
+ throw new Error(
348
+ `[ethisys-contract-b] ${label} "${value}" must be relative, not absolute.`
349
+ );
350
+ }
351
+ if (normalized.split(/[\\/]/).includes("..")) {
352
+ throw new Error(
353
+ `[ethisys-contract-b] ${label} "${value}" must not contain path traversal.`
354
+ );
355
+ }
356
+ }
28
357
  function slash(p) {
29
- return import_node_path.sep === "\\" ? p.replace(/\\/g, "/") : p;
358
+ return path.sep === "\\" ? p.replace(/\\/g, "/") : p;
359
+ }
360
+ function ethisysContractBPlugin(options = {}) {
361
+ const explicitRoot = options.root;
362
+ const manifestRel = options.manifestPath ?? "feature.manifest.json";
363
+ const outputPrefix = (options.outputPrefix ?? "worker/").replace(/\/+$/, "/").replace(/^\/+/, "");
364
+ let resolvedRoot;
365
+ let manifestAbsPath;
366
+ let workerEntryAbs = null;
367
+ let workerEntryRel = null;
368
+ function resolveRoot() {
369
+ if (resolvedRoot) {
370
+ return resolvedRoot;
371
+ }
372
+ resolvedRoot = explicitRoot ?? process.cwd();
373
+ manifestAbsPath = path.isAbsolute(manifestRel) ? manifestRel : path.resolve(resolvedRoot, manifestRel);
374
+ return resolvedRoot;
375
+ }
376
+ function readManifest() {
377
+ resolveRoot();
378
+ if (!fs.existsSync(manifestAbsPath)) {
379
+ return null;
380
+ }
381
+ const raw = fs.readFileSync(manifestAbsPath, "utf-8");
382
+ try {
383
+ return JSON.parse(raw);
384
+ } catch (e) {
385
+ throw new Error(
386
+ `[ethisys-contract-b] Failed to parse manifest at "${manifestAbsPath}": ${e.message}`
387
+ );
388
+ }
389
+ }
390
+ function validate() {
391
+ workerEntryAbs = null;
392
+ workerEntryRel = null;
393
+ const manifest = readManifest();
394
+ if (manifest === null) {
395
+ return;
396
+ }
397
+ if (manifest.renderMode !== "remote-runtime") {
398
+ return;
399
+ }
400
+ const entry = manifest.worker?.entry;
401
+ if (typeof entry !== "string" || entry.length === 0) {
402
+ throw new Error(
403
+ `[ethisys-contract-b] manifest declares renderMode='remote-runtime' but no worker.entry. Set "worker": { "entry": "src/worker.ts" } (relative path).`
404
+ );
405
+ }
406
+ assertHostOriginRelativePath(entry, "Worker entry");
407
+ const absPath = path.resolve(resolvedRoot, entry);
408
+ if (!fs.existsSync(absPath)) {
409
+ throw new Error(
410
+ `[ethisys-contract-b] Worker entry file "${entry}" does not exist on disk (resolved: ${absPath}).`
411
+ );
412
+ }
413
+ workerEntryAbs = absPath;
414
+ workerEntryRel = entry;
415
+ }
416
+ return {
417
+ name: "ethisys-contract-b",
418
+ // `pre` ordering: validation should fire before user-supplied plugins.
419
+ enforce: "pre",
420
+ configResolved(config) {
421
+ if (!explicitRoot) {
422
+ resolvedRoot = config.root;
423
+ manifestAbsPath = path.isAbsolute(manifestRel) ? manifestRel : path.resolve(resolvedRoot, manifestRel);
424
+ }
425
+ },
426
+ /**
427
+ * Declares the Rollup input table and locks the output to a single ESM
428
+ * module. Only fires when the manifest opts in (`renderMode: remote-runtime`).
429
+ */
430
+ config(_config, { command }) {
431
+ if (command !== "build") {
432
+ return;
433
+ }
434
+ validate();
435
+ if (workerEntryAbs === null) {
436
+ return;
437
+ }
438
+ const externalSpecifiers = new Set(CONTRACT_B_IMPORT_MAP_ALLOWLIST);
439
+ return {
440
+ build: {
441
+ rollupOptions: {
442
+ input: { worker: slash(workerEntryAbs) },
443
+ external: (id) => externalSpecifiers.has(id),
444
+ // CRITICAL: single-file output for the worker bundle. The
445
+ // E4.S4 frozen import map presumes ONE module entry per plugin —
446
+ // chunk splitting or dynamic-import shards would defeat it.
447
+ output: {
448
+ format: "es",
449
+ inlineDynamicImports: true,
450
+ entryFileNames: `${outputPrefix}[name].js`,
451
+ chunkFileNames: `${outputPrefix}[name]-[hash].js`,
452
+ assetFileNames: `${outputPrefix}[name][extname]`
453
+ }
454
+ }
455
+ }
456
+ };
457
+ },
458
+ buildStart() {
459
+ validate();
460
+ },
461
+ /**
462
+ * Reject `data:` / `blob:` / `javascript:` URL specifiers at resolve time.
463
+ *
464
+ * Rollup normally externalises unknown URL schemes silently, which would
465
+ * mean a malicious `import("data:text/javascript,...")` survives the
466
+ * build untouched and only fails at runtime (where the bootstrap script's
467
+ * `safeImport` would catch it). We want build-time failure too — the
468
+ * import-map contract is enforced at BOTH ends.
469
+ */
470
+ resolveId(source) {
471
+ if (workerEntryAbs === null) {
472
+ return null;
473
+ }
474
+ if (/^(data|blob|javascript):/i.test(source)) {
475
+ const scheme = source.split(":", 1)[0];
476
+ this.error(
477
+ `[ethisys-contract-b] Contract B bundle imports forbidden URL scheme '${scheme}:' (specifier: "${source.slice(0, 80)}${source.length > 80 ? "..." : ""}"). The worker realm rejects ${scheme}: imports at runtime; the build rejects them too.`
478
+ );
479
+ }
480
+ return null;
481
+ },
482
+ /**
483
+ * Rewrite the emitted worker chunk's static bare-specifier imports into
484
+ * top-level `await globalThis.__ethisysSafeImport(...)` calls. This is
485
+ * the build-time complement to the worker bootstrap's frozen IMPORT_MAP
486
+ * (defined in CoreConnect-Api's WorkerBootstrapScriptProvider.cs). The
487
+ * worker realm has no `<script type="importmap">` surface, so static
488
+ * bare specifiers reach the browser's native ES loader as
489
+ * `Failed to resolve module specifier` errors at module load time.
490
+ *
491
+ * Returns `null` (no transform) when the manifest is not Contract B —
492
+ * the Contract A path's emitted chunks are left untouched.
493
+ */
494
+ renderChunk(code, chunk) {
495
+ if (workerEntryAbs === null) {
496
+ return null;
497
+ }
498
+ if (chunk.type !== "chunk") {
499
+ return null;
500
+ }
501
+ const rewritten = rewriteSafeImports(code, CONTRACT_B_IMPORT_MAP_ALLOWLIST);
502
+ if (rewritten === code) {
503
+ return null;
504
+ }
505
+ return { code: rewritten };
506
+ },
507
+ /**
508
+ * Inspect the emitted bundle for out-of-allowlist bare specifiers and emit
509
+ * the per-bundle `worker-bundle.import-map.json` manifest.
510
+ *
511
+ * Because we marked allowlist specifiers as `external` in `config`, any
512
+ * other bare specifier that the plugin tried to import would have failed
513
+ * Rollup's resolver upstream — but we re-check the surviving `chunk.imports`
514
+ * here as belt-and-braces in case a future Rollup or Vite change relaxes
515
+ * the resolver path. Defence in depth at the build layer matches the
516
+ * defence in depth at the runtime layer (E4.S3 bootstrap).
517
+ */
518
+ generateBundle(_outputOptions, bundle) {
519
+ if (workerEntryAbs === null || workerEntryRel === null) {
520
+ return;
521
+ }
522
+ const importedSpecifiers = /* @__PURE__ */ new Set();
523
+ const violations = [];
524
+ for (const [, chunk] of Object.entries(bundle)) {
525
+ if (chunk.type !== "chunk") {
526
+ continue;
527
+ }
528
+ const candidates = [
529
+ ...chunk.imports ?? [],
530
+ ...chunk.dynamicImports ?? []
531
+ ];
532
+ for (const spec of candidates) {
533
+ if (spec.startsWith(".") || spec.startsWith("/") || path.isAbsolute(spec)) {
534
+ continue;
535
+ }
536
+ if (/^[a-z][a-z0-9+.-]*:/i.test(spec)) {
537
+ violations.push(spec);
538
+ continue;
539
+ }
540
+ if (!ALLOWLIST_SET.has(spec)) {
541
+ violations.push(spec);
542
+ continue;
543
+ }
544
+ importedSpecifiers.add(spec);
545
+ }
546
+ }
547
+ if (violations.length > 0) {
548
+ const unique = [...new Set(violations)].sort();
549
+ this.error(
550
+ `[ethisys-contract-b] Worker bundle imports specifier(s) outside the frozen import-map allowlist: ${unique.map((s) => `"${s}"`).join(", ")}. Allowed bare specifiers: ${CONTRACT_B_IMPORT_MAP_ALLOWLIST.map((s) => `"${s}"`).join(", ")}.`
551
+ );
552
+ }
553
+ const resolvedEntries = [...importedSpecifiers].sort();
554
+ const workerBundleManifest = {
555
+ version: 1,
556
+ renderMode: "remote-runtime",
557
+ worker: { entry: workerEntryRel }
558
+ };
559
+ this.emitFile({
560
+ type: "asset",
561
+ fileName: `${outputPrefix}worker-bundle.json`,
562
+ source: JSON.stringify(workerBundleManifest)
563
+ });
564
+ const importMapManifest = {
565
+ version: 1,
566
+ // Authoring contract: the bundle's bare-specifier surface area, in
567
+ // sorted order, restricted to the frozen allowlist.
568
+ imports: Object.fromEntries(resolvedEntries.map((spec) => [spec, null])),
569
+ // Mirror the canonical allowlist so consumers diffing manifests
570
+ // across plugin versions can see the contract the build verified
571
+ // against without re-reading SDK source.
572
+ allowlist: [...CONTRACT_B_IMPORT_MAP_ALLOWLIST]
573
+ };
574
+ this.emitFile({
575
+ type: "asset",
576
+ fileName: `${outputPrefix}worker-bundle.import-map.json`,
577
+ source: JSON.stringify(importMapManifest)
578
+ });
579
+ }
580
+ };
581
+ }
582
+
583
+ // src/index.ts
584
+ function slash2(p) {
585
+ return path.sep === "\\" ? p.replace(/\\/g, "/") : p;
30
586
  }
31
587
  function escapeHtml(str) {
32
588
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
33
589
  }
34
- function assertSafeRelativePath(value, label) {
35
- const normalized = (0, import_node_path.normalize)(value);
36
- if ((0, import_node_path.isAbsolute)(value) || (0, import_node_path.isAbsolute)(normalized)) {
590
+ function assertSafeRelativePath2(value, label) {
591
+ const normalized = path.normalize(value);
592
+ if (path.isAbsolute(value) || path.isAbsolute(normalized)) {
37
593
  throw new Error(
38
594
  `[ethisys-manifest] ${label} "${value}" must be relative, not absolute.`
39
595
  );
@@ -53,10 +609,10 @@ function ethisysManifestPlugin(options = {}) {
53
609
  const htmlCache = /* @__PURE__ */ new Map();
54
610
  function generateHtml(entry) {
55
611
  if (entry.template) {
56
- assertSafeRelativePath(entry.template, "Template path");
57
- const templatePath = (0, import_node_path.resolve)(rootDir, entry.template);
58
- if ((0, import_node_fs.existsSync)(templatePath)) {
59
- let html = (0, import_node_fs.readFileSync)(templatePath, "utf-8");
612
+ assertSafeRelativePath2(entry.template, "Template path");
613
+ const templatePath = path.resolve(rootDir, entry.template);
614
+ if (fs.existsSync(templatePath)) {
615
+ let html = fs.readFileSync(templatePath, "utf-8");
60
616
  const safeMountId2 = escapeHtml(mountId);
61
617
  const mountPattern = new RegExp(`id=["']${safeMountId2}["']`);
62
618
  if (!mountPattern.test(html)) {
@@ -85,10 +641,10 @@ function ethisysManifestPlugin(options = {}) {
85
641
  </html>`;
86
642
  }
87
643
  function readManifest() {
88
- if (!(0, import_node_fs.existsSync)(manifestAbsPath)) {
644
+ if (!fs.existsSync(manifestAbsPath)) {
89
645
  return [];
90
646
  }
91
- const raw = (0, import_node_fs.readFileSync)(manifestAbsPath, "utf-8");
647
+ const raw = fs.readFileSync(manifestAbsPath, "utf-8");
92
648
  let manifest;
93
649
  try {
94
650
  manifest = JSON.parse(raw);
@@ -114,9 +670,9 @@ function ethisysManifestPlugin(options = {}) {
114
670
  const entrypointSources = /* @__PURE__ */ new Map();
115
671
  for (const entry of entries) {
116
672
  const source = entry.source;
117
- assertSafeRelativePath(source, "Source path");
118
- const sourcePath = (0, import_node_path.resolve)(rootDir, source);
119
- if (!(0, import_node_fs.existsSync)(sourcePath)) {
673
+ assertSafeRelativePath2(source, "Source path");
674
+ const sourcePath = path.resolve(rootDir, source);
675
+ if (!fs.existsSync(sourcePath)) {
120
676
  throw new Error(
121
677
  `[ethisys-manifest] Source file "${source}" for entry "${entry.entrypoint}" does not exist.`
122
678
  );
@@ -135,7 +691,7 @@ function ethisysManifestPlugin(options = {}) {
135
691
  enforce: "pre",
136
692
  config(config, { command }) {
137
693
  rootDir = config.root ?? process.cwd();
138
- manifestAbsPath = (0, import_node_path.resolve)(rootDir, manifestRelPath);
694
+ manifestAbsPath = path.resolve(rootDir, manifestRelPath);
139
695
  entries = readManifest();
140
696
  if (entries.length === 0) {
141
697
  return;
@@ -150,7 +706,7 @@ function ethisysManifestPlugin(options = {}) {
150
706
  }
151
707
  seen.add(entry.entrypoint);
152
708
  const name = entry.entrypoint.replace(/\.html$/, "");
153
- input[name] = slash((0, import_node_path.resolve)(rootDir, entry.entrypoint));
709
+ input[name] = slash2(path.resolve(rootDir, entry.entrypoint));
154
710
  }
155
711
  return {
156
712
  build: {
@@ -215,7 +771,7 @@ function ethisysManifestPlugin(options = {}) {
215
771
  // Entire handler is wrapped in try/catch so JSON syntax errors in the
216
772
  // manifest don't crash the dev server (common during active editing).
217
773
  handleHotUpdate({ file, server }) {
218
- if (slash(file) === slash(manifestAbsPath)) {
774
+ if (slash2(file) === slash2(manifestAbsPath)) {
219
775
  try {
220
776
  entries = readManifest();
221
777
  validateEntries();
@@ -233,9 +789,9 @@ function ethisysManifestPlugin(options = {}) {
233
789
  },
234
790
  // Build: resolve virtual HTML module IDs (no physical files exist on disk)
235
791
  resolveId(id) {
236
- const normalizedId = slash(id);
792
+ const normalizedId = slash2(id);
237
793
  const match = entries.find(
238
- (e) => slash((0, import_node_path.resolve)(rootDir, e.entrypoint)) === normalizedId
794
+ (e) => slash2(path.resolve(rootDir, e.entrypoint)) === normalizedId
239
795
  );
240
796
  if (match) {
241
797
  return id;
@@ -243,9 +799,9 @@ function ethisysManifestPlugin(options = {}) {
243
799
  },
244
800
  // Build: provide virtual HTML content for Rollup
245
801
  load(id) {
246
- const normalizedId = slash(id);
802
+ const normalizedId = slash2(id);
247
803
  const match = entries.find(
248
- (e) => slash((0, import_node_path.resolve)(rootDir, e.entrypoint)) === normalizedId
804
+ (e) => slash2(path.resolve(rootDir, e.entrypoint)) === normalizedId
249
805
  );
250
806
  if (match) {
251
807
  return generateHtml(match);
@@ -253,7 +809,14 @@ function ethisysManifestPlugin(options = {}) {
253
809
  }
254
810
  };
255
811
  }
256
- // Annotate the CommonJS export names for ESM import in node:
257
- 0 && (module.exports = {
258
- ethisysManifestPlugin
259
- });
812
+
813
+ exports.CONTRACT_B_IMPORT_MAP_ALLOWLIST = CONTRACT_B_IMPORT_MAP_ALLOWLIST;
814
+ exports.CONTRACT_B_RUNTIME_IMPORTS = CONTRACT_B_RUNTIME_IMPORTS;
815
+ exports.CONTRACT_B_SEMANTIC_PRIMITIVES = CONTRACT_B_SEMANTIC_PRIMITIVES;
816
+ exports.ethisysContractAPlugin = ethisysContractAPlugin;
817
+ exports.ethisysContractBPlugin = ethisysContractBPlugin;
818
+ exports.ethisysManifestPlugin = ethisysManifestPlugin;
819
+ exports.validateDeclarativeResource = validateDeclarativeResource;
820
+ exports.validateReactiveRule = validateReactiveRule;
821
+ //# sourceMappingURL=index.cjs.map
822
+ //# sourceMappingURL=index.cjs.map