diff --git a/.github/actions/fern-generate/action.yml b/.github/actions/fern-generate/action.yml index 11c9be35e..47dfb80ef 100644 --- a/.github/actions/fern-generate/action.yml +++ b/.github/actions/fern-generate/action.yml @@ -56,8 +56,8 @@ runs: done # One command, the same one a contributor runs locally. generate-sdk.ts owns - # spec resolution, the heap-tuned generator image and the custom runtime - # overlay; the TypeScript package additionally chains sdk-map generation. + # spec resolution and the custom runtime overlay; the TypeScript package + # additionally chains sdk-map generation. - name: Generate ${{ inputs.language }} SDK shell: bash env: @@ -82,9 +82,9 @@ runs: fi echo "==> $(find "${OUTPUT_PATH}" -type f | wc -l | tr -d ' ') files in ${OUTPUT_PATH}" - # Saved after generation so the tarball holds the heap-tuned wrapper that - # generate-sdk.ts retagged, not the stock image. actions/cache uploads the - # path at post-job. + # Saved after generation so the tarball holds exactly the images the run + # pulled. Fern's TypeScript generator runs natively and pulls none. + # actions/cache uploads the path at post-job. - name: Save generator image if: steps.image-cache.outputs.cache-hit != 'true' shell: bash diff --git a/.github/workflows/sdk-typescript.yml b/.github/workflows/sdk-typescript.yml index e0f450933..62aaa4ce9 100644 --- a/.github/workflows/sdk-typescript.yml +++ b/.github/workflows/sdk-typescript.yml @@ -6,16 +6,17 @@ # # The job runs the workspace's own target, `pnpm --filter @cloudflare/forge-sdk-ts # generate`, which is the same command a contributor runs locally. Everything -# else -- spec resolution, the heap-tuned generator image, the custom runtime -# overlay, sdk-map -- lives in the packages, not here. +# else -- spec resolution, the custom runtime overlay, sdk-map -- lives in the +# packages, not here. # # Differences from the GitLab job, and why: # - GitLab bootstrapped Node from a pinned tarball because its VM shell runner -# had none. GitHub-hosted runners ship Docker and Node tooling, so +# had none. GitHub-hosted runners ship Node tooling, so # actions/setup-node + pnpm/action-setup replace ~35 lines of before_script. -# - GitLab sharded TypeScript generation into 3x2304 MB to fit an 8 GB runner. -# ubuntu-latest has 16 GB, so a single heap-tuned generator image is enough -# and the shard/merge machinery is dropped. +# - GitLab sharded TypeScript generation into 3x2304 MB Docker containers to +# fit an 8 GB runner. Fern's TypeScript generator now runs natively in one +# process with an 8 GB heap on ubuntu-latest (16 GB), so the shard/merge +# machinery is gone. name: SDK / TypeScript # GitHub Actions does not support YAML anchors, so the path filter is repeated. diff --git a/packages/cloudflare-fern-config/fern/generators.yml b/packages/cloudflare-fern-config/fern/generators.yml index 50675e44c..9cf5292c8 100644 --- a/packages/cloudflare-fern-config/fern/generators.yml +++ b/packages/cloudflare-fern-config/fern/generators.yml @@ -14,10 +14,12 @@ # - The other eight languages generate one folder per language under # packages/cloudflare-forge-sdk-{lang}. # -# The TypeScript generator needs a larger heap than the official image ships -# with: Cloudflare's IR is ~242 MB and node exits 133 on it with the default -# max-old-space-size. generate-sdk.ts retags a heap-tuned wrapper over the -# official fernapi/* name before invoking Fern. +# TypeScript runs Cloudflare's fork of Fern's TypeScript generator +# (fernapi/fern-typescript-sdk 3.88.3), vendored and run natively via +# `local-command` instead of the Docker image; see vendor/codegen-cli/README.md +# at the repo root. The command runs with this directory as cwd. Cloudflare's +# IR is ~242 MB, so node needs a larger heap than its default. The other +# languages still run the official fernapi/* images. api: specs: - openapi: ./openapi.json @@ -40,7 +42,11 @@ groups: typescript-sdk: generators: - name: fernapi/fern-typescript-sdk - version: 3.80.1 + version: 3.88.3 + local-command: + - node + - --max-old-space-size=8192 + - ../node_modules/@cloudflare/codegen-typescript-sdk/cli.cjs output: location: local-file-system path: ../../cloudflare-forge-sdk-ts/src/_generated diff --git a/packages/cloudflare-fern-config/package.json b/packages/cloudflare-fern-config/package.json index ba8bac64b..3e60ce44d 100644 --- a/packages/cloudflare-fern-config/package.json +++ b/packages/cloudflare-fern-config/package.json @@ -13,9 +13,10 @@ "generate": "tsx scripts/generate-sdk.ts" }, "devDependencies": { + "@cloudflare/codegen-typescript-sdk": "file:../../vendor/codegen-cli/cloudflare-codegen-typescript-sdk-3.88.3.tgz", "@cloudflare/forge": "workspace:*", "@types/node": "26.0.1", - "fern-api": "5.112.0", + "fern-api": "file:../../vendor/codegen-cli/fern-api-5.112.0.tgz", "tsx": "4.22.4", "typescript": "6.0.3" }, diff --git a/packages/cloudflare-fern-config/scripts/generate-sdk.ts b/packages/cloudflare-fern-config/scripts/generate-sdk.ts index 0aef4334f..4b3187ff6 100644 --- a/packages/cloudflare-fern-config/scripts/generate-sdk.ts +++ b/packages/cloudflare-fern-config/scripts/generate-sdk.ts @@ -8,22 +8,15 @@ // A language maps to the "-sdk" group in fern/generators.yml. // // Everything a generation run needs lives here, so CI and a laptop run the same -// command: spec resolution, the heap-tuned generator image, the Fern invocation, -// and the TypeScript custom-runtime overlay. +// command: spec resolution, the Fern invocation, and the TypeScript +// custom-runtime overlay. // -// Requires a Docker daemon -- Fern generators are distributed as container -// images and this uses `fern generate --local`. +// TypeScript runs the vendored fork of Fern's TypeScript generator natively +// (local-command in generators.yml). The other languages need a Docker daemon: +// their generators are distributed as container images and this uses +// `fern generate --local`. import { execFileSync } from 'node:child_process'; -import { - copyFileSync, - cpSync, - existsSync, - mkdirSync, - readFileSync, - readdirSync, - statSync, - writeFileSync, -} from 'node:fs'; +import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { applyFernCompatibilityFixes } from '@cloudflare/forge/fern-openapi-compat'; @@ -34,10 +27,6 @@ const FERN_DIR = join(PKG_ROOT, 'fern'); const GENERATORS = join(FERN_DIR, 'generators.yml'); const SPEC = join(FERN_DIR, 'openapi.json'); -// Heap for the TypeScript generator container. Cloudflare's IR is ~242 MB and -// the official image exits 133 partway through generation with node's default. -const TYPESCRIPT_HEAP_MB = process.env['FERN_TYPESCRIPT_HEAP_MB'] ?? '8192'; - const languages = process.argv.slice(2); if (languages.length === 0) { languages.push('typescript'); @@ -57,12 +46,21 @@ function run(command: string, args: string[], options: { cwd: string; env?: Node } // ------------------------------------------------------------------ preflight -try { - execFileSync('docker', ['info'], { stdio: 'ignore' }); -} catch { - fail('a running Docker daemon is required (fern generate --local runs generators as containers)'); +if (languages.some((language) => language !== 'typescript')) { + try { + execFileSync('docker', ['info'], { stdio: 'ignore' }); + } catch { + fail('a running Docker daemon is required (fern generate --local runs these generators as containers)'); + } } +const generatorGroups = new Set( + readFileSync(GENERATORS, 'utf8') + .split('\n') + .map((line) => /^ {2}(\S+-sdk):$/.exec(line)?.[1]) + .filter((group) => group !== undefined), +); + // ----------------------------------------------------------------------- spec const FORGE_OPENAPI_TAG = /^openapi\.v\d{8}\.\d+$/; @@ -125,150 +123,46 @@ if (fernCompatibilityFixCount > 0) { console.log(`==> Applied ${fernCompatibilityFixCount} Fern OpenAPI compatibility repair(s)`); } -// ---------------------------------------------------------- generator image -- -// Read the pinned image for a group straight out of generators.yml so the tag can -// never drift from the generator Fern is about to run. Deliberately a line scan -// rather than a YAML dependency: the shape is a two-line literal. -function generatorImage(language: string): string | undefined { - const lines = readFileSync(GENERATORS, 'utf8').split('\n'); - let inGroup = false; - let name: string | undefined; - for (const line of lines) { - const trimmed = line.trim(); - if (trimmed === `${language}-sdk:`) { - inGroup = true; - continue; - } - if (!inGroup) continue; - const nameMatch = /^-\s+name:\s*(\S+)$/.exec(trimmed); - if (nameMatch) name = nameMatch[1]; - const versionMatch = /^version:\s*(\S+)$/.exec(trimmed); - if (versionMatch && name !== undefined) return `${name}:${versionMatch[1]}`; - } - return undefined; -} - -// Fern always runs the official image name, so a heap-tuned wrapper has to be -// retagged over it. The label makes this idempotent across runs. -function ensureHeapTunedImage(image: string, heapMb: string) { - let current = ''; - try { - current = execFileSync( - 'docker', - ['image', 'inspect', image, '--format', '{{index .Config.Labels "forge.heap-mb"}}'], - { - encoding: 'utf8', - }, - ).trim(); - } catch { - current = ''; - } - if (current === heapMb) { - console.log(`==> ${image} already heap-tuned (${heapMb} MB)`); - return; - } - console.log(`==> Building heap-tuned ${image} (${heapMb} MB)`); - run('docker', ['pull', image]); - // Only the heap is tuned: node rejects --stack-size in NODE_OPTIONS, and - // RLIMIT_STACK is 8 MB in these images with no way to pass --ulimit. - const dockerfile = `FROM ${image}\nENV NODE_OPTIONS="--max-old-space-size=${heapMb}"\n`; - execFileSync('docker', ['build', '--tag', image, '--label', `forge.heap-mb=${heapMb}`, '-'], { - input: dockerfile, - stdio: ['pipe', 'inherit', 'inherit'], - }); -} - // ------------------------------------------------------------------ generate -- const groupArgs: string[] = []; for (const language of languages) { - const image = generatorImage(language); - if (image === undefined) { + if (!generatorGroups.has(`${language}-sdk`)) { fail(`no '${language}-sdk' group in ${GENERATORS}`); } - if (language === 'typescript') { - ensureHeapTunedImage(image, TYPESCRIPT_HEAP_MB); - } groupArgs.push('--group', `${language}-sdk`); } +const sdkTs = join(REPO_ROOT, 'packages', 'cloudflare-forge-sdk-ts'); +const generated = join(sdkTs, 'src', '_generated'); +if (languages.includes('typescript')) { + // The custom runtime (and its .fernignore) is re-installed below, so start from + // an empty dir: with a .fernignore present Fern copies output through a temp + // git repo, which costs ~45s on this SDK. + rmSync(generated, { recursive: true, force: true }); +} + // cwd must be at or below the package root so the CLI discovers fern/. run( join(PKG_ROOT, 'node_modules', '.bin', 'fern'), ['generate', ...groupArgs, '--local', '--no-prompt', '--force', '--log-level', 'info'], { cwd: PKG_ROOT, - // Heap for the Fern CLI itself (validation + IR), separate from the container. + // Heap for the Fern CLI itself (validation + IR); the TypeScript generator + // sets its own in generators.yml. env: { ...process.env, NODE_OPTIONS: process.env['NODE_OPTIONS'] ?? '--max-old-space-size=4096' }, }, ); // ------------------------------------------------------- typescript post-step -- if (languages.includes('typescript')) { - const sdkTs = join(REPO_ROOT, 'packages', 'cloudflare-forge-sdk-ts'); - const generated = join(sdkTs, 'src', '_generated'); - - // Fern can exit 0 having produced nothing useful when a generator crashes - // inside the container, so assert on a real entrypoint before going further. + // Fern can exit 0 having produced nothing useful when a generator crashes, + // so assert on a real entrypoint before going further. if (!isNonEmptyFile(join(generated, 'index.ts'))) { fail(`Fern exited 0 but ${join(generated, 'index.ts')} is missing or empty`); } - // Fern 3.80.1 emits invalid dotted/subtraction expressions for multipart - // fields whose wire names are not identifiers. Use bracket access so the - // request type and serializer retain the exact wire name. - const openApi = JSON.parse(readFileSync(SPEC, 'utf8')) as Record; - const propertyNames = new Set(); - for (const pathItem of Object.values((openApi['paths'] as Record | undefined) ?? {})) { - if (pathItem === null || typeof pathItem !== 'object') continue; - for (const operation of Object.values(pathItem as Record)) { - if (operation === null || typeof operation !== 'object') continue; - const requestBody = (operation as Record)['requestBody']; - if (requestBody === null || typeof requestBody !== 'object') continue; - const content = (requestBody as Record)['content']; - if (content === null || typeof content !== 'object') continue; - const multipart = (content as Record)['multipart/form-data']; - if (multipart === null || typeof multipart !== 'object') continue; - const schema = (multipart as Record)['schema']; - if (schema === null || typeof schema !== 'object') continue; - const properties = (schema as Record)['properties']; - if (properties === null || typeof properties !== 'object') continue; - for (const wireName of Object.keys(properties as Record)) { - if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(wireName)) propertyNames.add(wireName); - } - } - } - - let correctedAccessors = 0; - const correctFile = (path: string): void => { - for (const entry of readdirSync(path, { withFileTypes: true })) { - const entryPath = join(path, entry.name); - if (entry.isDirectory()) { - correctFile(entryPath); - } else if (entry.name.endsWith('.ts')) { - const source = readFileSync(entryPath, 'utf8'); - let corrected = source; - for (const wireName of propertyNames) { - const emittedAccess = [...wireName] - .map((character) => (character === '-' ? '\\s*-\\s*' : character.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) - .join(''); - corrected = corrected.replace( - new RegExp(`request\\.${emittedAccess}`, 'g'), - `request[${JSON.stringify(wireName)}]`, - ); - } - if (corrected !== source) { - correctedAccessors += 1; - writeFileSync(entryPath, corrected); - } - } - } - }; - correctFile(generated); - console.log(`==> Corrected multipart property access in ${correctedAccessors} generated file(s)`); - // allowCustomFetcher expects the Cloudflare envelope unwrap and URL join - // overrides layered over the generated core. The .fernignore travels with them - // so a later regeneration preserves them. + // overrides layered over the generated core. console.log('==> Installing TypeScript custom runtime'); cpSync(join(sdkTs, 'custom'), generated, { recursive: true }); } diff --git a/packages/cloudflare-forge-sdk-ts/custom/.fernignore b/packages/cloudflare-forge-sdk-ts/custom/.fernignore index 5eb045639..be90977ed 100644 --- a/packages/cloudflare-forge-sdk-ts/custom/.fernignore +++ b/packages/cloudflare-forge-sdk-ts/custom/.fernignore @@ -1,5 +1,5 @@ -# Cloudflare custom runtime. Installed after each generate (generate-fern.ts wipes -# the output dir first). Sources: packages/cloudflare-forge-sdk-ts/custom/. +# Cloudflare custom runtime. Installed after each generate (cloudflare-fern-config's +# generate-sdk.ts wipes the output dir first). Sources: packages/cloudflare-forge-sdk-ts/custom/. # - getResponseBody / unwrapCloudflareEnvelope: envelope unwrap for payload-first types (fix-26). # - url/join: preserve query/fragment suffixes so passthrough callers can pass query params in the path. core/fetcher/getResponseBody.ts diff --git a/packages/cloudflare-forge-sdk-ts/custom/core/url/join.ts b/packages/cloudflare-forge-sdk-ts/custom/core/url/join.ts index 1a87c2ed7..671360eb4 100644 --- a/packages/cloudflare-forge-sdk-ts/custom/core/url/join.ts +++ b/packages/cloudflare-forge-sdk-ts/custom/core/url/join.ts @@ -1,6 +1,6 @@ // Cloudflare custom runtime: query-string / fragment preservation in join(). // -// Divergence from stock Fern TS 3.80.1 `core/url/join.ts`: +// Divergence from stock Fern TS 3.88.3 `core/url/join.ts`: // - A path segment carrying a `?query` or `#fragment` suffix has that suffix // peeled off and reattached to the URL's search/hash, instead of being folded // into the pathname. diff --git a/packages/cloudflare-forge-sdk-ts/package.json b/packages/cloudflare-forge-sdk-ts/package.json index f141de919..df11340fa 100644 --- a/packages/cloudflare-forge-sdk-ts/package.json +++ b/packages/cloudflare-forge-sdk-ts/package.json @@ -33,7 +33,7 @@ "@cloudflare/fern-config": "workspace:*", "@cloudflare/forge": "workspace:*", "@types/node": "26.0.1", - "fern-api": "5.112.0", + "fern-api": "file:../../vendor/codegen-cli/fern-api-5.112.0.tgz", "tsx": "4.22.4", "typescript": "6.0.3" }, diff --git a/packages/cloudflare-forge-sdk-ts/scripts/build-generator-image.sh b/packages/cloudflare-forge-sdk-ts/scripts/build-generator-image.sh deleted file mode 100755 index 23dbe4a30..000000000 --- a/packages/cloudflare-forge-sdk-ts/scripts/build-generator-image.sh +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env bash -# Build a local Docker image that wraps an official Fern generator with: -# 1. NODE_OPTIONS=--max-old-space-size=${FERN_NODE_MAX_OLD_SPACE_SIZE:-8192} -# (Cloudflare's IR is ~250 MB and -# OOMs the default Node heap as exit code 133). -# 2. Cached wrapper reuse when the version, limits, CA, and patches match. -# 3. A scoped host CA installed in the container's CA store, so go mod / pip / -# cargo can verify TLS through corporate MITM proxies (e.g. Cloudflare for -# Teams). -# -# Retag the result over the official `fernapi/...` name so the host CLI's -# IR-version mapping stays intact. -# -# Usage: ./scripts/build-generator-image.sh -# e.g. ./scripts/build-generator-image.sh go 1.46.0 -set -euo pipefail - -lang=${1:?usage: $0 } -version=${2:?usage: $0 } -base="fernapi/fern-${lang}-sdk:${version}" -node_max_old_space_size=${FERN_NODE_MAX_OLD_SPACE_SIZE:-8192} -node_stack_size=${FERN_NODE_STACK_SIZE:-8192} - -if ! command -v docker >/dev/null 2>&1; then - echo "error: docker is required to build the Fern generator wrapper" >&2 - echo " run this on a Docker-capable machine/runner." >&2 - exit 1 -fi - -script_dir=$(cd "$(dirname "$0")" && pwd) - -patch_fern_cli_for_capture() { - local fern_cli - fern_cli=$(node --input-type=module - "${PKG_ROOT:-$(cd "${script_dir}/.." && pwd)}" <<'JS' -import { createRequire } from 'node:module'; -import { resolve } from 'node:path'; -const require = createRequire(resolve(process.argv[2], 'package.json')); -process.stdout.write(require.resolve('fern-api/cli.cjs')); -JS -) - node "${cli_patch_script}" "${fern_cli}" -} - -tmp=$(mktemp -d) -trap 'rm -rf "${tmp}"' EXIT - -# Export a scoped host trust root. Empty on Linux/CI is fine; the image just -# falls back to its bundled certs. -# -# Set FERN_GENERATOR_HOST_CA_PEM to a PEM file for exact control. On macOS, the -# fallback searches for a certificate common name containing -# FERN_GENERATOR_HOST_CA_COMMON_NAME (default: Cloudflare) instead of exporting -# the entire system keychain. -if [ -n "${FERN_GENERATOR_HOST_CA_PEM:-}" ]; then - cp "${FERN_GENERATOR_HOST_CA_PEM}" "${tmp}/host-ca.pem" -elif [ "$(uname)" = "Darwin" ]; then - security find-certificate -a -p -c "${FERN_GENERATOR_HOST_CA_COMMON_NAME:-Cloudflare}" \ - /Library/Keychains/System.keychain \ - /System/Library/Keychains/SystemRootCertificates.keychain \ - > "${tmp}/host-ca.pem" 2>/dev/null || true -fi -[ -s "${tmp}/host-ca.pem" ] || : > "${tmp}/host-ca.pem" - -# Exact version tags are immutable for our purposes. Reuse an already-built -# wrapper when its inputs match instead of rebuilding on every generation. -build_script="${script_dir}/build-generator-image.sh" -patch_script="${script_dir}/patch-fern-typescript-cli.mjs" -cli_patch_script="${script_dir}/patch-fern-cli-capture.mjs" -wrapper_key=$(node --input-type=module - "${lang}" "${version}" "${node_max_old_space_size}" "${node_stack_size}" "${tmp}/host-ca.pem" "${build_script}" "${patch_script}" <<'JS' -import { createHash } from 'node:crypto'; -import { readFileSync } from 'node:fs'; -const [, , lang, version, heap, stack, caPath, buildPath, patchPath] = process.argv; -const hash = createHash('sha256'); -for (const value of [lang, version, heap, stack]) hash.update(`${value}\0`); -hash.update(readFileSync(caPath)); -hash.update(readFileSync(buildPath)); -if (lang === 'typescript') { - hash.update(readFileSync(patchPath)); - hash.update(readFileSync(new URL('patch-fern-cli-capture.mjs', new URL(`file://${buildPath}`)))); - hash.update(readFileSync(new URL('fern-typescript-entrypoint.sh', new URL(`file://${buildPath}`)))); -} -process.stdout.write(hash.digest('hex')); -JS -) -current_wrapper_key=$(docker image inspect "${base}" --format '{{ index .Config.Labels "com.cloudflare.fern-wrapper-key" }}' 2>/dev/null || true) -if [ "${current_wrapper_key}" = "${wrapper_key}" ]; then - if [ "${lang}" = "typescript" ]; then - patch_fern_cli_for_capture - fi - echo "reusing cached Cloudflare wrapper ${base}" - exit 0 -fi - -docker pull "${base}" - -# TypeScript: apply Cloudflare-specific Fern generator patches (SDK-variable -# path params, non-identifier multipart property access, and undiscriminated- -# union base properties used by stream-condition literals). Replaces residual -# postgen codemods and preserves Fern extension semantics missing in TS 3.80.1. -if [ "${lang}" = "typescript" ]; then - echo "==> Patching fern-typescript-sdk:${version} for request wrappers / multipart keys / union base properties / HeadersIterator" - container_id=$(docker create "${base}") - docker cp "${container_id}:/cli.cjs" "${tmp}/cli.cjs" - docker cp "${container_id}:/assets/core-utilities/src/core/fetcher/Headers.ts" "${tmp}/Headers.ts" - docker rm "${container_id}" >/dev/null - node "${patch_script}" "${tmp}/cli.cjs" "${tmp}/Headers.ts" -fi - -orig_entrypoint=$(docker inspect "${base}" --format '{{json .Config.Entrypoint}}') -# Node-based generators (TS/Go/Java/C#/PHP/Ruby/Swift/Rust) recurse deeply -# through schema trees and overflow the default 1 MB stack. Splice -# --stack-size=8192 into the argv between `node` and the JS entry script. -# Python-based generator (fern-python-sdk uses /usr/local/bin/python) is -# untouched. -new_entrypoint=$(node - "${orig_entrypoint}" "${node_stack_size}" <<'JS' -const argv = JSON.parse(process.argv[2]); -const stackSize = process.argv[3]; -const command = argv[0]?.split('/').at(-1); -const stackArg = `--stack-size=${stackSize}`; -const next = command === 'node' && !argv.some((arg) => arg.startsWith('--stack-size=')) - ? [argv[0], stackArg, ...argv.slice(1)] - : argv; -process.stdout.write(JSON.stringify(next)); -JS -) - -typescript_copies="" -typescript_entrypoint_copy="" -if [ "${lang}" = "typescript" ] && [ -f "${tmp}/cli.cjs" ]; then - cp "${script_dir}/fern-typescript-entrypoint.sh" "${tmp}/fern-typescript-entrypoint.sh" - typescript_copies=$'COPY cli.cjs /cli.cjs\nCOPY Headers.ts /assets/core-utilities/src/core/fetcher/Headers.ts\n' - typescript_entrypoint_copy=$'COPY fern-typescript-entrypoint.sh /fern-typescript-entrypoint.sh\n' - # npm archives do not preserve executable bits for non-bin package files. - # Invoke the packaged wrapper through sh so extracted tarballs work on Linux. - new_entrypoint='["/bin/sh","/fern-typescript-entrypoint.sh"]' -fi - -cat > "${tmp}/Dockerfile" </dev/null 2>&1; then \ - update-ca-certificates; \ - elif command -v update-ca-trust >/dev/null 2>&1; then \ - cp /usr/local/share/ca-certificates/host-ca.crt /etc/pki/ca-trust/source/anchors/; \ - update-ca-trust; \ - else \ - cat /usr/local/share/ca-certificates/host-ca.crt >> /etc/ssl/certs/ca-certificates.crt 2>/dev/null || true; \ - fi -DOCKERFILE - -docker build --quiet -t "${base}" "${tmp}" -if [ "${lang}" = "typescript" ]; then - patch_fern_cli_for_capture -fi -echo "retagged ${base} with NODE_OPTIONS=--max-old-space-size=${node_max_old_space_size}, --stack-size=${node_stack_size}, and scoped host CA" diff --git a/packages/cloudflare-forge-sdk-ts/scripts/fern-typescript-entrypoint.sh b/packages/cloudflare-forge-sdk-ts/scripts/fern-typescript-entrypoint.sh deleted file mode 100755 index 54c13e512..000000000 --- a/packages/cloudflare-forge-sdk-ts/scripts/fern-typescript-entrypoint.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh -set -eu - -if [ -n "${CLOUDFLARE_FERN_CAPTURE_DIR:-}" ]; then - capture_dir=/capture - mkdir -p "${capture_dir}" - cp /fern/ir.json "${capture_dir}/ir.json" - cp /fern/config.json "${capture_dir}/config.json" - - # Fern requires an output archive before copying local results. - capture_output=$(mktemp -d) - printf '// Canonical IR capture only.\n' > "${capture_output}/index.ts" - (cd "${capture_output}" && zip -q /fern/output/output.zip index.ts) - exit 0 -fi - -if [ "${CLOUDFLARE_FERN_SHARD_COUNT:-1}" -gt 1 ]; then - shard_config=$(mktemp) - node --input-type=commonjs - /fern/config.json "${shard_config}" <<'JS' -const { readFileSync, writeFileSync } = require('node:fs'); -const config = JSON.parse(readFileSync(process.argv[2], 'utf8')); -config.output.snippetFilepath = null; -writeFileSync(process.argv[3], JSON.stringify(config)); -JS - set -- "${shard_config}" -fi - -exec node \ - "--stack-size=${FERN_NODE_STACK_SIZE:-8192}" \ - --enable-source-maps \ - /cli.cjs \ - "$@" diff --git a/packages/cloudflare-forge-sdk-ts/scripts/generate-fern-typescript-sharded.sh b/packages/cloudflare-forge-sdk-ts/scripts/generate-fern-typescript-sharded.sh deleted file mode 100755 index 516e04885..000000000 --- a/packages/cloudflare-forge-sdk-ts/scripts/generate-fern-typescript-sharded.sh +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env bash -# Generate TypeScript from one Fern IR across deterministic file shards. -set -euo pipefail - -SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) -PKG_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd) -REPO_ROOT=$(cd "${PKG_ROOT}/../.." && pwd) -WORKSPACE_ROOT=${FERN_TYPESCRIPT_WORKSPACE_ROOT:-${PKG_ROOT}} -OUT_DIR=${FERN_TYPESCRIPT_OUT_DIR:-${REPO_ROOT}/packages/forge-sdk-ts/src/_generated} -GENERATOR_IMAGE="fernapi/fern-typescript-sdk:${FERN_TYPESCRIPT_GENERATOR_VERSION:?set FERN_TYPESCRIPT_GENERATOR_VERSION}" -SHARD_COUNT=${FERN_TYPESCRIPT_SHARD_COUNT:-3} -SHARD_HEAP_MB=${FERN_TYPESCRIPT_SHARD_HEAP_MB:-2304} - -for command in docker unzip node; do - if ! command -v "${command}" >/dev/null 2>&1; then - echo "error: ${command} is required for sharded TypeScript generation" >&2 - exit 1 - fi -done - -if ! [[ "${SHARD_COUNT}" =~ ^[1-9][0-9]*$ ]]; then - echo "error: FERN_TYPESCRIPT_SHARD_COUNT must be a positive integer" >&2 - exit 1 -fi -if ! [[ "${SHARD_HEAP_MB}" =~ ^[1-9][0-9]*$ ]]; then - echo "error: FERN_TYPESCRIPT_SHARD_HEAP_MB must be a positive integer" >&2 - exit 1 -fi - -work_dir=$(mktemp -d) -backup_dir=$(mktemp -d) -cleanup() { - local status=$? - if [ "${status}" -ne 0 ]; then - rm -rf "${OUT_DIR}" - mkdir -p "${OUT_DIR}" - cp -R "${backup_dir}/." "${OUT_DIR}/" 2>/dev/null || true - echo "error: TypeScript shard generation failed; restored ${OUT_DIR}" >&2 - fi - rm -rf "${work_dir}" "${backup_dir}" -} -trap cleanup EXIT - -mkdir -p "${OUT_DIR}" -cp -R "${OUT_DIR}/." "${backup_dir}/" 2>/dev/null || true - -capture_dir="${work_dir}/capture" -mkdir -p "${capture_dir}" -capture_started=$(date +%s) -echo "==> Capturing Fern canonical TypeScript IR" -( - cd "${WORKSPACE_ROOT}" - if [ -n "${FERN_TYPESCRIPT_FERN_CLI:-}" ]; then - CLOUDFLARE_FERN_CAPTURE_DIR="${capture_dir}" \ - FERN_NO_VERSION_REDIRECTION=1 \ - "${FERN_TYPESCRIPT_FERN_CLI}" "${FERN_TYPESCRIPT_FERN_CLI_SCRIPT:?set FERN_TYPESCRIPT_FERN_CLI_SCRIPT}" \ - generate --group typescript-sdk --local --force - else - CLOUDFLARE_FERN_CAPTURE_DIR="${capture_dir}" \ - FERN_NO_VERSION_REDIRECTION=1 \ - pnpm exec fern generate --group typescript-sdk --local --force - fi -) - -echo "==> Canonical IR captured in $(( $(date +%s) - capture_started ))s" - -if [ ! -s "${capture_dir}/ir.json" ] || [ ! -s "${capture_dir}/config.json" ]; then - echo "error: Fern did not capture the canonical IR/config" >&2 - exit 1 -fi - -shards_started=$(date +%s) -pids=() -for ((index = 0; index < SHARD_COUNT; index++)); do - shard_dir="${work_dir}/shards/${index}" - mkdir -p "${shard_dir}/output" "${shard_dir}/files" - cp "${capture_dir}/config.json" "${shard_dir}/config.json" - # Avoid copying the 225 MB IR for each shard. - ln "${capture_dir}/ir.json" "${shard_dir}/ir.json" 2>/dev/null \ - || cp "${capture_dir}/ir.json" "${shard_dir}/ir.json" - - echo "==> TypeScript shard $((index + 1))/${SHARD_COUNT}" - docker run --rm \ - --user root \ - -e "NODE_OPTIONS=--max-old-space-size=${SHARD_HEAP_MB}" \ - -e "CLOUDFLARE_FERN_SHARD_COUNT=${SHARD_COUNT}" \ - -e "CLOUDFLARE_FERN_SHARD_INDEX=${index}" \ - -v "${shard_dir}/config.json:/fern/config.json:ro" \ - -v "${shard_dir}/ir.json:/fern/ir.json:ro" \ - -v "${shard_dir}/output:/fern/output" \ - "${GENERATOR_IMAGE}" /fern/config.json \ - > "${shard_dir}/generator.log" 2>&1 & - pids+=("$!") -done - -failed=0 -for ((index = 0; index < SHARD_COUNT; index++)); do - if ! wait "${pids[index]}"; then - failed=1 - echo "error: TypeScript shard $((index + 1))/${SHARD_COUNT} failed" >&2 - cat "${work_dir}/shards/${index}/generator.log" >&2 - elif [ "${FERN_TYPESCRIPT_SHARD_LOGS:-0}" = "1" ]; then - cat "${work_dir}/shards/${index}/generator.log" - else - grep '\[TIMING\]' "${work_dir}/shards/${index}/generator.log" || true - fi -done -[ "${failed}" -eq 0 ] || exit 1 -echo "==> ${SHARD_COUNT} TypeScript shards finished in $(( $(date +%s) - shards_started ))s" - -for ((index = 0; index < SHARD_COUNT; index++)); do - archive="${work_dir}/shards/${index}/output/output.zip" - if [ ! -s "${archive}" ]; then - echo "error: TypeScript shard $((index + 1)) produced no output archive" >&2 - exit 1 - fi - unzip -q "${archive}" -d "${work_dir}/shards/${index}/files" -done - -shard_outputs=() -for ((index = 0; index < SHARD_COUNT; index++)); do - shard_outputs+=("${work_dir}/shards/${index}/files") -done - -rm -rf "${OUT_DIR}" -node "${SCRIPT_DIR}/merge-fern-typescript-shards.mjs" "${OUT_DIR}" "${shard_outputs[@]}" - -# Some imports become complete only after merge. -cid=$(docker create "${GENERATOR_IMAGE}") -if ! docker cp "${cid}:/assets/asIs/oxfmtrc.json" "${work_dir}/oxfmtrc.json"; then - docker rm "${cid}" >/dev/null 2>&1 || true - exit 1 -fi -docker rm "${cid}" >/dev/null -cp "${work_dir}/oxfmtrc.json" "${OUT_DIR}/.oxfmtrc.json" -docker run --rm \ - --user root \ - -v "${OUT_DIR}:/work" \ - --entrypoint /bin/sh \ - "${GENERATOR_IMAGE}" \ - -c 'cd /work && oxfmt .' -rm "${OUT_DIR}/.oxfmtrc.json" - -if [ "${FERN_TYPESCRIPT_INSTALL_CUSTOM_RUNTIME:-1}" = "1" ]; then - echo "==> typescript-sdk: installing Fern custom runtime" - pnpm exec tsx "${SCRIPT_DIR}/install-fern-custom-runtime.ts" -fi - -echo "==> typescript-sdk: wrote ${OUT_DIR} from ${SHARD_COUNT} canonical-IR shards" diff --git a/packages/cloudflare-forge-sdk-ts/scripts/generate-fern.ts b/packages/cloudflare-forge-sdk-ts/scripts/generate-fern.ts index 90d1917b0..307a4180b 100644 --- a/packages/cloudflare-forge-sdk-ts/scripts/generate-fern.ts +++ b/packages/cloudflare-forge-sdk-ts/scripts/generate-fern.ts @@ -2,7 +2,7 @@ // // Shared Fern/OpenAPI generation lives in @cloudflare/fern-config so every SDK // language consumes the same spec and generator configuration. That package -// handles the generator image, the Fern run and the custom runtime overlay; this +// handles the Fern run and the custom runtime overlay; this // wrapper adds the TypeScript-only sdk-map on top. import { execFileSync } from 'node:child_process'; import { existsSync, statSync } from 'node:fs'; diff --git a/packages/cloudflare-forge-sdk-ts/scripts/merge-fern-typescript-shards.mjs b/packages/cloudflare-forge-sdk-ts/scripts/merge-fern-typescript-shards.mjs deleted file mode 100755 index a71800abe..000000000 --- a/packages/cloudflare-forge-sdk-ts/scripts/merge-fern-typescript-shards.mjs +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env node -import { createHash } from 'node:crypto'; -import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'; -import { dirname, join, relative, resolve } from 'node:path'; - -const [outputDir, ...shardDirs] = process.argv.slice(2); -if (!outputDir || shardDirs.length === 0) { - console.error('usage: merge-fern-typescript-shards.mjs ...'); - process.exit(2); -} - -const allowedAggregateConflicts = new Set([ - 'api/index.ts', - 'api/resources/index.ts', - 'core/exports.ts', - 'core/index.ts', - 'index.ts', -]); - -const seen = new Map(); -const caseInsensitivePaths = new Map(); -const aggregateVariants = new Map(); - -async function filesUnder(root, current = root) { - const files = []; - for (const entry of await readdir(current, { withFileTypes: true })) { - const path = join(current, entry.name); - if (entry.isDirectory()) files.push(...(await filesUnder(root, path))); - else files.push({ path, relativePath: relative(root, path) }); - } - return files; -} - -await rm(outputDir, { recursive: true, force: true }); -await mkdir(outputDir, { recursive: true }); - -for (const shardDir of shardDirs) { - for (const { path, relativePath } of await filesUnder(shardDir)) { - const normalizedPath = relativePath.toLowerCase(); - const previousPath = caseInsensitivePaths.get(normalizedPath); - if (previousPath && previousPath !== relativePath) { - throw new Error(`Fern shard case-only path collision: ${previousPath} and ${relativePath}`); - } - caseInsensitivePaths.set(normalizedPath, relativePath); - const content = await readFile(path); - const hash = createHash('sha256').update(content).digest('hex'); - const previous = seen.get(relativePath); - if (previous && previous !== hash && !allowedAggregateConflicts.has(relativePath)) { - throw new Error(`Fern shard conflict: ${relativePath}`); - } - if (allowedAggregateConflicts.has(relativePath)) { - const variants = aggregateVariants.get(relativePath) ?? []; - variants.push(content.toString('utf8')); - aggregateVariants.set(relativePath, variants); - } - if (!previous) { - seen.set(relativePath, hash); - const destination = join(outputDir, relativePath); - await mkdir(dirname(destination), { recursive: true }); - await writeFile(destination, content); - } - } -} - -// Rebuild the distributed root resource barrel in Fern order. -const resourceIndexPath = 'api/resources/index.ts'; -const resources = new Map(); -for (const source of aggregateVariants.get(resourceIndexPath) ?? []) { - for (const line of source.trim().split('\n')) { - if (!line) continue; - const resource = line.match(/from\s+["']\.\/([^/"']+)/)?.[1]; - if (!resource) throw new Error(`Unexpected Fern resource export: ${line}`); - const lines = resources.get(resource) ?? new Set(); - lines.add(line); - resources.set(resource, lines); - } -} -const rankResourceExport = (line) => { - if (line.startsWith('export * as ')) return 0; - if (line.includes('/client/requests')) return 1; - if (line.includes('/types')) return 2; - return 3; -}; -const resourceLines = []; -for (const resource of [...resources.keys()].sort((a, b) => a.localeCompare(b))) { - resourceLines.push( - ...[...resources.get(resource)].sort((a, b) => rankResourceExport(a) - rankResourceExport(b) || a.localeCompare(b)), - ); -} -await writeFile(join(outputDir, resourceIndexPath), `${resourceLines.join('\n')}\n`); - -// The remaining Fern root/core barrels are also distributed across shards. -// Preserve first-seen Fern order while retaining every unique export. -for (const aggregatePath of allowedAggregateConflicts) { - if (aggregatePath === resourceIndexPath) continue; - const lines = new Set(); - for (const source of aggregateVariants.get(aggregatePath) ?? []) { - for (const line of source.trim().split('\n')) { - if (!line) continue; - if (!line.startsWith('export ')) { - throw new Error(`Unexpected Fern aggregate export in ${aggregatePath}: ${line}`); - } - lines.add(line); - } - } - await writeFile(join(outputDir, aggregatePath), `${[...lines].join('\n')}\n`); -} - -// Resolve ESM paths against the complete tree. -const allFiles = await filesUnder(outputDir); -const filePaths = new Set(allFiles.map(({ path }) => path)); -const relativeSpecifier = /((?:from|import)\s+["']|export\s+\*\s+from\s+["'])(\.\.?\/[^"']+?)(["'])/g; -const unresolvedSpecifiers = []; -const unresolvedLimit = 20; -for (const { path } of allFiles) { - if (!path.endsWith('.ts')) continue; - const source = await readFile(path, 'utf8'); - const next = source.replace(relativeSpecifier, (match, prefix, specifier, suffix) => { - if (/\.(?:js|json|node)$/.test(specifier)) return match; - const target = resolve(dirname(path), specifier); - if (filePaths.has(`${target}.ts`)) return `${prefix}${specifier}.js${suffix}`; - if (filePaths.has(join(target, 'index.ts'))) return `${prefix}${specifier}/index.js${suffix}`; - if (unresolvedSpecifiers.length < unresolvedLimit) { - unresolvedSpecifiers.push(`${relative(outputDir, path)}: ${specifier}`); - } - return match; - }); - if (next !== source) await writeFile(path, next); -} -if (unresolvedSpecifiers.length > 0) { - console.warn( - `warning: unresolved ESM specifiers (showing up to ${unresolvedLimit}):\n${unresolvedSpecifiers.join('\n')}`, - ); -} - -console.log(`merged ${seen.size} Fern files from ${shardDirs.length} canonical-IR shards`); diff --git a/packages/cloudflare-forge-sdk-ts/scripts/patch-fern-cli-capture.mjs b/packages/cloudflare-forge-sdk-ts/scripts/patch-fern-cli-capture.mjs deleted file mode 100755 index fa3acdcfb..000000000 --- a/packages/cloudflare-forge-sdk-ts/scripts/patch-fern-cli-capture.mjs +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -// Targets fern-api 5.112.0. Fail closed when its minified anchors change. -import { readFileSync, writeFileSync } from 'node:fs'; - -const path = process.argv[2]; -if (!path) { - console.error('usage: patch-fern-cli-capture.mjs '); - process.exit(2); -} - -const original = readFileSync(path, 'utf8'); -let next = original; - -const envFrom = 'kfs=["FERN_STACK_TRACK",F2e]'; -const envTo = 'kfs=["FERN_STACK_TRACK",F2e,"CLOUDFLARE_FERN_CAPTURE_DIR"]'; -if (next.includes(envFrom)) { - next = next.replace(envFrom, envTo); - console.log('patched Fern container environment for canonical IR capture'); -} else if (!next.includes(envTo)) { - console.error('Fern runContainer environment marker not found'); - process.exit(1); -} - -const bindFrom = - 'let d=()=>T3h({logger:e,imageName:t,args:r,binds:n,envVars:i,ports:o,removeAfterCompletion:a,writeLogsToFile:s,runner:c,pull:u,platform:l,signal:p});'; -const bindTo = - 'process.env.CLOUDFLARE_FERN_CAPTURE_DIR&&n.push(`${process.env.CLOUDFLARE_FERN_CAPTURE_DIR}:/capture`);' + bindFrom; -if (next.includes(bindTo)) { - // Already patched. -} else if (next.includes(bindFrom)) { - next = next.replace(bindFrom, bindTo); - console.log('patched Fern canonical IR capture bind mount'); -} else { - console.error('Fern ContainerExecutionEnvironment bind marker not found'); - process.exit(1); -} - -if (next !== original) { - writeFileSync(path, next); - console.log('wrote', path); -} else { - console.log('Fern CLI already supports canonical IR capture'); -} diff --git a/packages/cloudflare-forge-sdk-ts/scripts/patch-fern-typescript-cli.mjs b/packages/cloudflare-forge-sdk-ts/scripts/patch-fern-typescript-cli.mjs deleted file mode 100644 index 736426a59..000000000 --- a/packages/cloudflare-forge-sdk-ts/scripts/patch-fern-typescript-cli.mjs +++ /dev/null @@ -1,269 +0,0 @@ -#!/usr/bin/env node -// Patch fern-typescript-sdk cli.cjs for Cloudflare: -// 2) Emit element-access expressions for non-identifier multipart file keys. -// 3) Preserve base properties on undiscriminated union TypeScript aliases. -// Fern's IR carries stream-condition as a literal base property, but 3.80.1 -// drops undiscriminatedUnion.baseProperties in generated types. -// 4) Align the native-fetch Headers fallback with TypeScript 6 HeadersIterator. -// 5) Support deterministic file ownership for canonical-IR generation shards. -import { readFileSync, writeFileSync } from 'node:fs'; - -const path = process.argv[2]; -const headersPath = process.argv[3]; -if (!path) { - console.error('usage: patch-fern-typescript-cli.mjs [Headers.ts]'); - process.exit(2); -} - -const original = readFileSync(path, 'utf8'); -let next = original; - -// ---- 2) multipart non-identifier keys ---- -// Replace: -// createIdentifier(getParameterNameForFile({...})) -// with a helper that returns PropertyAccess or ElementAccess expressions. -const helperName = 'createInlineFilePropertyAccess'; -if (!next.includes(`function ${helperName}(`)) { - const getParamFnStart = next.indexOf( - 'function getParameterNameForFile({ property: property3, wrapperName, includeSerdeLayer, retainOriginalCasing, inlineFileProperties, caseConverter }) {', - ); - if (getParamFnStart < 0) { - console.error('getParameterNameForFile not found'); - process.exit(1); - } - const appendMarker = '// ../client-class-generator/lib/endpoints/utils/appendPropertyToFormData.js'; - const appendAt = next.indexOf(appendMarker, getParamFnStart); - if (appendAt < 0) { - console.error('appendPropertyToFormData marker not found after getParameterNameForFile'); - process.exit(1); - } - - // Keep getParameterNameForFile returning only the property name (not request.name). - // The helper builds the request. / request["name"] expression. - const replacement = `function getParameterNameForFile({ property: property3, wrapperName, includeSerdeLayer, retainOriginalCasing, inlineFileProperties, caseConverter }) { - const parameterName = includeSerdeLayer && !retainOriginalCasing ? caseConverter.camelUnsafe(property3.key) : getOriginalName2(property3.key); - return parameterName; -} -function ${helperName}({ property: property3, wrapperName, includeSerdeLayer, retainOriginalCasing, inlineFileProperties, caseConverter }) { - const parameterName = getParameterNameForFile({ - property: property3, - wrapperName, - includeSerdeLayer, - retainOriginalCasing, - inlineFileProperties, - caseConverter - }); - if (!inlineFileProperties) { - return import_ts_morph67.ts.factory.createIdentifier(parameterName); - } - const object = import_ts_morph67.ts.factory.createIdentifier(wrapperName); - if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(parameterName)) { - return import_ts_morph67.ts.factory.createPropertyAccessExpression(object, import_ts_morph67.ts.factory.createIdentifier(parameterName)); - } - return import_ts_morph67.ts.factory.createElementAccessExpression(object, import_ts_morph67.ts.factory.createStringLiteral(parameterName)); -} - -`; - next = next.slice(0, getParamFnStart) + replacement + next.slice(appendAt); - - // Original: createIdentifier(getParameterNameForFile({ ... })) - // Target: createInlineFilePropertyAccess({ ... }) - // Must remove the outer createIdentifier(...) wrapping paren. - const callRe = /import_ts_morph67\.ts\.factory\.createIdentifier\(getParameterNameForFile\(([\s\S]*?)\)\)/g; - let callCount = 0; - next = next.replace(callRe, (_m, inner) => { - callCount += 1; - return `${helperName}(${inner})`; - }); - if (callCount === 0) { - console.error('createIdentifier(getParameterNameForFile(...)) call sites not found'); - process.exit(1); - } - console.log(`patched ${callCount} multipart file property access call site(s)`); -} else { - console.log('already has createInlineFilePropertyAccess helper'); -} - -// ---- 3) undiscriminated-union base properties ---- -// The direct importer correctly emits `baseProperties: [{ stream: literal }]` -// for conditional streaming unions. TS 3.80.1 ignores that IR field, so both the -// public type and serialized request lose `stream: true`. Intersect every member -// with a generated base-property type literal. This applies generically to any -// undiscriminated union base properties and is a no-op for ordinary unions. -const undiscriminatedClass = 'var GeneratedUndiscriminatedUnionTypeImpl = class extends AbstractGeneratedType {'; -const undiscriminatedFrom = ` generateForInlineUnion(context2) { - const typeReferenceNodes = this.shape.members.map((member) => this.getTypeReferenceNode(context2, member)); - return { - typeNode: import_ts_morph101.ts.factory.createUnionTypeNode(typeReferenceNodes.map((ref) => ref.typeNode)), - requestTypeNode: import_ts_morph101.ts.factory.createUnionTypeNode(typeReferenceNodes.map((ref) => ref.requestTypeNode ?? ref.typeNode)), - responseTypeNode: import_ts_morph101.ts.factory.createUnionTypeNode(typeReferenceNodes.map((ref) => ref.responseTypeNode ?? ref.typeNode)) - }; - }`; -const undiscriminatedTo = ` getBasePropertiesTypeNode(context2, whatFor = "normal") { - const properties = this.shape.baseProperties ?? []; - if (properties.length === 0) { - return void 0; - } - return import_ts_morph101.ts.factory.createTypeLiteralNode(properties.map((property3) => { - const type = context2.type.getReferenceToType(property3.valueType); - const typeNode = whatFor === "request" - ? type.requestTypeNode ?? type.typeNode - : whatFor === "response" - ? type.responseTypeNode ?? type.typeNode - : type.typeNode; - return import_ts_morph101.ts.factory.createPropertySignature( - void 0, - getPropertyKey(getWireValue2(property3.name)), - void 0, - typeNode - ); - })); - } - withBaseProperties(context2, node2, whatFor = "normal") { - const base = this.getBasePropertiesTypeNode(context2, whatFor); - return base == null ? node2 : import_ts_morph101.ts.factory.createIntersectionTypeNode([base, node2]); - } - generateForInlineUnion(context2) { - const typeReferenceNodes = this.shape.members.map((member) => this.getTypeReferenceNode(context2, member)); - return { - typeNode: import_ts_morph101.ts.factory.createUnionTypeNode(typeReferenceNodes.map((ref) => this.withBaseProperties(context2, ref.typeNode))), - requestTypeNode: import_ts_morph101.ts.factory.createUnionTypeNode(typeReferenceNodes.map((ref) => this.withBaseProperties(context2, ref.requestTypeNode ?? ref.typeNode, "request"))), - responseTypeNode: import_ts_morph101.ts.factory.createUnionTypeNode(typeReferenceNodes.map((ref) => this.withBaseProperties(context2, ref.responseTypeNode ?? ref.typeNode, "response"))) - }; - }`; -const aliasFrom = ` node: this.getTypeNodeForMember(context2, value)`; -const aliasTo = ` node: this.withBaseProperties(context2, this.getTypeNodeForMember(context2, value))`; -const requestAliasFrom = ` node: this.applyIndexSignatureSubstitution(context2, value.member, requestNode)`; -const requestAliasTo = ` node: this.withBaseProperties(context2, this.applyIndexSignatureSubstitution(context2, value.member, requestNode), "request")`; -const responseAliasFrom = ` node: this.applyIndexSignatureSubstitution(context2, value.member, responseNode)`; -const responseAliasTo = ` node: this.withBaseProperties(context2, this.applyIndexSignatureSubstitution(context2, value.member, responseNode), "response")`; -if (next.includes(undiscriminatedFrom)) { - const classStart = next.indexOf(undiscriminatedClass); - const classEnd = next.indexOf('// ../../model/type-generator/lib/union/GeneratedUnionTypeImpl.js', classStart); - if (classStart < 0 || classEnd < 0) { - console.error('GeneratedUndiscriminatedUnionTypeImpl boundaries not found'); - process.exit(1); - } - const before = next.slice(0, classStart); - let body = next.slice(classStart, classEnd); - const after = next.slice(classEnd); - for (const [from, to, label] of [ - [undiscriminatedFrom, undiscriminatedTo, 'inline union'], - [aliasFrom, aliasTo, 'normal alias'], - [requestAliasFrom, requestAliasTo, 'request alias'], - [responseAliasFrom, responseAliasTo, 'response alias'], - ]) { - if (!body.includes(from)) { - console.error(`undiscriminated-union ${label} target not found`); - process.exit(1); - } - body = body.replace(from, to); - } - next = before + body + after; - console.log('patched undiscriminated-union base properties into TS aliases'); -} else if ( - next.includes('getBasePropertiesTypeNode(context2, whatFor = "normal")') && - next.includes('GeneratedUndiscriminatedUnionTypeImpl') -) { - console.log('already preserves undiscriminated-union base properties'); -} else { - console.error('expected GeneratedUndiscriminatedUnionTypeImpl body not found'); - process.exit(1); -} - -// ---- 5) canonical-IR generation shards ---- -// Assign ownership after Fern resolves canonical paths and names. -const shardHelperName = 'cloudflareShardOwnsFile'; -if (!next.includes(`function ${shardHelperName}(`)) { - const sdkGeneratorMarker = 'var SdkGenerator = class {'; - const sdkGeneratorAt = next.indexOf(sdkGeneratorMarker); - if (sdkGeneratorAt < 0) { - console.error('SdkGenerator marker not found'); - process.exit(1); - } - const shardHelpers = `function cloudflareShardHash(value) { - let hash = 2166136261; - for (const character of value) { - hash ^= character.charCodeAt(0); - hash = Math.imul(hash, 16777619); - } - return hash >>> 0; -} -function ${shardHelperName}(filepath) { - const count = Math.max(1, Number.parseInt(process.env.CLOUDFLARE_FERN_SHARD_COUNT ?? "1", 10)); - const index = Math.max(0, Number.parseInt(process.env.CLOUDFLARE_FERN_SHARD_INDEX ?? "0", 10)); - if (count <= 1) { - return true; - } - if (filepath === "/src/Client.ts" || filepath === "/src/api/resources/index.ts" || !filepath.startsWith("/src/api/")) { - return index === 0; - } - const segments = filepath.split("/").filter(Boolean); - const resourceIndex = segments.indexOf("resources"); - return resourceIndex >= 0 && segments[resourceIndex + 1] - ? cloudflareShardHash(segments[resourceIndex + 1]) % count === index - : index === 0; -} -`; - next = next.slice(0, sdkGeneratorAt) + shardHelpers + next.slice(sdkGeneratorAt); - - const sourceFileFrom = ` withSourceFile({ run, filepath, addExportTypeModifier, dynamicExportTypeModifier, packagePath = this.relativePackagePath }) { - filepath.rootDir = packagePath; - const filepathStr = this.exportsManager.convertExportedFilePathToFilePath(filepath); - this.context.logger.debug(\`Generating \${filepathStr}\`);`; - const sourceFileTo = ` withSourceFile({ run, filepath, addExportTypeModifier, dynamicExportTypeModifier, packagePath = this.relativePackagePath }) { - filepath.rootDir = packagePath; - const filepathStr = this.exportsManager.convertExportedFilePathToFilePath(filepath); - if (!${shardHelperName}(filepathStr)) { - return; - } - this.context.logger.debug(\`Generating \${filepathStr}\`);`; - if (!next.includes(sourceFileFrom)) { - console.error('withSourceFile shard hook not found'); - process.exit(1); - } - next = next.replace(sourceFileFrom, sourceFileTo); - - // The reducer resolves ESM imports against the merged tree. - const esmFrom = ` if (customConfig.useLegacyExports === false) { - await fixImportsForEsm(persistedTypescriptProject.getRootDirectory()); - }`; - const esmTo = ` if (customConfig.useLegacyExports === false && Number.parseInt(process.env.CLOUDFLARE_FERN_SHARD_COUNT ?? "1", 10) <= 1) { - await fixImportsForEsm(persistedTypescriptProject.getRootDirectory()); - }`; - if (!next.includes(esmFrom)) { - console.error('ESM postprocess shard hook not found'); - process.exit(1); - } - next = next.replace(esmFrom, esmTo); - console.log('patched deterministic canonical-IR shard ownership'); -} else { - console.log('already supports canonical-IR shard ownership'); -} - -if (next !== original) { - writeFileSync(path, next); - console.log('wrote', path); -} else { - console.log('no cli.cjs changes'); -} - -// ---- 4) TypeScript 6 DOM HeadersIterator compatibility ---- -// TS 3.80.1's fallback Headers class declares IterableIterator, while current -// lib.dom.d.ts requires HeadersIterator (which includes the new disposable -// iterator fields). Runtime behavior is identical; align the declared types. -if (headersPath) { - const originalHeaders = readFileSync(headersPath, 'utf8'); - const headersNext = originalHeaders - .replaceAll('IterableIterator<[string, string]>', 'HeadersIterator<[string, string]>') - .replaceAll('IterableIterator', 'HeadersIterator'); - if (headersNext !== originalHeaders) { - writeFileSync(headersPath, headersNext); - console.log('patched Headers.ts iterator return types for TypeScript 6'); - } else if (originalHeaders.includes('HeadersIterator<')) { - console.log('Headers.ts already uses HeadersIterator'); - } else { - console.error('expected Headers.ts IterableIterator declarations not found'); - process.exit(1); - } -} diff --git a/packages/cloudflare-forge-transformer-sdk-ts/package.json b/packages/cloudflare-forge-transformer-sdk-ts/package.json index ffb92bdd1..14c8e2d33 100644 --- a/packages/cloudflare-forge-transformer-sdk-ts/package.json +++ b/packages/cloudflare-forge-transformer-sdk-ts/package.json @@ -24,16 +24,18 @@ "typecheck:source": "tsc -p tsconfig.source.json --noEmit" }, "dependencies": { - "fern-api": "5.112.0", "oxfmt": "0.57.0", + "oxlint": "1.72.0", "tar": "7.5.20", "typescript": "6.0.3" }, "devDependencies": { + "@cloudflare/codegen-typescript-sdk": "file:../../vendor/codegen-cli/cloudflare-codegen-typescript-sdk-3.88.3.tgz", "@cloudflare/forge": "workspace:*", "@types/node": "26.0.1", "@types/tar": "^7.0.87", "esbuild": "0.27.3", + "fern-api": "file:../../vendor/codegen-cli/fern-api-5.112.0.tgz", "tsx": "4.22.4" }, "engines": { diff --git a/packages/cloudflare-forge-transformer-sdk-ts/scripts/build-package.ts b/packages/cloudflare-forge-transformer-sdk-ts/scripts/build-package.ts index 19aa23e17..7158fd052 100644 --- a/packages/cloudflare-forge-transformer-sdk-ts/scripts/build-package.ts +++ b/packages/cloudflare-forge-transformer-sdk-ts/scripts/build-package.ts @@ -1,4 +1,5 @@ import { chmodSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { buildSync } from 'esbuild'; @@ -16,7 +17,7 @@ type OpenApiDocument = { paths?: Record rmSync(DIST, { recursive: true, force: true }); mkdirSync(join(DIST, 'generator', 'custom', 'core', 'fetcher'), { recursive: true }); -mkdirSync(join(DIST, 'vendor'), { recursive: true }); +mkdirSync(join(DIST, 'vendor', 'codegen-cli'), { recursive: true }); buildSync({ entryPoints: [join(PKG_ROOT, 'scripts', 'generate-from-openapi.ts')], @@ -24,7 +25,7 @@ buildSync({ platform: 'node', format: 'esm', target: 'node22', - external: ['fern-api', 'typescript'], + external: ['typescript'], outfile: join(DIST, 'cli.js'), }); @@ -77,30 +78,6 @@ for (const pathItem of Object.values(openapi.paths ?? {})) { writeFileSync(join(DIST, 'openapi.json'), `${JSON.stringify(openapi, null, 2)}\n`); console.log(`==> Packaged OpenAPI spec from ${spec} with metadata from ${metadataSpec}`); -cpSync( - join(REPO_ROOT, 'packages', 'cloudflare-forge-sdk-ts', 'scripts', 'fern-typescript-entrypoint.sh'), - join(DIST, 'generator', 'fern-typescript-entrypoint.sh'), -); -cpSync( - join(REPO_ROOT, 'packages', 'cloudflare-forge-sdk-ts', 'scripts', 'patch-fern-cli-capture.mjs'), - join(DIST, 'generator', 'patch-fern-cli-capture.mjs'), -); -cpSync( - join(REPO_ROOT, 'packages', 'cloudflare-forge-sdk-ts', 'scripts', 'merge-fern-typescript-shards.mjs'), - join(DIST, 'generator', 'merge-fern-typescript-shards.mjs'), -); -cpSync( - join(REPO_ROOT, 'packages', 'cloudflare-forge-sdk-ts', 'scripts', 'generate-fern-typescript-sharded.sh'), - join(DIST, 'generator', 'generate-fern-typescript-sharded.sh'), -); -cpSync( - join(REPO_ROOT, 'packages', 'cloudflare-forge-sdk-ts', 'scripts', 'build-generator-image.sh'), - join(DIST, 'generator', 'build-generator-image.sh'), -); -cpSync( - join(REPO_ROOT, 'packages', 'cloudflare-forge-sdk-ts', 'scripts', 'patch-fern-typescript-cli.mjs'), - join(DIST, 'generator', 'patch-fern-typescript-cli.mjs'), -); cpSync( join(REPO_ROOT, 'packages', 'cloudflare-forge-sdk-ts', 'custom', '.fernignore'), join(DIST, 'generator', 'custom', '.fernignore'), @@ -114,6 +91,18 @@ cpSync( join(DIST, 'generator', 'custom', 'core', 'fetcher', 'unwrapCloudflareEnvelope.ts'), ); +// Ship the vendored Fern CLI and Fern TypeScript generator forks inside dist so +// consumers of the packed tarball never resolve a file: dependency on this +// repo's vendor/ directory. The generator directory carries cli.cjs plus the +// assets and native dprint binaries it loads relative to itself. +const vendorRequire = createRequire(import.meta.url); +cpSync(vendorRequire.resolve('fern-api/cli.cjs'), join(DIST, 'vendor', 'codegen-cli', 'cli.cjs')); +cpSync( + dirname(vendorRequire.resolve('@cloudflare/codegen-typescript-sdk/cli.cjs')), + join(DIST, 'vendor', 'codegen-typescript-sdk'), + { recursive: true, filter: (source) => !source.endsWith('.map') }, +); + chmodSync(join(DIST, 'cli.js'), 0o755); console.log('==> Built self-contained @cloudflare/forge-transformer-sdk-ts generator'); diff --git a/packages/cloudflare-forge-transformer-sdk-ts/scripts/generate-from-openapi.ts b/packages/cloudflare-forge-transformer-sdk-ts/scripts/generate-from-openapi.ts index 887b11c9d..834b86931 100644 --- a/packages/cloudflare-forge-transformer-sdk-ts/scripts/generate-from-openapi.ts +++ b/packages/cloudflare-forge-transformer-sdk-ts/scripts/generate-from-openapi.ts @@ -1,18 +1,39 @@ #!/usr/bin/env node import { execFileSync } from 'node:child_process'; -import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { + chmodSync, + copyFileSync, + cpSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from 'node:fs'; import { createRequire } from 'node:module'; import { tmpdir } from 'node:os'; -import { dirname, join, resolve } from 'node:path'; +import { delimiter, dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { applyFernCompatibilityFixes } from '@cloudflare/forge/fern-openapi-compat'; -const requireFromHere = createRequire(import.meta.url); const DIST_ROOT = dirname(fileURLToPath(import.meta.url)); +// The vendored fern-api and @cloudflare/codegen-typescript-sdk packages +// (Cloudflare's forks of the Fern CLI and Fern TypeScript generator), copied +// into dist by build-package.ts so the packed tarball has no file: dependency +// on this repo's vendor/ directory. +const FERN_CLI = join(DIST_ROOT, 'vendor', 'codegen-cli', 'cli.cjs'); +const TYPESCRIPT_GENERATOR = join(DIST_ROOT, 'vendor', 'codegen-typescript-sdk', 'cli.cjs'); const GENERATOR_ROOT = join(DIST_ROOT, 'generator'); const BASELINE_SDK = join(DIST_ROOT, 'baseline-sdk'); const BASELINE_TAR = join(DIST_ROOT, 'baseline-sdk.tar'); -const TYPESCRIPT_GENERATOR_VERSION = '3.80.1'; +const TYPESCRIPT_GENERATOR_VERSION = '3.88.3'; +// Cloudflare's IR is ~242 MB; node's default heap is too small for it. +const TYPESCRIPT_GENERATOR_HEAP_MB = 8192; +// Fern's TypeScript generator formats and lints its output with these. Found on +// PATH, it uses them as-is; otherwise it installs them into the generated +// project each run. +const CHECK_FIX_TOOLS = ['oxfmt', 'oxlint'] as const; type OpenApiDoc = { paths?: Record>; @@ -32,8 +53,8 @@ function usage(): string { The OpenAPI may be a file path or - for stdin. With no positional OpenAPI, the pre-generated SDK is copied without running Fern and --base supplies its matching -complete OpenAPI. A positional OpenAPI uses sharded Fern generation and requires -Docker. The finalized OpenAPI, SDK source, and sdk-map.json are written to --out. +complete OpenAPI. A positional OpenAPI runs the vendored Fern generator natively. +The finalized OpenAPI, SDK source, and sdk-map.json are written to --out. Usage: forge [openapi.json|-] --out [options] @@ -117,11 +138,41 @@ function writeFernWorkspace(root: string, generated: string, fernSpec: string): writeJson(join(root, 'fern/fern.config.json'), { organization: 'cloudflare', version: '5.112.0' }); writeFileSync( join(apiDir, 'generators.yml'), - `api:\n specs:\n - openapi: ../../../openapi.json\n settings:\n only-include-referenced-schemas: false\n object-query-parameters: true\n respect-nullable-schemas: true\n coerce-enums-to-literals: true\n path-parameter-order: url-order\n resolve-schema-collisions: true\ndefault-group: typescript-sdk\ngroups:\n typescript-sdk:\n generators:\n - name: fernapi/fern-typescript-sdk\n version: ${TYPESCRIPT_GENERATOR_VERSION}\n output:\n location: local-file-system\n path: ${JSON.stringify(generated)}\n config:\n allowCustomFetcher: true\n formatter: oxfmt\n linter: oxlint\n skipResponseValidation: true\n fetchSupport: native\n formDataSupport: Node18\n fileResponseType: binary-response\n streamType: web\n omitUndefined: true\n offsetSemantics: page-index\n maxRetries: 2\n retryStatusCodes: recommended\n`, + `api:\n specs:\n - openapi: ../../../openapi.json\n settings:\n only-include-referenced-schemas: false\n object-query-parameters: true\n respect-nullable-schemas: true\n coerce-enums-to-literals: true\n path-parameter-order: url-order\n resolve-schema-collisions: true\ndefault-group: typescript-sdk\ngroups:\n typescript-sdk:\n generators:\n - name: fernapi/fern-typescript-sdk\n version: ${TYPESCRIPT_GENERATOR_VERSION}\n local-command:\n - ${JSON.stringify(process.execPath)}\n - --max-old-space-size=${TYPESCRIPT_GENERATOR_HEAP_MB}\n - ${JSON.stringify(TYPESCRIPT_GENERATOR)}\n output:\n location: local-file-system\n path: ${JSON.stringify(generated)}\n config:\n allowCustomFetcher: true\n formatter: oxfmt\n linter: oxlint\n skipResponseValidation: true\n fetchSupport: native\n formDataSupport: Node18\n fileResponseType: binary-response\n streamType: web\n omitUndefined: true\n offsetSemantics: page-index\n maxRetries: 2\n retryStatusCodes: recommended\n`, ); copyFileSync(fernSpec, join(root, 'openapi.json')); } +// npm allows `bin` as a single path (named after the package) or a name->path map. +function binScript(manifest: unknown, tool: string, manifestPath: string): string { + const bin = manifest && typeof manifest === 'object' && 'bin' in manifest ? manifest.bin : undefined; + const script = + typeof bin === 'string' + ? bin + : bin && typeof bin === 'object' + ? Object.entries(bin).find(([name]) => name === tool)?.[1] + : undefined; + if (typeof script !== 'string') { + throw new Error(`${manifestPath} has no "bin" entry for ${tool}`); + } + return script; +} + +// Expose this package's own oxfmt/oxlint to the generator through PATH shims; +// package managers do not put a dependency's bins on the consumer's PATH. +function writeCheckFixToolShims(binDir: string): void { + const require = createRequire(import.meta.url); + mkdirSync(binDir, { recursive: true }); + for (const tool of CHECK_FIX_TOOLS) { + const manifestPath = require.resolve(`${tool}/package.json`); + const bin = binScript(readJson(manifestPath), tool, manifestPath); + const script = join(dirname(manifestPath), bin); + const shim = join(binDir, tool); + writeFileSync(shim, `#!/bin/sh\nexec ${JSON.stringify(process.execPath)} ${JSON.stringify(script)} "$@"\n`); + chmodSync(shim, 0o755); + } +} + function installCustomRuntime(generated: string): void { cpSync(join(GENERATOR_ROOT, 'custom'), generated, { recursive: true }); } @@ -152,7 +203,6 @@ function main(): void { return; } - run('docker', ['--version'], process.cwd()); const source = readBundle(args.openapi); const sourceLabel = args.openapi === '-' ? 'stdin' : resolve(args.openapi); const work = mkdtempSync(join(tmpdir(), 'forge-transformer-sdk-ts-')); @@ -171,19 +221,28 @@ function main(): void { `forge-transformer-sdk-ts: applied ${fernCompatibilityFixCount} Fern OpenAPI compatibility repair(s)\n`, ); } - run('bash', [join(GENERATOR_ROOT, 'build-generator-image.sh'), 'typescript', TYPESCRIPT_GENERATOR_VERSION], work, { - PKG_ROOT: DIST_ROOT, - }); writeFernWorkspace(work, generated, fernSpec); - const fernCli = requireFromHere.resolve('fern-api/cli.cjs'); - run('bash', [join(GENERATOR_ROOT, 'generate-fern-typescript-sharded.sh')], work, { - FERN_TYPESCRIPT_GENERATOR_VERSION: TYPESCRIPT_GENERATOR_VERSION, - FERN_TYPESCRIPT_WORKSPACE_ROOT: work, - FERN_TYPESCRIPT_OUT_DIR: generated, - FERN_TYPESCRIPT_FERN_CLI: process.execPath, - FERN_TYPESCRIPT_FERN_CLI_SCRIPT: fernCli, - FERN_TYPESCRIPT_INSTALL_CUSTOM_RUNTIME: '0', - }); + const binDir = join(work, 'bin'); + writeCheckFixToolShims(binDir); + run( + process.execPath, + [ + // The CLI builds the ~242 MB IR in-process as well. + `--max-old-space-size=${TYPESCRIPT_GENERATOR_HEAP_MB}`, + FERN_CLI, + 'generate', + '--group', + 'typescript-sdk', + '--local', + '--force', + '--no-prompt', + ], + work, + { PATH: `${binDir}${delimiter}${process.env['PATH'] ?? ''}` }, + ); + if (!existsSync(join(generated, 'index.ts'))) { + throw new Error(`Fern exited 0 but produced no ${join(generated, 'index.ts')}`); + } installCustomRuntime(generated); run(process.execPath, [join(GENERATOR_ROOT, 'generate-sdk-map.js')], work, { FORGE_SDK_GENERATED: generated, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2da85c4a3..378222818 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: packages/cloudflare-fern-config: devDependencies: + '@cloudflare/codegen-typescript-sdk': + specifier: file:../../vendor/codegen-cli/cloudflare-codegen-typescript-sdk-3.88.3.tgz + version: file:vendor/codegen-cli/cloudflare-codegen-typescript-sdk-3.88.3.tgz '@cloudflare/forge': specifier: workspace:* version: link:../forge @@ -54,8 +57,8 @@ importers: specifier: 26.0.1 version: 26.0.1 fern-api: - specifier: 5.112.0 - version: 5.112.0 + specifier: file:../../vendor/codegen-cli/fern-api-5.112.0.tgz + version: file:vendor/codegen-cli/fern-api-5.112.0.tgz tsx: specifier: 4.22.4 version: 4.22.4 @@ -91,8 +94,8 @@ importers: specifier: 26.0.1 version: 26.0.1 fern-api: - specifier: 5.112.0 - version: 5.112.0 + specifier: file:../../vendor/codegen-cli/fern-api-5.112.0.tgz + version: file:vendor/codegen-cli/fern-api-5.112.0.tgz tsx: specifier: 4.22.4 version: 4.22.4 @@ -102,12 +105,12 @@ importers: packages/cloudflare-forge-transformer-sdk-ts: dependencies: - fern-api: - specifier: 5.112.0 - version: 5.112.0 oxfmt: specifier: 0.57.0 version: 0.57.0 + oxlint: + specifier: 1.72.0 + version: 1.72.0 tar: specifier: 7.5.20 version: 7.5.20 @@ -115,6 +118,9 @@ importers: specifier: 6.0.3 version: 6.0.3 devDependencies: + '@cloudflare/codegen-typescript-sdk': + specifier: file:../../vendor/codegen-cli/cloudflare-codegen-typescript-sdk-3.88.3.tgz + version: file:vendor/codegen-cli/cloudflare-codegen-typescript-sdk-3.88.3.tgz '@cloudflare/forge': specifier: workspace:* version: link:../forge @@ -127,6 +133,9 @@ importers: esbuild: specifier: 0.27.3 version: 0.27.3 + fern-api: + specifier: file:../../vendor/codegen-cli/fern-api-5.112.0.tgz + version: file:vendor/codegen-cli/fern-api-5.112.0.tgz tsx: specifier: 4.22.4 version: 4.22.4 @@ -236,24 +245,28 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@astrojs/compiler-binding-linux-arm64-musl@0.4.0': resolution: {integrity: sha512-HPbvWqbxFxyaoQJhLxCaSjtYBx9KBo7JGVzEFZCmMl968a2PsSH0UfiODYgYPXofTOIsIH2aoCcrHXML0IA3ig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@astrojs/compiler-binding-linux-x64-gnu@0.4.0': resolution: {integrity: sha512-tQKolMxoJ/+0AmLWm1PmJ/i+z3i10ZU1bNuVjEDulCf48azEMtUNjTZgHJ5MPtpYRNc7dlETr8QujUfduzoC7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@astrojs/compiler-binding-linux-x64-musl@0.4.0': resolution: {integrity: sha512-5v5YymudsxMHp3NBLCS8BUlu5CRqeLtWD9cKS/4nIhIEHCbpz9okmVV6I0HWqmBAPhWYcDa3vw/vltYPrOQCTA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@astrojs/compiler-binding-wasm32-wasi@0.4.0': resolution: {integrity: sha512-m/phuH3x3PREvv1OnkM44NoPh4MatUadix1fB1u5SvMLCyDTUZykDJbKnWf1cjnYmHdlB8HcjTjl6JrCqAIXcw==} @@ -374,24 +387,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@boundaryml/baml-linux-arm64-musl@0.219.0': resolution: {integrity: sha512-KjwJL5aXf4XvwpX3RwMau19XcHDcffhbiTX+tk41kGC4LZl7UXB4FgnOBtSOMvWtHj2f4cIN81+yCZBRsMxdNg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@boundaryml/baml-linux-x64-gnu@0.219.0': resolution: {integrity: sha512-JWMhzx1LDfCPwDbOUQbFaPgNb7TUvLCb4+1TjRAJR+Z2Nl08RqNWUUfFctMEzsN6HrcHVOZ0xSl/hJRxN8OcTA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@boundaryml/baml-linux-x64-musl@0.219.0': resolution: {integrity: sha512-lPosn6eJqI+8EjMI71AUEb3B9uk4dUfQgerbDyu6FvNMWx+HZDPB4UWl66+OlEeMA2Gb6DUp3LLE/Jl9/KDKSg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@boundaryml/baml-win32-arm64-msvc@0.219.0': resolution: {integrity: sha512-djy9pn8s1Ogr3ovTDxDcOiM1YOhkBwNsBnlZDQzrIdR/LgSh/GGmrwW5Ks3gfa2NvvNOIFPccdUp0nDZZNsjfQ==} @@ -434,41 +451,49 @@ packages: resolution: {integrity: sha512-glkYXZCJywjP13v67eAyAMSJdF+ncvEbYvgi/wOtffL9tQ27lr/zsyzUfgs+ovjJ9d8JNQKiXeiArJcX8PJL9w==} cpu: [arm64] os: [linux] + libc: [glibc] '@bruits/satteri-linux-arm64-gnu@0.9.5': resolution: {integrity: sha512-u51id17uJwNEMK9nBlICsq6U31c+XVqQueVBkwRIzZG+gMpS8TOJctt5h5Wz33Z8xnMdTd+adtACVz0yHgGuOA==} cpu: [arm64] os: [linux] + libc: [glibc] '@bruits/satteri-linux-arm64-musl@0.10.5': resolution: {integrity: sha512-yWdgG1g17Nh2QyGVlFUxGRa3FEFwiMcpZEyMNWkbM3deC94cmVc+/i9OuyFpdKuWo3GkgoCtYVOoxk1uCnCZIA==} cpu: [arm64] os: [linux] + libc: [musl] '@bruits/satteri-linux-arm64-musl@0.9.5': resolution: {integrity: sha512-v39HxiwGC5Rqm01HksP6+5Y+xKLPlsuVFgIgpEAo+SiQ22c+mJVhS3u7Z6ePAKdhL5NJoK1xq70kLz3L13AhpQ==} cpu: [arm64] os: [linux] + libc: [musl] '@bruits/satteri-linux-x64-gnu@0.10.5': resolution: {integrity: sha512-FVaLoPT1fBgGl0J+AYebyyXJYBachGl8Oyyrf1lye4RTqCB4S0Gwkj1uM9RJyThUOvx5VUmAT1CnNh1SFHA+kw==} cpu: [x64] os: [linux] + libc: [glibc] '@bruits/satteri-linux-x64-gnu@0.9.5': resolution: {integrity: sha512-F3uO8uFp3pAP5ZGXttwvh57GS7s0lL953tnNdyI2gRyP4kOOkp6pyGojNJzCjkDvWI2Cvb9iNrKok3aqQPauAw==} cpu: [x64] os: [linux] + libc: [glibc] '@bruits/satteri-linux-x64-musl@0.10.5': resolution: {integrity: sha512-EHpVAx2bqW3GINHTKkljtxVfQmVDGWIuwOYOP5YghTj+0PkBa2o8oKPRtQ9Kbsr1Fye8jtUcDjhwj2jMNugZKg==} cpu: [x64] os: [linux] + libc: [musl] '@bruits/satteri-linux-x64-musl@0.9.5': resolution: {integrity: sha512-bicEqglLlz++mWyADaZoP0JY20s4vDfLjaPYgQqC+NI4zZLTOOg1T4GB8aqtc822Pqji8SQBmSrTb7CrP8i08Q==} cpu: [x64] os: [linux] + libc: [musl] '@bruits/satteri-wasm32-wasi@0.10.5': resolution: {integrity: sha512-ypz8c/Zmipxp4IoeDa228Gstv6TLzVmNs3yC6wKCoNSOjx1iwpgzu87Y3hTkXFdwChVGU85qeUDuOIarGUZQLw==} @@ -512,6 +537,11 @@ packages: resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==} engines: {node: '>= 20.12.0'} + '@cloudflare/codegen-typescript-sdk@file:vendor/codegen-cli/cloudflare-codegen-typescript-sdk-3.88.3.tgz': + resolution: {integrity: sha512-uNJS7mcDZCBw6QDHfMSdw2DOwkB0cMYwyxh4HwF8Nb2LUTwXyMqo5UJi2P7u2G6HdZTMiMyV6sdTCdOTi4z9yw==, tarball: file:vendor/codegen-cli/cloudflare-codegen-typescript-sdk-3.88.3.tgz} + version: 3.88.3 + engines: {node: '>=22'} + '@cloudflare/kv-asset-handler@0.5.0': resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} engines: {node: '>=22.0.0'} @@ -533,61 +563,61 @@ packages: wrangler: ^4.124.0 '@cloudflare/workerd-darwin-64@1.20260815.1': - resolution: {integrity: sha512-7PsLdcz6pT9EMd1EJGZEgMyYRfs0CHxGs62PS2L1w3s6+xGmQcRXKm/zoMftmqZF45JBa4MzFeownRKbRt/x5g==} + resolution: {integrity: sha512-7PsLdcz6pT9EMd1EJGZEgMyYRfs0CHxGs62PS2L1w3s6+xGmQcRXKm/zoMftmqZF45JBa4MzFeownRKbRt/x5g==, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260815.1.tgz} engines: {node: '>=16'} cpu: [x64] os: [darwin] '@cloudflare/workerd-darwin-64@1.20260826.1': - resolution: {integrity: sha512-8UsGGY8ZUiYHOWdsxBlNsGmaHBGArVwJ3CM4nWpfBhthjjYe4M/OqrTpqKF7NNWb63qQiv1d8Z+Z6/hmUqNKIQ==} + resolution: {integrity: sha512-8UsGGY8ZUiYHOWdsxBlNsGmaHBGArVwJ3CM4nWpfBhthjjYe4M/OqrTpqKF7NNWb63qQiv1d8Z+Z6/hmUqNKIQ==, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260826.1.tgz} engines: {node: '>=16'} cpu: [x64] os: [darwin] '@cloudflare/workerd-darwin-arm64@1.20260815.1': - resolution: {integrity: sha512-60wtg8ng7FVWeOg/UMbZ9Ye0sslpRRAKoftPbdtuH2volq676quxVr6Zm2EjVULH/JFZeCn72dbLlrnbh0Mpcw==} + resolution: {integrity: sha512-60wtg8ng7FVWeOg/UMbZ9Ye0sslpRRAKoftPbdtuH2volq676quxVr6Zm2EjVULH/JFZeCn72dbLlrnbh0Mpcw==, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260815.1.tgz} engines: {node: '>=16'} cpu: [arm64] os: [darwin] '@cloudflare/workerd-darwin-arm64@1.20260826.1': - resolution: {integrity: sha512-0bLqVQYsQ3v3FdYGmzh23vi9fJeYTBx19o4LUySIsRcgBggGSlR39ml162vTXvZzUISOjVW2qfL7Y+SMrrfXmQ==} + resolution: {integrity: sha512-0bLqVQYsQ3v3FdYGmzh23vi9fJeYTBx19o4LUySIsRcgBggGSlR39ml162vTXvZzUISOjVW2qfL7Y+SMrrfXmQ==, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260826.1.tgz} engines: {node: '>=16'} cpu: [arm64] os: [darwin] '@cloudflare/workerd-linux-64@1.20260815.1': - resolution: {integrity: sha512-MuqKIHPo0Qyo8MZMmy0lP2B5PeAL7f4T9Fu4Usk3QdbV4JIrKG/OoybN3Ign7m/Dff+L1Oo/ZHydB+hEg1ueFw==} + resolution: {integrity: sha512-MuqKIHPo0Qyo8MZMmy0lP2B5PeAL7f4T9Fu4Usk3QdbV4JIrKG/OoybN3Ign7m/Dff+L1Oo/ZHydB+hEg1ueFw==, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260815.1.tgz} engines: {node: '>=16'} cpu: [x64] os: [linux] '@cloudflare/workerd-linux-64@1.20260826.1': - resolution: {integrity: sha512-DTC0yWzybX4gUH5Q1pJo3UwEQjp0Gmz0Q71I+39xT9SXBesX5QndOIQv45yHQ86Z84EzK2WwaENdlpmDSvJKmw==} + resolution: {integrity: sha512-DTC0yWzybX4gUH5Q1pJo3UwEQjp0Gmz0Q71I+39xT9SXBesX5QndOIQv45yHQ86Z84EzK2WwaENdlpmDSvJKmw==, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260826.1.tgz} engines: {node: '>=16'} cpu: [x64] os: [linux] '@cloudflare/workerd-linux-arm64@1.20260815.1': - resolution: {integrity: sha512-XNFtJ5rIqJxnY6ISjkfbhT/ODiWJ6LcBvNbntuPD6I/F2k7aZeKgPaXrvWvKde66LXyzFKzc8Hn+Ydx4shevQg==} + resolution: {integrity: sha512-XNFtJ5rIqJxnY6ISjkfbhT/ODiWJ6LcBvNbntuPD6I/F2k7aZeKgPaXrvWvKde66LXyzFKzc8Hn+Ydx4shevQg==, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260815.1.tgz} engines: {node: '>=16'} cpu: [arm64] os: [linux] '@cloudflare/workerd-linux-arm64@1.20260826.1': - resolution: {integrity: sha512-PFerWi+DP2Ckc6eATAS4dhotBt8IeXJjTzIgy18H+uqsswlXc7A8HsnAunHL9v/7/BjCgq46iMo2g4iUAsEpDw==} + resolution: {integrity: sha512-PFerWi+DP2Ckc6eATAS4dhotBt8IeXJjTzIgy18H+uqsswlXc7A8HsnAunHL9v/7/BjCgq46iMo2g4iUAsEpDw==, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260826.1.tgz} engines: {node: '>=16'} cpu: [arm64] os: [linux] '@cloudflare/workerd-windows-64@1.20260815.1': - resolution: {integrity: sha512-PiIUWrhbMg3quolwjgMvPOd75vKESjT4aDm7nL6mSjL5IOgmpO/zKstXnYfnEH3pq7sC0UCvKlF8ZPcfsh8NMw==} + resolution: {integrity: sha512-PiIUWrhbMg3quolwjgMvPOd75vKESjT4aDm7nL6mSjL5IOgmpO/zKstXnYfnEH3pq7sC0UCvKlF8ZPcfsh8NMw==, tarball: https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260815.1.tgz} engines: {node: '>=16'} cpu: [x64] os: [win32] '@cloudflare/workerd-windows-64@1.20260826.1': - resolution: {integrity: sha512-X26hulrG2MSSfpRmjZbCq98LNaZnrRqbmGcgo7g1U/U0nJ5npYruPm3fAyZ2y6VDr5CmQo9BLCahxP8VB/QR6A==} + resolution: {integrity: sha512-X26hulrG2MSSfpRmjZbCq98LNaZnrRqbmGcgo7g1U/U0nJ5npYruPm3fAyZ2y6VDr5CmQo9BLCahxP8VB/QR6A==, tarball: https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260826.1.tgz} engines: {node: '>=16'} cpu: [x64] os: [win32] @@ -1181,177 +1211,209 @@ packages: resolution: {integrity: sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm64@1.3.3': resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.3.1': resolution: {integrity: sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.3.3': resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.3.1': resolution: {integrity: sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.3.3': resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.3.1': resolution: {integrity: sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.3.3': resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.3.1': resolution: {integrity: sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.3.3': resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.3.1': resolution: {integrity: sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.3.3': resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.3.1': resolution: {integrity: sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-arm64@1.3.3': resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.3.1': resolution: {integrity: sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.3.3': resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.35.2': resolution: {integrity: sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm64@0.35.4': resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.35.2': resolution: {integrity: sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==} engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.35.4': resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==} engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.35.2': resolution: {integrity: sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==} engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.35.4': resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==} engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.35.2': resolution: {integrity: sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==} engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.35.4': resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==} engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.35.2': resolution: {integrity: sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==} engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.35.4': resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==} engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.35.2': resolution: {integrity: sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.35.4': resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.35.2': resolution: {integrity: sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-arm64@0.35.4': resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.35.2': resolution: {integrity: sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.35.4': resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.35.2': resolution: {integrity: sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==} @@ -1484,48 +1546,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-arm64-musl@0.57.0': resolution: {integrity: sha512-p4Y/+RYk9Bk5WO+zHSUXAClRmZ2fbJCejMuCAsU2HhyME4jqf6Ftt/mJYEwIah1wGCBDYOB7wEGV1x5bCEZ6hA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxfmt/binding-linux-ppc64-gnu@0.57.0': resolution: {integrity: sha512-By6tRALAZsno0F4zedmtG+wdMvJiJmJoXM4d3+A9zHE4HRXLqXITwRH8mgrlcXc5yJM2g2W3riRPwTYdgemZLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-riscv64-gnu@0.57.0': resolution: {integrity: sha512-skYeG+RgvyzspqVEBsEprL90OYYZfoVNqB3HcCNR6QDJyXKOzfDRT3zncnHmUaFluIlBHuY23mU1b5WGgR98hA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-riscv64-musl@0.57.0': resolution: {integrity: sha512-FFgACrZOXAXUh5KQh2mt1CDOVOZmn+QzHP71wM9QobNwyQvoFfyAeefVUltW83g3sm7LTiH3yfFqLLVUpA5ZFQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxfmt/binding-linux-s390x-gnu@0.57.0': resolution: {integrity: sha512-Nm/BAOfQeFiiKd502mZn/GAVKJwtd0RdCg17G3Wz/WSOIQmDi3+7/SZH4BHn1Ye5KvTVH3ua8WvfwLLycNIuvA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-x64-gnu@0.57.0': resolution: {integrity: sha512-BiSy5Ku3mQqyxS6YIqAJgd403wEUWvI7kerfzPxc2l/txZVmZM0pSj7oDM+4bGBExowxOi7o73jEam1W0EDTZg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-x64-musl@0.57.0': resolution: {integrity: sha512-BCRkJiotz5s9afLYD2LuMvzAoDYx9H17E/YbDyu4xK7l4zHDPeny9ErSXL//i/nJyaOwRk08x4b8cgJC00+JDg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxfmt/binding-openharmony-arm64@0.57.0': resolution: {integrity: sha512-4Oaxe1qrGgXfpCJ1C/ERJ2iCtV2rN1R79ga9fsfyVHfSQRu/hVW780u2KDqZWFZ/iGTHODJji0JemxqFZ63eIQ==} @@ -1598,48 +1668,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-arm64-musl@1.72.0': resolution: {integrity: sha512-JGHS9Mnr7iWyyLDxgCv1MhzVpAckgptg00F2gnxt/GD7lQ2SW1BRcxHqhSTaSdDpjWRrBkBxMMh4+Hn3aVtExg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxlint/binding-linux-ppc64-gnu@1.72.0': resolution: {integrity: sha512-AOYgBZqxNshrg83P9v0RYv+m8s10Cqkj4/PxXFDhcS3k7FqsIG5+CxErshZCIN7G8iy4Y+VGfAsuEdar8AcbBg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-riscv64-gnu@1.72.0': resolution: {integrity: sha512-QMybPS5ij3/vrKG67mqzHwW++91sYxK/PPUVi6SBtNCEzW4niS52fVBdXbQ6nou0wWbUPEpx8Sl/ZjtgE3clXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-riscv64-musl@1.72.0': resolution: {integrity: sha512-gOc3W7JV0PXRpIL7stUlLe3Wa9Gp0Kdlup87IT3gHDvPKck2xNgMIl/Gs2lldYY2lyXZDC4rWi3hmoLUobkgbQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxlint/binding-linux-s390x-gnu@1.72.0': resolution: {integrity: sha512-rpGxph+FjjHcYI5q6uxB3Az+tnfmEnDbSA8+PK9ZE/VzyUAkvBOMeuY7ZQMhu5mpZH7YQDsTdW6Cx4kV/msc6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxlint/binding-linux-x64-gnu@1.72.0': resolution: {integrity: sha512-WND+uhf/Ko13SLqQMWQUgsZuLvYYEvL0ZKgg0tgGYfLqxG7l8Ju123fHDMJyYSDl5E3bUbpFUuii/OvMreFQzw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-x64-musl@1.72.0': resolution: {integrity: sha512-SrpbrUL70nG9vh6zP4/oKHWgLuHquwsr7MW9XOn0olBVgh10Uqr8qscKhQoBGEn6olK/IUpn5GSKcdQ5AjUhGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxlint/binding-openharmony-arm64@1.72.0': resolution: {integrity: sha512-qkrsEn6NmgFKr7U/QnezQMb+q/vzAy0Dd9Y95gQGQTyjzDLN+HRZMuM5u70iyH4nBLCfKBzhjMsYCehKay2jyg==} @@ -1758,36 +1836,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.2.5': resolution: {integrity: sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.2.5': resolution: {integrity: sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.2.5': resolution: {integrity: sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.2.5': resolution: {integrity: sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.2.5': resolution: {integrity: sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.2.5': resolution: {integrity: sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==} @@ -2287,8 +2371,9 @@ packages: picomatch: optional: true - fern-api@5.112.0: - resolution: {integrity: sha512-bWrOBVRms+gsRXfn/DBA1Cx2AUfZHaEqPpwJ+ay0zUs7TGD2e7hBh0hfX4/jLeYUz6kCNz4+hEFhzFzTgXY7lQ==} + fern-api@file:vendor/codegen-cli/fern-api-5.112.0.tgz: + resolution: {integrity: sha512-yw53X6fSjbLD7yq4UWFJvkwd/PjdUj8x1TsVKCu2UvlyIBRiSR8bY08ADsN9T5L8OkTEgoT1xW8VhrFU+/B+rA==, tarball: file:vendor/codegen-cli/fern-api-5.112.0.tgz} + version: 5.112.0 hasBin: true find-proc@0.1.0: @@ -2500,24 +2585,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.33.0: resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.33.0: resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.33.0: resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.33.0: resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} @@ -3922,6 +4011,8 @@ snapshots: fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 + '@cloudflare/codegen-typescript-sdk@file:vendor/codegen-cli/cloudflare-codegen-typescript-sdk-3.88.3.tgz': {} + '@cloudflare/kv-asset-handler@0.5.0': {} '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260815.1)': @@ -5479,7 +5570,7 @@ snapshots: optionalDependencies: picomatch: 4.0.5 - fern-api@5.112.0: + fern-api@file:vendor/codegen-cli/fern-api-5.112.0.tgz: optionalDependencies: '@boundaryml/baml': 0.219.0 diff --git a/vendor/codegen-cli/README.md b/vendor/codegen-cli/README.md new file mode 100644 index 000000000..9c6eb3e4d --- /dev/null +++ b/vendor/codegen-cli/README.md @@ -0,0 +1,41 @@ +# Vendored Fern fork + +Cloudflare's fork of [Fern](https://github.com/fern-api/fern) (Apache-2.0), +packaged as two npm tarballs built from the `fern` branch of +. That branch starts from a snapshot of +fern-api/fern at the 5.112.0 release (`8bfa8e27e691eb29a3bd7053942780ba0ea6466d`), +keeps its `LICENSE`, and carries Cloudflare's patches on top. The tarballs are +checked in here so generation does not download Fern from npm or Docker Hub. + +| Tarball | Package | Upstream | Version | Built from | +| ---------------------------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------- | --------- | ------------------------------------------ | +| `fern-api-5.112.0.tgz` | `fern-api` (the `fern` command) | Fern CLI (`fern-api` on npm) | `5.112.0` | `2e7e57b4de410ae400e80ff8f4809c77eba50930` | +| `cloudflare-codegen-typescript-sdk-3.88.3.tgz` | `@cloudflare/codegen-typescript-sdk` (the TypeScript SDK generator) | Fern TypeScript generator (`fernapi/fern-typescript-sdk`) | `3.88.3` | `1d1bde1820d5fc17f6a1c8c91cdf657e13956836` | + +The commit is also the `gitHead` field in each tarball's `package.json`. +Versions are the upstream releases the fork is built on. The CLI keeps +upstream's package name, `fern-api`, but never contacts npm: it skips upgrade +checks, and it fails when `version` in `fern.config.json` does not match +instead of downloading another build. + +Fern's TypeScript generator runs natively (no Docker): `generators.yml` points +`local-command` at its `cli.cjs`, and the CLI runs it on the host. It shells +out to `oxfmt` and `oxlint`, which must be on `PATH`; otherwise it installs +them into the output project on every run. + +`@cloudflare/forge-transformer-sdk-ts` lists both tarballs as devDependencies +and its build copies them into `dist/vendor/`. The packed transformer therefore +carries the CLI and the generator itself and has no `file:` dependency on this +directory, so repos that vendor the transformer tarball need no change. + +## Updating + +```bash +# in a checkout of the fern branch +scripts/build-vendor-cli.sh /path/to/this/repo/vendor/codegen-cli +scripts/build-vendor-typescript-generator.sh /path/to/this/repo/vendor/codegen-cli +# then in this repo +pnpm install +``` + +Update the commits above in the same change. diff --git a/vendor/codegen-cli/cloudflare-codegen-typescript-sdk-3.88.3.tgz b/vendor/codegen-cli/cloudflare-codegen-typescript-sdk-3.88.3.tgz new file mode 100644 index 000000000..0d68297e7 Binary files /dev/null and b/vendor/codegen-cli/cloudflare-codegen-typescript-sdk-3.88.3.tgz differ diff --git a/vendor/codegen-cli/fern-api-5.112.0.tgz b/vendor/codegen-cli/fern-api-5.112.0.tgz new file mode 100644 index 000000000..3f79351ef Binary files /dev/null and b/vendor/codegen-cli/fern-api-5.112.0.tgz differ