@tinycloud/cli 1.3.0-beta.49 → 1.3.0-beta.50

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
@@ -47,15 +47,15 @@ function closeReplication(schedule = scheduleTimeout) {
47
47
  removeSignalHandlers();
48
48
  let resolveClosing;
49
49
  let rejectClosing;
50
- const closing = new Promise((resolve5, reject) => {
51
- resolveClosing = resolve5;
50
+ const closing = new Promise((resolve6, reject) => {
51
+ resolveClosing = resolve6;
52
52
  rejectClosing = reject;
53
53
  });
54
54
  state.closing = closing;
55
55
  void (async () => {
56
56
  let timeout;
57
- const deadline = new Promise((resolve5) => {
58
- timeout = schedule(() => resolve5(true), CLOSE_TIMEOUT_MS);
57
+ const deadline = new Promise((resolve6) => {
58
+ timeout = schedule(() => resolve6(true), CLOSE_TIMEOUT_MS);
59
59
  });
60
60
  let timedOut = false;
61
61
  const abandonQueuedControls = () => {
@@ -81,7 +81,7 @@ function closeReplication(schedule = scheduleTimeout) {
81
81
  if (state.generation === generation && state.allControls.size === 0) break;
82
82
  }
83
83
  if (timedOut) break;
84
- const turn = new Promise((resolve5) => setImmediate(() => resolve5(false)));
84
+ const turn = new Promise((resolve6) => setImmediate(() => resolve6(false)));
85
85
  timedOut ||= await Promise.race([turn, deadline]);
86
86
  if (timedOut) abandonQueuedControls();
87
87
  else if (state.allControls.size === 0) break;
@@ -166,17 +166,22 @@ function safeClipped(text, max = ECHO_FIELD_MAX) {
166
166
  const escaped = Array.from(safeInline(text));
167
167
  return escaped.length <= max ? escaped.join("") : `${escaped.slice(0, max).join("")}\u2026`;
168
168
  }
169
+ function safeClippedList(items) {
170
+ const shown = items.slice(0, ECHO_LIST_MAX).map((item) => `"${safeClipped(item)}"`).join(", ");
171
+ return items.length > ECHO_LIST_MAX ? `${shown} \u2026 and ${items.length - ECHO_LIST_MAX} more` : shown;
172
+ }
169
173
  function safeJsonStringify(value, space) {
170
174
  const text = JSON.stringify(value, null, space);
171
175
  return text === void 0 ? text : text.replace(JSON_UNSAFE, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
172
176
  }
173
- var UNSAFE_BLOCK, UNSAFE_INLINE, ECHO_FIELD_MAX, JSON_UNSAFE;
177
+ var UNSAFE_BLOCK, UNSAFE_INLINE, ECHO_FIELD_MAX, ECHO_LIST_MAX, JSON_UNSAFE;
174
178
  var init_safe_text = __esm({
175
179
  "src/output/safe-text.ts"() {
176
180
  "use strict";
177
181
  UNSAFE_BLOCK = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ufeff]/g;
178
182
  UNSAFE_INLINE = /[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ufeff]/g;
179
183
  ECHO_FIELD_MAX = 80;
184
+ ECHO_LIST_MAX = 5;
180
185
  JSON_UNSAFE = /[\u007f-\u009f\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ufeff]/g;
181
186
  }
182
187
  });
@@ -13237,31 +13242,31 @@ function parseArtifactManifest(bytes) {
13237
13242
  } catch {
13238
13243
  throw new ArtifactError("corrupt", "artifact manifest is not valid JSON");
13239
13244
  }
13240
- const fail = () => {
13245
+ const fail2 = () => {
13241
13246
  throw new ArtifactError("corrupt", "artifact manifest is malformed");
13242
13247
  };
13243
- if (value === null || typeof value !== "object" || Array.isArray(value)) return fail();
13248
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return fail2();
13244
13249
  const record2 = value;
13245
13250
  const allowed = ["v", "files", "createdAt", "parent", "note"];
13246
- if (Object.keys(record2).some((key) => !allowed.includes(key)) || record2.v !== ARTIFACT_MANIFEST_VERSION) return fail();
13247
- if (!Array.isArray(record2.files) || record2.files.length === 0) return fail();
13248
- if (typeof record2.createdAt !== "string" || Number.isNaN(Date.parse(record2.createdAt))) return fail();
13249
- if (record2.parent !== null && (typeof record2.parent !== "number" || !Number.isSafeInteger(record2.parent) || record2.parent < 1)) return fail();
13250
- if (record2.note !== void 0 && (typeof record2.note !== "string" || record2.note.length > 1024)) return fail();
13251
+ if (Object.keys(record2).some((key) => !allowed.includes(key)) || record2.v !== ARTIFACT_MANIFEST_VERSION) return fail2();
13252
+ if (!Array.isArray(record2.files) || record2.files.length === 0) return fail2();
13253
+ if (typeof record2.createdAt !== "string" || Number.isNaN(Date.parse(record2.createdAt))) return fail2();
13254
+ if (record2.parent !== null && (typeof record2.parent !== "number" || !Number.isSafeInteger(record2.parent) || record2.parent < 1)) return fail2();
13255
+ if (record2.note !== void 0 && (typeof record2.note !== "string" || record2.note.length > 1024)) return fail2();
13251
13256
  const seen = /* @__PURE__ */ new Set();
13252
13257
  const files = record2.files.map((entry) => {
13253
- if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return fail();
13258
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return fail2();
13254
13259
  const file = entry;
13255
- if (Object.keys(file).some((key) => !["path", "sha256", "bytes"].includes(key))) return fail();
13256
- if (typeof file.path !== "string" || file.path === ARTIFACT_MANIFEST_NAME || seen.has(file.path)) return fail();
13260
+ if (Object.keys(file).some((key) => !["path", "sha256", "bytes"].includes(key))) return fail2();
13261
+ if (typeof file.path !== "string" || file.path === ARTIFACT_MANIFEST_NAME || seen.has(file.path)) return fail2();
13257
13262
  try {
13258
13263
  canonicalShareFilename(file.path);
13259
13264
  } catch {
13260
- return fail();
13265
+ return fail2();
13261
13266
  }
13262
13267
  seen.add(file.path);
13263
- if (typeof file.sha256 !== "string" || !SHA256_HEX.test(file.sha256)) return fail();
13264
- if (typeof file.bytes !== "number" || !Number.isSafeInteger(file.bytes) || file.bytes < 0) return fail();
13268
+ if (typeof file.sha256 !== "string" || !SHA256_HEX.test(file.sha256)) return fail2();
13269
+ if (typeof file.bytes !== "number" || !Number.isSafeInteger(file.bytes) || file.bytes < 0) return fail2();
13265
13270
  return { path: file.path, sha256: file.sha256, bytes: file.bytes };
13266
13271
  });
13267
13272
  return {
@@ -26562,6 +26567,1173 @@ async function writeShareOutput(directory, filename, bytes, force) {
26562
26567
  return join11(outputDirectory, safeName);
26563
26568
  }
26564
26569
 
26570
+ // src/share/html-folder.ts
26571
+ import { constants as constants2, existsSync } from "fs";
26572
+ import { lstat as lstat7, open as open8, readdir as readdir6, realpath as realpath3, stat as stat5 } from "fs/promises";
26573
+ import { basename as basename3, dirname as dirname5, isAbsolute, join as join12, relative as relativePath, resolve as resolve5, sep as sep4 } from "path";
26574
+
26575
+ // ../share-sdk/dist/html-bundle.js
26576
+ var UNSAFE_FILENAME_CODE_POINT3 = /[\p{Cc}\p{Cf}\p{Cs}\u2028\u2029]/u;
26577
+ function hasUnsafeFilenameCodePoint3(value) {
26578
+ return UNSAFE_FILENAME_CODE_POINT3.test(value);
26579
+ }
26580
+ var ARTIFACT_ENTRY = "index.html";
26581
+ var MAX_ARTIFACT_FILES = 1e3;
26582
+ var MAX_ARTIFACT_SOURCE_BYTES = 100 * 1024 * 1024;
26583
+ var MAX_ARTIFACT_RENDER_BYTES = 10 * 1024 * 1024;
26584
+ var MAX_ARTIFACT_TEXT_BYTES = 5 * 1024 * 1024;
26585
+ var MAX_ARTIFACT_REFERENCES = 2e3;
26586
+ var MAX_ARTIFACT_CSS_IMPORT_DEPTH = 16;
26587
+ var MAX_ARTIFACT_COMMENTS = 2e4;
26588
+ var MAX_ARTIFACT_ATTRIBUTES_PER_ELEMENT = 5e3;
26589
+ var ArtifactBundleError = class extends Error {
26590
+ /**
26591
+ * `path` is the bundle file being processed when the refusal happened; `reference` is the reference text inside it
26592
+ * that was refused, when there is one. Both are the folder's own text: print them through a terminal-safe printer.
26593
+ */
26594
+ constructor(kind, message, path, reference) {
26595
+ super(message);
26596
+ this.kind = kind;
26597
+ this.path = path;
26598
+ this.reference = reference;
26599
+ this.name = "ArtifactBundleError";
26600
+ }
26601
+ };
26602
+ var MIME_BY_EXTENSION = Object.freeze({
26603
+ html: "text/html;charset=utf-8",
26604
+ htm: "text/html;charset=utf-8",
26605
+ css: "text/css;charset=utf-8",
26606
+ js: "text/javascript;charset=utf-8",
26607
+ json: "application/json;charset=utf-8",
26608
+ txt: "text/plain;charset=utf-8",
26609
+ md: "text/markdown;charset=utf-8",
26610
+ svg: "image/svg+xml",
26611
+ png: "image/png",
26612
+ jpg: "image/jpeg",
26613
+ jpeg: "image/jpeg",
26614
+ gif: "image/gif",
26615
+ webp: "image/webp",
26616
+ avif: "image/avif",
26617
+ ico: "image/x-icon",
26618
+ woff: "font/woff",
26619
+ woff2: "font/woff2",
26620
+ ttf: "font/ttf",
26621
+ otf: "font/otf",
26622
+ mp3: "audio/mpeg",
26623
+ wav: "audio/wav",
26624
+ mp4: "video/mp4",
26625
+ webm: "video/webm",
26626
+ pdf: "application/pdf"
26627
+ });
26628
+ var SCHEME = /^[a-z][a-z0-9+.-]*:/i;
26629
+ var ENCODED_PATH_ALIAS = /%2f|%5c|%2e/i;
26630
+ var SAFE_MEDIA_QUERY = /^[a-z0-9\s,().:%\-+/]+$/i;
26631
+ var UNSAFE_CLASSIC_SCRIPT = /\b(?:fetch|XMLHttpRequest|WebSocket|EventSource|Worker|SharedWorker|importScripts|sendBeacon)\s*\(|\b(?:window|self|globalThis|top|parent|opener)\s*\.\s*open\s*\(|\b(?:window|document|globalThis|self|top|parent|opener)\s*(?:\.\s*location|\[\s*["']location["']\s*\])|\blocation\s*(?:=|(?:\.\s*|\[\s*["'])href(?:["']\s*\])?|\.assign\s*\(|\.replace\s*\(|\.reload\s*\()|\bimport\s*\(|\beval\s*\(|\bWebAssembly\b/;
26632
+ function fail(kind, detail) {
26633
+ throw new ArtifactBundleError(kind, detail);
26634
+ }
26635
+ function defaultParseHtml(html) {
26636
+ if (typeof DOMParser === "undefined") {
26637
+ throw new TypeError("html-bundle needs a DOM parser: pass parseHtml outside a browser");
26638
+ }
26639
+ return new DOMParser().parseFromString(html, "text/html");
26640
+ }
26641
+ function canonicalArtifactPath(input) {
26642
+ const value = input.normalize("NFC");
26643
+ const encodedLength = new TextEncoder().encode(value).byteLength;
26644
+ if (value.length === 0 || encodedLength > 4096 || value.startsWith("/") || value.endsWith("/") || value.includes("\\") || hasUnsafeFilenameCodePoint3(value) || ENCODED_PATH_ALIAS.test(value)) {
26645
+ fail("malformed", "artifact path is not canonical");
26646
+ }
26647
+ const segments = value.split("/");
26648
+ if (segments.some((segment) => segment.length === 0 || segment === "." || segment === ".." || new TextEncoder().encode(segment).byteLength > 255)) {
26649
+ fail("malformed", "artifact path is not canonical");
26650
+ }
26651
+ return segments.join("/");
26652
+ }
26653
+ function artifactMediaType(path, supplied) {
26654
+ const normalized = supplied?.split(";")[0]?.trim().toLowerCase();
26655
+ if (normalized !== void 0 && /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(normalized)) {
26656
+ return supplied.trim();
26657
+ }
26658
+ const extension = path.split(".").at(-1)?.toLowerCase() ?? "";
26659
+ return MIME_BY_EXTENSION[extension] ?? "application/octet-stream";
26660
+ }
26661
+ function resolveArtifactReference(basePath, reference) {
26662
+ const raw = trimUrl(reference);
26663
+ if (raw.length === 0) return { path: canonicalArtifactPath(basePath), fragment: "", fragmentOnly: true };
26664
+ if (raw.startsWith("#")) {
26665
+ return { path: canonicalArtifactPath(basePath), fragment: raw, fragmentOnly: true };
26666
+ }
26667
+ if (raw.startsWith("//") || raw.startsWith("/") || SCHEME.test(raw)) {
26668
+ fail("unsupported", "external artifact reference");
26669
+ }
26670
+ const hashAt = raw.indexOf("#");
26671
+ const queryAt = raw.indexOf("?");
26672
+ const end = [hashAt, queryAt].filter((value) => value >= 0).reduce((min, value) => Math.min(min, value), raw.length);
26673
+ const pathname = raw.slice(0, end);
26674
+ const fragment = hashAt >= 0 ? raw.slice(hashAt) : "";
26675
+ if (pathname.length === 0) return { path: canonicalArtifactPath(basePath), fragment, fragmentOnly: true };
26676
+ if (pathname.includes("\\") || hasUnsafeFilenameCodePoint3(pathname) || ENCODED_PATH_ALIAS.test(pathname)) {
26677
+ fail("malformed", "artifact reference is not canonical");
26678
+ }
26679
+ const output = basePath.split("/").slice(0, -1);
26680
+ for (const segment of pathname.normalize("NFC").split("/")) {
26681
+ if (segment === "" || segment === ".") continue;
26682
+ if (segment === "..") {
26683
+ if (output.length === 0) fail("malformed", "artifact reference escapes its bundle");
26684
+ output.pop();
26685
+ continue;
26686
+ }
26687
+ output.push(segment);
26688
+ }
26689
+ return { path: canonicalArtifactPath(output.join("/")), fragment, fragmentOnly: false };
26690
+ }
26691
+ function detectHtmlArtifact(paths) {
26692
+ const seen = /* @__PURE__ */ new Set();
26693
+ let rootCount = 0;
26694
+ for (const rawPath of paths) {
26695
+ const path = canonicalArtifactPath(rawPath);
26696
+ const key = path.toLowerCase();
26697
+ if (seen.has(key)) fail("malformed", "artifact paths collide after canonicalization");
26698
+ seen.add(key);
26699
+ if (path === ARTIFACT_ENTRY) rootCount += 1;
26700
+ }
26701
+ if (rootCount === 1) return { kind: "html", entry: ARTIFACT_ENTRY };
26702
+ return { kind: "folder", reason: "missing-root-index" };
26703
+ }
26704
+ function toBase64(bytes) {
26705
+ let binary = "";
26706
+ const chunk = 32768;
26707
+ for (let offset = 0; offset < bytes.length; offset += chunk) {
26708
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk));
26709
+ }
26710
+ return btoa(binary);
26711
+ }
26712
+ function chargeInlinedBytes(context, bytes) {
26713
+ context.counter.inlinedBytes += bytes;
26714
+ if (context.counter.inlinedBytes > MAX_ARTIFACT_RENDER_BYTES) fail("limit", "artifact inlined resources exceed the render limit");
26715
+ }
26716
+ function asDataUrl(file, context) {
26717
+ if (file.bytes.byteLength > MAX_ARTIFACT_RENDER_BYTES) fail("limit", "artifact resource exceeds render limit");
26718
+ const prefix = `data:${artifactMediaType(file.path, file.mediaType)};base64,`;
26719
+ chargeInlinedBytes(context, prefix.length + 4 * Math.ceil(file.bytes.byteLength / 3));
26720
+ return `${prefix}${toBase64(file.bytes)}`;
26721
+ }
26722
+ function decodeText(file, context) {
26723
+ if (file.bytes.byteLength > MAX_ARTIFACT_TEXT_BYTES) fail("limit", "artifact text file exceeds render limit");
26724
+ if (context !== void 0) chargeInlinedBytes(context, file.bytes.byteLength);
26725
+ try {
26726
+ return new TextDecoder("utf-8", { fatal: true }).decode(file.bytes);
26727
+ } catch {
26728
+ return fail("malformed", "artifact text is not utf-8");
26729
+ }
26730
+ }
26731
+ function assertClassicScript(source) {
26732
+ if (UNSAFE_CLASSIC_SCRIPT.test(source)) fail("unsupported", "artifact script uses a blocked browser capability");
26733
+ }
26734
+ function normalizeMediaQuery(media, detail = "artifact uses an unsupported stylesheet media query") {
26735
+ const value = media?.trim() ?? "";
26736
+ if (value.length === 0 || value.toLowerCase() === "all") return null;
26737
+ if (/[\u0000-\u001f\u007f<>{};"'`\\]/.test(value) || !SAFE_MEDIA_QUERY.test(value)) fail("unsupported", detail);
26738
+ return value;
26739
+ }
26740
+ function wrapMediaCss(source, media, detail) {
26741
+ const value = normalizeMediaQuery(media, detail);
26742
+ if (value === null) return source;
26743
+ return `@media ${value} {
26744
+ ${source}
26745
+ }`;
26746
+ }
26747
+ function classifyLinkRelation(rel) {
26748
+ const tokens = rel?.trim().toLowerCase().split(/\s+/).filter(Boolean) ?? [];
26749
+ if (tokens.length === 0) return null;
26750
+ const tokenSet = new Set(tokens);
26751
+ if (tokenSet.has("stylesheet") && [...tokenSet].every((token) => token === "stylesheet" || token === "alternate")) return "stylesheet";
26752
+ if ((tokenSet.has("icon") || tokenSet.has("apple-touch-icon")) && [...tokenSet].every((token) => token === "icon" || token === "apple-touch-icon" || token === "shortcut")) return "icon";
26753
+ return null;
26754
+ }
26755
+ var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
26756
+ function documentRoots(doc) {
26757
+ const roots = [doc];
26758
+ for (let index = 0; index < roots.length; index += 1) {
26759
+ for (const template of roots[index].querySelectorAll("template")) roots.push(template.content);
26760
+ }
26761
+ return roots;
26762
+ }
26763
+ function queryAll(roots, selector) {
26764
+ return roots.flatMap((root) => [...root.querySelectorAll(selector)]);
26765
+ }
26766
+ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
26767
+ "href",
26768
+ "src",
26769
+ "srcset",
26770
+ "imagesrcset",
26771
+ "poster",
26772
+ "action",
26773
+ "formaction",
26774
+ "cite",
26775
+ "data",
26776
+ "codebase",
26777
+ "longdesc",
26778
+ "usemap",
26779
+ "ping",
26780
+ "background",
26781
+ "manifest",
26782
+ "icon",
26783
+ "archive",
26784
+ "classid",
26785
+ "profile",
26786
+ "lowsrc",
26787
+ "dynsrc",
26788
+ "attributionsrc",
26789
+ "itemtype",
26790
+ "itemid"
26791
+ ]);
26792
+ function trimUrl(value) {
26793
+ return value.replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, "");
26794
+ }
26795
+ function urlText(value) {
26796
+ return trimUrl(value).replace(/[\t\n\r]/g, "");
26797
+ }
26798
+ function isFragmentOnly(trimmed) {
26799
+ return /^#[^\u0000-\u0020\u007f-\u009f\\]*$/.test(trimmed);
26800
+ }
26801
+ var INLINE_DATA_URL = /^data:;base64(?:,[A-Za-z0-9+/=]*)?(?:#[^\u0000-\u0020\\]*)?$/;
26802
+ function isInlineReference(value) {
26803
+ const text = urlText(value);
26804
+ return isFragmentOnly(text) || INLINE_DATA_URL.test(text);
26805
+ }
26806
+ function assertInlineSrcset(value) {
26807
+ for (const raw of value.split(/[\t\n\f\r ]+/)) {
26808
+ const token = raw.replace(/,+$/, "");
26809
+ if (token === "" || /^\d+(?:\.\d+)?[wx]$/i.test(token)) continue;
26810
+ if (!isInlineReference(token)) throw new ArtifactBundleError("unsupported", "artifact page still references a resource", void 0, token);
26811
+ }
26812
+ }
26813
+ var JAVASCRIPT_TYPES = /* @__PURE__ */ new Set([
26814
+ "application/ecmascript",
26815
+ "application/javascript",
26816
+ "application/x-ecmascript",
26817
+ "application/x-javascript",
26818
+ "text/ecmascript",
26819
+ "text/javascript",
26820
+ "text/javascript1.0",
26821
+ "text/javascript1.1",
26822
+ "text/javascript1.2",
26823
+ "text/javascript1.3",
26824
+ "text/javascript1.4",
26825
+ "text/javascript1.5",
26826
+ "text/jscript",
26827
+ "text/livescript",
26828
+ "text/x-ecmascript",
26829
+ "text/x-javascript"
26830
+ ]);
26831
+ function isJavaScriptType(value) {
26832
+ const type = (value ?? "").replace(/^[\t\n\f\r ]+|[\t\n\f\r ]+$/g, "").toLowerCase();
26833
+ if (type === "") return true;
26834
+ return JAVASCRIPT_TYPES.has(type.split(";")[0].replace(/[\t\n\f\r ]+$/g, ""));
26835
+ }
26836
+ var BLOCKED_ELEMENTS = "base, form, iframe, frame, frameset, object, embed, portal, noscript, plaintext, xmp, noembed, noframes, applet, area[href], link:not([href]), [ping]";
26837
+ var DATA_URI = /data:[a-z0-9!#$&^_.+/-]*(?:;[a-z0-9!#$&^_.+=-]+)*;base64,[A-Za-z0-9+/=]*/gi;
26838
+ function withoutDataUris(html) {
26839
+ return html.replace(DATA_URI, "data:;base64,");
26840
+ }
26841
+ var TEXT_ATTRIBUTES = /^(?:alt|title|placeholder|value|label|content|name|id|class|lang|summary|abbr|datetime|pattern|xmlns(?::[\w.-]+)?|aria-[\w.:-]*|data-[\w.:-]*)$/;
26842
+ function looksLikeUrl(raw) {
26843
+ const value = raw.replace(/\\/g, "/");
26844
+ return /(?:^|[^a-z0-9+.-])(?:https?|ftps?|wss?|file|javascript|vbscript|blob|about|view-source|filesystem):/i.test(value) || /(?:^|[\s,;(="'])\/\/\S/.test(value) || /:\/\//.test(value) || /^\s*\.{0,2}\/[^\s]/.test(value);
26845
+ }
26846
+ var NAMED_CHARACTERS = {
26847
+ amp: "&",
26848
+ quot: '"',
26849
+ apos: "'",
26850
+ lt: "<",
26851
+ gt: ">",
26852
+ nbsp: "\xA0",
26853
+ colon: ":",
26854
+ sol: "/",
26855
+ bsol: "\\",
26856
+ period: ".",
26857
+ comma: ",",
26858
+ semi: ";",
26859
+ equals: "=",
26860
+ num: "#",
26861
+ quest: "?",
26862
+ lpar: "(",
26863
+ rpar: ")",
26864
+ tab: " ",
26865
+ newline: "\n"
26866
+ };
26867
+ function decodeAttributeValue(value) {
26868
+ return value.replace(/&(?:#[xX]([0-9a-fA-F]{1,6})|#([0-9]{1,7})|([A-Za-z]{2,8}));?/g, (whole, hex3, decimal, name) => {
26869
+ if (name !== void 0) return NAMED_CHARACTERS[name.toLowerCase()] ?? whole;
26870
+ const point = Number.parseInt(hex3 ?? decimal ?? "", hex3 === void 0 ? 10 : 16);
26871
+ return point > 0 && point <= 1114111 && !(point >= 55296 && point <= 57343) ? String.fromCodePoint(point) : "\uFFFD";
26872
+ });
26873
+ }
26874
+ var MARKUP_BLOCKED_NAMES = /* @__PURE__ */ new Set(["base", "form", "iframe", "frame", "frameset", "object", "embed", "portal", "noscript", "plaintext", "xmp", "noembed", "noframes", "applet"]);
26875
+ function unsupportedOutput(detail, reference) {
26876
+ throw new ArtifactBundleError("unsupported", detail, void 0, reference);
26877
+ }
26878
+ function isHrefAnimation(tag, attributeName, value) {
26879
+ return /^(?:animate|set|animatetransform|animatemotion)$/i.test(tag) && attributeName.toLowerCase() === "attributename" && /^(?:xlink:)?href$/i.test(trimUrl(value));
26880
+ }
26881
+ function checkAttribute(name, decoded) {
26882
+ const local = name.slice(name.lastIndexOf(":") + 1);
26883
+ if (/^on/.test(name)) unsupportedOutput("artifact output contains a blocked html feature");
26884
+ if (name === "style") {
26885
+ assertOnlyInlineCssReferences(decoded);
26886
+ return;
26887
+ }
26888
+ if (local === "srcset" || local === "imagesrcset") {
26889
+ assertInlineSrcset(decoded);
26890
+ return;
26891
+ }
26892
+ if (URL_ATTRIBUTES.has(local)) {
26893
+ if (!isInlineReference(decoded)) unsupportedOutput("artifact page still references a resource", decoded);
26894
+ return;
26895
+ }
26896
+ if (TEXT_ATTRIBUTES.test(name)) return;
26897
+ if (looksLikeUrl(urlText(decoded))) unsupportedOutput("artifact page still references a resource", decoded);
26898
+ if (/url\s*\(|image-set|@import|\\/i.test(decoded) || /(?:^|[^\w-])src\s*\(/i.test(decoded)) assertOnlyInlineCssReferences(decoded);
26899
+ }
26900
+ function isOwnPolicyMeta(httpEquiv, content) {
26901
+ return httpEquiv.trim().toLowerCase() === "content-security-policy" && content === INNER_CSP;
26902
+ }
26903
+ var HTML_SPACE = "\\t\\n\\f\\r ";
26904
+ var TAG_NAME = new RegExp(`[^${HTML_SPACE}/>]*`, "y");
26905
+ var ATTRIBUTE_NAME = new RegExp(`=?[^${HTML_SPACE}/>=]*`, "y");
26906
+ var UNQUOTED_VALUE = new RegExp(`[^${HTML_SPACE}>]*`, "y");
26907
+ var SKIPPED = new RegExp(`[${HTML_SPACE}/]`);
26908
+ var SPACE = new RegExp(`[${HTML_SPACE}]`);
26909
+ var ODD_TAG_NAME = /[\u00a0\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u3000\ufeff]/;
26910
+ var COMMENT_END = /--!?>/g;
26911
+ var SCRIPT_END = /<\/script/gi;
26912
+ var STYLE_END = /<\/style/gi;
26913
+ function scanMarkup(html, options) {
26914
+ const checkReferences = options.references;
26915
+ const length4 = html.length;
26916
+ let index = 0;
26917
+ let comments = 0;
26918
+ const malformed = (detail) => {
26919
+ if (checkReferences) unsupportedOutput(detail);
26920
+ return true;
26921
+ };
26922
+ const countComment = () => {
26923
+ comments += 1;
26924
+ if (comments > MAX_ARTIFACT_COMMENTS) fail("limit", "artifact page has too many comments");
26925
+ };
26926
+ while (index < length4) {
26927
+ const open9 = html.indexOf("<", index);
26928
+ if (open9 === -1) return;
26929
+ index = open9 + 1;
26930
+ const next = html[index] ?? "";
26931
+ if (html.startsWith("!--", index)) {
26932
+ countComment();
26933
+ let end;
26934
+ if (html[index + 3] === ">") end = index + 4;
26935
+ else if (html.startsWith("->", index + 3)) end = index + 5;
26936
+ else {
26937
+ COMMENT_END.lastIndex = index + 3;
26938
+ const close = COMMENT_END.exec(html);
26939
+ if (close === null) {
26940
+ if (malformed("artifact output has an unterminated comment")) return;
26941
+ return;
26942
+ }
26943
+ end = close.index + close[0].length;
26944
+ }
26945
+ index = end;
26946
+ continue;
26947
+ }
26948
+ if (next === "!" || next === "?" || next === "/") {
26949
+ if (next === "?" || next === "!" && !/^!doctype/i.test(html.slice(index, index + 8))) countComment();
26950
+ const close = html.indexOf(">", index);
26951
+ if (close === -1) {
26952
+ if (malformed("artifact output has an unterminated tag")) return;
26953
+ return;
26954
+ }
26955
+ index = close + 1;
26956
+ continue;
26957
+ }
26958
+ if (!/[A-Za-z]/.test(next)) continue;
26959
+ TAG_NAME.lastIndex = index;
26960
+ const rawTag = TAG_NAME.exec(html)[0];
26961
+ const tag = rawTag.toLowerCase();
26962
+ index = TAG_NAME.lastIndex;
26963
+ if (checkReferences) {
26964
+ if (ODD_TAG_NAME.test(rawTag)) unsupportedOutput("artifact output has a tag name with an unexpected character");
26965
+ if (MARKUP_BLOCKED_NAMES.has(tag)) unsupportedOutput("artifact output contains a blocked html feature");
26966
+ }
26967
+ const attributes = /* @__PURE__ */ new Map();
26968
+ let attributeCount = 0;
26969
+ for (; ; ) {
26970
+ while (index < length4 && SKIPPED.test(html[index])) index += 1;
26971
+ if (index >= length4) {
26972
+ if (malformed("artifact output has an unterminated tag")) return;
26973
+ return;
26974
+ }
26975
+ if (html[index] === ">") {
26976
+ index += 1;
26977
+ break;
26978
+ }
26979
+ ATTRIBUTE_NAME.lastIndex = index;
26980
+ const rawName = ATTRIBUTE_NAME.exec(html)[0];
26981
+ index = ATTRIBUTE_NAME.lastIndex;
26982
+ while (index < length4 && SPACE.test(html[index])) index += 1;
26983
+ let rawValue = "";
26984
+ if (html[index] === "=") {
26985
+ index += 1;
26986
+ while (index < length4 && SPACE.test(html[index])) index += 1;
26987
+ const quote = html[index];
26988
+ if (quote === '"' || quote === "'") {
26989
+ const close = html.indexOf(quote, index + 1);
26990
+ if (close === -1) {
26991
+ if (malformed("artifact output has an unterminated attribute")) return;
26992
+ return;
26993
+ }
26994
+ rawValue = html.slice(index + 1, close);
26995
+ index = close + 1;
26996
+ } else {
26997
+ UNQUOTED_VALUE.lastIndex = index;
26998
+ rawValue = UNQUOTED_VALUE.exec(html)[0];
26999
+ index = UNQUOTED_VALUE.lastIndex;
27000
+ }
27001
+ }
27002
+ if (rawName.length > 0) {
27003
+ attributeCount += 1;
27004
+ if (attributeCount > MAX_ARTIFACT_ATTRIBUTES_PER_ELEMENT) fail("limit", "artifact element has too many attributes");
27005
+ if (!checkReferences) continue;
27006
+ const name = rawName.toLowerCase();
27007
+ const decoded = decodeAttributeValue(rawValue);
27008
+ checkAttribute(name, decoded);
27009
+ if (isHrefAnimation(tag, name, decoded)) unsupportedOutput("artifact svg animates a resource reference");
27010
+ if (!attributes.has(name)) attributes.set(name, decoded);
27011
+ } else if (index < length4 && html[index] !== ">" && !SKIPPED.test(html[index])) {
27012
+ index += 1;
27013
+ }
27014
+ }
27015
+ if (checkReferences) {
27016
+ if (tag === "area" && attributes.has("href") || attributes.has("ping") || tag === "link" && !attributes.has("href")) {
27017
+ unsupportedOutput("artifact output contains a blocked html feature");
27018
+ }
27019
+ if (tag === "meta" && attributes.has("http-equiv") && !isOwnPolicyMeta(attributes.get("http-equiv"), attributes.get("content") ?? null)) {
27020
+ unsupportedOutput("artifact output contains a meta http-equiv", attributes.get("http-equiv"));
27021
+ }
27022
+ if (tag === "script" && !isJavaScriptType(attributes.get("type"))) {
27023
+ unsupportedOutput("artifact output holds a script type that is not JavaScript", attributes.get("type"));
27024
+ }
27025
+ }
27026
+ if (tag === "script") {
27027
+ SCRIPT_END.lastIndex = index;
27028
+ const close = SCRIPT_END.exec(html);
27029
+ if (close === null) return;
27030
+ index = close.index;
27031
+ } else if (tag === "style" && checkReferences) {
27032
+ STYLE_END.lastIndex = index;
27033
+ const close = STYLE_END.exec(html);
27034
+ assertOnlyInlineCssReferences(html.slice(index, close === null ? length4 : close.index));
27035
+ }
27036
+ }
27037
+ }
27038
+ function assertSelfContainedPage(output, options = {}) {
27039
+ const page = withoutDataUris(output);
27040
+ if (options.only !== "dom") scanMarkup(page, { references: true });
27041
+ if (options.only === "bytes") return;
27042
+ const doc = (options.parseHtml ?? defaultParseHtml)(page);
27043
+ const roots = documentRoots(doc);
27044
+ if (queryAll(roots, BLOCKED_ELEMENTS).length > 0) fail("unsupported", "artifact output contains a blocked html feature");
27045
+ for (const meta of queryAll(roots, "meta[http-equiv]")) {
27046
+ if (!isOwnPolicyMeta(meta.getAttribute("http-equiv") ?? "", meta.getAttribute("content"))) fail("unsupported", "artifact output contains a meta http-equiv");
27047
+ }
27048
+ if (queryAll(roots, "script[src]").length > 0) fail("unsupported", "artifact output still loads a script");
27049
+ for (const script of queryAll(roots, "script")) {
27050
+ if (!isJavaScriptType(script.getAttribute("type"))) fail("unsupported", "artifact output holds a script type that is not JavaScript");
27051
+ }
27052
+ for (const style of queryAll(roots, "style")) assertOnlyInlineCssReferences(style.textContent ?? "");
27053
+ for (const element of queryAll(roots, "*")) {
27054
+ const inSvg = element.namespaceURI === SVG_NAMESPACE;
27055
+ if (inSvg && element.localName === "script") fail("unsupported", "artifact svg contains a script");
27056
+ for (const name of element.getAttributeNames()) {
27057
+ const lower = name.toLowerCase();
27058
+ const local = lower.slice(lower.lastIndexOf(":") + 1);
27059
+ const value = element.getAttribute(name) ?? "";
27060
+ if (/^on/.test(lower)) fail("unsupported", "artifact output contains a blocked html feature");
27061
+ if (URL_ATTRIBUTES.has(local)) {
27062
+ if (local === "srcset" || local === "imagesrcset") assertInlineSrcset(value);
27063
+ else if (!isInlineReference(value)) throw new ArtifactBundleError("unsupported", "artifact page still references a resource", void 0, value);
27064
+ } else if (lower !== "style" && !TEXT_ATTRIBUTES.test(lower) && looksLikeUrl(urlText(value))) {
27065
+ throw new ArtifactBundleError("unsupported", "artifact page still references a resource", void 0, value);
27066
+ }
27067
+ if (lower === "style" || inSvg) assertOnlyInlineCssReferences(value);
27068
+ if (isHrefAnimation(element.localName, lower, value)) fail("unsupported", "artifact svg animates a resource reference");
27069
+ }
27070
+ }
27071
+ }
27072
+ function verifySelfContained(output, context) {
27073
+ assertSelfContainedPage(output, { parseHtml: context.parseHtml });
27074
+ }
27075
+ function rejectSvgResourceReferences(roots) {
27076
+ for (const element of queryAll(roots, "*")) {
27077
+ if (element.namespaceURI !== SVG_NAMESPACE) continue;
27078
+ const animatesHref = /^(?:animate|set|animatetransform|animatemotion)$/i.test(element.localName) && element.getAttributeNames().some((name) => name.toLowerCase() === "attributename" && /^(?:xlink:)?href$/i.test(trimUrl(element.getAttribute(name) ?? "")));
27079
+ if (animatesHref) fail("unsupported", "artifact svg animates a resource reference");
27080
+ if (element.localName === "script") fail("unsupported", "artifact svg contains a script");
27081
+ for (const attributeName of element.getAttributeNames()) {
27082
+ if (attributeName !== "href" && !attributeName.endsWith(":href")) continue;
27083
+ if (element.localName === "a" && attributeName === "href") continue;
27084
+ if (!isFragmentOnly(urlText(element.getAttribute(attributeName) ?? ""))) fail("unsupported", "artifact svg references a resource");
27085
+ }
27086
+ }
27087
+ }
27088
+ function rejectEventHandlerAttributes(roots) {
27089
+ for (const element of queryAll(roots, "*")) {
27090
+ for (const attributeName of element.getAttributeNames()) {
27091
+ if (/^on/i.test(attributeName)) fail("unsupported", "artifact contains a blocked html feature");
27092
+ }
27093
+ }
27094
+ }
27095
+ function replaceAsync(input, expression, replacer) {
27096
+ const matches = [...input.matchAll(expression)];
27097
+ return matches.reduceRight(
27098
+ async (next, match) => {
27099
+ const value = await next;
27100
+ const replacement = await replacer(...match.map((entry) => entry ?? ""));
27101
+ return `${value.slice(0, match.index)}${replacement}${value.slice((match.index ?? 0) + match[0].length)}`;
27102
+ },
27103
+ Promise.resolve(input)
27104
+ );
27105
+ }
27106
+ function newDeferred() {
27107
+ const bytes = new Uint8Array(8);
27108
+ if (globalThis.crypto?.getRandomValues !== void 0) globalThis.crypto.getRandomValues(bytes);
27109
+ else for (let index = 0; index < bytes.length; index += 1) bytes[index] = Math.floor(Math.random() * 256);
27110
+ return { token: Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(""), urls: [] };
27111
+ }
27112
+ function parkUrl(deferred, url) {
27113
+ deferred.urls.push(url);
27114
+ return `data:;base64,TCPH${deferred.token}x${deferred.urls.length - 1}x`;
27115
+ }
27116
+ function restoreParkedUrls(page, deferred) {
27117
+ if (deferred.urls.length === 0) return page;
27118
+ const pattern = new RegExp(`data:;base64,TCPH${deferred.token}x(\\d+)x`, "g");
27119
+ return page.replace(pattern, (_whole, index) => deferred.urls[Number(index)]);
27120
+ }
27121
+ function resolveReferenceFrom(basePath, reference) {
27122
+ try {
27123
+ return resolveArtifactReference(basePath, reference);
27124
+ } catch (error) {
27125
+ if (error instanceof ArtifactBundleError) throw new ArtifactBundleError(error.kind, error.message, basePath, reference);
27126
+ throw error;
27127
+ }
27128
+ }
27129
+ function requiredFile(context, basePath, reference) {
27130
+ context.counter.references += 1;
27131
+ if (context.counter.references > MAX_ARTIFACT_REFERENCES) fail("limit", "artifact has too many resource references");
27132
+ const resolved = resolveReferenceFrom(basePath, reference);
27133
+ const file = context.files.get(resolved.path);
27134
+ if (file === void 0) throw new ArtifactBundleError("missing", "artifact resource is missing", basePath, reference);
27135
+ return { file, resolved };
27136
+ }
27137
+ var CSS_REFERENCE_NAMES = /* @__PURE__ */ new Set(["url", "image-set", "-webkit-image-set", "src", "import"]);
27138
+ function isCssIdentifierCode(code4) {
27139
+ return code4 >= 128 || code4 >= 48 && code4 <= 57 || code4 >= 65 && code4 <= 90 || code4 >= 97 && code4 <= 122 || code4 === 95 || code4 === 45;
27140
+ }
27141
+ function normalizeCssEscapes(source) {
27142
+ let output = "";
27143
+ let index = 0;
27144
+ const length4 = source.length;
27145
+ while (index < length4) {
27146
+ const char = source[index];
27147
+ if (char === "/" && source[index + 1] === "*") {
27148
+ const end = source.indexOf("*/", index + 2);
27149
+ const stop = end === -1 ? length4 : end + 2;
27150
+ output += source.slice(index, stop);
27151
+ index = stop;
27152
+ continue;
27153
+ }
27154
+ if (char === '"' || char === "'") {
27155
+ let cursor = index + 1;
27156
+ while (cursor < length4 && source[cursor] !== char && source[cursor] !== "\n") cursor += source[cursor] === "\\" ? 2 : 1;
27157
+ const stop = Math.min(source[cursor] === char ? cursor + 1 : cursor, length4);
27158
+ output += source.slice(index, stop);
27159
+ index = stop;
27160
+ continue;
27161
+ }
27162
+ if (char === "\\" || isCssIdentifierCode(source.charCodeAt(index))) {
27163
+ let cursor = index;
27164
+ let decoded = "";
27165
+ let escaped = false;
27166
+ while (cursor < length4) {
27167
+ if (source[cursor] === "\\") {
27168
+ const next = source[cursor + 1];
27169
+ if (next === void 0) {
27170
+ decoded += "\uFFFD";
27171
+ escaped = true;
27172
+ cursor += 1;
27173
+ break;
27174
+ }
27175
+ if (next === "\n" || next === "\r" || next === "\f") break;
27176
+ escaped = true;
27177
+ const hex3 = /^[0-9a-fA-F]{1,6}/.exec(source.slice(cursor + 1, cursor + 7));
27178
+ if (hex3 !== null) {
27179
+ const point = Number.parseInt(hex3[0], 16);
27180
+ cursor += 1 + hex3[0].length;
27181
+ if (source[cursor] === "\r" && source[cursor + 1] === "\n") cursor += 2;
27182
+ else if (/[ \t\n\r\f]/.test(source[cursor] ?? "")) cursor += 1;
27183
+ decoded += point === 0 || point > 1114111 || point >= 55296 && point <= 57343 ? "\uFFFD" : String.fromCodePoint(point);
27184
+ } else {
27185
+ const point = source.codePointAt(cursor + 1);
27186
+ decoded += String.fromCodePoint(point);
27187
+ cursor += 1 + (point > 65535 ? 2 : 1);
27188
+ }
27189
+ } else if (isCssIdentifierCode(source.charCodeAt(cursor))) {
27190
+ decoded += source[cursor];
27191
+ cursor += 1;
27192
+ } else break;
27193
+ }
27194
+ const raw = source.slice(index, cursor);
27195
+ const startsReference = escaped && (source[cursor] === "(" || output.endsWith("@")) && CSS_REFERENCE_NAMES.has(decoded.toLowerCase());
27196
+ output += startsReference ? decoded : raw;
27197
+ index = cursor;
27198
+ continue;
27199
+ }
27200
+ output += char;
27201
+ index += 1;
27202
+ }
27203
+ return output;
27204
+ }
27205
+ function assertOnlyInlineCssReferences(css) {
27206
+ const text = normalizeCssEscapes(css);
27207
+ if (/@import/i.test(text)) fail("unsupported", "artifact css still imports a stylesheet");
27208
+ if (/(?:^|[^\w-])(?:-webkit-)?image-set\s*\(/i.test(text) || /(?:^|[^\w-])src\s*\(/i.test(text)) fail("unsupported", "artifact css uses an unsupported resource function");
27209
+ const opened = text.match(/url\(/gi)?.length ?? 0;
27210
+ let read4 = 0;
27211
+ for (const match of text.matchAll(/url\([ \t\n\r\f]*(?:"([^"]*)"|'([^']*)'|([^)"' \t\n\r\f]*))[ \t\n\r\f]*\)/gi)) {
27212
+ read4 += 1;
27213
+ const value = match[1] ?? match[2] ?? match[3] ?? "";
27214
+ if (!isInlineReference(value) || value.includes("\\")) {
27215
+ throw new ArtifactBundleError("unsupported", "artifact css references a resource", void 0, value);
27216
+ }
27217
+ }
27218
+ if (read4 !== opened) fail("unsupported", "artifact css has a url() that cannot be read");
27219
+ }
27220
+ async function transformCss(sourceText, basePath, context) {
27221
+ if (context.cssStack.length > MAX_ARTIFACT_CSS_IMPORT_DEPTH) fail("limit", "artifact stylesheet import depth exceeded");
27222
+ const source = normalizeCssEscapes(sourceText);
27223
+ const imported = await replaceAsync(
27224
+ source,
27225
+ /@import\s+(?:url\(\s*(?:(["'])([^"']+)\1|([^"')\s]+))\s*\)|(["'])([^"']+)\4)\s*([^;]*);/gi,
27226
+ async (_whole, _urlQuote, quotedUrl, unquotedUrl, _quote, quotedReference, media) => {
27227
+ const reference = quotedUrl || unquotedUrl || quotedReference;
27228
+ if (/^(?:layer|supports)\b/i.test(media.trim()) || /\b(?:layer|supports)\b/i.test(media)) fail("unsupported", "artifact uses an unsupported stylesheet import condition");
27229
+ const { file, resolved } = requiredFile(context, basePath, reference);
27230
+ if (context.cssStack.includes(resolved.path)) fail("unsupported", "artifact stylesheet import cycle");
27231
+ const css = await transformCss(decodeText(file, context), resolved.path, { ...context, cssStack: [...context.cssStack, resolved.path] });
27232
+ return wrapMediaCss(css, media, "artifact uses an unsupported stylesheet import condition");
27233
+ }
27234
+ );
27235
+ return replaceAsync(
27236
+ imported,
27237
+ /url\(\s*(["']?)([^"')]+)\1\s*\)/gi,
27238
+ async (_whole, _quote, reference) => {
27239
+ if (trimUrl(reference).startsWith("#")) return `url("${trimUrl(reference)}")`;
27240
+ const { file, resolved } = requiredFile(context, basePath, reference);
27241
+ const dataUrl = asDataUrl(file, context);
27242
+ return `url("${context.deferred === void 0 ? dataUrl : parkUrl(context.deferred, dataUrl)}${resolved.fragment}")`;
27243
+ }
27244
+ );
27245
+ }
27246
+ function serializeSrcset(value, basePath, context) {
27247
+ return value.split(",").map((candidate) => {
27248
+ const trimmed = candidate.trim();
27249
+ const separator = trimmed.search(/\s/);
27250
+ const reference = separator === -1 ? trimmed : trimmed.slice(0, separator);
27251
+ const descriptor = separator === -1 ? "" : trimmed.slice(separator).trim();
27252
+ const { file, resolved } = requiredFile(context, basePath, reference);
27253
+ return `${asDataUrl(file, context)}${resolved.fragment}${descriptor.length === 0 ? "" : ` ${descriptor}`}`;
27254
+ }).join(", ");
27255
+ }
27256
+ var CHILD_BOOTSTRAP = `(function(){
27257
+ "use strict";
27258
+ document.addEventListener("click",function(event){
27259
+ var target=event.target instanceof Element?event.target.closest("a[data-tc-artifact-path]"):null;
27260
+ if(!target)return;
27261
+ event.preventDefault();
27262
+ window.parent.postMessage({type:"navigate",path:target.getAttribute("data-tc-artifact-path"),fragment:target.getAttribute("data-tc-artifact-fragment")||""},"*");
27263
+ },true);
27264
+ window.addEventListener("message",function(event){
27265
+ if(event.source!==window.parent||event.origin!=="null"||!event.data||event.data.type!=="artifact-fragment"||typeof event.data.fragment!=="string")return;
27266
+ var fragment=event.data.fragment;
27267
+ if(!/^#[^\\u0000-\\u001f\\u007f]{1,1024}$/.test(fragment))return;
27268
+ try{var target=document.getElementById(decodeURIComponent(fragment.slice(1)));if(target)target.scrollIntoView();}catch(_){}
27269
+ });
27270
+ window.parent.postMessage({type:"artifact-ready"},"*");
27271
+ })();`;
27272
+ var INNER_CSP = [
27273
+ "default-src 'none'",
27274
+ "script-src 'unsafe-inline'",
27275
+ "style-src 'unsafe-inline'",
27276
+ "img-src data: blob:",
27277
+ "font-src data: blob:",
27278
+ "media-src data: blob:",
27279
+ "connect-src 'none'",
27280
+ "frame-src 'none'",
27281
+ "child-src 'none'",
27282
+ "object-src 'none'",
27283
+ "base-uri 'none'",
27284
+ "form-action 'none'"
27285
+ ].join("; ");
27286
+ async function transformHtml(path, file, context) {
27287
+ const text = decodeText(file);
27288
+ scanMarkup(text, { references: false });
27289
+ const doc = context.parseHtml(text);
27290
+ const roots = documentRoots(doc);
27291
+ const deferred = newDeferred();
27292
+ if (doc.querySelector("parsererror") !== null) fail("malformed", "artifact html is malformed");
27293
+ if (queryAll(roots, BLOCKED_ELEMENTS).length > 0) {
27294
+ fail("unsupported", "artifact contains a blocked html feature");
27295
+ }
27296
+ rejectEventHandlerAttributes(roots);
27297
+ rejectSvgResourceReferences(roots);
27298
+ const inlineStyles = queryAll(roots, "style");
27299
+ for (const meta of queryAll(roots, "meta[http-equiv]")) meta.remove();
27300
+ for (const link3 of queryAll(roots, "link[href]")) {
27301
+ const relation = classifyLinkRelation(link3.getAttribute("rel"));
27302
+ if (relation === "stylesheet") {
27303
+ const { file: stylesheet, resolved } = requiredFile(context, path, link3.getAttribute("href") ?? "");
27304
+ const style = doc.createElement("style");
27305
+ style.textContent = wrapMediaCss(await transformCss(decodeText(stylesheet, context), resolved.path, { ...context, cssStack: [resolved.path] }), link3.getAttribute("media"));
27306
+ link3.replaceWith(style);
27307
+ continue;
27308
+ }
27309
+ if (relation === "icon") {
27310
+ const { file: icon, resolved } = requiredFile(context, path, link3.getAttribute("href") ?? "");
27311
+ link3.href = `${asDataUrl(icon, context)}${resolved.fragment}`;
27312
+ continue;
27313
+ }
27314
+ fail("unsupported", "artifact contains an unsupported link relation");
27315
+ }
27316
+ for (const style of inlineStyles) {
27317
+ style.textContent = wrapMediaCss(await transformCss(style.textContent ?? "", path, { ...context, cssStack: [] }), style.getAttribute("media"));
27318
+ }
27319
+ for (const element of queryAll(roots, "[style]")) {
27320
+ const value = element.getAttribute("style");
27321
+ if (value !== null) element.setAttribute("style", await transformCss(value, path, { ...context, cssStack: [], deferred }));
27322
+ }
27323
+ for (const script of queryAll(roots, "script")) {
27324
+ if ((script.getAttribute("type") ?? "").trim().toLowerCase() === "module" || script.hasAttribute("async") || script.hasAttribute("defer")) {
27325
+ fail("unsupported", "artifact uses an unsupported script mode");
27326
+ }
27327
+ if (!isJavaScriptType(script.getAttribute("type"))) fail("unsupported", "artifact uses a script type that is not JavaScript");
27328
+ let source = script.textContent ?? "";
27329
+ const reference = script.getAttribute("src");
27330
+ if (reference !== null) {
27331
+ const { file: scriptFile } = requiredFile(context, path, reference);
27332
+ source = decodeText(scriptFile, context);
27333
+ script.removeAttribute("src");
27334
+ }
27335
+ assertClassicScript(source);
27336
+ script.textContent = source.replace(/<\/script/gi, "<\\/script");
27337
+ }
27338
+ const resourceAttributes = [
27339
+ ["img[src]", "src"],
27340
+ ["source[src]", "src"],
27341
+ ["video[src]", "src"],
27342
+ ["video[poster]", "poster"],
27343
+ ["audio[src]", "src"],
27344
+ ["track[src]", "src"],
27345
+ ["input[type=image][src]", "src"]
27346
+ ];
27347
+ for (const [selector, attribute] of resourceAttributes) {
27348
+ for (const element of queryAll(roots, selector)) {
27349
+ const reference = element.getAttribute(attribute);
27350
+ if (reference === null) continue;
27351
+ const { file: resource, resolved } = requiredFile(context, path, reference);
27352
+ element.setAttribute(attribute, `${asDataUrl(resource, context)}${resolved.fragment}`);
27353
+ }
27354
+ }
27355
+ for (const element of queryAll(roots, "[srcset]")) {
27356
+ const value = element.getAttribute("srcset");
27357
+ if (value !== null) element.setAttribute("srcset", serializeSrcset(value, path, context));
27358
+ }
27359
+ for (const anchor of queryAll(roots, "a[href]")) {
27360
+ anchor.removeAttribute("target");
27361
+ anchor.removeAttribute("download");
27362
+ const reference = anchor.getAttribute("href") ?? "";
27363
+ if (reference.startsWith("#")) continue;
27364
+ const resolved = resolveReferenceFrom(path, reference);
27365
+ if (context.singlePage) throw new ArtifactBundleError("unsupported", "artifact links to another page", path, reference);
27366
+ if (!context.files.has(resolved.path) || !/\.html?$/i.test(resolved.path)) fail("unsupported", "artifact link leaves its html bundle");
27367
+ anchor.href = "#";
27368
+ anchor.dataset.tcArtifactPath = resolved.path;
27369
+ if (resolved.fragment.length > 0) anchor.dataset.tcArtifactFragment = resolved.fragment;
27370
+ }
27371
+ const head = doc.head ?? doc.documentElement.insertBefore(doc.createElement("head"), doc.body);
27372
+ const csp = doc.createElement("meta");
27373
+ csp.httpEquiv = "Content-Security-Policy";
27374
+ csp.content = INNER_CSP;
27375
+ const bootstrap = doc.createElement("script");
27376
+ bootstrap.textContent = CHILD_BOOTSTRAP;
27377
+ head.prepend(bootstrap);
27378
+ head.prepend(csp);
27379
+ const output = restoreParkedUrls(`<!DOCTYPE html>
27380
+ ${doc.documentElement.outerHTML}`, deferred);
27381
+ verifySelfContained(output, context);
27382
+ return output;
27383
+ }
27384
+ async function prepareHtmlArtifact(input, options = {}) {
27385
+ if (input.length === 0) fail("malformed", "artifact bundle is empty");
27386
+ if (input.length > MAX_ARTIFACT_FILES) fail("limit", "artifact has too many files");
27387
+ const files = /* @__PURE__ */ new Map();
27388
+ const aliases = /* @__PURE__ */ new Set();
27389
+ let sourceBytes = 0;
27390
+ for (const candidate of input) {
27391
+ let path;
27392
+ try {
27393
+ path = canonicalArtifactPath(candidate.path);
27394
+ } catch (error) {
27395
+ if (error instanceof ArtifactBundleError) throw new ArtifactBundleError(error.kind, error.message, candidate.path);
27396
+ throw error;
27397
+ }
27398
+ const alias = path.toLowerCase();
27399
+ if (aliases.has(alias)) throw new ArtifactBundleError("malformed", "artifact paths collide after canonicalization", path);
27400
+ aliases.add(alias);
27401
+ sourceBytes += candidate.bytes.byteLength;
27402
+ if (!Number.isSafeInteger(sourceBytes) || sourceBytes > MAX_ARTIFACT_SOURCE_BYTES) throw new ArtifactBundleError("limit", "artifact source exceeds limit", path);
27403
+ files.set(path, { ...candidate, path });
27404
+ }
27405
+ const detection = detectHtmlArtifact([...files.keys()]);
27406
+ if (detection.kind !== "html") fail("malformed", "artifact root index is missing");
27407
+ const renderBytes = [...files.values()].filter((file) => /^(?:text\/|application\/(?:javascript|json)|image\/svg\+xml)/i.test(artifactMediaType(file.path, file.mediaType))).reduce((total, file) => total + file.bytes.byteLength, 0);
27408
+ if (renderBytes > MAX_ARTIFACT_RENDER_BYTES) fail("limit", "artifact render aggregate exceeds limit");
27409
+ const context = {
27410
+ files,
27411
+ cssStack: [],
27412
+ counter: { references: 0, inlinedBytes: 0 },
27413
+ parseHtml: options.parseHtml ?? defaultParseHtml,
27414
+ singlePage: options.singlePage === true
27415
+ };
27416
+ const pages = {};
27417
+ let renderedBytes = 0;
27418
+ for (const [path, file] of files) {
27419
+ if (!/\.html?$/i.test(path)) continue;
27420
+ if (context.singlePage && path !== ARTIFACT_ENTRY) continue;
27421
+ let page;
27422
+ try {
27423
+ page = await transformHtml(path, file, context);
27424
+ } catch (error) {
27425
+ if (error instanceof ArtifactBundleError && error.path === void 0) throw new ArtifactBundleError(error.kind, error.message, path, error.reference);
27426
+ throw error;
27427
+ }
27428
+ renderedBytes += new TextEncoder().encode(page).byteLength;
27429
+ if (!Number.isSafeInteger(renderedBytes) || renderedBytes > MAX_ARTIFACT_RENDER_BYTES) {
27430
+ fail("limit", "artifact rendered output exceeds limit");
27431
+ }
27432
+ pages[path] = page;
27433
+ }
27434
+ return { entry: ARTIFACT_ENTRY, pages: Object.freeze(pages), fileCount: files.size, sourceBytes };
27435
+ }
27436
+ function isPassiveArtifactPath(canonicalPath) {
27437
+ const lower = canonicalPath.toLowerCase();
27438
+ return lower === "manifest.json" || lower.startsWith("knowledge/");
27439
+ }
27440
+ function canonicalWithPath(path) {
27441
+ try {
27442
+ return canonicalArtifactPath(path);
27443
+ } catch (error) {
27444
+ if (error instanceof ArtifactBundleError) throw new ArtifactBundleError(error.kind, error.message, path);
27445
+ throw error;
27446
+ }
27447
+ }
27448
+ async function inlineHtmlFolder(files, options = {}) {
27449
+ if (files.length === 0) throw new ArtifactBundleError("malformed", "artifact bundle is empty");
27450
+ if (files.length > MAX_ARTIFACT_FILES) throw new ArtifactBundleError("limit", "artifact has too many files");
27451
+ const canonical = /* @__PURE__ */ new Map();
27452
+ const aliases = /* @__PURE__ */ new Map();
27453
+ let sourceBytes = 0;
27454
+ for (const candidate of files) {
27455
+ const path = canonicalWithPath(candidate.path);
27456
+ const alias = path.toLowerCase();
27457
+ if (aliases.has(alias)) throw new ArtifactBundleError("malformed", "artifact paths collide after canonicalization", path);
27458
+ aliases.set(alias, path);
27459
+ sourceBytes += candidate.bytes.byteLength;
27460
+ if (!Number.isSafeInteger(sourceBytes) || sourceBytes > MAX_ARTIFACT_SOURCE_BYTES) {
27461
+ throw new ArtifactBundleError("limit", "artifact source exceeds limit", path);
27462
+ }
27463
+ canonical.set(path, { ...candidate, path });
27464
+ }
27465
+ if (!canonical.has(ARTIFACT_ENTRY)) throw new ArtifactBundleError("missing", "artifact root index is missing", ARTIFACT_ENTRY);
27466
+ const active = [];
27467
+ const passive = [];
27468
+ for (const file of canonical.values()) (isPassiveArtifactPath(file.path) ? passive : active).push(file);
27469
+ const prepared = await prepareHtmlArtifact(active, { ...options, singlePage: true });
27470
+ passive.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
27471
+ return {
27472
+ entry: ARTIFACT_ENTRY,
27473
+ html: new TextEncoder().encode(prepared.pages[ARTIFACT_ENTRY]),
27474
+ passive,
27475
+ fileCount: canonical.size,
27476
+ sourceBytes
27477
+ };
27478
+ }
27479
+
27480
+ // src/share/html-folder.ts
27481
+ init_errors();
27482
+ init_safe_text();
27483
+ var MAX_HTML_FOLDER_DEPTH = 16;
27484
+ var VIEWER_PREVIEW_BYTES = 1024 * 1024;
27485
+ var FOLDER_HINT = "A folder is published as one self-contained page: every stylesheet, script and image it uses is inlined, and external URLs, frames, forms, <base>, module scripts and links to other pages are refused.";
27486
+ var FOLDER_NOT_AVAILABLE = "Folders can only be published as a single page for an anyone-with-the-link share (`--to anyone`); for others publish a single file";
27487
+ async function isDirectoryInput(input) {
27488
+ if (input === "-") return false;
27489
+ try {
27490
+ return (await stat5(resolve5(input))).isDirectory();
27491
+ } catch {
27492
+ return false;
27493
+ }
27494
+ }
27495
+ function limitError(message) {
27496
+ return new CLIError("MAX_BYTES_EXCEEDED", message, 7);
27497
+ }
27498
+ function isWithinFolder(root, candidate) {
27499
+ const path = relativePath(root, candidate);
27500
+ return path !== "" && path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute(path);
27501
+ }
27502
+ var CHANGED = "changed while it was read";
27503
+ function isRaceError(error) {
27504
+ const code4 = error?.code;
27505
+ return code4 === "ENOENT" || code4 === "ENOTDIR" || code4 === "ELOOP";
27506
+ }
27507
+ var PROC_FD = "/proc/self/fd";
27508
+ async function linuxDescriptorPath(fd) {
27509
+ if (process.platform !== "linux" || !existsSync(PROC_FD)) return void 0;
27510
+ return realpath3(`${PROC_FD}/${fd}`);
27511
+ }
27512
+ async function parentChainIsRealDirectories(root, path) {
27513
+ let current = dirname5(path);
27514
+ while (current !== root && isWithinFolder(root, current)) {
27515
+ const info = await lstat7(current);
27516
+ if (info.isSymbolicLink() || !info.isDirectory()) return false;
27517
+ current = dirname5(current);
27518
+ }
27519
+ return current === root;
27520
+ }
27521
+ async function readRegularFile(root, path, relative, remaining, limit, hooks = {}) {
27522
+ const problem = (reason) => ({ code: "UNSUPPORTED_FOLDER_ENTRY", path: relative, reason });
27523
+ const descriptorPath = hooks.descriptorPath ?? linuxDescriptorPath;
27524
+ let handle;
27525
+ let before;
27526
+ try {
27527
+ await hooks.beforeOpen?.();
27528
+ before = await lstat7(path);
27529
+ await hooks.afterLstat?.();
27530
+ handle = await open8(path, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0) | (constants2.O_NONBLOCK ?? 0));
27531
+ } catch (error) {
27532
+ if (error.code === "ELOOP") return problem("symbolic link");
27533
+ if (isRaceError(error)) return problem(CHANGED);
27534
+ throw error;
27535
+ }
27536
+ try {
27537
+ await hooks.afterOpen?.();
27538
+ const info = await handle.stat();
27539
+ if (!info.isFile()) return problem("not a regular file");
27540
+ let byDescriptor;
27541
+ try {
27542
+ if (before.dev !== info.dev || before.ino !== info.ino) return problem(CHANGED);
27543
+ byDescriptor = await descriptorPath(handle.fd);
27544
+ if (byDescriptor !== void 0) {
27545
+ if (byDescriptor !== path || !isWithinFolder(root, byDescriptor)) return problem("symbolic link");
27546
+ } else {
27547
+ const real = await realpath3(path);
27548
+ if (real !== path || !isWithinFolder(root, real)) return problem("symbolic link");
27549
+ }
27550
+ } catch (error) {
27551
+ if (isRaceError(error)) return problem(CHANGED);
27552
+ throw error;
27553
+ }
27554
+ if (info.size > remaining) throw limitError(`folder is larger than the ${limit} byte limit (at "${safeClipped(relative)}")`);
27555
+ const chunks = [];
27556
+ let total = 0;
27557
+ for (; ; ) {
27558
+ const chunk = Buffer.allocUnsafe(Math.min(1 << 20, remaining - total + 1));
27559
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);
27560
+ if (bytesRead === 0) break;
27561
+ total += bytesRead;
27562
+ if (total > remaining) throw limitError(`folder is larger than the ${limit} byte limit (at "${safeClipped(relative)}")`);
27563
+ chunks.push(chunk.subarray(0, bytesRead));
27564
+ }
27565
+ await hooks.afterRead?.();
27566
+ if (byDescriptor === void 0) {
27567
+ try {
27568
+ const again = await lstat7(path);
27569
+ if (again.isSymbolicLink() || again.dev !== info.dev || again.ino !== info.ino || !await parentChainIsRealDirectories(root, path)) return problem(CHANGED);
27570
+ } catch (error) {
27571
+ if (isRaceError(error)) return problem(CHANGED);
27572
+ throw error;
27573
+ }
27574
+ }
27575
+ return new Uint8Array(Buffer.concat(chunks, total));
27576
+ } finally {
27577
+ await handle.close();
27578
+ }
27579
+ }
27580
+ async function listFolderDirectory(root, absolute, relative, hooks = {}) {
27581
+ const problem = (reason) => ({ code: "UNSUPPORTED_FOLDER_ENTRY", path: relative === "" ? "." : relative, reason });
27582
+ const descriptorPath = hooks.descriptorPath ?? linuxDescriptorPath;
27583
+ try {
27584
+ await hooks.beforeOpen?.();
27585
+ const before = await lstat7(absolute);
27586
+ if (before.isSymbolicLink()) return problem("symbolic link");
27587
+ if (!before.isDirectory()) return problem(CHANGED);
27588
+ await hooks.afterLstat?.();
27589
+ const handle = await open8(absolute, constants2.O_RDONLY | (constants2.O_DIRECTORY ?? 0) | (constants2.O_NOFOLLOW ?? 0));
27590
+ try {
27591
+ await hooks.afterOpen?.();
27592
+ const info = await handle.stat();
27593
+ if (!info.isDirectory() || info.dev !== before.dev || info.ino !== before.ino) return problem(CHANGED);
27594
+ const byDescriptor = await descriptorPath(handle.fd);
27595
+ if (byDescriptor !== void 0) {
27596
+ if (byDescriptor !== absolute || absolute !== root && !isWithinFolder(root, byDescriptor)) return problem("symbolic link");
27597
+ await hooks.beforeList?.();
27598
+ return { entries: await readdir6(`${PROC_FD}/${handle.fd}`, { withFileTypes: true }) };
27599
+ }
27600
+ const real = await realpath3(absolute);
27601
+ if (real !== absolute || absolute !== root && !isWithinFolder(root, real)) return problem("symbolic link");
27602
+ const entries = await readdir6(absolute, { withFileTypes: true });
27603
+ const after = await lstat7(absolute);
27604
+ if (after.isSymbolicLink() || after.dev !== info.dev || after.ino !== info.ino) return problem(CHANGED);
27605
+ if (absolute !== root && !await parentChainIsRealDirectories(root, absolute)) return problem(CHANGED);
27606
+ return { entries };
27607
+ } finally {
27608
+ await handle.close();
27609
+ }
27610
+ } catch (error) {
27611
+ if (isRaceError(error)) return problem(CHANGED);
27612
+ throw error;
27613
+ }
27614
+ }
27615
+ function problemError(problems) {
27616
+ const first = problems[0];
27617
+ const listed = problems.slice(0, ECHO_LIST_MAX).map((problem) => `"${safeClipped(problem.path)}" (${problem.reason})`).join(", ");
27618
+ const more = problems.length > ECHO_LIST_MAX ? ` \u2026 and ${problems.length - ECHO_LIST_MAX} more` : "";
27619
+ const noun = first.code === "UNSAFE_PATH" ? "paths that cannot be published safely" : "entries that cannot be published";
27620
+ return new CLIError(
27621
+ first.code,
27622
+ `folder has ${problems.length} ${noun}: ${listed}${more}`,
27623
+ first.code === "UNSAFE_PATH" ? 8 : 2,
27624
+ { hint: first.code === "UNSAFE_PATH" ? "Rename these files to plain names without control, invisible or direction-changing characters." : "Symbolic links and special files are never followed or read; copy the real files into the folder." }
27625
+ );
27626
+ }
27627
+ async function readHtmlFolder(directory, limit) {
27628
+ const root = await realpath3(resolve5(directory));
27629
+ const files = [];
27630
+ const problems = [];
27631
+ let skippedHidden = 0;
27632
+ let total = 0;
27633
+ const walk = async (absolute, relative, depth) => {
27634
+ const listed = await listFolderDirectory(root, absolute, relative);
27635
+ if ("reason" in listed) {
27636
+ problems.push(listed);
27637
+ return;
27638
+ }
27639
+ const entries = listed.entries.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
27640
+ for (const entry of entries) {
27641
+ if (entry.name.startsWith(".")) {
27642
+ skippedHidden += 1;
27643
+ continue;
27644
+ }
27645
+ const path = relative === "" ? entry.name : `${relative}/${entry.name}`;
27646
+ const child = join12(absolute, entry.name);
27647
+ if (entry.isSymbolicLink()) {
27648
+ problems.push({ code: "UNSUPPORTED_FOLDER_ENTRY", path, reason: "symbolic link" });
27649
+ } else if (entry.isDirectory()) {
27650
+ if (depth >= MAX_HTML_FOLDER_DEPTH) problems.push({ code: "UNSUPPORTED_FOLDER_ENTRY", path, reason: `nested deeper than ${MAX_HTML_FOLDER_DEPTH} folders` });
27651
+ else await walk(child, path, depth + 1);
27652
+ } else if (!entry.isFile()) {
27653
+ problems.push({ code: "UNSUPPORTED_FOLDER_ENTRY", path, reason: "not a regular file" });
27654
+ } else {
27655
+ try {
27656
+ canonicalArtifactPath(path);
27657
+ } catch {
27658
+ problems.push({ code: "UNSAFE_PATH", path, reason: "unsafe name" });
27659
+ continue;
27660
+ }
27661
+ if (files.length >= MAX_ARTIFACT_FILES) throw limitError(`folder has more than ${MAX_ARTIFACT_FILES} files`);
27662
+ const read4 = await readRegularFile(root, child, path, limit - total, limit);
27663
+ if (read4 instanceof Uint8Array) {
27664
+ total += read4.byteLength;
27665
+ files.push({ path: path.normalize("NFC"), bytes: read4 });
27666
+ } else problems.push(read4);
27667
+ }
27668
+ }
27669
+ };
27670
+ await walk(root, "", 0);
27671
+ if (problems.length > 0) throw problemError(problems);
27672
+ return { files, skippedHidden };
27673
+ }
27674
+ function htmlFolderError(error) {
27675
+ const file = error.path === void 0 ? "" : `"${safeClipped(error.path)}"`;
27676
+ const reference = error.reference === void 0 ? "" : ` "${safeClipped(error.reference)}"`;
27677
+ if (error.kind === "limit") return limitError(`folder cannot be published as one page: ${error.message.replace(/^artifact /, "")}${file === "" ? "" : ` (${file})`}`);
27678
+ if (error.kind === "missing") {
27679
+ if (error.reference === void 0) {
27680
+ return new CLIError("INVALID_ARGUMENT", "folder needs an index.html at its top level", 2, { hint: "Publish the folder that directly contains index.html." });
27681
+ }
27682
+ return new CLIError(
27683
+ "HTML_RESOURCE_MISSING",
27684
+ `${file} refers to${reference}, which is not in the folder`,
27685
+ 2,
27686
+ { hint: "manifest.json and knowledge/ are never inlined, so a page cannot refer to them. Names are case-sensitive." }
27687
+ );
27688
+ }
27689
+ if (error.kind === "unsupported") {
27690
+ return new CLIError("UNSUPPORTED_HTML", `${file === "" ? "folder" : file} cannot be published as one page: ${error.message.replace(/^artifact /, "")}${reference}`, 2, { hint: FOLDER_HINT });
27691
+ }
27692
+ const pathProblem = /\b(?:path|paths|reference)\b/.test(error.message);
27693
+ return new CLIError(
27694
+ pathProblem ? "UNSAFE_PATH" : "INVALID_ARGUMENT",
27695
+ `folder cannot be published as one page: ${error.message.replace(/^artifact /, "")}${file === "" ? "" : ` (${file}${reference})`}`,
27696
+ pathProblem ? 8 : 2
27697
+ );
27698
+ }
27699
+ async function jsdomParser() {
27700
+ const { JSDOM } = await import("jsdom");
27701
+ const window = new JSDOM("").window;
27702
+ const parser = new window.DOMParser();
27703
+ return { parseHtml: (html) => parser.parseFromString(html, "text/html"), close: () => window.close() };
27704
+ }
27705
+ function htmlFolderFilename(directory, name) {
27706
+ const filename = shareFilename(name ?? `${basename3(resolve5(directory))}.html`);
27707
+ if (!/\.html?$/i.test(filename)) throw new CLIError("INVALID_ARGUMENT", "a folder is published as an .html file; --name must end in .html", 2);
27708
+ return filename;
27709
+ }
27710
+ async function bundleHtmlFolder(directory, options = {}) {
27711
+ const filename = htmlFolderFilename(directory, options.name);
27712
+ const { files, skippedHidden } = await readHtmlFolder(directory, Math.min(options.maxBytes ?? MAX_ARTIFACT_SOURCE_BYTES, MAX_ARTIFACT_SOURCE_BYTES));
27713
+ const parser = await jsdomParser();
27714
+ try {
27715
+ const inlined = await inlineHtmlFolder(files, { parseHtml: parser.parseHtml });
27716
+ return { filename, bytes: inlined.html, passive: inlined.passive, skippedHidden, fileCount: inlined.fileCount };
27717
+ } catch (error) {
27718
+ if (error instanceof ArtifactBundleError) throw htmlFolderError(error);
27719
+ throw error;
27720
+ } finally {
27721
+ parser.close();
27722
+ }
27723
+ }
27724
+ function htmlFolderNotice(bundle) {
27725
+ const parts = [];
27726
+ if (bundle.passive.length > 0) {
27727
+ parts.push(`Left out of the page and not published (never inlined): ${safeClippedList(bundle.passive.map((file) => file.path))}.`);
27728
+ }
27729
+ if (bundle.bytes.byteLength > VIEWER_PREVIEW_BYTES) {
27730
+ parts.push(`The page is ${(bundle.bytes.byteLength / (1024 * 1024)).toFixed(1)} MiB; the Share viewer previews pages up to 1 MiB and offers a download above that.`);
27731
+ }
27732
+ if (bundle.skippedHidden > 0) parts.push(`Skipped ${bundle.skippedHidden} hidden ${bundle.skippedHidden === 1 ? "entry" : "entries"} (names starting with a dot).`);
27733
+ return parts.length === 0 ? void 0 : `${parts.join(" ")}
27734
+ `;
27735
+ }
27736
+
26565
27737
  // src/share/errors.ts
26566
27738
  var SharePublishAuthorityError = class extends Error {
26567
27739
  constructor(failure, options) {
@@ -26836,17 +28008,30 @@ function assertAggregateInputLimit(inputs, maxBytes) {
26836
28008
  }
26837
28009
  function registerShareCommand(program2) {
26838
28010
  const share = program2.command("share").description("Publish and consume TinyCloud Share links");
26839
- share.command("publish <files...>").description("Publish one or more bounded files as a Share").option("--name <filename>", "Filename for stdin input").option("--to <target>", "Share target", "anyone").option("--notify", "Email an exact-email recipient, or a domain mailbox on node 1.17.3+").option("--notify-to <address>", "Mailbox to invite for --to domain:<name> --notify (node 1.17.3+)").option("--expires <duration>", "Share lifetime").option("--max-bytes <bytes>", "Bound input bytes").option("--media-type <type>", "Media type for a single input").option("--action <actions...>", "Addressed permission: read, list, or edit").option("--prefix", "Publish multiple inputs beneath one addressed prefix").option("--binary", "Allow non-UTF-8 bearer content").option("--artifact <id>", 'Publish as the next write-once version of an artifact ("new" starts one); the share is pinned to that version').addOption(new Option("--ongoing").hideHelp()).option("--note <text>", "Note stored with the artifact version").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN).action(async (files, options, command) => {
28011
+ share.command("publish <files...>").description("Publish one or more bounded files as a Share, or a folder with an index.html as one self-contained page (anyone links only)").option("--name <filename>", "Filename for stdin input, or the published .html name of a folder").option("--to <target>", "Share target", "anyone").option("--notify", "Email an exact-email recipient, or a domain mailbox on node 1.17.3+").option("--notify-to <address>", "Mailbox to invite for --to domain:<name> --notify (node 1.17.3+)").option("--expires <duration>", "Share lifetime").option("--max-bytes <bytes>", "Bound input bytes").option("--media-type <type>", "Media type for a single input").option("--action <actions...>", "Addressed permission: read, list, or edit").option("--prefix", "Publish multiple inputs beneath one addressed prefix").option("--binary", "Allow non-UTF-8 bearer content").option("--artifact <id>", 'Publish as the next write-once version of an artifact ("new" starts one); the share is pinned to that version').addOption(new Option("--ongoing").hideHelp()).option("--note <text>", "Note stored with the artifact version").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN).action(async (files, options, command) => {
26840
28012
  try {
26841
28013
  const json = jsonOutput(options, command);
26842
28014
  if (options.ongoing === true) throw new CLIError("UNSUPPORTED_TARGET", ONGOING_UNAVAILABLE, 2);
26843
28015
  const maxBytes = byteLimit(options.maxBytes);
26844
28016
  if (files.length === 0 || files.includes("-") && files.length > 1) throw new CLIError("INVALID_ARGUMENT", "stdin must be the only publish input", 2);
26845
28017
  const name = files.length === 1 ? options.name : void 0;
26846
- for (const file of files) shareInputFilename(file, name);
26847
- const inputs = await Promise.all(files.map((file) => readShareInput(file, name, maxBytes)));
26848
- assertAggregateInputLimit(inputs, maxBytes);
26849
28018
  const target = parseShareTarget(options.to);
28019
+ const folder = files.length === 1 && target.kind === "bearer" && options.prefix !== true && await isDirectoryInput(files[0]);
28020
+ let inputs;
28021
+ if (folder) {
28022
+ if (options.mediaType !== void 0 && options.mediaType.split(";")[0].trim().toLowerCase() !== "text/html") {
28023
+ throw new CLIError("INVALID_ARGUMENT", "a folder is published as text/html; drop --media-type", 2);
28024
+ }
28025
+ const bundle = await bundleHtmlFolder(files[0], { ...name === void 0 ? {} : { name }, ...maxBytes === void 0 ? {} : { maxBytes } });
28026
+ const notice = htmlFolderNotice(bundle);
28027
+ if (notice !== void 0) process.stderr.write(notice);
28028
+ inputs = [{ bytes: bundle.bytes, filename: bundle.filename }];
28029
+ } else {
28030
+ for (const file of files) if (await isDirectoryInput(file)) throw new CLIError("MAX_BYTES_EXCEEDED", FOLDER_NOT_AVAILABLE, 7);
28031
+ for (const file of files) shareInputFilename(file, name);
28032
+ inputs = await Promise.all(files.map((file) => readShareInput(file, name, maxBytes)));
28033
+ }
28034
+ assertAggregateInputLimit(inputs, maxBytes);
26850
28035
  const actions = requestedActions(options.action);
26851
28036
  if (options.prefix && target.kind === "bearer") throw new CLIError("INVALID_ARGUMENT", "--prefix requires an addressed target", 2);
26852
28037
  if (target.kind === "bearer" && actions.includes("edit")) throw new CLIError("INVALID_ARGUMENT", `--action edit is not available with --to ${options.to}: a link can only be read; address a recipient to allow edits`, 2);
@@ -27140,7 +28325,7 @@ init_constants();
27140
28325
  init_errors();
27141
28326
  init_session_expired();
27142
28327
  import { readFile as readFile7 } from "fs/promises";
27143
- import { join as join12 } from "path";
28328
+ import { join as join13 } from "path";
27144
28329
  import { createHash } from "crypto";
27145
28330
  import { writeJsonAtomic as writeJsonAtomic2 } from "@tinycloud/operations/state";
27146
28331
  import { withoutBearer } from "@tinycloud/operations/delegation-binding";
@@ -27274,8 +28459,8 @@ function createEncryptedProfileHistory(profileName, sessionSigner) {
27274
28459
  const observedProfiles = /* @__PURE__ */ new Set();
27275
28460
  let preparedKeys;
27276
28461
  let preparedKey;
27277
- const path = async (profile) => join12(await ProfileManager.getCacheDir(profile), "share-history-v2.json");
27278
- const legacyPath = async (profile) => join12(await ProfileManager.getCacheDir(profile), "share-history-v1.bin");
28462
+ const path = async (profile) => join13(await ProfileManager.getCacheDir(profile), "share-history-v2.json");
28463
+ const legacyPath = async (profile) => join13(await ProfileManager.getCacheDir(profile), "share-history-v1.bin");
27279
28464
  const b64 = (value) => Buffer.from(value).toString("base64url");
27280
28465
  const unb64 = (value) => {
27281
28466
  if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value)) throw new ShareHistoryUnreadableError();