@ox-content/code-play 3.0.0-alpha.1 → 3.0.0-alpha.10

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/plugin2.mjs CHANGED
@@ -1,28 +1,332 @@
1
- import { c as DEV_TYPECHECK_PATH, i as escapeAttribute, n as encodePayload, o as DEFAULT_ENDPOINTS, p as resolveLanguage, r as decodeHtml, s as DEFAULT_VIEWERS, t as decodePayload, u as resolveCodePlayOptions } from "./payload.mjs";
2
- import { readFile, writeFile } from "node:fs/promises";
1
+ import { f as resolveCodePlayOptions, h as resolveLanguage, i as escapeAttribute, n as encodePayload, o as DEFAULT_ENDPOINTS, r as decodeHtml, s as DEFAULT_VIEWERS, t as decodePayload, u as DEV_TYPECHECK_PATH } from "./payload.mjs";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ //#region src/authoring.ts
7
+ function parsePlayMeta(meta) {
8
+ const options = emptyPlayOptions();
9
+ for (const token of splitPlayInfo(meta)) {
10
+ if (token === "typecheck") {
11
+ options.typecheck = true;
12
+ continue;
13
+ }
14
+ if (token === "play-compact") {
15
+ options.ui = "compact";
16
+ continue;
17
+ }
18
+ if (token === "play-headless") {
19
+ options.ui = "headless";
20
+ continue;
21
+ }
22
+ const pair = readTokenPair(token);
23
+ if (!pair) continue;
24
+ applyPlayOption(options, pair.name, pair.value);
25
+ }
26
+ return options;
27
+ }
28
+ function parseCodePlayAttributes(attrs) {
29
+ const options = emptyPlayOptions();
30
+ for (const [name, value] of readAttributes(attrs)) {
31
+ if (name === "typecheck") {
32
+ options.typecheck = value !== "false";
33
+ continue;
34
+ }
35
+ if (name === "title") {
36
+ options.title = value;
37
+ continue;
38
+ }
39
+ if (name === "ui") {
40
+ options.ui = parseUi(value);
41
+ continue;
42
+ }
43
+ if (name === "timeout" || name === "timeout-ms" || name === "timeoutms") {
44
+ options.timeoutMs = parsePositiveInt(value);
45
+ continue;
46
+ }
47
+ if (name === "viewers") {
48
+ options.viewers = parseViewers(value);
49
+ continue;
50
+ }
51
+ if (name.startsWith("config-")) options.config[configKeyFromAttribute(name.slice(7))] = coerceOptionValue(value);
52
+ }
53
+ return options;
54
+ }
55
+ function readCodePlayAttribute(attrs, name) {
56
+ return readAttributes(attrs).get(name.toLowerCase());
57
+ }
58
+ function splitPlayInfo(info) {
59
+ const tokens = [];
60
+ let current = "";
61
+ let quote;
62
+ let escaped = false;
63
+ for (const char of info.trim()) {
64
+ if (escaped) {
65
+ current += `\\${char}`;
66
+ escaped = false;
67
+ continue;
68
+ }
69
+ if (char === "\\") {
70
+ escaped = true;
71
+ continue;
72
+ }
73
+ if (quote) {
74
+ if (char === quote) {
75
+ current += char;
76
+ quote = void 0;
77
+ } else current += char;
78
+ continue;
79
+ }
80
+ if (char === "\"" || char === "'") {
81
+ current += char;
82
+ quote = char;
83
+ continue;
84
+ }
85
+ if (/\s/.test(char)) {
86
+ if (current) {
87
+ tokens.push(current);
88
+ current = "";
89
+ }
90
+ continue;
91
+ }
92
+ current += char;
93
+ }
94
+ if (current) tokens.push(current);
95
+ return tokens;
96
+ }
97
+ function emptyPlayOptions() {
98
+ return {
99
+ typecheck: false,
100
+ config: {}
101
+ };
102
+ }
103
+ function applyPlayOption(options, rawName, value) {
104
+ const name = rawName.toLowerCase();
105
+ if (name === "play-title") {
106
+ options.title = value;
107
+ return;
108
+ }
109
+ if (name === "play-ui") {
110
+ options.ui = parseUi(value);
111
+ return;
112
+ }
113
+ if (name === "play-timeout" || name === "play-timeout-ms" || name === "play-timeoutms") {
114
+ options.timeoutMs = parsePositiveInt(value);
115
+ return;
116
+ }
117
+ if (name === "play-viewers") {
118
+ options.viewers = parseViewers(value);
119
+ return;
120
+ }
121
+ const configKey = name.startsWith("play-config:") || name.startsWith("play-config.") ? rawName.slice(12) : name.startsWith("play-") ? rawName.slice(5) : "";
122
+ if (configKey) options.config[configKey] = coerceOptionValue(value);
123
+ }
124
+ function readTokenPair(token) {
125
+ const index = token.indexOf("=");
126
+ if (index === -1) return;
127
+ return {
128
+ name: token.slice(0, index),
129
+ value: unquote(token.slice(index + 1))
130
+ };
131
+ }
132
+ function readAttributes(attrs) {
133
+ const values = /* @__PURE__ */ new Map();
134
+ for (const match of attrs.matchAll(/([:\w-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g)) {
135
+ const name = match[1]?.toLowerCase();
136
+ if (!name) continue;
137
+ values.set(name, match[2] ?? match[3] ?? match[4] ?? "true");
138
+ }
139
+ return values;
140
+ }
141
+ function parseUi(value) {
142
+ return value === "default" || value === "compact" || value === "headless" ? value : void 0;
143
+ }
144
+ function parsePositiveInt(value) {
145
+ const parsed = Number.parseInt(value, 10);
146
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
147
+ }
148
+ function parseViewers(value) {
149
+ const viewers = {};
150
+ for (const token of value.split(",")) {
151
+ const trimmed = token.trim();
152
+ const enabled = !trimmed.startsWith("-");
153
+ const key = enabled ? trimmed : trimmed.slice(1);
154
+ if (key === "config" || key === "stdio" || key === "stderr" || key === "provenance" || key === "timing") viewers[key] = enabled;
155
+ }
156
+ return Object.keys(viewers).length > 0 ? viewers : void 0;
157
+ }
158
+ function coerceOptionValue(value) {
159
+ if (value === "true") return true;
160
+ if (value === "false") return false;
161
+ const numeric = Number(value);
162
+ return value.trim() !== "" && Number.isFinite(numeric) ? numeric : value;
163
+ }
164
+ function configKeyFromAttribute(value) {
165
+ return value.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
166
+ }
167
+ function unquote(value) {
168
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
169
+ return value;
170
+ }
171
+ //#endregion
172
+ //#region src/markdown.ts
173
+ const FENCE_OPEN = /^( {0,3})(`{3,}|~{3,})([^\n]*)$/;
174
+ function parseTopLevelFences(source) {
175
+ const lines = source.split("\n");
176
+ const fences = [];
177
+ let index = 0;
178
+ let offset = 0;
179
+ while (index < lines.length) {
180
+ const line = lines[index] ?? "";
181
+ const open = FENCE_OPEN.exec(line);
182
+ if (!open) {
183
+ offset += line.length + 1;
184
+ index += 1;
185
+ continue;
186
+ }
187
+ const indent = open[1] ?? "";
188
+ const marker = open[2] ?? "```";
189
+ const { language, meta } = readFenceInfo((open[3] ?? "").trim());
190
+ const start = offset;
191
+ index += 1;
192
+ offset += line.length + 1;
193
+ const body = [];
194
+ while (index < lines.length) {
195
+ const candidate = lines[index] ?? "";
196
+ if (new RegExp(`^ {0,3}${escapeRegExp(marker)}[ \t]*$`).exec(candidate)) {
197
+ const raw = `${line}\n${body.join("\n")}${body.length > 0 ? "\n" : ""}${candidate}`;
198
+ fences.push({
199
+ language,
200
+ meta,
201
+ code: body.join("\n"),
202
+ raw,
203
+ start,
204
+ end: start + raw.length,
205
+ indent,
206
+ marker
207
+ });
208
+ offset += candidate.length + 1;
209
+ index += 1;
210
+ break;
211
+ }
212
+ body.push(candidate);
213
+ offset += candidate.length + 1;
214
+ index += 1;
215
+ }
216
+ }
217
+ return fences;
218
+ }
219
+ function parsePlayFences(source) {
220
+ return parseTopLevelFences(source).filter((fence) => hasToken(fence.meta, "play")).map((fence) => {
221
+ const options = parsePlayMeta(fence.meta);
222
+ return {
223
+ language: fence.language,
224
+ meta: fence.meta,
225
+ code: fence.code,
226
+ raw: fence.raw,
227
+ start: fence.start,
228
+ end: fence.end,
229
+ typecheck: options.typecheck,
230
+ title: options.title,
231
+ config: options.config,
232
+ ui: options.ui,
233
+ viewers: options.viewers,
234
+ timeoutMs: options.timeoutMs
235
+ };
236
+ });
237
+ }
238
+ function stripPlayMeta(meta) {
239
+ return splitPlayInfo(meta).filter((token) => token && token !== "play" && token !== "typecheck" && !token.startsWith("play-") && !token.startsWith("play:")).join(" ");
240
+ }
241
+ function rewritePlayFences(source, encode) {
242
+ const fences = parsePlayFences(source);
243
+ if (fences.length === 0) return source;
244
+ let cursor = 0;
245
+ let output = "";
246
+ for (const fence of fences) {
247
+ output += source.slice(cursor, fence.start);
248
+ const encoded = encode(fence);
249
+ if (encoded === null) output += source.slice(fence.start, fence.end);
250
+ else {
251
+ const cleanedMeta = stripPlayMeta(fence.meta);
252
+ const info = [fence.language, cleanedMeta].filter(Boolean).join(" ");
253
+ output += `<!--ox-code-play:${encoded}-->\n\`\`\`${info}\n${fence.code}\n\`\`\``;
254
+ }
255
+ cursor = fence.end;
256
+ }
257
+ output += source.slice(cursor);
258
+ return output;
259
+ }
260
+ function parseCodePlayTags(source) {
261
+ const tags = [];
262
+ for (const match of source.matchAll(/<CodePlay\b([^>]*)>([\s\S]*?)<\/CodePlay>/gi)) {
263
+ const attrs = match[1] ?? "";
264
+ const language = readCodePlayAttribute(attrs, "lang") ?? readCodePlayAttribute(attrs, "language") ?? "text";
265
+ const options = parseCodePlayAttributes(attrs);
266
+ tags.push({
267
+ language,
268
+ meta: "play",
269
+ code: stripIndent((match[2] ?? "").replace(/^\n/, "").replace(/\n$/, "")),
270
+ raw: match[0] ?? "",
271
+ start: match.index ?? 0,
272
+ end: (match.index ?? 0) + (match[0]?.length ?? 0),
273
+ typecheck: options.typecheck,
274
+ title: options.title,
275
+ config: options.config,
276
+ ui: options.ui,
277
+ viewers: options.viewers,
278
+ timeoutMs: options.timeoutMs
279
+ });
280
+ }
281
+ return tags;
282
+ }
283
+ function readFenceInfo(info) {
284
+ const match = /^(\S+)(?:\s+([\s\S]*))?$/.exec(info);
285
+ return {
286
+ language: match?.[1] ?? "",
287
+ meta: match?.[2]?.trim() ?? ""
288
+ };
289
+ }
290
+ function hasToken(meta, token) {
291
+ return splitPlayInfo(meta).includes(token);
292
+ }
293
+ function escapeRegExp(value) {
294
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
295
+ }
296
+ function stripIndent(value) {
297
+ const lines = value.split("\n");
298
+ const indents = lines.filter((line) => line.trim()).map((line) => line.match(/^ */)?.[0].length ?? 0);
299
+ const indent = indents.length > 0 ? Math.min(...indents) : 0;
300
+ return lines.map((line) => line.slice(indent)).join("\n");
301
+ }
302
+ //#endregion
6
303
  //#region src/payload-factory.ts
7
304
  function payloadFromFence(fence, options) {
8
305
  const definition = resolveLanguage(fence.language);
9
306
  const enabled = definition ? options.languages.get(definition.id) : void 0;
10
- return {
307
+ const payload = {
11
308
  language: definition?.id ?? fence.language,
12
309
  code: fence.code,
310
+ title: fence.title,
13
311
  capabilities: {
14
312
  execute: enabled?.execute ?? Boolean(definition?.capabilities.execute),
15
313
  typecheck: payloadTypecheckEnabled(fence.typecheck, enabled?.typecheck, definition, options.endpoints)
16
314
  },
17
315
  config: {
18
316
  ...definition?.defaultConfig,
19
- ...enabled?.config
317
+ ...enabled?.config,
318
+ ...fence.config
20
319
  },
21
- viewers: options.viewers,
22
- ui: options.ui,
23
- timeoutMs: options.timeoutMs,
320
+ viewers: {
321
+ ...options.viewers,
322
+ ...fence.viewers
323
+ },
324
+ ui: fence.ui ?? options.ui,
325
+ timeoutMs: fence.timeoutMs ?? options.timeoutMs,
24
326
  endpoints: options.endpoints
25
327
  };
328
+ if (enabled?.endpoint) payload.endpoint = enabled.endpoint;
329
+ return payload;
26
330
  }
27
331
  /** TypeScript typecheck in the browser needs a reachable endpoint; hide the dead button otherwise. */
28
332
  function payloadTypecheckEnabled(fenceTypecheck, enabledTypecheck, definition, endpoints) {
@@ -60,21 +364,29 @@ function wrapCommentedBlocks(html, _options) {
60
364
  function upgradeCodePlayTags(html, options) {
61
365
  return html.replace(CODEPLAY_TAG_PATTERN, (all, attrs, body) => {
62
366
  if (/\bdata-ox-code-play=/.test(all)) return all;
63
- const language = readAttr$1(attrs, "lang") ?? readAttr$1(attrs, "language") ?? "text";
367
+ const language = readAttr(attrs, "lang") ?? readAttr(attrs, "language") ?? "text";
64
368
  const code = decodeHtml(body).replace(/^\n/, "").replace(/\n$/, "");
65
369
  const definition = resolveLanguage(language);
66
370
  const endpoints = options.endpoints ?? DEFAULT_ENDPOINTS;
371
+ const playOptions = parseCodePlayAttributes(attrs);
67
372
  return wrapWidget(options.encodePayload({
68
373
  language: definition?.id ?? language,
69
374
  code,
375
+ title: playOptions.title,
70
376
  capabilities: {
71
377
  execute: true,
72
- typecheck: payloadTypecheckEnabled(/\btypecheck\b/i.test(attrs), void 0, definition, endpoints)
378
+ typecheck: payloadTypecheckEnabled(playOptions.typecheck, void 0, definition, endpoints)
379
+ },
380
+ config: {
381
+ ...definition?.defaultConfig,
382
+ ...playOptions.config
383
+ },
384
+ viewers: {
385
+ ...DEFAULT_VIEWERS,
386
+ ...playOptions.viewers
73
387
  },
74
- config: {},
75
- viewers: { ...DEFAULT_VIEWERS },
76
- ui: "default",
77
- timeoutMs: 1e4,
388
+ ui: playOptions.ui ?? "default",
389
+ timeoutMs: playOptions.timeoutMs ?? 1e4,
78
390
  endpoints
79
391
  }), `<pre><code class="language-${escapeAttribute(language)}">${body}</code></pre>`);
80
392
  });
@@ -85,7 +397,7 @@ function wrapMatchingFences(html, matches) {
85
397
  used: false
86
398
  }));
87
399
  return html.replace(PRE_PATTERN, (all, attrs, body) => {
88
- const language = (readAttr$1(attrs, "class") ?? "").split(/\s+/).find((token) => token.startsWith("language-"))?.slice(9);
400
+ const language = (readAttr(attrs, "class") ?? "").split(/\s+/).find((token) => token.startsWith("language-"))?.slice(9);
89
401
  const code = normalizeCode(decodeHtml(stripTags(body)));
90
402
  const match = unused.find((item) => !item.used && aliasesEqual(item.language, language) && normalizeCode(item.code) === code);
91
403
  if (!match) return all;
@@ -94,7 +406,7 @@ function wrapMatchingFences(html, matches) {
94
406
  });
95
407
  }
96
408
  function wrapWidget(payload, inner) {
97
- return `<ox-code-play data-ox-code-play="${escapeAttribute(payload)}">${inner}</ox-code-play>`;
409
+ return `<ox-code-play data-ox-code-play="${escapeAttribute(payload)}" inert>${inner}</ox-code-play>`;
98
410
  }
99
411
  function readJsonString(source, start) {
100
412
  try {
@@ -119,7 +431,7 @@ function sliceJsonString(source, start) {
119
431
  }
120
432
  throw new Error("Unterminated HTML JSON string.");
121
433
  }
122
- function readAttr$1(attrs, name) {
434
+ function readAttr(attrs, name) {
123
435
  return new RegExp(`(?:^|\\s)${name}\\s*=\\s*"([^"]+)"`, "i").exec(attrs)?.[1];
124
436
  }
125
437
  function stripTags(value) {
@@ -133,122 +445,6 @@ function aliasesEqual(left, right) {
133
445
  return left.toLowerCase() === right.toLowerCase();
134
446
  }
135
447
  //#endregion
136
- //#region src/markdown.ts
137
- const FENCE_OPEN = /^( {0,3})(`{3,}|~{3,})([^\n]*)$/;
138
- function parseTopLevelFences(source) {
139
- const lines = source.split("\n");
140
- const fences = [];
141
- let index = 0;
142
- let offset = 0;
143
- while (index < lines.length) {
144
- const line = lines[index] ?? "";
145
- const open = FENCE_OPEN.exec(line);
146
- if (!open) {
147
- offset += line.length + 1;
148
- index += 1;
149
- continue;
150
- }
151
- const indent = open[1] ?? "";
152
- const marker = open[2] ?? "```";
153
- const [language = "", ...metaParts] = splitInfo((open[3] ?? "").trim());
154
- const start = offset;
155
- index += 1;
156
- offset += line.length + 1;
157
- const body = [];
158
- while (index < lines.length) {
159
- const candidate = lines[index] ?? "";
160
- if (new RegExp(`^ {0,3}${escapeRegExp(marker)}[ \t]*$`).exec(candidate)) {
161
- const raw = `${line}\n${body.join("\n")}${body.length > 0 ? "\n" : ""}${candidate}`;
162
- fences.push({
163
- language,
164
- meta: metaParts.join(" "),
165
- code: body.join("\n"),
166
- raw,
167
- start,
168
- end: start + raw.length,
169
- indent,
170
- marker
171
- });
172
- offset += candidate.length + 1;
173
- index += 1;
174
- break;
175
- }
176
- body.push(candidate);
177
- offset += candidate.length + 1;
178
- index += 1;
179
- }
180
- }
181
- return fences;
182
- }
183
- function parsePlayFences(source) {
184
- return parseTopLevelFences(source).filter((fence) => hasToken(fence.meta, "play")).map((fence) => ({
185
- language: fence.language,
186
- meta: fence.meta,
187
- code: fence.code,
188
- raw: fence.raw,
189
- start: fence.start,
190
- end: fence.end,
191
- typecheck: hasToken(fence.meta, "typecheck")
192
- }));
193
- }
194
- function stripPlayMeta(meta) {
195
- return meta.split(/\s+/).filter((token) => token && token !== "play" && token !== "typecheck" && !token.startsWith("play-")).join(" ");
196
- }
197
- function rewritePlayFences(source, encode) {
198
- const fences = parsePlayFences(source);
199
- if (fences.length === 0) return source;
200
- let cursor = 0;
201
- let output = "";
202
- for (const fence of fences) {
203
- output += source.slice(cursor, fence.start);
204
- const encoded = encode(fence);
205
- if (encoded === null) output += source.slice(fence.start, fence.end);
206
- else {
207
- const cleanedMeta = stripPlayMeta(fence.meta);
208
- const info = [fence.language, cleanedMeta].filter(Boolean).join(" ");
209
- output += `<!--ox-code-play:${encoded}-->\n\`\`\`${info}\n${fence.code}\n\`\`\``;
210
- }
211
- cursor = fence.end;
212
- }
213
- output += source.slice(cursor);
214
- return output;
215
- }
216
- function parseCodePlayTags(source) {
217
- const tags = [];
218
- for (const match of source.matchAll(/<CodePlay\b([^>]*)>([\s\S]*?)<\/CodePlay>/gi)) {
219
- const attrs = match[1] ?? "";
220
- const language = readAttr(attrs, "lang") ?? readAttr(attrs, "language") ?? "text";
221
- tags.push({
222
- language,
223
- meta: "play",
224
- code: stripIndent((match[2] ?? "").replace(/^\n/, "").replace(/\n$/, "")),
225
- raw: match[0] ?? "",
226
- start: match.index ?? 0,
227
- end: (match.index ?? 0) + (match[0]?.length ?? 0),
228
- typecheck: /\btypecheck\b/i.test(attrs)
229
- });
230
- }
231
- return tags;
232
- }
233
- function splitInfo(info) {
234
- return info.trim().split(/\s+/).filter(Boolean);
235
- }
236
- function hasToken(meta, token) {
237
- return meta.split(/\s+/).includes(token);
238
- }
239
- function escapeRegExp(value) {
240
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
241
- }
242
- function readAttr(attrs, name) {
243
- return new RegExp(`(?:^|\\s)${name}\\s*=\\s*"([^"]+)"`, "i").exec(attrs)?.[1];
244
- }
245
- function stripIndent(value) {
246
- const lines = value.split("\n");
247
- const indents = lines.filter((line) => line.trim()).map((line) => line.match(/^ */)?.[0].length ?? 0);
248
- const indent = indents.length > 0 ? Math.min(...indents) : 0;
249
- return lines.map((line) => line.slice(indent)).join("\n");
250
- }
251
- //#endregion
252
448
  //#region src/plugin-client.ts
253
449
  const CLIENT_FILE_CANDIDATES = [
254
450
  "browser.mjs",
@@ -273,6 +469,13 @@ function assertBrowserClientSource(source) {
273
469
  return source;
274
470
  }
275
471
  //#endregion
472
+ //#region src/plugin-assets.ts
473
+ async function writeClientAsset(outDir, source) {
474
+ if (!source) return;
475
+ await mkdir(outDir, { recursive: true });
476
+ await writeFile(path.join(outDir, "ox-code-play.js"), source);
477
+ }
478
+ //#endregion
276
479
  //#region src/plugin-proxy.ts
277
480
  const PROXY_MAX_BODY_BYTES = 262144;
278
481
  var ProxyRequestError = class extends Error {
@@ -356,10 +559,17 @@ const MARKDOWN_RE = /\.(?:md|markdown|mdx)(?:$|\?)/i;
356
559
  function codePlay(options = {}) {
357
560
  const resolved = resolveCodePlayOptions(options);
358
561
  const explicitTypecheck = options.endpoints?.typecheck;
562
+ const proxyTargets = {
563
+ rust: resolved.endpoints.rust,
564
+ go: resolved.endpoints.go
565
+ };
359
566
  let base = resolved.base;
360
567
  let command = "serve";
361
568
  let outDir = resolved.outDir;
362
569
  let root = process.cwd();
570
+ let needsClientAsset = false;
571
+ let emittedClientAsset = false;
572
+ let clientSource;
363
573
  const enhanceOptions = (matchFences) => ({
364
574
  scriptSrc: `${base}ox-code-play.js`,
365
575
  decodePayload,
@@ -374,10 +584,12 @@ function codePlay(options = {}) {
374
584
  root = config.root;
375
585
  base = resolved.base === "/" ? normalizeBase(config.base) : resolved.base;
376
586
  outDir = resolved.outDir ?? config.build.outDir;
377
- if (explicitTypecheck === void 0) {
378
- if (command === "serve" && resolved.proxy) resolved.endpoints.typecheck = DEV_TYPECHECK_PATH;
379
- else delete resolved.endpoints.typecheck;
587
+ if (command === "serve" && resolved.proxy) {
588
+ resolved.endpoints.rust = options.endpoints?.rust ?? "/__ox-code-play/rust";
589
+ resolved.endpoints.go = options.endpoints?.go ?? "/__ox-code-play/go";
380
590
  }
591
+ if (explicitTypecheck === void 0 && command === "serve" && resolved.proxy) resolved.endpoints.typecheck = DEV_TYPECHECK_PATH;
592
+ else if (explicitTypecheck === void 0) delete resolved.endpoints.typecheck;
381
593
  },
382
594
  resolveId(id) {
383
595
  return id === VIRTUAL_ID ? RESOLVED_VIRTUAL : null;
@@ -388,7 +600,11 @@ function codePlay(options = {}) {
388
600
  },
389
601
  transform(code, id) {
390
602
  if (!MARKDOWN_RE.test(id)) {
391
- if (code.includes("export const html = ") && code.includes("ox-code-play:")) return enhanceGeneratedModule(code, enhanceOptions());
603
+ if (code.includes("export const html = ") && code.includes("ox-code-play:")) {
604
+ const enhanced = enhanceGeneratedModule(code, enhanceOptions());
605
+ if (enhanced !== code) needsClientAsset = true;
606
+ return enhanced;
607
+ }
392
608
  return null;
393
609
  }
394
610
  const rewritten = rewritePlayFences(code, (fence) => {
@@ -396,10 +612,12 @@ function codePlay(options = {}) {
396
612
  if (!definition || !resolved.languages.has(definition.id)) return null;
397
613
  return encodePayload(payloadFromFence(fence, resolved));
398
614
  });
399
- return rewritten === code ? null : rewritten;
615
+ if (rewritten === code) return null;
616
+ needsClientAsset = true;
617
+ return rewritten;
400
618
  },
401
619
  configureServer(server) {
402
- if (resolved.proxy) mountProxies(server, resolved.endpoints.rust, resolved.endpoints.go);
620
+ if (resolved.proxy) mountProxies(server, proxyTargets.rust, proxyTargets.go);
403
621
  server.middlewares.use(async (req, res, next) => {
404
622
  const urlPath = req.url?.split("?")[0] ?? "";
405
623
  if (urlPath === `${base}ox-code-play.js`.replace(/\/{2,}/g, "/")) {
@@ -414,22 +632,33 @@ function codePlay(options = {}) {
414
632
  });
415
633
  },
416
634
  async generateBundle() {
417
- const file = resolveClientFile();
418
- if (!file || command !== "build") return;
635
+ if (command !== "build" || !needsClientAsset) return;
636
+ const source = await loadClientSource();
637
+ if (!source) return;
419
638
  this.emitFile({
420
639
  type: "asset",
421
640
  fileName: "ox-code-play.js",
422
- source: assertBrowserClientSource(await readFile(file, "utf8"))
641
+ source
423
642
  });
643
+ emittedClientAsset = true;
424
644
  },
425
645
  async closeBundle() {
426
646
  if (command !== "build") return;
427
647
  const srcDir = path.resolve(root, resolved.srcDir ?? "docs");
428
648
  const destination = path.resolve(root, outDir ?? "dist");
429
649
  if (!existsSync(srcDir) || !existsSync(destination)) return;
430
- await enhanceWrittenPages(srcDir, destination, resolved, enhanceOptions);
650
+ if (!await enhanceWrittenPages(srcDir, destination, resolved, enhanceOptions)) return;
651
+ needsClientAsset = true;
652
+ if (!emittedClientAsset) await writeClientAsset(destination, await loadClientSource());
431
653
  }
432
654
  };
655
+ async function loadClientSource() {
656
+ if (clientSource) return clientSource;
657
+ const file = resolveClientFile();
658
+ if (!file) return;
659
+ clientSource = assertBrowserClientSource(await readFile(file, "utf8"));
660
+ return clientSource;
661
+ }
433
662
  }
434
663
  function interceptHtml(res, enhance) {
435
664
  const originalEnd = res.end.bind(res);
@@ -481,6 +710,7 @@ function mountProxies(server, rustUrl, goUrl) {
481
710
  });
482
711
  }
483
712
  async function enhanceWrittenPages(srcDir, outDir, resolved, enhance) {
713
+ let enhancedAny = false;
484
714
  for (const file of walkFiles(srcDir)) {
485
715
  if (!MARKDOWN_RE.test(file)) continue;
486
716
  const source = await readFile(file, "utf8");
@@ -497,8 +727,12 @@ async function enhanceWrittenPages(srcDir, outDir, resolved, enhance) {
497
727
  code: fence.code,
498
728
  payload: encodePayload(payloadFromFence(fence, resolved))
499
729
  }))));
500
- if (enhanced !== html) await writeFile(htmlPath, enhanced);
730
+ if (enhanced !== html) {
731
+ await writeFile(htmlPath, enhanced);
732
+ enhancedAny = true;
733
+ }
501
734
  }
735
+ return enhancedAny;
502
736
  }
503
737
  function guessHtmlPath(file, srcDir, outDir) {
504
738
  const relative = path.relative(srcDir, file).replace(/\.(?:md|markdown|mdx)$/i, "");
@@ -519,6 +753,6 @@ function normalizeBase(base) {
519
753
  return base.endsWith("/") ? base : `${base}/`;
520
754
  }
521
755
  //#endregion
522
- export { stripPlayMeta as a, rewritePlayFences as i, parseCodePlayTags as n, enhanceGeneratedModule as o, parsePlayFences as r, enhancePlayHtml as s, codePlay as t };
756
+ export { parsePlayFences as a, parseCodePlayTags as i, enhanceGeneratedModule as n, rewritePlayFences as o, enhancePlayHtml as r, stripPlayMeta as s, codePlay as t };
523
757
 
524
758
  //# sourceMappingURL=plugin2.mjs.map