@ethisyscore/vite-plugin 1.5.2 → 1.6.0

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