From 6fe730027e372353d58af11ac596cb6a95264593 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:38:10 -0700 Subject: [PATCH 1/2] fix(ci): reformat generated ProcessEnv union to eliminate recurring merge conflicts worker-configuration.d.ts (generated by `wrangler types`) packs every declared env var into one long single-line `Pick>` union in the ProcessEnv interface. Two independent PRs that each add a different new var to wrangler.jsonc's `vars` both regenerate that same line with their own insertion; git's line-based 3-way merge sees "the same line changed differently in both branches" and reports a real conflict, even though the two additions are logically unrelated. This hit several same-day PRs in a row during the Wraps `wrangler types` in scripts/gen-cf-typegen.mjs, which still regenerates via the real CLI (so it can never drift from wrangler's own output) but reformats that one union onto one sorted entry per line, same as every other interface body in the file already is. Two PRs adding different vars now land on different, non-adjacent lines and merge cleanly. cf-typegen:check is rebuilt on the same script (a temp-path regeneration compared against the committed file) rather than wrangler's own --check flag, which can't see the reformatting step. Includes a regression test that reproduces the original conflict via a real `git merge-file` before the fix and confirms it resolves cleanly after. --- .gitignore | 1 + package.json | 4 +- scripts/gen-cf-typegen.d.mts | 5 + scripts/gen-cf-typegen.mjs | 72 ++++++++++ test/unit/ci-cf-typegen-check.test.ts | 2 +- test/unit/ci-cf-typegen.test.ts | 107 ++++++++++++++ worker-configuration.d.ts | 193 ++++++-------------------- 7 files changed, 231 insertions(+), 153 deletions(-) create mode 100644 scripts/gen-cf-typegen.d.mts create mode 100644 scripts/gen-cf-typegen.mjs create mode 100644 test/unit/ci-cf-typegen.test.ts diff --git a/.gitignore b/.gitignore index 8d2d687772..e786709023 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ coverage/ site/.vitepress/cache/ !migrations/*.sql apps/gittensory-ui/public/downloads/gittensory-extension.zip +.worker-configuration.gen-check.d.ts diff --git a/package.json b/package.json index 162faae8da..1efaf04e12 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "command-reference:check": "node scripts/gen-command-reference.mjs --check", "selfhost:validate-observability": "node scripts/validate-observability-configs.mjs", "selfhost:config-lint": "tsx scripts/gittensory-config-lint.ts", - "cf-typegen": "wrangler types && perl -pi -e 's/[[:blank:]]+$//' worker-configuration.d.ts", - "cf-typegen:check": "wrangler types --check", + "cf-typegen": "node scripts/gen-cf-typegen.mjs", + "cf-typegen:check": "node scripts/gen-cf-typegen.mjs --check", "db:migrate:local": "wrangler d1 migrations apply gittensory --local", "db:migrate:remote": "wrangler d1 migrations apply gittensory --remote", "drizzle:generate": "drizzle-kit generate", diff --git a/scripts/gen-cf-typegen.d.mts b/scripts/gen-cf-typegen.d.mts new file mode 100644 index 0000000000..924b38f0c8 --- /dev/null +++ b/scripts/gen-cf-typegen.d.mts @@ -0,0 +1,5 @@ +export const OUTPUT_PATH: string; + +export function formatCfTypegenOutput(source: string): string; + +export function genCfTypegen(options?: { check?: boolean }): { changed: boolean | undefined }; diff --git a/scripts/gen-cf-typegen.mjs b/scripts/gen-cf-typegen.mjs new file mode 100644 index 0000000000..21ae493218 --- /dev/null +++ b/scripts/gen-cf-typegen.mjs @@ -0,0 +1,72 @@ +#!/usr/bin/env node +import { execFileSync } from "node:child_process"; +import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs"; +import { pathToFileURL } from "node:url"; + +// Cloudflare's own `wrangler types` output packs every declared env var into ONE long single-line +// `Pick>` union (the generated ProcessEnv interface). Two independent PRs +// that each add a DIFFERENT new var to wrangler.jsonc's `vars` regenerate that SAME line with their own +// insertion -- git's line-based 3-way merge sees "the same line changed differently in both branches" and +// reports a conflict on worker-configuration.d.ts, even though the two additions are logically unrelated +// (hit repeatedly across the #1667 incident-followup PRs). This wrapper still regenerates via the real +// `wrangler types` CLI (so it can never drift from wrangler's own output), then reformats JUST that one +// union onto one sorted entry per line -- two PRs adding different vars now land on different, non-adjacent +// lines and merge cleanly like every other interface body in this file already does. +export const OUTPUT_PATH = "worker-configuration.d.ts"; +const TEMP_PATH = ".worker-configuration.gen-check.d.ts"; +const PICK_RE = /Pick>/; + +/** Pure: strip trailing whitespace (wrangler's own raw output convention this repo has always normalized), + * normalize wrangler's own "Generated by ... running `wrangler types [path]`" header comment (it echoes + * whatever path argument was passed, which check mode intentionally sets to a different temp path than + * the real OUTPUT_PATH — without this, check mode would always report false staleness on that line alone), + * then reformat the single-line ProcessEnv Pick union into one sorted entry per line. */ +export function formatCfTypegenOutput(source) { + const stripped = source + .replace(/[ \t]+$/gm, "") + .replace(/(Generated by Wrangler by running `wrangler types)[^`]*`/, "$1`"); + const match = stripped.match(PICK_RE); + if (!match) { + throw new Error(`Could not find the ProcessEnv Pick union in the generated output to reformat.`); + } + const keys = match[1] + .split(" | ") + .map((quoted) => quoted.slice(1, -1)) + .sort((a, b) => a.localeCompare(b)); + const lines = keys.map((key) => `\t\t| "${key}"`).join("\n"); + return stripped.replace(PICK_RE, `Pick>`); +} + +export function genCfTypegen({ check = false } = {}) { + const target = check ? TEMP_PATH : OUTPUT_PATH; + try { + execFileSync("wrangler", ["types", target], { stdio: "inherit" }); + const generated = formatCfTypegenOutput(readFileSync(target, "utf8")); + if (!check) { + writeFileSync(OUTPUT_PATH, generated); + return { changed: undefined }; + } + const current = readFileSync(OUTPUT_PATH, "utf8"); + return { changed: current !== generated }; + } finally { + if (check && existsSync(TEMP_PATH)) unlinkSync(TEMP_PATH); + } +} + +function main(argv) { + const check = argv.includes("--check"); + const { changed } = genCfTypegen({ check }); + if (check) { + if (changed) { + process.stderr.write(`gen-cf-typegen: ${OUTPUT_PATH} is stale; run npm run cf-typegen.\n`); + process.exit(1); + } + process.stdout.write(`gen-cf-typegen: ${OUTPUT_PATH} is up to date.\n`); + return; + } + process.stdout.write(`gen-cf-typegen: wrote ${OUTPUT_PATH}.\n`); +} + +if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { + main(process.argv.slice(2)); +} diff --git a/test/unit/ci-cf-typegen-check.test.ts b/test/unit/ci-cf-typegen-check.test.ts index 7d22c85189..fa222e765d 100644 --- a/test/unit/ci-cf-typegen-check.test.ts +++ b/test/unit/ci-cf-typegen-check.test.ts @@ -30,7 +30,7 @@ describe("cf-typegen staleness guard (#2557)", () => { const pkg = record(JSON.parse(readFileSync("package.json", "utf8")), "package.json"); const scripts = record(pkg.scripts, "package.json.scripts"); - expect(scripts["cf-typegen:check"]).toBe("wrangler types --check"); + expect(scripts["cf-typegen:check"]).toBe("node scripts/gen-cf-typegen.mjs --check"); expect(String(scripts["test:ci"])).toContain("npm run cf-typegen:check"); // Must run before typecheck, mirroring db:migrations:check's position -- a drift-check failure should // surface before the more expensive type/test/build steps run. diff --git a/test/unit/ci-cf-typegen.test.ts b/test/unit/ci-cf-typegen.test.ts new file mode 100644 index 0000000000..1c78a34c7f --- /dev/null +++ b/test/unit/ci-cf-typegen.test.ts @@ -0,0 +1,107 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { formatCfTypegenOutput } from "../../scripts/gen-cf-typegen.mjs"; + +// #1667-followup: wrangler's own `wrangler types` output packs every declared env var into ONE long +// single-line `Pick>` union. Two independent PRs that each add a DIFFERENT +// new var regenerate that SAME line with their own insertion -- git's line-based 3-way merge sees "the same +// line changed differently in both branches" and reports a real merge conflict on worker-configuration.d.ts, +// even though the two additions are logically unrelated. This hit several same-day PRs in a row. The fix: +// reformat that one union onto one sorted entry per line, so two independent additions land on different, +// non-adjacent lines and merge cleanly like every other interface body in this file already does. +function rawFixture(vars: string[]): string { + const union = vars.map((v) => `"${v}"`).join(" | "); + return [ + "/* eslint-disable */", + "// Generated by Wrangler by running `wrangler types` (hash: deadbeef)", + "// Runtime types generated with workerd@1.0.0 2026-01-01 nodejs_compat", + "interface Env {}", + "declare namespace NodeJS {", + `\tinterface ProcessEnv extends StringifyValues> {}`, + "}", + "", + ].join("\n"); +} + +describe("gen-cf-typegen: formatCfTypegenOutput", () => { + it("reformats the single-line Pick union into one sorted entry per line", () => { + const out = formatCfTypegenOutput(rawFixture(["ZEBRA", "ALPHA", "MID"])); + expect(out).toContain('Pick>'); + }); + + it("sorts regardless of the original declaration order", () => { + const outAsc = formatCfTypegenOutput(rawFixture(["A", "B", "C"])); + const outDesc = formatCfTypegenOutput(rawFixture(["C", "B", "A"])); + expect(outAsc).toBe(outDesc); + }); + + it("normalizes the wrangler header's echoed path argument so a different invocation path does not read as a content change", () => { + const bare = formatCfTypegenOutput(rawFixture(["A"])); + const withPath = formatCfTypegenOutput(rawFixture(["A"]).replace("wrangler types` (hash", "wrangler types .some-temp-path.d.ts` (hash")); + expect(bare).toBe(withPath); + }); + + it("strips trailing whitespace from every line", () => { + const withTrailingWhitespace = rawFixture(["A"]).replace("interface Env {}", "interface Env {} "); + const out = formatCfTypegenOutput(withTrailingWhitespace); + expect(out).not.toMatch(/ +\n/); + expect(out).toContain("interface Env {}\n"); + }); + + it("throws when the ProcessEnv Pick union is not found", () => { + expect(() => formatCfTypegenOutput("interface Env {}\n")).toThrow(/Could not find the ProcessEnv/); + }); + + it("REGRESSION: two independent single-var additions no longer conflict under a real git 3-way merge", () => { + const dir = mkdtempSync(join(tmpdir(), "cf-typegen-merge-")); + try { + const base = formatCfTypegenOutput(rawFixture(["ALPHA", "MID", "ZEBRA"])); + const ours = formatCfTypegenOutput(rawFixture(["ALPHA", "BRAVO", "MID", "ZEBRA"])); // adds BRAVO + const theirs = formatCfTypegenOutput(rawFixture(["ALPHA", "MID", "YANKEE", "ZEBRA"])); // adds YANKEE + const basePath = join(dir, "base.d.ts"); + const oursPath = join(dir, "ours.d.ts"); + const theirsPath = join(dir, "theirs.d.ts"); + writeFileSync(basePath, base); + writeFileSync(oursPath, ours); + writeFileSync(theirsPath, theirs); + + const merged = execFileSync("git", ["merge-file", "-p", oursPath, basePath, theirsPath], { encoding: "utf8" }); + expect(merged).not.toContain("<<<<<<<"); + expect(merged).toContain('| "BRAVO"'); + expect(merged).toContain('| "YANKEE"'); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("BASELINE: the SAME two additions DO conflict on the original unformatted single-line union", () => { + const dir = mkdtempSync(join(tmpdir(), "cf-typegen-merge-baseline-")); + try { + const base = rawFixture(["ALPHA", "MID", "ZEBRA"]); + const ours = rawFixture(["ALPHA", "BRAVO", "MID", "ZEBRA"]); + const theirs = rawFixture(["ALPHA", "MID", "YANKEE", "ZEBRA"]); + const basePath = join(dir, "base.d.ts"); + const oursPath = join(dir, "ours.d.ts"); + const theirsPath = join(dir, "theirs.d.ts"); + writeFileSync(basePath, base); + writeFileSync(oursPath, ours); + writeFileSync(theirsPath, theirs); + + let conflicted = false; + let merged = ""; + try { + merged = execFileSync("git", ["merge-file", "-p", oursPath, basePath, theirsPath], { encoding: "utf8" }); + } catch (error) { + conflicted = true; + merged = String((error as { stdout?: string }).stdout ?? ""); + } + expect(conflicted).toBe(true); + expect(merged).toContain("<<<<<<<"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); +}); diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index 163cc474d3..579c1c0d6c 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -56,7 +56,46 @@ type StringifyValues> = { [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; }; declare namespace NodeJS { - interface ProcessEnv extends StringifyValues> {} + interface ProcessEnv extends StringifyValues> {} } // Begin runtime types @@ -482,8 +521,7 @@ interface ExecutionContext { readonly exports: Cloudflare.Exports; readonly props: Props; cache?: CacheContext; - readonly access?: CloudflareAccessContext; - tracing: Tracing; + tracing?: Tracing; } type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise; type ExportedHandlerConnectHandler = (socket: Socket, env: Env, ctx: ExecutionContext) => void | Promise; @@ -539,10 +577,6 @@ interface CachePurgeOptions { interface CacheContext { purge(options: CachePurgeOptions): Promise; } -interface CloudflareAccessContext { - readonly aud: string; - getIdentity(): Promise; -} declare abstract class ColoLocalActorNamespace { get(actorId: string): Fetcher; } @@ -576,7 +610,7 @@ type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high"; interface DurableObjectNamespaceNewUniqueIdOptions { jurisdiction?: DurableObjectJurisdiction; } -type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "apac-ne" | "apac-se" | "oc" | "afr" | "me"; +type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "oc" | "afr" | "me"; type DurableObjectRoutingMode = "primary-only"; interface DurableObjectNamespaceGetDurableObjectOptions { locationHint?: DurableObjectLocationHint; @@ -672,7 +706,6 @@ interface DurableObjectFacets { get(name: string, getStartupOptions: () => FacetStartupOptions | Promise>): Fetcher; abort(name: string, reason: any): void; delete(name: string): void; - clone(src: string, dst: string): void; } interface FacetStartupOptions { id?: DurableObjectId | string; @@ -3348,28 +3381,6 @@ interface EventSourceEventSourceInit { withCredentials?: boolean; fetcher?: Fetcher; } -interface ExecOutput { - readonly stdout: ArrayBuffer; - readonly stderr: ArrayBuffer; - readonly exitCode: number; -} -interface ContainerExecOptions { - cwd?: string; - env?: Record; - user?: string; - stdin?: ReadableStream | "pipe"; - stdout?: "pipe" | "ignore"; - stderr?: "pipe" | "ignore" | "combined"; -} -interface ExecProcess { - readonly stdin: WritableStream | null; - readonly stdout: ReadableStream | null; - readonly stderr: ReadableStream | null; - readonly pid: number; - readonly exitCode: Promise; - output(): Promise; - kill(signal?: number): void; -} interface Container { get running(): boolean; start(options?: ContainerStartupOptions): void; @@ -3383,7 +3394,6 @@ interface Container { snapshotDirectory(options: ContainerDirectorySnapshotOptions): Promise; snapshotContainer(options: ContainerSnapshotOptions): Promise; interceptOutboundHttps(addr: string, binding: Fetcher): Promise; - exec(cmd: string[], options?: ContainerExecOptions): Promise; } interface ContainerDirectorySnapshot { id: string; @@ -3554,58 +3564,11 @@ declare abstract class Performance { } interface Tracing { enterSpan(name: string, callback: (span: Span, ...args: A) => T, ...args: A): T; - startActiveSpan(name: string, callback: (span: Span, ...args: A) => T, ...args: A): T; Span: typeof Span; } declare abstract class Span { get isTraced(): boolean; setAttribute(key: string, value?: (boolean | number | string)): void; - end(): void; -} -/** - * Represents the identity of a user authenticated via Cloudflare Access. - * This matches the result of calling /cdn-cgi/access/get-identity. - * - * The exact structure of the returned object depends on the identity provider - * configuration for the Access application. The fields below represent commonly - * available properties, but additional provider-specific fields may be present. - */ -interface CloudflareAccessIdentity extends Record { - /** The user's email address, if available from the identity provider. */ - email?: string; - /** The user's display name. */ - name?: string; - /** The user's unique identifier. */ - user_uuid?: string; - /** The Cloudflare account ID. */ - account_id?: string; - /** Login timestamp (Unix epoch seconds). */ - iat?: number; - /** The user's IP address at authentication time. */ - ip?: string; - /** Authentication methods used (e.g., "pwd"). */ - amr?: string[]; - /** Identity provider information. */ - idp?: { - id: string; - type: string; - }; - /** Geographic information about where the user authenticated. */ - geo?: { - country: string; - }; - /** Group memberships from the identity provider. */ - groups?: Array<{ - id: string; - name: string; - email?: string; - }>; - /** Device posture check results, keyed by check ID. */ - devicePosture?: Record; - /** True if the user connected via Cloudflare WARP. */ - is_warp?: boolean; - /** True if the user is authenticated via Cloudflare Gateway. */ - is_gateway?: boolean; } // ============================================================================ // Agent Memory @@ -11236,8 +11199,6 @@ interface RequestInitCfProperties extends Record { * (e.g. { '200-299': 86400, '404': 1, '500-599': 0 }) */ cacheTtlByStatus?: Record; - /** Controls how responses with a `Vary` header are cached for this request. */ - vary?: RequestInitCfPropertiesVary; /** * Explicit Cache-Control header value to set on the response stored in cache. * This gives full control over cache directives (e.g. 'public, max-age=3600, s-maxage=86400'). @@ -11275,17 +11236,6 @@ interface RequestInitCfProperties extends Record { cacheReserveMinimumFileSize?: number; scrapeShield?: boolean; apps?: boolean; - /** - * Controls whether an outbound gRPC-web subrequest from this Worker is - * converted to gRPC at the Cloudflare edge. - * - * - `"passthrough"`: forward the subrequest unchanged as gRPC-web (default). - * - `"convert"`: convert the gRPC-web subrequest to gRPC at the edge. - * - * Provides per-request control over the same edge conversion behavior - * gated by the `auto_grpc_convert` compatibility flag. - */ - grpcWeb?: "passthrough" | "convert"; image?: RequestInitCfPropertiesImage; minify?: RequestInitCfPropertiesImageMinify; mirage?: boolean; @@ -11306,63 +11256,6 @@ interface RequestInitCfProperties extends Record { */ resolveOverride?: string; } -/** - * Controls how Workers Standard Vary handles a request header listed by an - * origin `Vary` response header: - * - * - `"normalize"`: normalize the request header value before it is used in the - * cache variance key. - * - `"passthrough"`: use the raw request header value in the cache variance - * key. - * - `"bypass"`: bypass cache when the header appears in the origin `Vary` - * response header. - */ -type RequestInitCfPropertiesVaryAction = "normalize" | "passthrough" | "bypass"; -/** Configuration for Workers Standard Vary support. */ -interface RequestInitCfPropertiesVary { - /** The fallback action for varied request headers not listed in `headers`. */ - default: RequestInitCfPropertiesVaryHeader; - /** - * Lowercase request header names and their Vary configuration. - * - * The `accept` header can include `media_types`, the `accept-language` - * header can include `languages`, and other headers support only `action`. - */ - headers?: RequestInitCfPropertiesVaryHeaders; -} -/** Common Vary behavior for a single request header. */ -interface RequestInitCfPropertiesVaryHeader { - /** How this request header contributes to cache variance. */ - action: RequestInitCfPropertiesVaryAction; -} -/** Vary behavior for the `accept` request header. */ -interface RequestInitCfPropertiesVaryAcceptHeader extends RequestInitCfPropertiesVaryHeader { - /** - * Media types to keep when normalizing the `Accept` request header. - * - * Named `media_types` to match the serialized `cf.vary` configuration. - */ - media_types?: string[]; -} -/** Vary behavior for the `accept-language` request header. */ -interface RequestInitCfPropertiesVaryAcceptLanguageHeader extends RequestInitCfPropertiesVaryHeader { - /** - * Language tags to keep when normalizing the `Accept-Language` request - * header. - */ - languages?: string[]; -} -/** - * Lowercase request header names and their Vary behavior. - * - * The index signature allows arbitrary custom request headers beyond the - * well-known `accept` and `accept-language` specializations. - */ -interface RequestInitCfPropertiesVaryHeaders { - accept?: RequestInitCfPropertiesVaryAcceptHeader; - "accept-language"?: RequestInitCfPropertiesVaryAcceptLanguageHeader; - [header: string]: RequestInitCfPropertiesVaryHeader | RequestInitCfPropertiesVaryAcceptHeader | RequestInitCfPropertiesVaryAcceptLanguageHeader | undefined; -} interface BasicImageTransformations { /** * Maximum width in image pixels. The value must be an integer. @@ -14071,7 +13964,7 @@ declare namespace TailStream { interface ConnectEventInfo { readonly type: "connect"; } - type EventOutcome = "ok" | "canceled" | "exception" | "unknown" | "killSwitch" | "daemonDown" | "exceededCpu" | "exceededMemory" | "loadShed" | "responseStreamDisconnected" | "scriptNotFound" | "internalError" | "exceededWallTime"; + type EventOutcome = "ok" | "canceled" | "exception" | "unknown" | "killSwitch" | "daemonDown" | "exceededCpu" | "exceededMemory" | "loadShed" | "responseStreamDisconnected" | "scriptNotFound" | "internalError"; interface ScriptVersion { readonly id: string; readonly tag?: string; From 0a4b8cb072519e0e21e261b0895842715f6d6607 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Mon, 6 Jul 2026 19:08:41 -0700 Subject: [PATCH 2/2] chore(ci): regenerate worker-configuration.d.ts against the current workerd release --- worker-configuration.d.ts | 152 +++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 3 deletions(-) diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index 579c1c0d6c..24ad833367 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -521,7 +521,8 @@ interface ExecutionContext { readonly exports: Cloudflare.Exports; readonly props: Props; cache?: CacheContext; - tracing?: Tracing; + readonly access?: CloudflareAccessContext; + tracing: Tracing; } type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise; type ExportedHandlerConnectHandler = (socket: Socket, env: Env, ctx: ExecutionContext) => void | Promise; @@ -577,6 +578,10 @@ interface CachePurgeOptions { interface CacheContext { purge(options: CachePurgeOptions): Promise; } +interface CloudflareAccessContext { + readonly aud: string; + getIdentity(): Promise; +} declare abstract class ColoLocalActorNamespace { get(actorId: string): Fetcher; } @@ -610,7 +615,7 @@ type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high"; interface DurableObjectNamespaceNewUniqueIdOptions { jurisdiction?: DurableObjectJurisdiction; } -type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "oc" | "afr" | "me"; +type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "apac-ne" | "apac-se" | "oc" | "afr" | "me"; type DurableObjectRoutingMode = "primary-only"; interface DurableObjectNamespaceGetDurableObjectOptions { locationHint?: DurableObjectLocationHint; @@ -706,6 +711,7 @@ interface DurableObjectFacets { get(name: string, getStartupOptions: () => FacetStartupOptions | Promise>): Fetcher; abort(name: string, reason: any): void; delete(name: string): void; + clone(src: string, dst: string): void; } interface FacetStartupOptions { id?: DurableObjectId | string; @@ -3381,6 +3387,28 @@ interface EventSourceEventSourceInit { withCredentials?: boolean; fetcher?: Fetcher; } +interface ExecOutput { + readonly stdout: ArrayBuffer; + readonly stderr: ArrayBuffer; + readonly exitCode: number; +} +interface ContainerExecOptions { + cwd?: string; + env?: Record; + user?: string; + stdin?: ReadableStream | "pipe"; + stdout?: "pipe" | "ignore"; + stderr?: "pipe" | "ignore" | "combined"; +} +interface ExecProcess { + readonly stdin: WritableStream | null; + readonly stdout: ReadableStream | null; + readonly stderr: ReadableStream | null; + readonly pid: number; + readonly exitCode: Promise; + output(): Promise; + kill(signal?: number): void; +} interface Container { get running(): boolean; start(options?: ContainerStartupOptions): void; @@ -3394,6 +3422,7 @@ interface Container { snapshotDirectory(options: ContainerDirectorySnapshotOptions): Promise; snapshotContainer(options: ContainerSnapshotOptions): Promise; interceptOutboundHttps(addr: string, binding: Fetcher): Promise; + exec(cmd: string[], options?: ContainerExecOptions): Promise; } interface ContainerDirectorySnapshot { id: string; @@ -3564,11 +3593,58 @@ declare abstract class Performance { } interface Tracing { enterSpan(name: string, callback: (span: Span, ...args: A) => T, ...args: A): T; + startActiveSpan(name: string, callback: (span: Span, ...args: A) => T, ...args: A): T; Span: typeof Span; } declare abstract class Span { get isTraced(): boolean; setAttribute(key: string, value?: (boolean | number | string)): void; + end(): void; +} +/** + * Represents the identity of a user authenticated via Cloudflare Access. + * This matches the result of calling /cdn-cgi/access/get-identity. + * + * The exact structure of the returned object depends on the identity provider + * configuration for the Access application. The fields below represent commonly + * available properties, but additional provider-specific fields may be present. + */ +interface CloudflareAccessIdentity extends Record { + /** The user's email address, if available from the identity provider. */ + email?: string; + /** The user's display name. */ + name?: string; + /** The user's unique identifier. */ + user_uuid?: string; + /** The Cloudflare account ID. */ + account_id?: string; + /** Login timestamp (Unix epoch seconds). */ + iat?: number; + /** The user's IP address at authentication time. */ + ip?: string; + /** Authentication methods used (e.g., "pwd"). */ + amr?: string[]; + /** Identity provider information. */ + idp?: { + id: string; + type: string; + }; + /** Geographic information about where the user authenticated. */ + geo?: { + country: string; + }; + /** Group memberships from the identity provider. */ + groups?: Array<{ + id: string; + name: string; + email?: string; + }>; + /** Device posture check results, keyed by check ID. */ + devicePosture?: Record; + /** True if the user connected via Cloudflare WARP. */ + is_warp?: boolean; + /** True if the user is authenticated via Cloudflare Gateway. */ + is_gateway?: boolean; } // ============================================================================ // Agent Memory @@ -11199,6 +11275,8 @@ interface RequestInitCfProperties extends Record { * (e.g. { '200-299': 86400, '404': 1, '500-599': 0 }) */ cacheTtlByStatus?: Record; + /** Controls how responses with a `Vary` header are cached for this request. */ + vary?: RequestInitCfPropertiesVary; /** * Explicit Cache-Control header value to set on the response stored in cache. * This gives full control over cache directives (e.g. 'public, max-age=3600, s-maxage=86400'). @@ -11236,6 +11314,17 @@ interface RequestInitCfProperties extends Record { cacheReserveMinimumFileSize?: number; scrapeShield?: boolean; apps?: boolean; + /** + * Controls whether an outbound gRPC-web subrequest from this Worker is + * converted to gRPC at the Cloudflare edge. + * + * - `"passthrough"`: forward the subrequest unchanged as gRPC-web (default). + * - `"convert"`: convert the gRPC-web subrequest to gRPC at the edge. + * + * Provides per-request control over the same edge conversion behavior + * gated by the `auto_grpc_convert` compatibility flag. + */ + grpcWeb?: "passthrough" | "convert"; image?: RequestInitCfPropertiesImage; minify?: RequestInitCfPropertiesImageMinify; mirage?: boolean; @@ -11256,6 +11345,63 @@ interface RequestInitCfProperties extends Record { */ resolveOverride?: string; } +/** + * Controls how Workers Standard Vary handles a request header listed by an + * origin `Vary` response header: + * + * - `"normalize"`: normalize the request header value before it is used in the + * cache variance key. + * - `"passthrough"`: use the raw request header value in the cache variance + * key. + * - `"bypass"`: bypass cache when the header appears in the origin `Vary` + * response header. + */ +type RequestInitCfPropertiesVaryAction = "normalize" | "passthrough" | "bypass"; +/** Configuration for Workers Standard Vary support. */ +interface RequestInitCfPropertiesVary { + /** The fallback action for varied request headers not listed in `headers`. */ + default: RequestInitCfPropertiesVaryHeader; + /** + * Lowercase request header names and their Vary configuration. + * + * The `accept` header can include `media_types`, the `accept-language` + * header can include `languages`, and other headers support only `action`. + */ + headers?: RequestInitCfPropertiesVaryHeaders; +} +/** Common Vary behavior for a single request header. */ +interface RequestInitCfPropertiesVaryHeader { + /** How this request header contributes to cache variance. */ + action: RequestInitCfPropertiesVaryAction; +} +/** Vary behavior for the `accept` request header. */ +interface RequestInitCfPropertiesVaryAcceptHeader extends RequestInitCfPropertiesVaryHeader { + /** + * Media types to keep when normalizing the `Accept` request header. + * + * Named `media_types` to match the serialized `cf.vary` configuration. + */ + media_types?: string[]; +} +/** Vary behavior for the `accept-language` request header. */ +interface RequestInitCfPropertiesVaryAcceptLanguageHeader extends RequestInitCfPropertiesVaryHeader { + /** + * Language tags to keep when normalizing the `Accept-Language` request + * header. + */ + languages?: string[]; +} +/** + * Lowercase request header names and their Vary behavior. + * + * The index signature allows arbitrary custom request headers beyond the + * well-known `accept` and `accept-language` specializations. + */ +interface RequestInitCfPropertiesVaryHeaders { + accept?: RequestInitCfPropertiesVaryAcceptHeader; + "accept-language"?: RequestInitCfPropertiesVaryAcceptLanguageHeader; + [header: string]: RequestInitCfPropertiesVaryHeader | RequestInitCfPropertiesVaryAcceptHeader | RequestInitCfPropertiesVaryAcceptLanguageHeader | undefined; +} interface BasicImageTransformations { /** * Maximum width in image pixels. The value must be an integer. @@ -13964,7 +14110,7 @@ declare namespace TailStream { interface ConnectEventInfo { readonly type: "connect"; } - type EventOutcome = "ok" | "canceled" | "exception" | "unknown" | "killSwitch" | "daemonDown" | "exceededCpu" | "exceededMemory" | "loadShed" | "responseStreamDisconnected" | "scriptNotFound" | "internalError"; + type EventOutcome = "ok" | "canceled" | "exception" | "unknown" | "killSwitch" | "daemonDown" | "exceededCpu" | "exceededMemory" | "loadShed" | "responseStreamDisconnected" | "scriptNotFound" | "internalError" | "exceededWallTime"; interface ScriptVersion { readonly id: string; readonly tag?: string;