← Files DataARCHIVED FILE

skills/publish-artifact-to-sites/scripts/package-data-app-for-sites-source.mjs

41.5 KB · Oct 5, 2026 · 12:04 UTC

↓ Download file

#!/usr/bin/env node
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import {
  existsSync,
  lstatSync,
  mkdirSync,
  mkdtempSync,
  readFileSync,
  realpathSync,
  renameSync,
  rmSync,
  writeFileSync,
} from "node:fs";
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
import { isDeepStrictEqual, parseArgs } from "node:util";
import { pathToFileURL } from "node:url";
import { encodeDataAssetFragment } from "../../../scripts/data-url.mjs";
import { digestProtectedFile } from "../../../templates/data-app/base/scripts/protected-file-digest.mjs";
import { parseJsonBytes } from "../../../templates/data-app/base/src/streaming-json.js";
import { createPublicationAssets, externalizeSourceWorker, OFFLINE_HTML_PATH, PUBLICATION_ASSET_DIRECTORY, publicationUploadAuthorization, snapshotResponseFingerprint } from "./publication-assets.mjs";

// Explicit source-build compatibility adapter. Default publication packages
// existing HTML without compiling or upgrading the project runtime.
const { values } = parseArgs({
  options: {
    "project-dir": { type: "string" },
    "project-id": { type: "string" },
    "deployment-token-sha256": { type: "string" },
    "deployment-token-expires-at": { type: "string" },
    "migrate-packaging-manifest": { type: "boolean" },
    "presentation-file": { type: "string" },
    "query-loading": { type: "string" },
    source: { type: "boolean", default: false },
  },
  strict: true,
});

if (values["query-loading"] !== undefined) {
  throw new Error("--query-loading is not supported by custom source Worker packaging.");
}

for (const option of ["project-dir", "project-id"]) {
  if (!values[option]?.trim()) throw new Error(`Missing required --${option}.`);
}

if (!values.source) {
  throw new Error("Source Worker packaging requires explicit --source.");
}

const deploymentUploadAuthorization = publicationUploadAuthorization(values);

const projectRoot = realpathSync(values["project-dir"]);
const projectId = values["project-id"].trim();
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(projectId)) {
  throw new Error(
    "The Sites project ID must begin with a letter or digit and contain only letters, digits, underscores, or hyphens.",
  );
}

function jsonExpression(value) {
  // Parse a quoted JSON document instead of embedding an object literal. This
  // preserves own "__proto__" keys and keeps every authored byte inside a string.
  const document = JSON.stringify(JSON.stringify(value))
    .replaceAll("\u2028", "\\u2028")
    .replaceAll("\u2029", "\\u2029");
  return `JSON.parse(${document})`;
}

function escapeAttribute(value) {
  return value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
}

function localReferenceVariants(paths) {
  const variants = new Set(
    paths.flatMap((path) => [path, escapeAttribute(path), encodeURI(path), encodeURIComponent(path)]),
  );
  for (let depth = 0; depth < 3; depth += 1) {
    for (const value of [...variants]) variants.add(JSON.stringify(value).slice(1, -1));
  }
  return [...variants];
}

const GENERATED_DATA_URL = /data:([a-z\d!#$%&'*+.^_`|~-]+\/[a-z\d!#$%&'*+.^_`|~-]+)(;charset=utf-8)?;base64,/giu;
const MAX_EMBEDDED_DATA_BYTES = 64 * 1024 * 1024;
const MAX_TOTAL_EMBEDDED_DATA_BYTES = 128 * 1024 * 1024;
const MAX_EMBEDDED_DATA_URLS = 256;
const MAX_EMBEDDED_DATA_DEPTH = 8;
const dataLimitError = () => new Error("The Data app exceeds the supported embedded-data privacy inspection limits.");
const malformedDataUrl = () => new Error("The Data app contains a malformed embedded data URL.");

function precedingBackslashes(text, index) {
  let count = 0;
  while (index > 0 && text[--index] === "\\") count += 1;
  return count;
}

function dynamicTemplate(text) {
  for (const match of text.matchAll(/\$\{/gu)) {
    if (precedingBackslashes(text, match.index) % 2 === 0) return true;
  }
  return false;
}

function htmlDataUrlContext(text) {
  const attributes = new Map();
  const styles = [];
  // Only markup attributes are strict HTML URLs. Skip raw-text element bodies
  // so an authored JavaScript template that constructs markup is still source.
  const tags = /<!--[\s\S]*?(?:-->|$)|<\/?([a-z][a-z\d:-]*)(?=[\t\n\f\r />])(?:[^"'<>]|"[^"]*"|'[^']*')*>/giu;
  for (let tag; (tag = tags.exec(text)); ) {
    if (!tag[1] || tag[0].startsWith("</")) continue;
    const name = tag[1].toLowerCase();
    const attributePattern =
      /[\t\n\f\r ](?:src|href)[\t\n\f\r ]*=[\t\n\f\r ]*(?:"([^"]*)"|'([^']*)'|([^\t\n\f\r "'=<>`]+))/giu;
    for (const attribute of tag[0].matchAll(attributePattern)) {
      const value = attribute[1] ?? attribute[2] ?? attribute[3];
      const prefix = value.matchAll(GENERATED_DATA_URL).next().value;
      if (!prefix || prefix.index !== 0) continue;
      const quoted = attribute[3] === undefined;
      const offset = tag.index + attribute.index + attribute[0].length - value.length - Number(quoted);
      attributes.set(offset, { value: value.slice(prefix[0].length), strict: true });
    }
    if (name === "plaintext") break;
    if (!/^(?:script|style|title|textarea|xmp|iframe|noembed|noframes|noscript)$/u.test(name)) continue;
    const close = new RegExp(`</${name}(?=[\\t\\n\\f\\r />])[^>]*>`, "giu");
    close.lastIndex = tags.lastIndex;
    const end = close.exec(text);
    if (name === "style") styles.push(text.slice(tags.lastIndex, end?.index));
    tags.lastIndex = end ? close.lastIndex : text.length;
  }
  return { attributes, styles };
}

function dataUrlCandidate(text, match, wholeValue, context) {
  const start = match.index + match[0].length;
  if (wholeValue && !text.slice(0, match.index).trim()) {
    return { value: text.slice(start), strict: true };
  }
  const attribute = context.attributes?.get(match.index);
  if (attribute) return attribute;
  const before = text.slice(Math.max(0, match.index - 256), match.index);
  const quotedCssUrl = context.css && /\burl\(\s*\\*["']$/iu.test(before);
  const quote = text[match.index - 1];
  if (['"', "'", "`"].includes(quote)) {
    const escapes = precedingBackslashes(text, match.index - 1);
    let end = -1;
    for (let cursor = start; cursor < text.length; cursor += 1) {
      if (text[cursor] !== quote) continue;
      const closingEscapes = precedingBackslashes(text, cursor);
      if (escapes > closingEscapes) continue;
      if (!quotedCssUrl && !escapes && closingEscapes % 2) continue;
      end = cursor - escapes;
      break;
    }
    if (end < start) return quotedCssUrl ? { value: text.slice(start), strict: true } : null;
    const value = text.slice(start, end);
    if (!quotedCssUrl && quote === "`" && dynamicTemplate(value)) return null;
    return { value, strict: Boolean(quotedCssUrl) };
  }
  if (context.css && /\burl\(\s*$/iu.test(before)) {
    const end = text.indexOf(")", start);
    return {
      value: text.slice(start, end < 0 ? undefined : end).replace(/[\t\n\f\r ]+$/u, ""),
      strict: true,
    };
  }
  const value = /^[^\s"'`\\<>()\[\]{}]*/u.exec(text.slice(start))[0];
  return { value, strict: false };
}

function asciiFold(bytes) {
  const folded = Buffer.from(bytes);
  for (let index = 0; index < folded.length; index += 1) {
    if (folded[index] >= 65 && folded[index] <= 90) folded[index] += 32;
  }
  return folded;
}

function textualMime(mime, charset) {
  return (
    Boolean(charset) ||
    mime.startsWith("text/") ||
    mime === "image/svg+xml" ||
    /^application\/(?:json|javascript|ecmascript|xml|[^/]+\+(?:json|xml))$/u.test(mime)
  );
}

function embeddedTextKind(mime) {
  if (mime === "text/css") return "css";
  if (mime === "text/html" || mime === "application/xhtml+xml" || mime === "image/svg+xml") return "html";
  return "source";
}

function assertPublicationPrivacy(
  source,
  { taskValues, referenceText, taskBytes, referenceBytes },
  worker = false,
  { structured = false, kind = "source" } = {},
) {
  const pending = [{ value: source, depth: 0, structured, kind }];
  const objects = new WeakSet();
  const visitedUrls = new Set();
  let count = 0;
  let totalBytes = 0;
  const rejectTask = () => {
    throw new Error(
      worker
        ? "The published Sites Worker must not contain a local task identifier."
        : "The reviewed Data app contains a local task identifier outside its preview metadata.",
    );
  };
  const rejectReference = () => {
    throw new Error(
      worker
        ? "The published Sites Worker must not contain a local filesystem reference."
        : "The reviewed Data app contains a local filesystem reference outside its preview metadata.",
    );
  };
  const checkText = (text) => {
    const lower = text.toLowerCase();
    if (taskValues.some((value) => lower.includes(value))) rejectTask();
    if (referenceText.some((value) => lower.includes(value))) rejectReference();
  };
  const checkBytes = (bytes) => {
    const folded = asciiFold(bytes);
    if (taskBytes.some((value) => folded.includes(value))) rejectTask();
    if (referenceBytes.some((value) => folded.includes(value))) rejectReference();
  };
  while (pending.length) {
    const { value, depth, structured: wholeValue, kind: textKind } = pending.pop();
    if (wholeValue && value && typeof value === "object") {
      if (objects.has(value)) continue;
      objects.add(value);
      for (const [key, child] of Object.entries(value)) {
        pending.push(
          { value: key, depth, structured: true, kind: "source" },
          { value: child, depth, structured: true, kind: "source" },
        );
      }
      continue;
    }
    if (typeof value !== "string") continue;
    checkText(value);
    const context = textKind === "html" ? htmlDataUrlContext(value) : { css: textKind === "css" };
    for (const style of context.styles ?? []) {
      pending.push({ value: style, depth, structured: false, kind: "css" });
    }
    // The generator emits MIME/base64 URLs, not remote fetches. Strictly check
    // complete HTML/CSS URL values and parsed configuration strings. Elsewhere
    // in source, inspect canonical literal candidates without mistaking a
    // dynamic JavaScript prefix or documentation for a malformed emitted URL.
    for (const match of value.matchAll(GENERATED_DATA_URL)) {
      const candidate = dataUrlCandidate(value, match, wholeValue, context);
      if (!candidate) continue;
      const fragmentOffset = candidate.value.indexOf("#");
      const encoded = fragmentOffset < 0 ? candidate.value : candidate.value.slice(0, fragmentOffset);
      const fragment = fragmentOffset < 0 ? "" : candidate.value.slice(fragmentOffset + 1);
      const invalid = () => {
        if (candidate.strict) throw malformedDataUrl();
      };
      if (encoded.length % 4 !== 0 || !/^[a-z\d+/]*={0,2}$/iu.test(encoded)) {
        invalid();
        continue;
      }
      if (encoded.length > Math.ceil(MAX_EMBEDDED_DATA_BYTES / 3) * 4 || fragment.length > MAX_EMBEDDED_DATA_BYTES * 3)
        throw dataLimitError();
      const mime = match[1].toLowerCase();
      const key = `${mime}${match[2] ? ";charset=utf-8" : ""},${candidate.value}`;
      if (visitedUrls.has(key)) continue;
      let decodedFragment;
      try {
        decodedFragment = decodeURIComponent(fragment);
        if (encodeDataAssetFragment(decodedFragment) !== fragment) {
          invalid();
          continue;
        }
      } catch {
        invalid();
        continue;
      }
      const bytes = Buffer.from(encoded, "base64");
      if (bytes.toString("base64") !== encoded) {
        invalid();
        continue;
      }
      if (++count > MAX_EMBEDDED_DATA_URLS || depth >= MAX_EMBEDDED_DATA_DEPTH) throw dataLimitError();
      visitedUrls.add(key);
      totalBytes += bytes.length + Buffer.byteLength(decodedFragment);
      if (bytes.length > MAX_EMBEDDED_DATA_BYTES || totalBytes > MAX_TOTAL_EMBEDDED_DATA_BYTES) throw dataLimitError();
      checkBytes(bytes);
      if (decodedFragment) {
        pending.push({ value: decodedFragment, depth: depth + 1, structured: true, kind: "source" });
      }
      let decoded;
      try {
        decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
      } catch {
        if (textualMime(mime, match[2])) {
          throw new Error("The Data app contains a non-UTF-8 embedded text data URL.");
        }
      }
      if (decoded) {
        pending.push({ value: decoded, depth: depth + 1, structured: false, kind: embeddedTextKind(mime) });
        if (mime === "application/json" || mime.endsWith("+json")) {
          try {
            pending.push({ value: JSON.parse(decoded), depth: depth + 1, structured: true, kind: "source" });
          } catch {
            // A ?url asset need not itself be valid JSON; its exact text was
            // still inspected. Do not impose a new asset-format requirement.
          }
        }
      }
    }
  }
}

function projectPath(path) {
  const realPath = realpathSync(path);
  const nested = relative(projectRoot, realPath);
  if (isAbsolute(nested) || nested === ".." || nested.startsWith(`..${sep}`) || resolve(projectRoot, nested) !== realPath) {
    throw new Error("Every Sites packaging input must stay inside the Data app project.");
  }
  return realPath;
}

function projectOutput(path) {
  const absolutePath = resolve(path);
  const nested = relative(projectRoot, absolutePath);
  if (isAbsolute(nested) || nested === ".." || nested.startsWith(`..${sep}`)) {
    throw new Error("Every Sites packaging output must stay inside the Data app project.");
  }
  let container = existsSync(absolutePath) ? absolutePath : dirname(absolutePath);
  while (!existsSync(container)) container = dirname(container);
  projectPath(container);
  return absolutePath;
}

const sensitiveMetadataKey = /authorization|authentication|authheaders?|session|cookie|csrf|xsrf|bearer|tokens?|pass(?:word|wd)|secrets?|credentials?|(?:api|access|private)keys?|connectionstrings?|signedurls?|signatures?|xamz|xgoog|sharedaccesssignature/iu;
const credentialValue = /(?:\bbearer\s+[a-z\d._~+/-]+=*|\beyj[a-z\d_-]+\.eyj[a-z\d_-]+\.[a-z\d_-]+\b|\b(?:access[_-]?token|tokens?|api[_-]?key|session(?:[_-]?id)?|(?:j|php)?sess(?:ion)?id|password|passwd|secret|signature|sig|x-amz-signature|x-goog-signature)\s*[=:]\s*[^\s&,;]+)/iu;
const credentialPath = /(?:^|\/)(?:bearer|tokens?|access[_-]?tokens?|api[_-]?keys?|password|passwd|secrets?|credentials?|signatures?|sig|signed(?:url)?|x[_-]?(?:amz|goog)[_-]?signature)(?:\/|[=:_-]|$)/iu;
const credentialJwtPath = /(?:^|\/)eyj[a-z\d_-]+\.eyj[a-z\d_-]+\.[a-z\d_-]+(?:\/|$)/iu;
const embeddedSession = /(?:^|[;/])(?:j|php)?sess(?:ion)?id\s*=/iu;
const encodedUrlDelimiter = /%(?:25)*(?:3f|23)/iu;
const embeddedUrl = /(?:[a-z][a-z\d+.-]*:\/\/|\/\/|(?<![\w/])\/|(?<![\w/])(?:[a-z\d._~%-]+(?:\/[a-z\d._~%-]+)*)?(?=[?#][^\s<>"'`]*=))[^\s<>"'`]+/giu;

function isCalendarEventSourceLocator(rawUrl, sourceValue) {
  // Calendar htmlLink's eid locates an event; it is not an access credential.
  // Require the exact raw URL so normalization cannot hide additional parameters.
  return (
    /^https:\/\/www\.google\.com(?::443)?\/calendar\/event\?eid=[A-Za-z0-9_-]{1,2048}$/u.test(rawUrl) &&
    (!sourceValue.trimStart().startsWith(rawUrl) || sourceValue === rawUrl) &&
    !/[\0-\x1f\x7f]/u.test(sourceValue)
  );
}

function rejectCredentials(value, location = "snapshot", path = []) {
  if (typeof value === "string") {
    if (credentialValue.test(value) || encodedUrlDelimiter.test(value)) {
      throw new Error(`The Data app snapshot contains embedded credentials at ${location}.`);
    }

    for (const match of value.matchAll(embeddedUrl)) {
      let url;
      try {
        url = new URL(match[0], "https://data-app.invalid");
      } catch {
        throw new Error(`The Data app snapshot contains an invalid source URL at ${location}.`);
      }
      let pathname;
      try {
        pathname = url.pathname;
        for (let depth = 0; depth < 8; depth += 1) {
          pathname = decodeURIComponent(pathname);
          if (!/%[a-f\d]{2}/iu.test(pathname)) break;
          if (depth === 7) throw new Error("Source URL encoding exceeds the supported depth.");
        }
      } catch {
        throw new Error(`The Data app snapshot contains an invalid source URL at ${location}.`);
      }
      if (url.username || url.password || (url.search && !isCalendarEventSourceLocator(match[0], value))
        || url.hash || /[?#]/u.test(pathname)
        || credentialPath.test(pathname) || credentialJwtPath.test(pathname)
        || embeddedSession.test(pathname) || credentialValue.test(pathname)) {
        throw new Error(`The Data app snapshot contains a credential-bearing source URL at ${location}.`);
      }
    }
    return;
  }

  if (Array.isArray(value)) {
    for (const [index, entry] of value.entries()) {
      rejectCredentials(entry, `${location}[${index}]`, [...path, index]);
    }
    return;
  }
  if (!value || typeof value !== "object") return;

  for (const [key, entry] of Object.entries(value)) {
    const normalizedKey = key.toLowerCase().replace(/[^a-z\d]/gu, "");
    if (normalizedKey === "rows" && Array.isArray(entry)
      && path.length === 2 && path[0] === "queries") continue;
    const queryId = path.length === 1 && path[0] === "queries";
    const harmlessMetric = typeof entry === "boolean" || (
      typeof entry === "number"
      && /(?:counts?|total|rate|ratio|duration|usage|score|percent(?:age)?)$/iu.test(normalizedKey)
    );
    if (!queryId && entry !== null && !harmlessMetric && sensitiveMetadataKey.test(normalizedKey)) {
      throw new Error(`The Data app snapshot contains credential metadata at ${location}.${key}.`);
    }
    rejectCredentials(entry, `${location}.${key}`, [...path, key]);
  }
}

const hostingPath = projectPath(join(projectRoot, ".openai/hosting.json"));
const hostingSource = readFileSync(hostingPath, "utf8");
const hosting = JSON.parse(hostingSource);
if (hosting?.project_id && hosting.project_id !== projectId) {
  throw new Error("The Data app does not belong to the requested Sites project.");
}
const htmlPath = projectPath(join(projectRoot, "dist/index.html"));
const snapshotPath = projectPath(join(projectRoot, "src/data.json"));
const workerPath = projectPath(join(projectRoot, "src/worker.js"));
const protectedManifestPath = projectPath(join(projectRoot, "protected-runtime.json"));
const serverDirectory = projectOutput(join(projectRoot, "dist/server"));
const serverOutputPath = projectOutput(join(serverDirectory, "index.js"));
const distHostingPath = projectOutput(join(projectRoot, "dist/.openai/hosting.json"));

const protectedManifestSource = readFileSync(protectedManifestPath, "utf8");
const protectedManifest = JSON.parse(protectedManifestSource);
const packagingOwnedPaths = new Set([".openai/hosting.json"]);
const approvedEditablePaths = ["src/content/", "src/theme.css", "src/data.json"];
if (
  protectedManifest.version !== 1 ||
  JSON.stringify(protectedManifest.editablePaths) !== JSON.stringify(approvedEditablePaths) ||
  !protectedManifest.files ||
  typeof protectedManifest.files !== "object" ||
  Array.isArray(protectedManifest.files)
) {
  throw new Error("The Data app protected runtime manifest is invalid.");
}
for (const requiredPath of [...packagingOwnedPaths, "src/worker.js", "src/data-app-worker.js", "src/presentation-state.js", "scripts/verify-protected-runtime.mjs"]) {
  if (!Object.hasOwn(protectedManifest.files, requiredPath)) {
    throw new Error(`The Data app protected runtime manifest does not protect ${requiredPath}.`);
  }
}
const digestHelperName = "scripts/protected-file-digest.mjs";
const protectsDigestHelper = Object.hasOwn(protectedManifest.files, digestHelperName);
let digestHelperExists = false;
try {
  lstatSync(join(projectRoot, digestHelperName));
  digestHelperExists = true;
} catch (error) {
  if (error.code !== "ENOENT") throw error;
}
if (!protectsDigestHelper && digestHelperExists) {
  throw new Error(
    "The Data app protected runtime manifest must protect its existing digest helper before any source verification.",
  );
}
const legacySourceManifest = !protectsDigestHelper;
const packagingDigest = (path, contents) =>
  legacySourceManifest ? createHash("sha256").update(contents).digest("hex") : digestProtectedFile(path, contents);
const protectedMismatches = [];
// Copied runtime source is only consumed by an explicitly selected source build.
for (const [protectedPath, expectedHash] of Object.entries(protectedManifest.files)) {
  if (typeof expectedHash !== "string" || !/^[a-f\d]{64}$/u.test(expectedHash)) {
    throw new Error(`The Data app protected runtime hash is invalid: ${protectedPath}.`);
  }
  const actualPath = projectPath(join(projectRoot, protectedPath));
  const actualHash = packagingDigest(protectedPath, readFileSync(actualPath));
  if (actualHash !== expectedHash) protectedMismatches.push(protectedPath);
}
const unrelatedProtectedMismatches = protectedMismatches.filter((path) => !packagingOwnedPaths.has(path));
if (unrelatedProtectedMismatches.length) {
  throw new Error(
    `Protected Data app runtime files were modified outside packaging ownership: ${unrelatedProtectedMismatches.join(
      ", ",
    )}. Restore the canonical starter, or explicitly authorize the source change before using --source.`,
  );
}
if (protectedMismatches.length && !values["migrate-packaging-manifest"]) {
  throw new Error(
    "Protected Data app packaging metadata is out of sync. Use --source --migrate-packaging-manifest only " +
      "to repair a verified existing Site's hosting manifest entry.",
  );
}
if (values["migrate-packaging-manifest"] && !protectedMismatches.length) {
  throw new Error("Packaging manifest migration requires an existing legacy hosting hash mismatch.");
}

const assetManifestPath = projectOutput(join(projectRoot, PUBLICATION_ASSET_DIRECTORY, "manifest.json"));
const offlineHtmlPath = projectOutput(join(projectRoot, OFFLINE_HTML_PATH));
const previousHtml = readFileSync(htmlPath);
let reviewedHtml = previousHtml;
if (existsSync(assetManifestPath)) {
  const previous = JSON.parse(readFileSync(projectPath(assetManifestPath), "utf8"));
  const inputHash = createHash("sha256").update(reviewedHtml).digest("hex");
  if (previous.version === 1 && previous.thinBootstrap === true && previous.assets?.html?.sha256 === inputHash) {
    if (previous.projectId !== projectId || previous.source?.offlineHtmlPath !== OFFLINE_HTML_PATH) {
      throw new Error("The thin Data app package does not match its offline source or Site identity.");
    }
    const original = readFileSync(projectPath(offlineHtmlPath));
    if (createHash("sha256").update(original).digest("hex") !== previous.source.htmlSha256) {
      throw new Error("The original offline Data app changed after publication. Rebuild before packaging.");
    }
    reviewedHtml = original;
  }
}
const reviewedHtmlSource = reviewedHtml.toString("utf8");
const localThreadMarkers = [
  ...reviewedHtmlSource.matchAll(/<meta\b[^>]*\bname\s*=\s*["']?data-app-local-thread(?=["'\s/>]|$)[^>]*(?:>|$)/giu),
];
if (localThreadMarkers.length > 1) {
  throw new Error("The reviewed Data app contains duplicate local task metadata.");
}
const localThreadMarker = localThreadMarkers[0];
const localThreadMatch = localThreadMarker?.[0].match(
  /^<meta\s+name="data-app-local-thread"\s+content="([\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})"\s*\/?>$/iu,
);
if (localThreadMarker && !localThreadMatch) {
  throw new Error("The reviewed Data app contains malformed local task metadata.");
}
const localThreadId = localThreadMatch?.[1] ?? null;
const localReferenceMarkers = [
  ...reviewedHtmlSource.matchAll(/<meta\b[^>]*\bname\s*=\s*["']?data-app-local-reference(?=["'\s/>]|$)[^>]*(?:>|$)/giu),
];
if (localReferenceMarkers.length > 1) {
  throw new Error("The reviewed Data app contains duplicate local-reference metadata.");
}
const localReferenceMarker = localReferenceMarkers[0];
const localReferencePaths = [projectRoot, htmlPath];
if (localReferenceMarker) {
  try {
    const content = /^<meta name="data-app-local-reference" content="([^"]*)">$/u.exec(localReferenceMarker[0])?.[1];
    if (content === undefined) throw new Error("Invalid marker.");
    const decoded = content
      .replaceAll("&quot;", '"')
      .replaceAll("&lt;", "<")
      .replaceAll("&gt;", ">")
      .replaceAll("&amp;", "&");
    const reference = JSON.parse(decoded);
    const canonical = JSON.stringify({ root: reference.root, htmlPath: reference.htmlPath });
    if (
      content !== escapeAttribute(canonical) ||
      [reference.root, reference.htmlPath].some(
        (path) => typeof path !== "string" || !isAbsolute(path) || /[\0-\x1f\x7f]/u.test(path),
      ) ||
      reference.root !== projectRoot ||
      reference.htmlPath !== htmlPath ||
      realpathSync(reference.root) !== reference.root ||
      projectPath(reference.htmlPath) !== reference.htmlPath ||
      !/<head(?:\s[^>]*)?>[\s\S]*?<\/head\s*>/iu.exec(reviewedHtmlSource)?.[0].includes(localReferenceMarker[0])
    ) {
      throw new Error("Invalid reference.");
    }
  } catch {
    throw new Error("The reviewed Data app contains malformed local-reference metadata.");
  }
}
const transientMarkers = [localThreadMarker, localReferenceMarker]
  .filter(Boolean)
  .sort((left, right) => left.index - right.index);
const htmlParts = [];
let htmlOffset = 0;
for (const marker of transientMarkers) {
  const markerOffset = Buffer.byteLength(reviewedHtmlSource.slice(0, marker.index), "utf8");
  if (markerOffset < htmlOffset) throw new Error("The reviewed Data app contains overlapping local metadata.");
  htmlParts.push(reviewedHtml.subarray(htmlOffset, markerOffset));
  htmlOffset = markerOffset + Buffer.byteLength(marker[0], "utf8");
}
htmlParts.push(reviewedHtml.subarray(htmlOffset));
const sourceHtml = transientMarkers.length ? Buffer.concat(htmlParts) : reviewedHtml;
const sourceHtmlText = sourceHtml.toString("utf8");
const localTaskIds = [localThreadId, process.env.CODEX_SESSION_ID, process.env.CODEX_THREAD_ID].filter((value) =>
  /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu.test(value ?? ""),
);
const taskValues = localTaskIds.map((value) => value.toLowerCase());
const referenceValues = localReferenceVariants(localReferencePaths);
const privacyMatchers = {
  taskValues,
  referenceText: referenceValues.map((value) => value.toLowerCase()),
  taskBytes: taskValues.map((value) => Buffer.from(value)),
  referenceBytes: referenceValues.map((value) => asciiFold(Buffer.from(value))),
};
assertPublicationPrivacy(sourceHtmlText, privacyMatchers, false, { kind: "html" });

const sourceSnapshot = readFileSync(snapshotPath);
let snapshot;
try {
  snapshot = parseJsonBytes(sourceSnapshot, { fatal: true, ignoreBOM: false });
} catch {
  throw new Error("The reviewed Data app snapshot must be valid UTF-8 JSON.");
}
const artifactSurface = snapshot.surface === undefined ? "dashboard" : snapshot.surface;
if (!["dashboard", "report"].includes(artifactSurface)) {
  throw new Error("The reviewed Data app surface must be dashboard or report.");
}

if (!snapshot.queries || typeof snapshot.queries !== "object" || Array.isArray(snapshot.queries)) {
  throw new Error("The Data app must contain a reviewed query snapshot.");
}
rejectCredentials(snapshot);

const snapshotHash = createHash("sha256").update(sourceSnapshot).digest("hex");
const documentHead = /<head(?:\s[^>]*)?>([\s\S]*?)<\/head\s*>/iu.exec(sourceHtmlText)?.[1];
const snapshotMarkers = [...(documentHead ?? "").matchAll(
  /<meta\b[^>]*\bname\s*=\s*(["'])data-app-snapshot-sha256\1[^>]*>/giu,
)];
const snapshotMarkerHash = snapshotMarkers.length === 1
  ? /\bcontent\s*=\s*(["'])([a-f\d]{64})\1/u.exec(snapshotMarkers[0][0])?.[2]
  : null;
if (snapshotMarkerHash !== snapshotHash) {
  throw new Error("The built Data app HTML must contain exactly one matching reviewed snapshot integrity marker.");
}

const presentationPath = values["presentation-file"] ? projectPath(values["presentation-file"]) : null;
const requestedPresentation = presentationPath ? JSON.parse(readFileSync(presentationPath, "utf8")) : {};
let persistedPresentation = requestedPresentation;
if (
  requestedPresentation &&
  typeof requestedPresentation === "object" &&
  !Array.isArray(requestedPresentation) &&
  Object.hasOwn(requestedPresentation, "filterDefinitions")
) {
  const reviewedFilterDefinitions = snapshot.filters ?? [];
  if (
    !Array.isArray(reviewedFilterDefinitions) ||
    !Array.isArray(requestedPresentation.filterDefinitions) ||
    !isDeepStrictEqual(requestedPresentation.filterDefinitions, reviewedFilterDefinitions)
  ) {
    throw new Error("Presentation filter definitions must exactly match the reviewed Data app snapshot.");
  }
  persistedPresentation = { ...requestedPresentation };
  delete persistedPresentation.filterDefinitions;
}
function validatedInitialPresentation(validatePresentation) {
  const initialPresentation = { ...validatePresentation(persistedPresentation) };
  // Verification can only originate from an authenticated creator in the deployed Worker.
  delete initialPresentation.verification;
  return initialPresentation;
}

if (values["migrate-packaging-manifest"] && (hosting.project_id !== projectId || hosting.d1 !== "DB")) {
  throw new Error("Packaging manifest migration requires the existing exact Sites project identity and DB binding.");
}

const temporaryWorkerPath = join(dirname(workerPath), ".sites-worker-entry.mjs");
if (existsSync(temporaryWorkerPath)) throw new Error("A stale Sites Worker entry must be removed first.");
const workerEnvironment = { ...process.env, CODEX_SESSION_ID: "", CODEX_THREAD_ID: "" };
const workerConfiguration = (initialPresentation) => ({
  html: sourceHtmlText,
  projectId,
  seedSnapshot: snapshot,
  initialPresentation,
});
let vitePath;
const verifierPath = projectPath(join(projectRoot, "scripts/verify-protected-runtime.mjs"));
function packagingManifest(hostingBytes) {
  return `${JSON.stringify(
    {
      ...protectedManifest,
      files: {
        ...protectedManifest.files,
        ".openai/hosting.json": packagingDigest(".openai/hosting.json", hostingBytes),
      },
    },
    null,
    2,
  )}\n`;
}
// A legacy migration repairs only the preflight-checked hosting hash.
// Run the project's verifier against that prospective repair, then restore
// the original manifest even if verification fails before publication starts.
const repairedCurrentManifest = values["migrate-packaging-manifest"]
  ? packagingManifest(readFileSync(hostingPath))
  : null;
let verification;
try {
  if (repairedCurrentManifest !== null) writeFileSync(protectedManifestPath, repairedCurrentManifest);
  verification = spawnSync(process.execPath, [verifierPath], {
    cwd: projectRoot,
    encoding: "utf8",
    env: workerEnvironment,
    stdio: ["ignore", "pipe", "pipe"],
  });
} finally {
  if (repairedCurrentManifest !== null) writeFileSync(protectedManifestPath, protectedManifestSource);
}
if (verification.error) throw verification.error;
if (verification.status !== 0) {
  throw new Error(`Protected Data app source verification failed with status ${verification.status}.`);
}
const localVite = join(projectRoot, "node_modules/vite/bin/vite.js");
if (!existsSync(localVite)) {
  throw new Error(
    "Source-mode Sites packaging requires an already-installed project Vite. No dependencies will be downloaded.",
  );
}
vitePath = projectPath(localVite);
const presentationModule = projectPath(join(projectRoot, "src/presentation-state.js"));
const { validatePresentation } = await import(pathToFileURL(presentationModule).href);
const initialPresentation = validatedInitialPresentation(validatePresentation);
assertPublicationPrivacy(workerConfiguration(initialPresentation), privacyMatchers, true, {
  structured: true,
});
const workerSource = readFileSync(workerPath, "utf8");
const factoryEntry = "export default createDataAppWorker({";
const incompatibleOwnerRuntime = () => new Error(
  "Environment-based owner authorization requires a confirmed, scoped protected runtime upgrade " +
  "of src/worker.js and src/data-app-worker.js, or use prebuilt packaging.",
);
if (
  workerSource.split(factoryEntry).length !== 2 ||
  !/import\s*\{\s*createDataAppWorker\s*\}\s+from\s+["']\.\/data-app-worker\.js["']/u.test(workerSource) ||
  /data-app-owner|\bowner(?:UserId|Email)Sha256\b/u.test(workerSource)
) {
  throw incompatibleOwnerRuntime();
}
if (/\b(?:initialPresentation|projectId)\s*:/u.test(workerSource)) {
  throw new Error("The source Data app Worker does not contain its expected presentation factory.");
}
let createDataAppWorker;
// Reject copied runtimes that still authorize a packaged identity. An invalid
// JSON body reaches 400 only after authorization, without reading or writing D1.
try {
  ({ createDataAppWorker } = await import(pathToFileURL(projectPath(join(projectRoot, "src/data-app-worker.js"))).href));
  const email = "source-package-probe@example.invalid";
  const sha256 = createHash("sha256").update(email).digest("hex");
  const worker = createDataAppWorker({
    html: "<!doctype html><html><head></head><body></body></html>",
    seedSnapshot: { queries: {} },
    initialPresentation: {},
    ownerEmailSha256: sha256,
  });
  for (const [ownerHash, status] of [[undefined, 403], ["", 403], ["invalid", 403], ["0".repeat(64), 403], [sha256, 400]]) {
    const response = await worker.fetch(new Request("https://publication.invalid/api/presentation", {
      method: "PUT", headers: { "oai-authenticated-user-email": email }, body: "{",
    }), { DATA_APP_OWNER_EMAIL_SHA256: ownerHash });
    if (response.status !== status) throw incompatibleOwnerRuntime();
  }
} catch {
  throw incompatibleOwnerRuntime();
}
async function supportsExternalAssets() {
  // Probe only tiny local values. Source Workers may retain inline-only assets;
  // a failed object-asset probe keeps their existing HTML and snapshot contract.
  try {
    const html = "<!doctype html><html><head></head><body>Object asset probe</body></html>";
    const deploymentAssets = Object.fromEntries(Object.entries({ html, snapshot: '{"queries":{}}' }).map(([kind, value]) => {
      const sha256 = createHash("sha256").update(value).digest("hex");
      return [kind, { key: `data-app/${kind}/${sha256}`, sha256, bytes: Buffer.byteLength(value) }];
    }));
    let read = false;
    const worker = createDataAppWorker({ projectId, initialPresentation: {}, deploymentAssets });
    const response = await worker.fetch(new Request("https://publication.invalid/"), { BUCKET: {
      async get(key) {
        if (key !== deploymentAssets.html.key) throw new Error("Unexpected probe object.");
        read = true;
        return { body: new Response(html).body, size: deploymentAssets.html.bytes,
          customMetadata: { sha256: deploymentAssets.html.sha256 } };
      },
    } });
    return read && response.status === 200 && await response.text() === html;
  } catch { return false; }
}
const assets = await supportsExternalAssets() ? createPublicationAssets({ html: sourceHtmlText, seedSnapshot: snapshot, projectId }) : null;
const assetFiles = assets ? { html: projectOutput(join(projectRoot, PUBLICATION_ASSET_DIRECTORY, "html.html")), snapshot: projectOutput(join(projectRoot, PUBLICATION_ASSET_DIRECTORY, "snapshot.json")) } : null;
const assetManifest = assets ? {
  version: 1, projectId,
  assets: Object.fromEntries(Object.entries(assets.deploymentAssets).map(([kind, descriptor]) => [kind, { ...descriptor, path: kind === "html" ? "html.html" : "snapshot.json" }])),
  thinBootstrap: assets.thinBootstrap,
  snapshotResponse: snapshotResponseFingerprint(snapshot),
  source: { htmlSha256: createHash("sha256").update(reviewedHtml).digest("hex"), offlineHtmlPath: OFFLINE_HTML_PATH },
} : null;
const externalWorker = assets ? externalizeSourceWorker(workerSource, assets.deploymentAssets, deploymentUploadAuthorization) : workerSource;
const packagedWorker = externalWorker.replace(
  factoryEntry,
  `${factoryEntry}\n  projectId: ${JSON.stringify(projectId)},\n  initialPresentation: ${jsonExpression(initialPresentation)},`,
);

const existingDistHosting = existsSync(distHostingPath) ? readFileSync(distHostingPath) : null;
const existingServer = existsSync(serverOutputPath) ? readFileSync(projectPath(serverOutputPath)) : null;
const packagedHosting = { ...hosting, project_id: projectId, d1: "DB", ...(assets ? { r2: "BUCKET" } : {}) };
const packagedHostingSource = `${JSON.stringify(packagedHosting, null, 2)}\n`;
const packagedDistHostingSource = `${JSON.stringify(
  { ...packagedHosting, artifact_metadata: { surface: artifactSurface, producer: "data-analytics" } },
  null,
  2,
)}\n`;
const packagedProtectedManifestSource = packagingManifest(packagedHostingSource);
const assetOutputs = assets ? [
  [offlineHtmlPath, reviewedHtml],
  [assetFiles.html, assets.bytes.html],
  [assetFiles.snapshot, assets.bytes.snapshot],
  [assetManifestPath, `${JSON.stringify(assetManifest, null, 2)}\n`],
] : [];
for (const [path] of assetOutputs) {
  const entry = lstatSync(path, { throwIfNoEntry: false });
  if (entry && !entry.isFile()) throw new Error("Publication asset outputs must be regular files.");
}
const writtenAssets = [];
let assetTransaction;
let htmlUpdated = false;
let hostingUpdated = false;
let distHostingUpdated = false;
let temporaryWorkerCreated = false;
let protectedManifestUpdated = false;
let serverPath;
try {
  const packagedHtml = assets?.thinBootstrap ? assets.bytes.html : sourceHtml;
  if (!packagedHtml.equals(previousHtml)) {
    htmlUpdated = true;
    writeFileSync(htmlPath, packagedHtml);
  }
  temporaryWorkerCreated = true;
  writeFileSync(temporaryWorkerPath, packagedWorker);
  const result = spawnSync(
    process.execPath,
    [vitePath, "build", "--ssr", temporaryWorkerPath, "--outDir", serverDirectory, "--emptyOutDir"],
    { cwd: projectRoot, encoding: "utf8", env: workerEnvironment, stdio: "inherit" },
  );
  if (result.error) throw result.error;
  if (result.status !== 0) throw new Error(`Sites Worker packaging failed with status ${result.status}.`);
  rmSync(temporaryWorkerPath, { force: true });
  temporaryWorkerCreated = false;

  serverPath = projectPath(join(projectRoot, "dist/server/index.js"));
  assertPublicationPrivacy(readFileSync(serverPath, "utf8"), privacyMatchers, true);

  assert.deepEqual(
    readFileSync(htmlPath),
    packagedHtml,
    "Sites packaging must preserve the reviewed Data app HTML after removing local task metadata.",
  );
  assert.deepEqual(
    readFileSync(snapshotPath),
    sourceSnapshot,
    "Sites packaging must preserve the exact reviewed Data app snapshot.",
  );

  if (assetOutputs.length) assetTransaction = mkdtempSync(join(projectRoot, ".data-app-package-source-"));
  for (const [index, [path, bytes]] of assetOutputs.entries()) {
    mkdirSync(dirname(path), { recursive: true });
    const previous = existsSync(path) ? join(assetTransaction, `old-${index}`) : null;
    const mode = previous ? lstatSync(projectPath(path)).mode & 0o777 : undefined;
    if (previous) renameSync(projectPath(path), previous);
    writtenAssets.push({ path, previous });
    writeFileSync(path, bytes, { mode });
  }
  mkdirSync(dirname(distHostingPath), { recursive: true });
  hostingUpdated = true;
  writeFileSync(hostingPath, packagedHostingSource);
  distHostingUpdated = true;
  writeFileSync(distHostingPath, packagedDistHostingSource);
  protectedManifestUpdated = true;
  writeFileSync(protectedManifestPath, packagedProtectedManifestSource);
} catch (error) {
  const rollbackErrors = [];
  const restore = (action) => {
    try {
      action();
    } catch (rollbackError) {
      rollbackErrors.push(rollbackError);
    }
  };
  for (const { path, previous } of writtenAssets.reverse()) {
    restore(() => {
      rmSync(path, { force: true });
      if (previous) renameSync(previous, path);
    });
  }
  if (protectedManifestUpdated) restore(() => writeFileSync(protectedManifestPath, protectedManifestSource));
  restore(() => {
    if (existingServer) {
      mkdirSync(dirname(serverOutputPath), { recursive: true });
      writeFileSync(serverOutputPath, existingServer);
    } else {
      rmSync(serverOutputPath, { force: true });
    }
  });
  if (hostingUpdated) restore(() => writeFileSync(hostingPath, hostingSource));
  if (distHostingUpdated)
    restore(() => {
      if (existingDistHosting) writeFileSync(distHostingPath, existingDistHosting);
      else rmSync(distHostingPath, { force: true });
    });
  if (htmlUpdated) restore(() => writeFileSync(htmlPath, previousHtml));
  if (temporaryWorkerCreated) {
    restore(() => rmSync(temporaryWorkerPath, { force: true }));
    temporaryWorkerCreated = false;
  }
  if (rollbackErrors.length) {
    throw new AggregateError(
      [error, ...rollbackErrors],
      "Sites packaging failed and some prior artifacts could not be restored." +
        (assetTransaction ? ` Recovery files remain in ${assetTransaction}.` : ""),
    );
  }
  if (assetTransaction) rmSync(assetTransaction, { recursive: true, force: true });
  throw error;
} finally {
  if (temporaryWorkerCreated) rmSync(temporaryWorkerPath, { force: true });
}
if (assetTransaction) {
  try { rmSync(assetTransaction, { recursive: true, force: true }); }
  catch { throw new Error("Package outputs were installed, but backup cleanup failed. Resolve the project-local .data-app-package-source directory before publishing."); }
}

console.log(
  JSON.stringify({
    projectRoot,
    projectId,
    htmlPath,
    serverPath,
    htmlSha256: createHash("sha256").update(sourceHtml).digest("hex"),
    snapshotSha256: snapshotHash,
    ...(assets ? {
      hostedHtmlSha256: assets.deploymentAssets.html.sha256,
      assetManifestPath,
      offlineHtmlPath,
      assetFiles,
      deploymentAssets: assets.deploymentAssets,
      thinBootstrap: assets.thinBootstrap,
      externalAssetBytes: assets.deploymentAssets.html.bytes + assets.deploymentAssets.snapshot.bytes,
      assetBinding: "BUCKET",
    } : {}),
    workerBytes: readFileSync(serverPath).byteLength,
    generatedAt: snapshot.generatedAt ?? null,
    queryIds: Object.keys(snapshot.queries),
    ownerAuthorization: "environment",
    ownerEnvironmentVariable: "DATA_APP_OWNER_EMAIL_SHA256",
    presentationSeeded: true,
    databaseBinding: "DB",
    buildMode: "source",
  }),
);

SHA-256: e6d913de55e03e26f8c454335c260c89746e009f961837a462eb9f3ab88776ef