@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.
@@ -213,15 +213,15 @@ function closeReplication(schedule = scheduleTimeout) {
213
213
  removeSignalHandlers();
214
214
  let resolveClosing;
215
215
  let rejectClosing;
216
- const closing = new Promise((resolve8, reject) => {
217
- resolveClosing = resolve8;
216
+ const closing = new Promise((resolve9, reject) => {
217
+ resolveClosing = resolve9;
218
218
  rejectClosing = reject;
219
219
  });
220
220
  state.closing = closing;
221
221
  void (async () => {
222
222
  let timeout;
223
- const deadline = new Promise((resolve8) => {
224
- timeout = schedule(() => resolve8(true), CLOSE_TIMEOUT_MS);
223
+ const deadline = new Promise((resolve9) => {
224
+ timeout = schedule(() => resolve9(true), CLOSE_TIMEOUT_MS);
225
225
  });
226
226
  let timedOut = false;
227
227
  const abandonQueuedControls = () => {
@@ -247,7 +247,7 @@ function closeReplication(schedule = scheduleTimeout) {
247
247
  if (state.generation === generation && state.allControls.size === 0) break;
248
248
  }
249
249
  if (timedOut) break;
250
- const turn = new Promise((resolve8) => setImmediate(() => resolve8(false)));
250
+ const turn = new Promise((resolve9) => setImmediate(() => resolve9(false)));
251
251
  timedOut ||= await Promise.race([turn, deadline]);
252
252
  if (timedOut) abandonQueuedControls();
253
253
  else if (state.allControls.size === 0) break;
@@ -10760,7 +10760,7 @@ function shouldOpenBrowser(options) {
10760
10760
  return env !== "1" && env !== "true";
10761
10761
  }
10762
10762
  async function callbackFlow(did, options = {}, env = {}) {
10763
- return new Promise((resolve8, reject) => {
10763
+ return new Promise((resolve9, reject) => {
10764
10764
  let timeout;
10765
10765
  let hintTimer;
10766
10766
  const deadlineMs = env.deadlineMs ?? CALLBACK_DEADLINE_MS;
@@ -10780,7 +10780,7 @@ async function callbackFlow(did, options = {}, env = {}) {
10780
10780
  rl.close();
10781
10781
  }
10782
10782
  if (result.data) {
10783
- resolve8(result.data);
10783
+ resolve9(result.data);
10784
10784
  } else {
10785
10785
  reject(result.error);
10786
10786
  }
@@ -10861,8 +10861,8 @@ async function callbackFlow(did, options = {}, env = {}) {
10861
10861
  }
10862
10862
  if (openBrowser) {
10863
10863
  try {
10864
- const open13 = (await import("open")).default;
10865
- await open13(authUrl);
10864
+ const open14 = (await import("open")).default;
10865
+ await open14(authUrl);
10866
10866
  } catch {
10867
10867
  settle({ error: new Error("Failed to open browser") });
10868
10868
  return;
@@ -10911,13 +10911,13 @@ Open this URL in a browser to authenticate:
10911
10911
  input: process.stdin,
10912
10912
  output: process.stderr
10913
10913
  });
10914
- return new Promise((resolve8, reject) => {
10914
+ return new Promise((resolve9, reject) => {
10915
10915
  let answered = false;
10916
10916
  rl.on("line", (input) => {
10917
10917
  if (answered || input.trim() === "") return;
10918
10918
  answered = true;
10919
10919
  rl.close();
10920
- void delegationFromInput(input, did, options).then(resolve8, reject);
10920
+ void delegationFromInput(input, did, options).then(resolve9, reject);
10921
10921
  });
10922
10922
  rl.on("close", () => {
10923
10923
  if (answered) return;
@@ -11844,7 +11844,7 @@ async function acquireDeviceDelegation(input) {
11844
11844
  expiresAt: new Date(deadline).toISOString().replace(/\.\d{3}Z$/, "Z")
11845
11845
  });
11846
11846
  let interval = started.interval;
11847
- const wait = input.wait ?? ((milliseconds) => new Promise((resolve8) => setTimeout(resolve8, milliseconds)));
11847
+ const wait = input.wait ?? ((milliseconds) => new Promise((resolve9) => setTimeout(resolve9, milliseconds)));
11848
11848
  while (Date.now() < deadline) {
11849
11849
  await wait(interval * 1e3);
11850
11850
  let response;
@@ -11974,7 +11974,7 @@ async function awaitSpaceSetup(report, deps) {
11974
11974
  process.stderr.write(`${line}
11975
11975
  `);
11976
11976
  });
11977
- const sleep = deps.sleep ?? ((ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2)));
11977
+ const sleep = deps.sleep ?? ((ms2) => new Promise((resolve9) => setTimeout(resolve9, ms2)));
11978
11978
  const now = deps.now ?? Date.now;
11979
11979
  const timeoutMs = deps.timeoutMs ?? SPACE_SETUP_TIMEOUT_MS;
11980
11980
  const started = now();
@@ -12142,8 +12142,8 @@ async function startPreparedOpenKeyHandoff(options) {
12142
12142
  let acceptResponse;
12143
12143
  let resolveResult;
12144
12144
  let rejectResult;
12145
- const result = new Promise((resolve8, reject) => {
12146
- resolveResult = resolve8;
12145
+ const result = new Promise((resolve9, reject) => {
12146
+ resolveResult = resolve9;
12147
12147
  rejectResult = reject;
12148
12148
  });
12149
12149
  let delivered = false;
@@ -12235,9 +12235,9 @@ async function startPreparedOpenKeyHandoff(options) {
12235
12235
  });
12236
12236
  server.headersTimeout = 1e4;
12237
12237
  server.requestTimeout = 1e4;
12238
- await new Promise((resolve8, reject) => {
12238
+ await new Promise((resolve9, reject) => {
12239
12239
  server.once("error", reject);
12240
- server.listen(0, "127.0.0.1", resolve8);
12240
+ server.listen(0, "127.0.0.1", resolve9);
12241
12241
  });
12242
12242
  }
12243
12243
  const address = server?.address();
@@ -13260,7 +13260,7 @@ async function promptAuthMethod() {
13260
13260
  input: process.stdin,
13261
13261
  output: process.stderr
13262
13262
  });
13263
- return new Promise((resolve8) => {
13263
+ return new Promise((resolve9) => {
13264
13264
  process.stderr.write("\n" + theme.heading("Choose authentication method:") + "\n");
13265
13265
  process.stderr.write(` ${theme.accent("1)")} OpenKey ${theme.muted("(browser-based, for interactive use)")}
13266
13266
  `);
@@ -13271,9 +13271,9 @@ async function promptAuthMethod() {
13271
13271
  rl.close();
13272
13272
  const trimmed = answer.trim();
13273
13273
  if (trimmed === "2" || trimmed.toLowerCase() === "local") {
13274
- resolve8("local");
13274
+ resolve9("local");
13275
13275
  } else {
13276
- resolve8("openkey");
13276
+ resolve9("openkey");
13277
13277
  }
13278
13278
  });
13279
13279
  });
@@ -14493,13 +14493,13 @@ async function readStdin() {
14493
14493
  return Buffer.concat(chunks).toString("utf8");
14494
14494
  }
14495
14495
  function readUrl(source) {
14496
- return new Promise((resolve8, reject) => {
14496
+ return new Promise((resolve9, reject) => {
14497
14497
  const getter = source.startsWith("https://") ? httpsGet : httpGet;
14498
14498
  const request = getter(source, (response) => {
14499
14499
  const status = response.statusCode ?? 0;
14500
14500
  if (status >= 300 && status < 400 && response.headers.location) {
14501
14501
  response.resume();
14502
- readUrl(new URL(response.headers.location, source).toString()).then(resolve8, reject);
14502
+ readUrl(new URL(response.headers.location, source).toString()).then(resolve9, reject);
14503
14503
  return;
14504
14504
  }
14505
14505
  if (status < 200 || status >= 300) {
@@ -14515,7 +14515,7 @@ function readUrl(source) {
14515
14515
  response.on("data", (chunk) => {
14516
14516
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
14517
14517
  });
14518
- response.on("end", () => resolve8(Buffer.concat(chunks).toString("utf8")));
14518
+ response.on("end", () => resolve9(Buffer.concat(chunks).toString("utf8")));
14519
14519
  });
14520
14520
  request.on("error", reject);
14521
14521
  });
@@ -14755,7 +14755,7 @@ function permissionGrantReason(context, permissions) {
14755
14755
  return `${context} Requested: ${summary}${more}.`;
14756
14756
  }
14757
14757
  function execCapturedCommand(command) {
14758
- return new Promise((resolve8, reject) => {
14758
+ return new Promise((resolve9, reject) => {
14759
14759
  const child = spawn(process.execPath, [process.argv[1], ...command.argv], {
14760
14760
  cwd: command.cwd,
14761
14761
  env: process.env,
@@ -14774,7 +14774,7 @@ function execCapturedCommand(command) {
14774
14774
  if (code4 && code4 !== 0) {
14775
14775
  process.exitCode = code4;
14776
14776
  }
14777
- resolve8();
14777
+ resolve9();
14778
14778
  });
14779
14779
  });
14780
14780
  }
@@ -14796,8 +14796,8 @@ async function askYesNo(question, cancelMessage) {
14796
14796
  input: process.stdin,
14797
14797
  output: process.stderr
14798
14798
  });
14799
- const answer = await new Promise((resolve8) => {
14800
- rl.question(question, resolve8);
14799
+ const answer = await new Promise((resolve9) => {
14800
+ rl.question(question, resolve9);
14801
14801
  });
14802
14802
  rl.close();
14803
14803
  if (!/^y(es)?$/i.test(answer.trim())) {
@@ -15601,7 +15601,7 @@ async function confirmCurrentSessionRevoke(yes) {
15601
15601
  `);
15602
15602
  const prompt = createInterface4({ input: process.stdin, output: process.stderr });
15603
15603
  try {
15604
- const answer = await new Promise((resolve8) => prompt.question("Revoke this session? [y/N] ", resolve8));
15604
+ const answer = await new Promise((resolve9) => prompt.question("Revoke this session? [y/N] ", resolve9));
15605
15605
  if (!/^y(es)?$/i.test(answer.trim())) {
15606
15606
  throw new CLIError("REQUEST_CANCELLED", "Current session revocation cancelled.", ExitCode.ERROR);
15607
15607
  }
@@ -16999,8 +16999,8 @@ Examples:
16999
16999
  try {
17000
17000
  if (isInteractive()) {
17001
17001
  const rl = createInterface5({ input: process.stdin, output: process.stderr });
17002
- const answer = await new Promise((resolve8) => {
17003
- rl.question(`Delete profile "${name}"? This cannot be undone. [y/N] `, resolve8);
17002
+ const answer = await new Promise((resolve9) => {
17003
+ rl.question(`Delete profile "${name}"? This cannot be undone. [y/N] `, resolve9);
17004
17004
  });
17005
17005
  rl.close();
17006
17006
  if (answer.toLowerCase() !== "y") {
@@ -17970,15 +17970,15 @@ async function tryOpenBrowser(url) {
17970
17970
  } catch {
17971
17971
  return false;
17972
17972
  }
17973
- return new Promise((resolve8) => {
17974
- const timer = setTimeout(() => resolve8(true), LAUNCH_CHECK_MS);
17973
+ return new Promise((resolve9) => {
17974
+ const timer = setTimeout(() => resolve9(true), LAUNCH_CHECK_MS);
17975
17975
  launcher.once("error", () => {
17976
17976
  clearTimeout(timer);
17977
- resolve8(false);
17977
+ resolve9(false);
17978
17978
  });
17979
17979
  launcher.once("exit", (code4) => {
17980
17980
  clearTimeout(timer);
17981
- resolve8(code4 === 0);
17981
+ resolve9(code4 === 0);
17982
17982
  });
17983
17983
  });
17984
17984
  }
@@ -18022,9 +18022,9 @@ function runProbeChild(request, options = {}) {
18022
18022
  const killGraceMs = options.killGraceMs ?? KILL_GRACE_MS;
18023
18023
  const spawn2 = options.spawn ?? nodeSpawn;
18024
18024
  const signal = options.signal;
18025
- return new Promise((resolve8) => {
18025
+ return new Promise((resolve9) => {
18026
18026
  if (signal?.aborted) {
18027
- resolve8({ kind: "cancelled" });
18027
+ resolve9({ kind: "cancelled" });
18028
18028
  return;
18029
18029
  }
18030
18030
  const child = spawn2(command, args, { env: process.env, stdio: ["ignore", "pipe", "pipe"] });
@@ -18060,7 +18060,7 @@ function runProbeChild(request, options = {}) {
18060
18060
  const envelope = stdout.length > 0 && stdout.length <= MAX_PROBE_LINE_BYTES ? parseStatusEnvelope(stdout.toString("utf8")) : void 0;
18061
18061
  outcome = envelope !== void 0 ? { kind: "ok", envelope } : probeFailed(stderr.toString("utf8"));
18062
18062
  }
18063
- resolve8(outcome);
18063
+ resolve9(outcome);
18064
18064
  };
18065
18065
  if (child.pid !== void 0) options.onSpawn?.(child.pid);
18066
18066
  child.stdout?.on("data", (chunk) => {
@@ -18152,15 +18152,15 @@ var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
18152
18152
  var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
18153
18153
  var EXPLICIT_KEY_HINT = `${CLI_NAME27} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME27} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
18154
18154
  function abortableSleep(ms2, signal) {
18155
- return new Promise((resolve8) => {
18155
+ return new Promise((resolve9) => {
18156
18156
  if (signal.aborted) {
18157
- resolve8();
18157
+ resolve9();
18158
18158
  return;
18159
18159
  }
18160
18160
  const done = () => {
18161
18161
  clearTimeout(timer);
18162
18162
  signal.removeEventListener("abort", done);
18163
- resolve8();
18163
+ resolve9();
18164
18164
  };
18165
18165
  const timer = setTimeout(done, ms2);
18166
18166
  signal.addEventListener("abort", done, { once: true });
@@ -24969,31 +24969,31 @@ function parseArtifactManifest(bytes) {
24969
24969
  } catch {
24970
24970
  throw new ArtifactError("corrupt", "artifact manifest is not valid JSON");
24971
24971
  }
24972
- const fail = () => {
24972
+ const fail2 = () => {
24973
24973
  throw new ArtifactError("corrupt", "artifact manifest is malformed");
24974
24974
  };
24975
- if (value === null || typeof value !== "object" || Array.isArray(value)) return fail();
24975
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return fail2();
24976
24976
  const record3 = value;
24977
24977
  const allowed = ["v", "files", "createdAt", "parent", "note"];
24978
- if (Object.keys(record3).some((key) => !allowed.includes(key)) || record3.v !== ARTIFACT_MANIFEST_VERSION) return fail();
24979
- if (!Array.isArray(record3.files) || record3.files.length === 0) return fail();
24980
- if (typeof record3.createdAt !== "string" || Number.isNaN(Date.parse(record3.createdAt))) return fail();
24981
- if (record3.parent !== null && (typeof record3.parent !== "number" || !Number.isSafeInteger(record3.parent) || record3.parent < 1)) return fail();
24982
- if (record3.note !== void 0 && (typeof record3.note !== "string" || record3.note.length > 1024)) return fail();
24978
+ if (Object.keys(record3).some((key) => !allowed.includes(key)) || record3.v !== ARTIFACT_MANIFEST_VERSION) return fail2();
24979
+ if (!Array.isArray(record3.files) || record3.files.length === 0) return fail2();
24980
+ if (typeof record3.createdAt !== "string" || Number.isNaN(Date.parse(record3.createdAt))) return fail2();
24981
+ if (record3.parent !== null && (typeof record3.parent !== "number" || !Number.isSafeInteger(record3.parent) || record3.parent < 1)) return fail2();
24982
+ if (record3.note !== void 0 && (typeof record3.note !== "string" || record3.note.length > 1024)) return fail2();
24983
24983
  const seen = /* @__PURE__ */ new Set();
24984
24984
  const files = record3.files.map((entry) => {
24985
- if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return fail();
24985
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return fail2();
24986
24986
  const file = entry;
24987
- if (Object.keys(file).some((key) => !["path", "sha256", "bytes"].includes(key))) return fail();
24988
- if (typeof file.path !== "string" || file.path === ARTIFACT_MANIFEST_NAME || seen.has(file.path)) return fail();
24987
+ if (Object.keys(file).some((key) => !["path", "sha256", "bytes"].includes(key))) return fail2();
24988
+ if (typeof file.path !== "string" || file.path === ARTIFACT_MANIFEST_NAME || seen.has(file.path)) return fail2();
24989
24989
  try {
24990
24990
  canonicalShareFilename(file.path);
24991
24991
  } catch {
24992
- return fail();
24992
+ return fail2();
24993
24993
  }
24994
24994
  seen.add(file.path);
24995
- if (typeof file.sha256 !== "string" || !SHA256_HEX.test(file.sha256)) return fail();
24996
- if (typeof file.bytes !== "number" || !Number.isSafeInteger(file.bytes) || file.bytes < 0) return fail();
24995
+ if (typeof file.sha256 !== "string" || !SHA256_HEX.test(file.sha256)) return fail2();
24996
+ if (typeof file.bytes !== "number" || !Number.isSafeInteger(file.bytes) || file.bytes < 0) return fail2();
24997
24997
  return { path: file.path, sha256: file.sha256, bytes: file.bytes };
24998
24998
  });
24999
24999
  return {
@@ -37680,6 +37680,1173 @@ async function writeShareOutput(directory, filename, bytes, force) {
37680
37680
  return join17(outputDirectory, safeName);
37681
37681
  }
37682
37682
 
37683
+ // src/share/html-folder.ts
37684
+ import { constants as constants2, existsSync } from "fs";
37685
+ import { lstat as lstat11, open as open13, readdir as readdir7, realpath as realpath3, stat as stat6 } from "fs/promises";
37686
+ import { basename as basename5, dirname as dirname8, isAbsolute, join as join18, relative as relativePath, resolve as resolve7, sep as sep4 } from "path";
37687
+
37688
+ // ../share-sdk/dist/html-bundle.js
37689
+ var UNSAFE_FILENAME_CODE_POINT3 = /[\p{Cc}\p{Cf}\p{Cs}\u2028\u2029]/u;
37690
+ function hasUnsafeFilenameCodePoint3(value) {
37691
+ return UNSAFE_FILENAME_CODE_POINT3.test(value);
37692
+ }
37693
+ var ARTIFACT_ENTRY = "index.html";
37694
+ var MAX_ARTIFACT_FILES = 1e3;
37695
+ var MAX_ARTIFACT_SOURCE_BYTES = 100 * 1024 * 1024;
37696
+ var MAX_ARTIFACT_RENDER_BYTES = 10 * 1024 * 1024;
37697
+ var MAX_ARTIFACT_TEXT_BYTES = 5 * 1024 * 1024;
37698
+ var MAX_ARTIFACT_REFERENCES = 2e3;
37699
+ var MAX_ARTIFACT_CSS_IMPORT_DEPTH = 16;
37700
+ var MAX_ARTIFACT_COMMENTS = 2e4;
37701
+ var MAX_ARTIFACT_ATTRIBUTES_PER_ELEMENT = 5e3;
37702
+ var ArtifactBundleError = class extends Error {
37703
+ /**
37704
+ * `path` is the bundle file being processed when the refusal happened; `reference` is the reference text inside it
37705
+ * that was refused, when there is one. Both are the folder's own text: print them through a terminal-safe printer.
37706
+ */
37707
+ constructor(kind, message, path, reference) {
37708
+ super(message);
37709
+ this.kind = kind;
37710
+ this.path = path;
37711
+ this.reference = reference;
37712
+ this.name = "ArtifactBundleError";
37713
+ }
37714
+ };
37715
+ var MIME_BY_EXTENSION = Object.freeze({
37716
+ html: "text/html;charset=utf-8",
37717
+ htm: "text/html;charset=utf-8",
37718
+ css: "text/css;charset=utf-8",
37719
+ js: "text/javascript;charset=utf-8",
37720
+ json: "application/json;charset=utf-8",
37721
+ txt: "text/plain;charset=utf-8",
37722
+ md: "text/markdown;charset=utf-8",
37723
+ svg: "image/svg+xml",
37724
+ png: "image/png",
37725
+ jpg: "image/jpeg",
37726
+ jpeg: "image/jpeg",
37727
+ gif: "image/gif",
37728
+ webp: "image/webp",
37729
+ avif: "image/avif",
37730
+ ico: "image/x-icon",
37731
+ woff: "font/woff",
37732
+ woff2: "font/woff2",
37733
+ ttf: "font/ttf",
37734
+ otf: "font/otf",
37735
+ mp3: "audio/mpeg",
37736
+ wav: "audio/wav",
37737
+ mp4: "video/mp4",
37738
+ webm: "video/webm",
37739
+ pdf: "application/pdf"
37740
+ });
37741
+ var SCHEME = /^[a-z][a-z0-9+.-]*:/i;
37742
+ var ENCODED_PATH_ALIAS = /%2f|%5c|%2e/i;
37743
+ var SAFE_MEDIA_QUERY = /^[a-z0-9\s,().:%\-+/]+$/i;
37744
+ 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/;
37745
+ function fail(kind, detail) {
37746
+ throw new ArtifactBundleError(kind, detail);
37747
+ }
37748
+ function defaultParseHtml(html) {
37749
+ if (typeof DOMParser === "undefined") {
37750
+ throw new TypeError("html-bundle needs a DOM parser: pass parseHtml outside a browser");
37751
+ }
37752
+ return new DOMParser().parseFromString(html, "text/html");
37753
+ }
37754
+ function canonicalArtifactPath(input) {
37755
+ const value = input.normalize("NFC");
37756
+ const encodedLength = new TextEncoder().encode(value).byteLength;
37757
+ if (value.length === 0 || encodedLength > 4096 || value.startsWith("/") || value.endsWith("/") || value.includes("\\") || hasUnsafeFilenameCodePoint3(value) || ENCODED_PATH_ALIAS.test(value)) {
37758
+ fail("malformed", "artifact path is not canonical");
37759
+ }
37760
+ const segments = value.split("/");
37761
+ if (segments.some((segment) => segment.length === 0 || segment === "." || segment === ".." || new TextEncoder().encode(segment).byteLength > 255)) {
37762
+ fail("malformed", "artifact path is not canonical");
37763
+ }
37764
+ return segments.join("/");
37765
+ }
37766
+ function artifactMediaType(path, supplied) {
37767
+ const normalized = supplied?.split(";")[0]?.trim().toLowerCase();
37768
+ if (normalized !== void 0 && /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(normalized)) {
37769
+ return supplied.trim();
37770
+ }
37771
+ const extension = path.split(".").at(-1)?.toLowerCase() ?? "";
37772
+ return MIME_BY_EXTENSION[extension] ?? "application/octet-stream";
37773
+ }
37774
+ function resolveArtifactReference(basePath, reference) {
37775
+ const raw = trimUrl(reference);
37776
+ if (raw.length === 0) return { path: canonicalArtifactPath(basePath), fragment: "", fragmentOnly: true };
37777
+ if (raw.startsWith("#")) {
37778
+ return { path: canonicalArtifactPath(basePath), fragment: raw, fragmentOnly: true };
37779
+ }
37780
+ if (raw.startsWith("//") || raw.startsWith("/") || SCHEME.test(raw)) {
37781
+ fail("unsupported", "external artifact reference");
37782
+ }
37783
+ const hashAt = raw.indexOf("#");
37784
+ const queryAt = raw.indexOf("?");
37785
+ const end = [hashAt, queryAt].filter((value) => value >= 0).reduce((min, value) => Math.min(min, value), raw.length);
37786
+ const pathname = raw.slice(0, end);
37787
+ const fragment = hashAt >= 0 ? raw.slice(hashAt) : "";
37788
+ if (pathname.length === 0) return { path: canonicalArtifactPath(basePath), fragment, fragmentOnly: true };
37789
+ if (pathname.includes("\\") || hasUnsafeFilenameCodePoint3(pathname) || ENCODED_PATH_ALIAS.test(pathname)) {
37790
+ fail("malformed", "artifact reference is not canonical");
37791
+ }
37792
+ const output2 = basePath.split("/").slice(0, -1);
37793
+ for (const segment of pathname.normalize("NFC").split("/")) {
37794
+ if (segment === "" || segment === ".") continue;
37795
+ if (segment === "..") {
37796
+ if (output2.length === 0) fail("malformed", "artifact reference escapes its bundle");
37797
+ output2.pop();
37798
+ continue;
37799
+ }
37800
+ output2.push(segment);
37801
+ }
37802
+ return { path: canonicalArtifactPath(output2.join("/")), fragment, fragmentOnly: false };
37803
+ }
37804
+ function detectHtmlArtifact(paths) {
37805
+ const seen = /* @__PURE__ */ new Set();
37806
+ let rootCount = 0;
37807
+ for (const rawPath of paths) {
37808
+ const path = canonicalArtifactPath(rawPath);
37809
+ const key = path.toLowerCase();
37810
+ if (seen.has(key)) fail("malformed", "artifact paths collide after canonicalization");
37811
+ seen.add(key);
37812
+ if (path === ARTIFACT_ENTRY) rootCount += 1;
37813
+ }
37814
+ if (rootCount === 1) return { kind: "html", entry: ARTIFACT_ENTRY };
37815
+ return { kind: "folder", reason: "missing-root-index" };
37816
+ }
37817
+ function toBase64(bytes) {
37818
+ let binary = "";
37819
+ const chunk = 32768;
37820
+ for (let offset = 0; offset < bytes.length; offset += chunk) {
37821
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk));
37822
+ }
37823
+ return btoa(binary);
37824
+ }
37825
+ function chargeInlinedBytes(context, bytes) {
37826
+ context.counter.inlinedBytes += bytes;
37827
+ if (context.counter.inlinedBytes > MAX_ARTIFACT_RENDER_BYTES) fail("limit", "artifact inlined resources exceed the render limit");
37828
+ }
37829
+ function asDataUrl(file, context) {
37830
+ if (file.bytes.byteLength > MAX_ARTIFACT_RENDER_BYTES) fail("limit", "artifact resource exceeds render limit");
37831
+ const prefix = `data:${artifactMediaType(file.path, file.mediaType)};base64,`;
37832
+ chargeInlinedBytes(context, prefix.length + 4 * Math.ceil(file.bytes.byteLength / 3));
37833
+ return `${prefix}${toBase64(file.bytes)}`;
37834
+ }
37835
+ function decodeText(file, context) {
37836
+ if (file.bytes.byteLength > MAX_ARTIFACT_TEXT_BYTES) fail("limit", "artifact text file exceeds render limit");
37837
+ if (context !== void 0) chargeInlinedBytes(context, file.bytes.byteLength);
37838
+ try {
37839
+ return new TextDecoder("utf-8", { fatal: true }).decode(file.bytes);
37840
+ } catch {
37841
+ return fail("malformed", "artifact text is not utf-8");
37842
+ }
37843
+ }
37844
+ function assertClassicScript(source) {
37845
+ if (UNSAFE_CLASSIC_SCRIPT.test(source)) fail("unsupported", "artifact script uses a blocked browser capability");
37846
+ }
37847
+ function normalizeMediaQuery(media, detail = "artifact uses an unsupported stylesheet media query") {
37848
+ const value = media?.trim() ?? "";
37849
+ if (value.length === 0 || value.toLowerCase() === "all") return null;
37850
+ if (/[\u0000-\u001f\u007f<>{};"'`\\]/.test(value) || !SAFE_MEDIA_QUERY.test(value)) fail("unsupported", detail);
37851
+ return value;
37852
+ }
37853
+ function wrapMediaCss(source, media, detail) {
37854
+ const value = normalizeMediaQuery(media, detail);
37855
+ if (value === null) return source;
37856
+ return `@media ${value} {
37857
+ ${source}
37858
+ }`;
37859
+ }
37860
+ function classifyLinkRelation(rel) {
37861
+ const tokens = rel?.trim().toLowerCase().split(/\s+/).filter(Boolean) ?? [];
37862
+ if (tokens.length === 0) return null;
37863
+ const tokenSet = new Set(tokens);
37864
+ if (tokenSet.has("stylesheet") && [...tokenSet].every((token) => token === "stylesheet" || token === "alternate")) return "stylesheet";
37865
+ if ((tokenSet.has("icon") || tokenSet.has("apple-touch-icon")) && [...tokenSet].every((token) => token === "icon" || token === "apple-touch-icon" || token === "shortcut")) return "icon";
37866
+ return null;
37867
+ }
37868
+ var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
37869
+ function documentRoots(doc) {
37870
+ const roots = [doc];
37871
+ for (let index = 0; index < roots.length; index += 1) {
37872
+ for (const template of roots[index].querySelectorAll("template")) roots.push(template.content);
37873
+ }
37874
+ return roots;
37875
+ }
37876
+ function queryAll(roots, selector) {
37877
+ return roots.flatMap((root) => [...root.querySelectorAll(selector)]);
37878
+ }
37879
+ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
37880
+ "href",
37881
+ "src",
37882
+ "srcset",
37883
+ "imagesrcset",
37884
+ "poster",
37885
+ "action",
37886
+ "formaction",
37887
+ "cite",
37888
+ "data",
37889
+ "codebase",
37890
+ "longdesc",
37891
+ "usemap",
37892
+ "ping",
37893
+ "background",
37894
+ "manifest",
37895
+ "icon",
37896
+ "archive",
37897
+ "classid",
37898
+ "profile",
37899
+ "lowsrc",
37900
+ "dynsrc",
37901
+ "attributionsrc",
37902
+ "itemtype",
37903
+ "itemid"
37904
+ ]);
37905
+ function trimUrl(value) {
37906
+ return value.replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, "");
37907
+ }
37908
+ function urlText(value) {
37909
+ return trimUrl(value).replace(/[\t\n\r]/g, "");
37910
+ }
37911
+ function isFragmentOnly(trimmed) {
37912
+ return /^#[^\u0000-\u0020\u007f-\u009f\\]*$/.test(trimmed);
37913
+ }
37914
+ var INLINE_DATA_URL = /^data:;base64(?:,[A-Za-z0-9+/=]*)?(?:#[^\u0000-\u0020\\]*)?$/;
37915
+ function isInlineReference(value) {
37916
+ const text = urlText(value);
37917
+ return isFragmentOnly(text) || INLINE_DATA_URL.test(text);
37918
+ }
37919
+ function assertInlineSrcset(value) {
37920
+ for (const raw of value.split(/[\t\n\f\r ]+/)) {
37921
+ const token = raw.replace(/,+$/, "");
37922
+ if (token === "" || /^\d+(?:\.\d+)?[wx]$/i.test(token)) continue;
37923
+ if (!isInlineReference(token)) throw new ArtifactBundleError("unsupported", "artifact page still references a resource", void 0, token);
37924
+ }
37925
+ }
37926
+ var JAVASCRIPT_TYPES = /* @__PURE__ */ new Set([
37927
+ "application/ecmascript",
37928
+ "application/javascript",
37929
+ "application/x-ecmascript",
37930
+ "application/x-javascript",
37931
+ "text/ecmascript",
37932
+ "text/javascript",
37933
+ "text/javascript1.0",
37934
+ "text/javascript1.1",
37935
+ "text/javascript1.2",
37936
+ "text/javascript1.3",
37937
+ "text/javascript1.4",
37938
+ "text/javascript1.5",
37939
+ "text/jscript",
37940
+ "text/livescript",
37941
+ "text/x-ecmascript",
37942
+ "text/x-javascript"
37943
+ ]);
37944
+ function isJavaScriptType(value) {
37945
+ const type = (value ?? "").replace(/^[\t\n\f\r ]+|[\t\n\f\r ]+$/g, "").toLowerCase();
37946
+ if (type === "") return true;
37947
+ return JAVASCRIPT_TYPES.has(type.split(";")[0].replace(/[\t\n\f\r ]+$/g, ""));
37948
+ }
37949
+ var BLOCKED_ELEMENTS = "base, form, iframe, frame, frameset, object, embed, portal, noscript, plaintext, xmp, noembed, noframes, applet, area[href], link:not([href]), [ping]";
37950
+ var DATA_URI = /data:[a-z0-9!#$&^_.+/-]*(?:;[a-z0-9!#$&^_.+=-]+)*;base64,[A-Za-z0-9+/=]*/gi;
37951
+ function withoutDataUris(html) {
37952
+ return html.replace(DATA_URI, "data:;base64,");
37953
+ }
37954
+ var TEXT_ATTRIBUTES = /^(?:alt|title|placeholder|value|label|content|name|id|class|lang|summary|abbr|datetime|pattern|xmlns(?::[\w.-]+)?|aria-[\w.:-]*|data-[\w.:-]*)$/;
37955
+ function looksLikeUrl(raw) {
37956
+ const value = raw.replace(/\\/g, "/");
37957
+ 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);
37958
+ }
37959
+ var NAMED_CHARACTERS = {
37960
+ amp: "&",
37961
+ quot: '"',
37962
+ apos: "'",
37963
+ lt: "<",
37964
+ gt: ">",
37965
+ nbsp: "\xA0",
37966
+ colon: ":",
37967
+ sol: "/",
37968
+ bsol: "\\",
37969
+ period: ".",
37970
+ comma: ",",
37971
+ semi: ";",
37972
+ equals: "=",
37973
+ num: "#",
37974
+ quest: "?",
37975
+ lpar: "(",
37976
+ rpar: ")",
37977
+ tab: " ",
37978
+ newline: "\n"
37979
+ };
37980
+ function decodeAttributeValue(value) {
37981
+ return value.replace(/&(?:#[xX]([0-9a-fA-F]{1,6})|#([0-9]{1,7})|([A-Za-z]{2,8}));?/g, (whole, hex2, decimal, name) => {
37982
+ if (name !== void 0) return NAMED_CHARACTERS[name.toLowerCase()] ?? whole;
37983
+ const point = Number.parseInt(hex2 ?? decimal ?? "", hex2 === void 0 ? 10 : 16);
37984
+ return point > 0 && point <= 1114111 && !(point >= 55296 && point <= 57343) ? String.fromCodePoint(point) : "\uFFFD";
37985
+ });
37986
+ }
37987
+ var MARKUP_BLOCKED_NAMES = /* @__PURE__ */ new Set(["base", "form", "iframe", "frame", "frameset", "object", "embed", "portal", "noscript", "plaintext", "xmp", "noembed", "noframes", "applet"]);
37988
+ function unsupportedOutput(detail, reference) {
37989
+ throw new ArtifactBundleError("unsupported", detail, void 0, reference);
37990
+ }
37991
+ function isHrefAnimation(tag, attributeName, value) {
37992
+ return /^(?:animate|set|animatetransform|animatemotion)$/i.test(tag) && attributeName.toLowerCase() === "attributename" && /^(?:xlink:)?href$/i.test(trimUrl(value));
37993
+ }
37994
+ function checkAttribute(name, decoded) {
37995
+ const local = name.slice(name.lastIndexOf(":") + 1);
37996
+ if (/^on/.test(name)) unsupportedOutput("artifact output contains a blocked html feature");
37997
+ if (name === "style") {
37998
+ assertOnlyInlineCssReferences(decoded);
37999
+ return;
38000
+ }
38001
+ if (local === "srcset" || local === "imagesrcset") {
38002
+ assertInlineSrcset(decoded);
38003
+ return;
38004
+ }
38005
+ if (URL_ATTRIBUTES.has(local)) {
38006
+ if (!isInlineReference(decoded)) unsupportedOutput("artifact page still references a resource", decoded);
38007
+ return;
38008
+ }
38009
+ if (TEXT_ATTRIBUTES.test(name)) return;
38010
+ if (looksLikeUrl(urlText(decoded))) unsupportedOutput("artifact page still references a resource", decoded);
38011
+ if (/url\s*\(|image-set|@import|\\/i.test(decoded) || /(?:^|[^\w-])src\s*\(/i.test(decoded)) assertOnlyInlineCssReferences(decoded);
38012
+ }
38013
+ function isOwnPolicyMeta(httpEquiv, content) {
38014
+ return httpEquiv.trim().toLowerCase() === "content-security-policy" && content === INNER_CSP;
38015
+ }
38016
+ var HTML_SPACE = "\\t\\n\\f\\r ";
38017
+ var TAG_NAME = new RegExp(`[^${HTML_SPACE}/>]*`, "y");
38018
+ var ATTRIBUTE_NAME = new RegExp(`=?[^${HTML_SPACE}/>=]*`, "y");
38019
+ var UNQUOTED_VALUE = new RegExp(`[^${HTML_SPACE}>]*`, "y");
38020
+ var SKIPPED = new RegExp(`[${HTML_SPACE}/]`);
38021
+ var SPACE2 = new RegExp(`[${HTML_SPACE}]`);
38022
+ var ODD_TAG_NAME = /[\u00a0\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u3000\ufeff]/;
38023
+ var COMMENT_END = /--!?>/g;
38024
+ var SCRIPT_END = /<\/script/gi;
38025
+ var STYLE_END = /<\/style/gi;
38026
+ function scanMarkup(html, options) {
38027
+ const checkReferences = options.references;
38028
+ const length4 = html.length;
38029
+ let index = 0;
38030
+ let comments = 0;
38031
+ const malformed = (detail) => {
38032
+ if (checkReferences) unsupportedOutput(detail);
38033
+ return true;
38034
+ };
38035
+ const countComment = () => {
38036
+ comments += 1;
38037
+ if (comments > MAX_ARTIFACT_COMMENTS) fail("limit", "artifact page has too many comments");
38038
+ };
38039
+ while (index < length4) {
38040
+ const open14 = html.indexOf("<", index);
38041
+ if (open14 === -1) return;
38042
+ index = open14 + 1;
38043
+ const next = html[index] ?? "";
38044
+ if (html.startsWith("!--", index)) {
38045
+ countComment();
38046
+ let end;
38047
+ if (html[index + 3] === ">") end = index + 4;
38048
+ else if (html.startsWith("->", index + 3)) end = index + 5;
38049
+ else {
38050
+ COMMENT_END.lastIndex = index + 3;
38051
+ const close = COMMENT_END.exec(html);
38052
+ if (close === null) {
38053
+ if (malformed("artifact output has an unterminated comment")) return;
38054
+ return;
38055
+ }
38056
+ end = close.index + close[0].length;
38057
+ }
38058
+ index = end;
38059
+ continue;
38060
+ }
38061
+ if (next === "!" || next === "?" || next === "/") {
38062
+ if (next === "?" || next === "!" && !/^!doctype/i.test(html.slice(index, index + 8))) countComment();
38063
+ const close = html.indexOf(">", index);
38064
+ if (close === -1) {
38065
+ if (malformed("artifact output has an unterminated tag")) return;
38066
+ return;
38067
+ }
38068
+ index = close + 1;
38069
+ continue;
38070
+ }
38071
+ if (!/[A-Za-z]/.test(next)) continue;
38072
+ TAG_NAME.lastIndex = index;
38073
+ const rawTag = TAG_NAME.exec(html)[0];
38074
+ const tag = rawTag.toLowerCase();
38075
+ index = TAG_NAME.lastIndex;
38076
+ if (checkReferences) {
38077
+ if (ODD_TAG_NAME.test(rawTag)) unsupportedOutput("artifact output has a tag name with an unexpected character");
38078
+ if (MARKUP_BLOCKED_NAMES.has(tag)) unsupportedOutput("artifact output contains a blocked html feature");
38079
+ }
38080
+ const attributes = /* @__PURE__ */ new Map();
38081
+ let attributeCount = 0;
38082
+ for (; ; ) {
38083
+ while (index < length4 && SKIPPED.test(html[index])) index += 1;
38084
+ if (index >= length4) {
38085
+ if (malformed("artifact output has an unterminated tag")) return;
38086
+ return;
38087
+ }
38088
+ if (html[index] === ">") {
38089
+ index += 1;
38090
+ break;
38091
+ }
38092
+ ATTRIBUTE_NAME.lastIndex = index;
38093
+ const rawName = ATTRIBUTE_NAME.exec(html)[0];
38094
+ index = ATTRIBUTE_NAME.lastIndex;
38095
+ while (index < length4 && SPACE2.test(html[index])) index += 1;
38096
+ let rawValue = "";
38097
+ if (html[index] === "=") {
38098
+ index += 1;
38099
+ while (index < length4 && SPACE2.test(html[index])) index += 1;
38100
+ const quote = html[index];
38101
+ if (quote === '"' || quote === "'") {
38102
+ const close = html.indexOf(quote, index + 1);
38103
+ if (close === -1) {
38104
+ if (malformed("artifact output has an unterminated attribute")) return;
38105
+ return;
38106
+ }
38107
+ rawValue = html.slice(index + 1, close);
38108
+ index = close + 1;
38109
+ } else {
38110
+ UNQUOTED_VALUE.lastIndex = index;
38111
+ rawValue = UNQUOTED_VALUE.exec(html)[0];
38112
+ index = UNQUOTED_VALUE.lastIndex;
38113
+ }
38114
+ }
38115
+ if (rawName.length > 0) {
38116
+ attributeCount += 1;
38117
+ if (attributeCount > MAX_ARTIFACT_ATTRIBUTES_PER_ELEMENT) fail("limit", "artifact element has too many attributes");
38118
+ if (!checkReferences) continue;
38119
+ const name = rawName.toLowerCase();
38120
+ const decoded = decodeAttributeValue(rawValue);
38121
+ checkAttribute(name, decoded);
38122
+ if (isHrefAnimation(tag, name, decoded)) unsupportedOutput("artifact svg animates a resource reference");
38123
+ if (!attributes.has(name)) attributes.set(name, decoded);
38124
+ } else if (index < length4 && html[index] !== ">" && !SKIPPED.test(html[index])) {
38125
+ index += 1;
38126
+ }
38127
+ }
38128
+ if (checkReferences) {
38129
+ if (tag === "area" && attributes.has("href") || attributes.has("ping") || tag === "link" && !attributes.has("href")) {
38130
+ unsupportedOutput("artifact output contains a blocked html feature");
38131
+ }
38132
+ if (tag === "meta" && attributes.has("http-equiv") && !isOwnPolicyMeta(attributes.get("http-equiv"), attributes.get("content") ?? null)) {
38133
+ unsupportedOutput("artifact output contains a meta http-equiv", attributes.get("http-equiv"));
38134
+ }
38135
+ if (tag === "script" && !isJavaScriptType(attributes.get("type"))) {
38136
+ unsupportedOutput("artifact output holds a script type that is not JavaScript", attributes.get("type"));
38137
+ }
38138
+ }
38139
+ if (tag === "script") {
38140
+ SCRIPT_END.lastIndex = index;
38141
+ const close = SCRIPT_END.exec(html);
38142
+ if (close === null) return;
38143
+ index = close.index;
38144
+ } else if (tag === "style" && checkReferences) {
38145
+ STYLE_END.lastIndex = index;
38146
+ const close = STYLE_END.exec(html);
38147
+ assertOnlyInlineCssReferences(html.slice(index, close === null ? length4 : close.index));
38148
+ }
38149
+ }
38150
+ }
38151
+ function assertSelfContainedPage(output2, options = {}) {
38152
+ const page = withoutDataUris(output2);
38153
+ if (options.only !== "dom") scanMarkup(page, { references: true });
38154
+ if (options.only === "bytes") return;
38155
+ const doc = (options.parseHtml ?? defaultParseHtml)(page);
38156
+ const roots = documentRoots(doc);
38157
+ if (queryAll(roots, BLOCKED_ELEMENTS).length > 0) fail("unsupported", "artifact output contains a blocked html feature");
38158
+ for (const meta of queryAll(roots, "meta[http-equiv]")) {
38159
+ if (!isOwnPolicyMeta(meta.getAttribute("http-equiv") ?? "", meta.getAttribute("content"))) fail("unsupported", "artifact output contains a meta http-equiv");
38160
+ }
38161
+ if (queryAll(roots, "script[src]").length > 0) fail("unsupported", "artifact output still loads a script");
38162
+ for (const script of queryAll(roots, "script")) {
38163
+ if (!isJavaScriptType(script.getAttribute("type"))) fail("unsupported", "artifact output holds a script type that is not JavaScript");
38164
+ }
38165
+ for (const style of queryAll(roots, "style")) assertOnlyInlineCssReferences(style.textContent ?? "");
38166
+ for (const element of queryAll(roots, "*")) {
38167
+ const inSvg = element.namespaceURI === SVG_NAMESPACE;
38168
+ if (inSvg && element.localName === "script") fail("unsupported", "artifact svg contains a script");
38169
+ for (const name of element.getAttributeNames()) {
38170
+ const lower = name.toLowerCase();
38171
+ const local = lower.slice(lower.lastIndexOf(":") + 1);
38172
+ const value = element.getAttribute(name) ?? "";
38173
+ if (/^on/.test(lower)) fail("unsupported", "artifact output contains a blocked html feature");
38174
+ if (URL_ATTRIBUTES.has(local)) {
38175
+ if (local === "srcset" || local === "imagesrcset") assertInlineSrcset(value);
38176
+ else if (!isInlineReference(value)) throw new ArtifactBundleError("unsupported", "artifact page still references a resource", void 0, value);
38177
+ } else if (lower !== "style" && !TEXT_ATTRIBUTES.test(lower) && looksLikeUrl(urlText(value))) {
38178
+ throw new ArtifactBundleError("unsupported", "artifact page still references a resource", void 0, value);
38179
+ }
38180
+ if (lower === "style" || inSvg) assertOnlyInlineCssReferences(value);
38181
+ if (isHrefAnimation(element.localName, lower, value)) fail("unsupported", "artifact svg animates a resource reference");
38182
+ }
38183
+ }
38184
+ }
38185
+ function verifySelfContained(output2, context) {
38186
+ assertSelfContainedPage(output2, { parseHtml: context.parseHtml });
38187
+ }
38188
+ function rejectSvgResourceReferences(roots) {
38189
+ for (const element of queryAll(roots, "*")) {
38190
+ if (element.namespaceURI !== SVG_NAMESPACE) continue;
38191
+ 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) ?? "")));
38192
+ if (animatesHref) fail("unsupported", "artifact svg animates a resource reference");
38193
+ if (element.localName === "script") fail("unsupported", "artifact svg contains a script");
38194
+ for (const attributeName of element.getAttributeNames()) {
38195
+ if (attributeName !== "href" && !attributeName.endsWith(":href")) continue;
38196
+ if (element.localName === "a" && attributeName === "href") continue;
38197
+ if (!isFragmentOnly(urlText(element.getAttribute(attributeName) ?? ""))) fail("unsupported", "artifact svg references a resource");
38198
+ }
38199
+ }
38200
+ }
38201
+ function rejectEventHandlerAttributes(roots) {
38202
+ for (const element of queryAll(roots, "*")) {
38203
+ for (const attributeName of element.getAttributeNames()) {
38204
+ if (/^on/i.test(attributeName)) fail("unsupported", "artifact contains a blocked html feature");
38205
+ }
38206
+ }
38207
+ }
38208
+ function replaceAsync(input, expression, replacer) {
38209
+ const matches = [...input.matchAll(expression)];
38210
+ return matches.reduceRight(
38211
+ async (next, match) => {
38212
+ const value = await next;
38213
+ const replacement = await replacer(...match.map((entry) => entry ?? ""));
38214
+ return `${value.slice(0, match.index)}${replacement}${value.slice((match.index ?? 0) + match[0].length)}`;
38215
+ },
38216
+ Promise.resolve(input)
38217
+ );
38218
+ }
38219
+ function newDeferred() {
38220
+ const bytes = new Uint8Array(8);
38221
+ if (globalThis.crypto?.getRandomValues !== void 0) globalThis.crypto.getRandomValues(bytes);
38222
+ else for (let index = 0; index < bytes.length; index += 1) bytes[index] = Math.floor(Math.random() * 256);
38223
+ return { token: Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(""), urls: [] };
38224
+ }
38225
+ function parkUrl(deferred, url) {
38226
+ deferred.urls.push(url);
38227
+ return `data:;base64,TCPH${deferred.token}x${deferred.urls.length - 1}x`;
38228
+ }
38229
+ function restoreParkedUrls(page, deferred) {
38230
+ if (deferred.urls.length === 0) return page;
38231
+ const pattern = new RegExp(`data:;base64,TCPH${deferred.token}x(\\d+)x`, "g");
38232
+ return page.replace(pattern, (_whole, index) => deferred.urls[Number(index)]);
38233
+ }
38234
+ function resolveReferenceFrom(basePath, reference) {
38235
+ try {
38236
+ return resolveArtifactReference(basePath, reference);
38237
+ } catch (error) {
38238
+ if (error instanceof ArtifactBundleError) throw new ArtifactBundleError(error.kind, error.message, basePath, reference);
38239
+ throw error;
38240
+ }
38241
+ }
38242
+ function requiredFile(context, basePath, reference) {
38243
+ context.counter.references += 1;
38244
+ if (context.counter.references > MAX_ARTIFACT_REFERENCES) fail("limit", "artifact has too many resource references");
38245
+ const resolved = resolveReferenceFrom(basePath, reference);
38246
+ const file = context.files.get(resolved.path);
38247
+ if (file === void 0) throw new ArtifactBundleError("missing", "artifact resource is missing", basePath, reference);
38248
+ return { file, resolved };
38249
+ }
38250
+ var CSS_REFERENCE_NAMES = /* @__PURE__ */ new Set(["url", "image-set", "-webkit-image-set", "src", "import"]);
38251
+ function isCssIdentifierCode(code4) {
38252
+ return code4 >= 128 || code4 >= 48 && code4 <= 57 || code4 >= 65 && code4 <= 90 || code4 >= 97 && code4 <= 122 || code4 === 95 || code4 === 45;
38253
+ }
38254
+ function normalizeCssEscapes(source) {
38255
+ let output2 = "";
38256
+ let index = 0;
38257
+ const length4 = source.length;
38258
+ while (index < length4) {
38259
+ const char = source[index];
38260
+ if (char === "/" && source[index + 1] === "*") {
38261
+ const end = source.indexOf("*/", index + 2);
38262
+ const stop = end === -1 ? length4 : end + 2;
38263
+ output2 += source.slice(index, stop);
38264
+ index = stop;
38265
+ continue;
38266
+ }
38267
+ if (char === '"' || char === "'") {
38268
+ let cursor = index + 1;
38269
+ while (cursor < length4 && source[cursor] !== char && source[cursor] !== "\n") cursor += source[cursor] === "\\" ? 2 : 1;
38270
+ const stop = Math.min(source[cursor] === char ? cursor + 1 : cursor, length4);
38271
+ output2 += source.slice(index, stop);
38272
+ index = stop;
38273
+ continue;
38274
+ }
38275
+ if (char === "\\" || isCssIdentifierCode(source.charCodeAt(index))) {
38276
+ let cursor = index;
38277
+ let decoded = "";
38278
+ let escaped = false;
38279
+ while (cursor < length4) {
38280
+ if (source[cursor] === "\\") {
38281
+ const next = source[cursor + 1];
38282
+ if (next === void 0) {
38283
+ decoded += "\uFFFD";
38284
+ escaped = true;
38285
+ cursor += 1;
38286
+ break;
38287
+ }
38288
+ if (next === "\n" || next === "\r" || next === "\f") break;
38289
+ escaped = true;
38290
+ const hex2 = /^[0-9a-fA-F]{1,6}/.exec(source.slice(cursor + 1, cursor + 7));
38291
+ if (hex2 !== null) {
38292
+ const point = Number.parseInt(hex2[0], 16);
38293
+ cursor += 1 + hex2[0].length;
38294
+ if (source[cursor] === "\r" && source[cursor + 1] === "\n") cursor += 2;
38295
+ else if (/[ \t\n\r\f]/.test(source[cursor] ?? "")) cursor += 1;
38296
+ decoded += point === 0 || point > 1114111 || point >= 55296 && point <= 57343 ? "\uFFFD" : String.fromCodePoint(point);
38297
+ } else {
38298
+ const point = source.codePointAt(cursor + 1);
38299
+ decoded += String.fromCodePoint(point);
38300
+ cursor += 1 + (point > 65535 ? 2 : 1);
38301
+ }
38302
+ } else if (isCssIdentifierCode(source.charCodeAt(cursor))) {
38303
+ decoded += source[cursor];
38304
+ cursor += 1;
38305
+ } else break;
38306
+ }
38307
+ const raw = source.slice(index, cursor);
38308
+ const startsReference = escaped && (source[cursor] === "(" || output2.endsWith("@")) && CSS_REFERENCE_NAMES.has(decoded.toLowerCase());
38309
+ output2 += startsReference ? decoded : raw;
38310
+ index = cursor;
38311
+ continue;
38312
+ }
38313
+ output2 += char;
38314
+ index += 1;
38315
+ }
38316
+ return output2;
38317
+ }
38318
+ function assertOnlyInlineCssReferences(css) {
38319
+ const text = normalizeCssEscapes(css);
38320
+ if (/@import/i.test(text)) fail("unsupported", "artifact css still imports a stylesheet");
38321
+ if (/(?:^|[^\w-])(?:-webkit-)?image-set\s*\(/i.test(text) || /(?:^|[^\w-])src\s*\(/i.test(text)) fail("unsupported", "artifact css uses an unsupported resource function");
38322
+ const opened = text.match(/url\(/gi)?.length ?? 0;
38323
+ let read4 = 0;
38324
+ for (const match of text.matchAll(/url\([ \t\n\r\f]*(?:"([^"]*)"|'([^']*)'|([^)"' \t\n\r\f]*))[ \t\n\r\f]*\)/gi)) {
38325
+ read4 += 1;
38326
+ const value = match[1] ?? match[2] ?? match[3] ?? "";
38327
+ if (!isInlineReference(value) || value.includes("\\")) {
38328
+ throw new ArtifactBundleError("unsupported", "artifact css references a resource", void 0, value);
38329
+ }
38330
+ }
38331
+ if (read4 !== opened) fail("unsupported", "artifact css has a url() that cannot be read");
38332
+ }
38333
+ async function transformCss(sourceText, basePath, context) {
38334
+ if (context.cssStack.length > MAX_ARTIFACT_CSS_IMPORT_DEPTH) fail("limit", "artifact stylesheet import depth exceeded");
38335
+ const source = normalizeCssEscapes(sourceText);
38336
+ const imported = await replaceAsync(
38337
+ source,
38338
+ /@import\s+(?:url\(\s*(?:(["'])([^"']+)\1|([^"')\s]+))\s*\)|(["'])([^"']+)\4)\s*([^;]*);/gi,
38339
+ async (_whole, _urlQuote, quotedUrl, unquotedUrl, _quote, quotedReference, media) => {
38340
+ const reference = quotedUrl || unquotedUrl || quotedReference;
38341
+ if (/^(?:layer|supports)\b/i.test(media.trim()) || /\b(?:layer|supports)\b/i.test(media)) fail("unsupported", "artifact uses an unsupported stylesheet import condition");
38342
+ const { file, resolved } = requiredFile(context, basePath, reference);
38343
+ if (context.cssStack.includes(resolved.path)) fail("unsupported", "artifact stylesheet import cycle");
38344
+ const css = await transformCss(decodeText(file, context), resolved.path, { ...context, cssStack: [...context.cssStack, resolved.path] });
38345
+ return wrapMediaCss(css, media, "artifact uses an unsupported stylesheet import condition");
38346
+ }
38347
+ );
38348
+ return replaceAsync(
38349
+ imported,
38350
+ /url\(\s*(["']?)([^"')]+)\1\s*\)/gi,
38351
+ async (_whole, _quote, reference) => {
38352
+ if (trimUrl(reference).startsWith("#")) return `url("${trimUrl(reference)}")`;
38353
+ const { file, resolved } = requiredFile(context, basePath, reference);
38354
+ const dataUrl = asDataUrl(file, context);
38355
+ return `url("${context.deferred === void 0 ? dataUrl : parkUrl(context.deferred, dataUrl)}${resolved.fragment}")`;
38356
+ }
38357
+ );
38358
+ }
38359
+ function serializeSrcset(value, basePath, context) {
38360
+ return value.split(",").map((candidate) => {
38361
+ const trimmed = candidate.trim();
38362
+ const separator = trimmed.search(/\s/);
38363
+ const reference = separator === -1 ? trimmed : trimmed.slice(0, separator);
38364
+ const descriptor = separator === -1 ? "" : trimmed.slice(separator).trim();
38365
+ const { file, resolved } = requiredFile(context, basePath, reference);
38366
+ return `${asDataUrl(file, context)}${resolved.fragment}${descriptor.length === 0 ? "" : ` ${descriptor}`}`;
38367
+ }).join(", ");
38368
+ }
38369
+ var CHILD_BOOTSTRAP = `(function(){
38370
+ "use strict";
38371
+ document.addEventListener("click",function(event){
38372
+ var target=event.target instanceof Element?event.target.closest("a[data-tc-artifact-path]"):null;
38373
+ if(!target)return;
38374
+ event.preventDefault();
38375
+ window.parent.postMessage({type:"navigate",path:target.getAttribute("data-tc-artifact-path"),fragment:target.getAttribute("data-tc-artifact-fragment")||""},"*");
38376
+ },true);
38377
+ window.addEventListener("message",function(event){
38378
+ if(event.source!==window.parent||event.origin!=="null"||!event.data||event.data.type!=="artifact-fragment"||typeof event.data.fragment!=="string")return;
38379
+ var fragment=event.data.fragment;
38380
+ if(!/^#[^\\u0000-\\u001f\\u007f]{1,1024}$/.test(fragment))return;
38381
+ try{var target=document.getElementById(decodeURIComponent(fragment.slice(1)));if(target)target.scrollIntoView();}catch(_){}
38382
+ });
38383
+ window.parent.postMessage({type:"artifact-ready"},"*");
38384
+ })();`;
38385
+ var INNER_CSP = [
38386
+ "default-src 'none'",
38387
+ "script-src 'unsafe-inline'",
38388
+ "style-src 'unsafe-inline'",
38389
+ "img-src data: blob:",
38390
+ "font-src data: blob:",
38391
+ "media-src data: blob:",
38392
+ "connect-src 'none'",
38393
+ "frame-src 'none'",
38394
+ "child-src 'none'",
38395
+ "object-src 'none'",
38396
+ "base-uri 'none'",
38397
+ "form-action 'none'"
38398
+ ].join("; ");
38399
+ async function transformHtml(path, file, context) {
38400
+ const text = decodeText(file);
38401
+ scanMarkup(text, { references: false });
38402
+ const doc = context.parseHtml(text);
38403
+ const roots = documentRoots(doc);
38404
+ const deferred = newDeferred();
38405
+ if (doc.querySelector("parsererror") !== null) fail("malformed", "artifact html is malformed");
38406
+ if (queryAll(roots, BLOCKED_ELEMENTS).length > 0) {
38407
+ fail("unsupported", "artifact contains a blocked html feature");
38408
+ }
38409
+ rejectEventHandlerAttributes(roots);
38410
+ rejectSvgResourceReferences(roots);
38411
+ const inlineStyles = queryAll(roots, "style");
38412
+ for (const meta of queryAll(roots, "meta[http-equiv]")) meta.remove();
38413
+ for (const link3 of queryAll(roots, "link[href]")) {
38414
+ const relation = classifyLinkRelation(link3.getAttribute("rel"));
38415
+ if (relation === "stylesheet") {
38416
+ const { file: stylesheet, resolved } = requiredFile(context, path, link3.getAttribute("href") ?? "");
38417
+ const style = doc.createElement("style");
38418
+ style.textContent = wrapMediaCss(await transformCss(decodeText(stylesheet, context), resolved.path, { ...context, cssStack: [resolved.path] }), link3.getAttribute("media"));
38419
+ link3.replaceWith(style);
38420
+ continue;
38421
+ }
38422
+ if (relation === "icon") {
38423
+ const { file: icon, resolved } = requiredFile(context, path, link3.getAttribute("href") ?? "");
38424
+ link3.href = `${asDataUrl(icon, context)}${resolved.fragment}`;
38425
+ continue;
38426
+ }
38427
+ fail("unsupported", "artifact contains an unsupported link relation");
38428
+ }
38429
+ for (const style of inlineStyles) {
38430
+ style.textContent = wrapMediaCss(await transformCss(style.textContent ?? "", path, { ...context, cssStack: [] }), style.getAttribute("media"));
38431
+ }
38432
+ for (const element of queryAll(roots, "[style]")) {
38433
+ const value = element.getAttribute("style");
38434
+ if (value !== null) element.setAttribute("style", await transformCss(value, path, { ...context, cssStack: [], deferred }));
38435
+ }
38436
+ for (const script of queryAll(roots, "script")) {
38437
+ if ((script.getAttribute("type") ?? "").trim().toLowerCase() === "module" || script.hasAttribute("async") || script.hasAttribute("defer")) {
38438
+ fail("unsupported", "artifact uses an unsupported script mode");
38439
+ }
38440
+ if (!isJavaScriptType(script.getAttribute("type"))) fail("unsupported", "artifact uses a script type that is not JavaScript");
38441
+ let source = script.textContent ?? "";
38442
+ const reference = script.getAttribute("src");
38443
+ if (reference !== null) {
38444
+ const { file: scriptFile } = requiredFile(context, path, reference);
38445
+ source = decodeText(scriptFile, context);
38446
+ script.removeAttribute("src");
38447
+ }
38448
+ assertClassicScript(source);
38449
+ script.textContent = source.replace(/<\/script/gi, "<\\/script");
38450
+ }
38451
+ const resourceAttributes = [
38452
+ ["img[src]", "src"],
38453
+ ["source[src]", "src"],
38454
+ ["video[src]", "src"],
38455
+ ["video[poster]", "poster"],
38456
+ ["audio[src]", "src"],
38457
+ ["track[src]", "src"],
38458
+ ["input[type=image][src]", "src"]
38459
+ ];
38460
+ for (const [selector, attribute] of resourceAttributes) {
38461
+ for (const element of queryAll(roots, selector)) {
38462
+ const reference = element.getAttribute(attribute);
38463
+ if (reference === null) continue;
38464
+ const { file: resource, resolved } = requiredFile(context, path, reference);
38465
+ element.setAttribute(attribute, `${asDataUrl(resource, context)}${resolved.fragment}`);
38466
+ }
38467
+ }
38468
+ for (const element of queryAll(roots, "[srcset]")) {
38469
+ const value = element.getAttribute("srcset");
38470
+ if (value !== null) element.setAttribute("srcset", serializeSrcset(value, path, context));
38471
+ }
38472
+ for (const anchor of queryAll(roots, "a[href]")) {
38473
+ anchor.removeAttribute("target");
38474
+ anchor.removeAttribute("download");
38475
+ const reference = anchor.getAttribute("href") ?? "";
38476
+ if (reference.startsWith("#")) continue;
38477
+ const resolved = resolveReferenceFrom(path, reference);
38478
+ if (context.singlePage) throw new ArtifactBundleError("unsupported", "artifact links to another page", path, reference);
38479
+ if (!context.files.has(resolved.path) || !/\.html?$/i.test(resolved.path)) fail("unsupported", "artifact link leaves its html bundle");
38480
+ anchor.href = "#";
38481
+ anchor.dataset.tcArtifactPath = resolved.path;
38482
+ if (resolved.fragment.length > 0) anchor.dataset.tcArtifactFragment = resolved.fragment;
38483
+ }
38484
+ const head = doc.head ?? doc.documentElement.insertBefore(doc.createElement("head"), doc.body);
38485
+ const csp = doc.createElement("meta");
38486
+ csp.httpEquiv = "Content-Security-Policy";
38487
+ csp.content = INNER_CSP;
38488
+ const bootstrap = doc.createElement("script");
38489
+ bootstrap.textContent = CHILD_BOOTSTRAP;
38490
+ head.prepend(bootstrap);
38491
+ head.prepend(csp);
38492
+ const output2 = restoreParkedUrls(`<!DOCTYPE html>
38493
+ ${doc.documentElement.outerHTML}`, deferred);
38494
+ verifySelfContained(output2, context);
38495
+ return output2;
38496
+ }
38497
+ async function prepareHtmlArtifact(input, options = {}) {
38498
+ if (input.length === 0) fail("malformed", "artifact bundle is empty");
38499
+ if (input.length > MAX_ARTIFACT_FILES) fail("limit", "artifact has too many files");
38500
+ const files = /* @__PURE__ */ new Map();
38501
+ const aliases = /* @__PURE__ */ new Set();
38502
+ let sourceBytes = 0;
38503
+ for (const candidate of input) {
38504
+ let path;
38505
+ try {
38506
+ path = canonicalArtifactPath(candidate.path);
38507
+ } catch (error) {
38508
+ if (error instanceof ArtifactBundleError) throw new ArtifactBundleError(error.kind, error.message, candidate.path);
38509
+ throw error;
38510
+ }
38511
+ const alias = path.toLowerCase();
38512
+ if (aliases.has(alias)) throw new ArtifactBundleError("malformed", "artifact paths collide after canonicalization", path);
38513
+ aliases.add(alias);
38514
+ sourceBytes += candidate.bytes.byteLength;
38515
+ if (!Number.isSafeInteger(sourceBytes) || sourceBytes > MAX_ARTIFACT_SOURCE_BYTES) throw new ArtifactBundleError("limit", "artifact source exceeds limit", path);
38516
+ files.set(path, { ...candidate, path });
38517
+ }
38518
+ const detection = detectHtmlArtifact([...files.keys()]);
38519
+ if (detection.kind !== "html") fail("malformed", "artifact root index is missing");
38520
+ 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);
38521
+ if (renderBytes > MAX_ARTIFACT_RENDER_BYTES) fail("limit", "artifact render aggregate exceeds limit");
38522
+ const context = {
38523
+ files,
38524
+ cssStack: [],
38525
+ counter: { references: 0, inlinedBytes: 0 },
38526
+ parseHtml: options.parseHtml ?? defaultParseHtml,
38527
+ singlePage: options.singlePage === true
38528
+ };
38529
+ const pages = {};
38530
+ let renderedBytes = 0;
38531
+ for (const [path, file] of files) {
38532
+ if (!/\.html?$/i.test(path)) continue;
38533
+ if (context.singlePage && path !== ARTIFACT_ENTRY) continue;
38534
+ let page;
38535
+ try {
38536
+ page = await transformHtml(path, file, context);
38537
+ } catch (error) {
38538
+ if (error instanceof ArtifactBundleError && error.path === void 0) throw new ArtifactBundleError(error.kind, error.message, path, error.reference);
38539
+ throw error;
38540
+ }
38541
+ renderedBytes += new TextEncoder().encode(page).byteLength;
38542
+ if (!Number.isSafeInteger(renderedBytes) || renderedBytes > MAX_ARTIFACT_RENDER_BYTES) {
38543
+ fail("limit", "artifact rendered output exceeds limit");
38544
+ }
38545
+ pages[path] = page;
38546
+ }
38547
+ return { entry: ARTIFACT_ENTRY, pages: Object.freeze(pages), fileCount: files.size, sourceBytes };
38548
+ }
38549
+ function isPassiveArtifactPath(canonicalPath) {
38550
+ const lower = canonicalPath.toLowerCase();
38551
+ return lower === "manifest.json" || lower.startsWith("knowledge/");
38552
+ }
38553
+ function canonicalWithPath(path) {
38554
+ try {
38555
+ return canonicalArtifactPath(path);
38556
+ } catch (error) {
38557
+ if (error instanceof ArtifactBundleError) throw new ArtifactBundleError(error.kind, error.message, path);
38558
+ throw error;
38559
+ }
38560
+ }
38561
+ async function inlineHtmlFolder(files, options = {}) {
38562
+ if (files.length === 0) throw new ArtifactBundleError("malformed", "artifact bundle is empty");
38563
+ if (files.length > MAX_ARTIFACT_FILES) throw new ArtifactBundleError("limit", "artifact has too many files");
38564
+ const canonical = /* @__PURE__ */ new Map();
38565
+ const aliases = /* @__PURE__ */ new Map();
38566
+ let sourceBytes = 0;
38567
+ for (const candidate of files) {
38568
+ const path = canonicalWithPath(candidate.path);
38569
+ const alias = path.toLowerCase();
38570
+ if (aliases.has(alias)) throw new ArtifactBundleError("malformed", "artifact paths collide after canonicalization", path);
38571
+ aliases.set(alias, path);
38572
+ sourceBytes += candidate.bytes.byteLength;
38573
+ if (!Number.isSafeInteger(sourceBytes) || sourceBytes > MAX_ARTIFACT_SOURCE_BYTES) {
38574
+ throw new ArtifactBundleError("limit", "artifact source exceeds limit", path);
38575
+ }
38576
+ canonical.set(path, { ...candidate, path });
38577
+ }
38578
+ if (!canonical.has(ARTIFACT_ENTRY)) throw new ArtifactBundleError("missing", "artifact root index is missing", ARTIFACT_ENTRY);
38579
+ const active = [];
38580
+ const passive = [];
38581
+ for (const file of canonical.values()) (isPassiveArtifactPath(file.path) ? passive : active).push(file);
38582
+ const prepared = await prepareHtmlArtifact(active, { ...options, singlePage: true });
38583
+ passive.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
38584
+ return {
38585
+ entry: ARTIFACT_ENTRY,
38586
+ html: new TextEncoder().encode(prepared.pages[ARTIFACT_ENTRY]),
38587
+ passive,
38588
+ fileCount: canonical.size,
38589
+ sourceBytes
38590
+ };
38591
+ }
38592
+
38593
+ // src/share/html-folder.ts
38594
+ init_errors();
38595
+ init_safe_text();
38596
+ var MAX_HTML_FOLDER_DEPTH = 16;
38597
+ var VIEWER_PREVIEW_BYTES = 1024 * 1024;
38598
+ 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.";
38599
+ 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";
38600
+ async function isDirectoryInput(input) {
38601
+ if (input === "-") return false;
38602
+ try {
38603
+ return (await stat6(resolve7(input))).isDirectory();
38604
+ } catch {
38605
+ return false;
38606
+ }
38607
+ }
38608
+ function limitError(message) {
38609
+ return new CLIError("MAX_BYTES_EXCEEDED", message, 7);
38610
+ }
38611
+ function isWithinFolder(root, candidate) {
38612
+ const path = relativePath(root, candidate);
38613
+ return path !== "" && path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute(path);
38614
+ }
38615
+ var CHANGED = "changed while it was read";
38616
+ function isRaceError(error) {
38617
+ const code4 = error?.code;
38618
+ return code4 === "ENOENT" || code4 === "ENOTDIR" || code4 === "ELOOP";
38619
+ }
38620
+ var PROC_FD = "/proc/self/fd";
38621
+ async function linuxDescriptorPath(fd) {
38622
+ if (process.platform !== "linux" || !existsSync(PROC_FD)) return void 0;
38623
+ return realpath3(`${PROC_FD}/${fd}`);
38624
+ }
38625
+ async function parentChainIsRealDirectories(root, path) {
38626
+ let current = dirname8(path);
38627
+ while (current !== root && isWithinFolder(root, current)) {
38628
+ const info = await lstat11(current);
38629
+ if (info.isSymbolicLink() || !info.isDirectory()) return false;
38630
+ current = dirname8(current);
38631
+ }
38632
+ return current === root;
38633
+ }
38634
+ async function readRegularFile(root, path, relative, remaining, limit, hooks = {}) {
38635
+ const problem = (reason) => ({ code: "UNSUPPORTED_FOLDER_ENTRY", path: relative, reason });
38636
+ const descriptorPath = hooks.descriptorPath ?? linuxDescriptorPath;
38637
+ let handle;
38638
+ let before;
38639
+ try {
38640
+ await hooks.beforeOpen?.();
38641
+ before = await lstat11(path);
38642
+ await hooks.afterLstat?.();
38643
+ handle = await open13(path, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0) | (constants2.O_NONBLOCK ?? 0));
38644
+ } catch (error) {
38645
+ if (error.code === "ELOOP") return problem("symbolic link");
38646
+ if (isRaceError(error)) return problem(CHANGED);
38647
+ throw error;
38648
+ }
38649
+ try {
38650
+ await hooks.afterOpen?.();
38651
+ const info = await handle.stat();
38652
+ if (!info.isFile()) return problem("not a regular file");
38653
+ let byDescriptor;
38654
+ try {
38655
+ if (before.dev !== info.dev || before.ino !== info.ino) return problem(CHANGED);
38656
+ byDescriptor = await descriptorPath(handle.fd);
38657
+ if (byDescriptor !== void 0) {
38658
+ if (byDescriptor !== path || !isWithinFolder(root, byDescriptor)) return problem("symbolic link");
38659
+ } else {
38660
+ const real = await realpath3(path);
38661
+ if (real !== path || !isWithinFolder(root, real)) return problem("symbolic link");
38662
+ }
38663
+ } catch (error) {
38664
+ if (isRaceError(error)) return problem(CHANGED);
38665
+ throw error;
38666
+ }
38667
+ if (info.size > remaining) throw limitError(`folder is larger than the ${limit} byte limit (at "${safeClipped(relative)}")`);
38668
+ const chunks = [];
38669
+ let total = 0;
38670
+ for (; ; ) {
38671
+ const chunk = Buffer.allocUnsafe(Math.min(1 << 20, remaining - total + 1));
38672
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);
38673
+ if (bytesRead === 0) break;
38674
+ total += bytesRead;
38675
+ if (total > remaining) throw limitError(`folder is larger than the ${limit} byte limit (at "${safeClipped(relative)}")`);
38676
+ chunks.push(chunk.subarray(0, bytesRead));
38677
+ }
38678
+ await hooks.afterRead?.();
38679
+ if (byDescriptor === void 0) {
38680
+ try {
38681
+ const again = await lstat11(path);
38682
+ if (again.isSymbolicLink() || again.dev !== info.dev || again.ino !== info.ino || !await parentChainIsRealDirectories(root, path)) return problem(CHANGED);
38683
+ } catch (error) {
38684
+ if (isRaceError(error)) return problem(CHANGED);
38685
+ throw error;
38686
+ }
38687
+ }
38688
+ return new Uint8Array(Buffer.concat(chunks, total));
38689
+ } finally {
38690
+ await handle.close();
38691
+ }
38692
+ }
38693
+ async function listFolderDirectory(root, absolute, relative, hooks = {}) {
38694
+ const problem = (reason) => ({ code: "UNSUPPORTED_FOLDER_ENTRY", path: relative === "" ? "." : relative, reason });
38695
+ const descriptorPath = hooks.descriptorPath ?? linuxDescriptorPath;
38696
+ try {
38697
+ await hooks.beforeOpen?.();
38698
+ const before = await lstat11(absolute);
38699
+ if (before.isSymbolicLink()) return problem("symbolic link");
38700
+ if (!before.isDirectory()) return problem(CHANGED);
38701
+ await hooks.afterLstat?.();
38702
+ const handle = await open13(absolute, constants2.O_RDONLY | (constants2.O_DIRECTORY ?? 0) | (constants2.O_NOFOLLOW ?? 0));
38703
+ try {
38704
+ await hooks.afterOpen?.();
38705
+ const info = await handle.stat();
38706
+ if (!info.isDirectory() || info.dev !== before.dev || info.ino !== before.ino) return problem(CHANGED);
38707
+ const byDescriptor = await descriptorPath(handle.fd);
38708
+ if (byDescriptor !== void 0) {
38709
+ if (byDescriptor !== absolute || absolute !== root && !isWithinFolder(root, byDescriptor)) return problem("symbolic link");
38710
+ await hooks.beforeList?.();
38711
+ return { entries: await readdir7(`${PROC_FD}/${handle.fd}`, { withFileTypes: true }) };
38712
+ }
38713
+ const real = await realpath3(absolute);
38714
+ if (real !== absolute || absolute !== root && !isWithinFolder(root, real)) return problem("symbolic link");
38715
+ const entries = await readdir7(absolute, { withFileTypes: true });
38716
+ const after = await lstat11(absolute);
38717
+ if (after.isSymbolicLink() || after.dev !== info.dev || after.ino !== info.ino) return problem(CHANGED);
38718
+ if (absolute !== root && !await parentChainIsRealDirectories(root, absolute)) return problem(CHANGED);
38719
+ return { entries };
38720
+ } finally {
38721
+ await handle.close();
38722
+ }
38723
+ } catch (error) {
38724
+ if (isRaceError(error)) return problem(CHANGED);
38725
+ throw error;
38726
+ }
38727
+ }
38728
+ function problemError(problems) {
38729
+ const first = problems[0];
38730
+ const listed = problems.slice(0, ECHO_LIST_MAX).map((problem) => `"${safeClipped(problem.path)}" (${problem.reason})`).join(", ");
38731
+ const more = problems.length > ECHO_LIST_MAX ? ` \u2026 and ${problems.length - ECHO_LIST_MAX} more` : "";
38732
+ const noun = first.code === "UNSAFE_PATH" ? "paths that cannot be published safely" : "entries that cannot be published";
38733
+ return new CLIError(
38734
+ first.code,
38735
+ `folder has ${problems.length} ${noun}: ${listed}${more}`,
38736
+ first.code === "UNSAFE_PATH" ? 8 : 2,
38737
+ { 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." }
38738
+ );
38739
+ }
38740
+ async function readHtmlFolder(directory, limit) {
38741
+ const root = await realpath3(resolve7(directory));
38742
+ const files = [];
38743
+ const problems = [];
38744
+ let skippedHidden = 0;
38745
+ let total = 0;
38746
+ const walk = async (absolute, relative, depth) => {
38747
+ const listed = await listFolderDirectory(root, absolute, relative);
38748
+ if ("reason" in listed) {
38749
+ problems.push(listed);
38750
+ return;
38751
+ }
38752
+ const entries = listed.entries.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
38753
+ for (const entry of entries) {
38754
+ if (entry.name.startsWith(".")) {
38755
+ skippedHidden += 1;
38756
+ continue;
38757
+ }
38758
+ const path = relative === "" ? entry.name : `${relative}/${entry.name}`;
38759
+ const child = join18(absolute, entry.name);
38760
+ if (entry.isSymbolicLink()) {
38761
+ problems.push({ code: "UNSUPPORTED_FOLDER_ENTRY", path, reason: "symbolic link" });
38762
+ } else if (entry.isDirectory()) {
38763
+ if (depth >= MAX_HTML_FOLDER_DEPTH) problems.push({ code: "UNSUPPORTED_FOLDER_ENTRY", path, reason: `nested deeper than ${MAX_HTML_FOLDER_DEPTH} folders` });
38764
+ else await walk(child, path, depth + 1);
38765
+ } else if (!entry.isFile()) {
38766
+ problems.push({ code: "UNSUPPORTED_FOLDER_ENTRY", path, reason: "not a regular file" });
38767
+ } else {
38768
+ try {
38769
+ canonicalArtifactPath(path);
38770
+ } catch {
38771
+ problems.push({ code: "UNSAFE_PATH", path, reason: "unsafe name" });
38772
+ continue;
38773
+ }
38774
+ if (files.length >= MAX_ARTIFACT_FILES) throw limitError(`folder has more than ${MAX_ARTIFACT_FILES} files`);
38775
+ const read4 = await readRegularFile(root, child, path, limit - total, limit);
38776
+ if (read4 instanceof Uint8Array) {
38777
+ total += read4.byteLength;
38778
+ files.push({ path: path.normalize("NFC"), bytes: read4 });
38779
+ } else problems.push(read4);
38780
+ }
38781
+ }
38782
+ };
38783
+ await walk(root, "", 0);
38784
+ if (problems.length > 0) throw problemError(problems);
38785
+ return { files, skippedHidden };
38786
+ }
38787
+ function htmlFolderError(error) {
38788
+ const file = error.path === void 0 ? "" : `"${safeClipped(error.path)}"`;
38789
+ const reference = error.reference === void 0 ? "" : ` "${safeClipped(error.reference)}"`;
38790
+ if (error.kind === "limit") return limitError(`folder cannot be published as one page: ${error.message.replace(/^artifact /, "")}${file === "" ? "" : ` (${file})`}`);
38791
+ if (error.kind === "missing") {
38792
+ if (error.reference === void 0) {
38793
+ return new CLIError("INVALID_ARGUMENT", "folder needs an index.html at its top level", 2, { hint: "Publish the folder that directly contains index.html." });
38794
+ }
38795
+ return new CLIError(
38796
+ "HTML_RESOURCE_MISSING",
38797
+ `${file} refers to${reference}, which is not in the folder`,
38798
+ 2,
38799
+ { hint: "manifest.json and knowledge/ are never inlined, so a page cannot refer to them. Names are case-sensitive." }
38800
+ );
38801
+ }
38802
+ if (error.kind === "unsupported") {
38803
+ return new CLIError("UNSUPPORTED_HTML", `${file === "" ? "folder" : file} cannot be published as one page: ${error.message.replace(/^artifact /, "")}${reference}`, 2, { hint: FOLDER_HINT });
38804
+ }
38805
+ const pathProblem = /\b(?:path|paths|reference)\b/.test(error.message);
38806
+ return new CLIError(
38807
+ pathProblem ? "UNSAFE_PATH" : "INVALID_ARGUMENT",
38808
+ `folder cannot be published as one page: ${error.message.replace(/^artifact /, "")}${file === "" ? "" : ` (${file}${reference})`}`,
38809
+ pathProblem ? 8 : 2
38810
+ );
38811
+ }
38812
+ async function jsdomParser() {
38813
+ const { JSDOM } = await import("jsdom");
38814
+ const window = new JSDOM("").window;
38815
+ const parser = new window.DOMParser();
38816
+ return { parseHtml: (html) => parser.parseFromString(html, "text/html"), close: () => window.close() };
38817
+ }
38818
+ function htmlFolderFilename(directory, name) {
38819
+ const filename = shareFilename(name ?? `${basename5(resolve7(directory))}.html`);
38820
+ if (!/\.html?$/i.test(filename)) throw new CLIError("INVALID_ARGUMENT", "a folder is published as an .html file; --name must end in .html", 2);
38821
+ return filename;
38822
+ }
38823
+ async function bundleHtmlFolder(directory, options = {}) {
38824
+ const filename = htmlFolderFilename(directory, options.name);
38825
+ const { files, skippedHidden } = await readHtmlFolder(directory, Math.min(options.maxBytes ?? MAX_ARTIFACT_SOURCE_BYTES, MAX_ARTIFACT_SOURCE_BYTES));
38826
+ const parser = await jsdomParser();
38827
+ try {
38828
+ const inlined = await inlineHtmlFolder(files, { parseHtml: parser.parseHtml });
38829
+ return { filename, bytes: inlined.html, passive: inlined.passive, skippedHidden, fileCount: inlined.fileCount };
38830
+ } catch (error) {
38831
+ if (error instanceof ArtifactBundleError) throw htmlFolderError(error);
38832
+ throw error;
38833
+ } finally {
38834
+ parser.close();
38835
+ }
38836
+ }
38837
+ function htmlFolderNotice(bundle) {
38838
+ const parts = [];
38839
+ if (bundle.passive.length > 0) {
38840
+ parts.push(`Left out of the page and not published (never inlined): ${safeClippedList(bundle.passive.map((file) => file.path))}.`);
38841
+ }
38842
+ if (bundle.bytes.byteLength > VIEWER_PREVIEW_BYTES) {
38843
+ 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.`);
38844
+ }
38845
+ if (bundle.skippedHidden > 0) parts.push(`Skipped ${bundle.skippedHidden} hidden ${bundle.skippedHidden === 1 ? "entry" : "entries"} (names starting with a dot).`);
38846
+ return parts.length === 0 ? void 0 : `${parts.join(" ")}
38847
+ `;
38848
+ }
38849
+
37683
38850
  // src/share/errors.ts
37684
38851
  var SharePublishAuthorityError = class extends Error {
37685
38852
  constructor(failure, options) {
@@ -37951,17 +39118,30 @@ function assertAggregateInputLimit(inputs, maxBytes) {
37951
39118
  }
37952
39119
  function registerShareCommand(program) {
37953
39120
  const share = program.command("share").description("Publish and consume TinyCloud Share links");
37954
- 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) => {
39121
+ 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) => {
37955
39122
  try {
37956
39123
  const json = jsonOutput(options, command);
37957
39124
  if (options.ongoing === true) throw new CLIError("UNSUPPORTED_TARGET", ONGOING_UNAVAILABLE, 2);
37958
39125
  const maxBytes = byteLimit(options.maxBytes);
37959
39126
  if (files.length === 0 || files.includes("-") && files.length > 1) throw new CLIError("INVALID_ARGUMENT", "stdin must be the only publish input", 2);
37960
39127
  const name = files.length === 1 ? options.name : void 0;
37961
- for (const file of files) shareInputFilename(file, name);
37962
- const inputs = await Promise.all(files.map((file) => readShareInput(file, name, maxBytes)));
37963
- assertAggregateInputLimit(inputs, maxBytes);
37964
39128
  const target = parseShareTarget(options.to);
39129
+ const folder = files.length === 1 && target.kind === "bearer" && options.prefix !== true && await isDirectoryInput(files[0]);
39130
+ let inputs;
39131
+ if (folder) {
39132
+ if (options.mediaType !== void 0 && options.mediaType.split(";")[0].trim().toLowerCase() !== "text/html") {
39133
+ throw new CLIError("INVALID_ARGUMENT", "a folder is published as text/html; drop --media-type", 2);
39134
+ }
39135
+ const bundle = await bundleHtmlFolder(files[0], { ...name === void 0 ? {} : { name }, ...maxBytes === void 0 ? {} : { maxBytes } });
39136
+ const notice = htmlFolderNotice(bundle);
39137
+ if (notice !== void 0) process.stderr.write(notice);
39138
+ inputs = [{ bytes: bundle.bytes, filename: bundle.filename }];
39139
+ } else {
39140
+ for (const file of files) if (await isDirectoryInput(file)) throw new CLIError("MAX_BYTES_EXCEEDED", FOLDER_NOT_AVAILABLE, 7);
39141
+ for (const file of files) shareInputFilename(file, name);
39142
+ inputs = await Promise.all(files.map((file) => readShareInput(file, name, maxBytes)));
39143
+ }
39144
+ assertAggregateInputLimit(inputs, maxBytes);
37965
39145
  const actions = requestedActions(options.action);
37966
39146
  if (options.prefix && target.kind === "bearer") throw new CLIError("INVALID_ARGUMENT", "--prefix requires an addressed target", 2);
37967
39147
  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);
@@ -38204,7 +39384,7 @@ import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
38204
39384
  import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
38205
39385
  import { randomBytes as randomBytes9 } from "crypto";
38206
39386
  import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
38207
- import { dirname as dirname8 } from "path";
39387
+ import { dirname as dirname9 } from "path";
38208
39388
  function didWithoutFragment2(did) {
38209
39389
  const fragment = did.indexOf("#");
38210
39390
  return fragment === -1 ? did : did.slice(0, fragment);
@@ -38360,7 +39540,7 @@ it directly with \`${CLI_NAME30} space host <name>\` (no request needed).
38360
39540
  }
38361
39541
  async function emitHostRequestArtifact(artifact, emitOption) {
38362
39542
  if (typeof emitOption === "string" && emitOption.length > 0) {
38363
- await mkdir7(dirname8(emitOption), { recursive: true });
39543
+ await mkdir7(dirname9(emitOption), { recursive: true });
38364
39544
  await writeFile4(emitOption, JSON.stringify(artifact, null, 2) + "\n", "utf8");
38365
39545
  outputJson({
38366
39546
  emitted: true,
@@ -38386,7 +39566,7 @@ init_host();
38386
39566
  init_theme();
38387
39567
  import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
38388
39568
  import { writeFile as writeFile5 } from "fs/promises";
38389
- import { resolve as resolve7 } from "path";
39569
+ import { resolve as resolve8 } from "path";
38390
39570
  async function dbHandle(node, dbName, spaceInput, profileName2) {
38391
39571
  const spaceUri = await resolveSpaceUri(spaceInput, profileName2);
38392
39572
  const sql = spaceUri ? node.sqlForSpace(spaceUri) : node.sql;
@@ -38528,7 +39708,7 @@ Output:
38528
39708
  }
38529
39709
  const blob = result.data;
38530
39710
  const buffer = Buffer.from(await blob.arrayBuffer());
38531
- const outputPath = resolve7(options.output);
39711
+ const outputPath = resolve8(options.output);
38532
39712
  await writeFile5(outputPath, buffer);
38533
39713
  outputJson({
38534
39714
  file: outputPath,