mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
ci: commit openapi.json and gate it with a verify-codegen script
Adds the proxy's OpenAPI spec to the repo as openapi.json so diff tooling has a committed baseline, and replaces the inline drift check with the update/verify script pair Kubernetes uses so the same command runs locally and in CI. - scripts/update-codegen.sh is the single writer for generated artifacts - scripts/verify-codegen.sh runs it and fails on any git status change - the sync job now runs on every PR instead of a path filter, so routes defined outside litellm/proxy (enterprise/**) can no longer land a stale spec - git status --porcelain replaces git diff, which was blind to untracked files
This commit is contained in:
parent
f6587faef5
commit
cca1b8f06e
7 changed files with 89765 additions and 45 deletions
3
.gitattributes
vendored
3
.gitattributes
vendored
|
|
@ -1,2 +1,3 @@
|
|||
*.ipynb linguist-vendored
|
||||
ui/litellm-dashboard/src/lib/http/schema.d.ts linguist-generated
|
||||
ui/litellm-dashboard/src/lib/http/schema.d.ts linguist-generated
|
||||
openapi.json linguist-generated
|
||||
|
|
|
|||
28
.github/workflows/check-ui-api-types.yml
vendored
28
.github/workflows/check-ui-api-types.yml
vendored
|
|
@ -2,21 +2,13 @@ name: Check UI API Types Sync
|
|||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "litellm/proxy/**"
|
||||
- "litellm/types/**"
|
||||
- "ui/litellm-dashboard/src/lib/http/schema.d.ts"
|
||||
- "ui/litellm-dashboard/scripts/gen-api-types.mjs"
|
||||
- "ui/litellm-dashboard/package.json"
|
||||
- "ui/litellm-dashboard/package-lock.json"
|
||||
- ".github/workflows/check-ui-api-types.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-sync:
|
||||
name: Verify schema.d.ts matches the proxy OpenAPI spec
|
||||
name: Verify openapi.json and schema.d.ts match the proxy
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
|
|
@ -64,21 +56,7 @@ jobs:
|
|||
working-directory: ui/litellm-dashboard
|
||||
run: npm ci
|
||||
|
||||
- name: Regenerate types from the live spec
|
||||
working-directory: ui/litellm-dashboard
|
||||
- name: Verify generated files are up to date
|
||||
env:
|
||||
LITELLM_PYTHON: "uv run --no-sync python"
|
||||
run: npm run gen:api
|
||||
|
||||
- name: Fail if types are stale
|
||||
run: |
|
||||
if ! git diff --exit-code -- ui/litellm-dashboard/src/lib/http/schema.d.ts; then
|
||||
echo "::error file=ui/litellm-dashboard/src/lib/http/schema.d.ts::Generated API types are out of sync with the proxy OpenAPI spec."
|
||||
echo ""
|
||||
echo "A backend route or model changed without regenerating the dashboard types."
|
||||
echo "To fix, run from ui/litellm-dashboard:"
|
||||
echo " npm run gen:api"
|
||||
echo "then commit the updated src/lib/http/schema.d.ts."
|
||||
exit 1
|
||||
fi
|
||||
echo "schema.d.ts is in sync with the proxy OpenAPI spec."
|
||||
run: ./scripts/verify-codegen.sh
|
||||
|
|
|
|||
89686
openapi.json
generated
Normal file
89686
openapi.json
generated
Normal file
File diff suppressed because one or more lines are too long
20
scripts/update-codegen.sh
Executable file
20
scripts/update-codegen.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
DASHBOARD_DIR="${REPO_ROOT}/ui/litellm-dashboard"
|
||||
|
||||
if [[ -z "${LITELLM_PYTHON:-}" && -x "${REPO_ROOT}/.venv/bin/python" ]]; then
|
||||
export LITELLM_PYTHON="${REPO_ROOT}/.venv/bin/python"
|
||||
fi
|
||||
|
||||
if [[ ! -x "${DASHBOARD_DIR}/node_modules/.bin/openapi-typescript" ]]; then
|
||||
echo "Installing dashboard dependencies..." >&2
|
||||
npm ci --prefix "${DASHBOARD_DIR}"
|
||||
fi
|
||||
|
||||
cd "${DASHBOARD_DIR}"
|
||||
npm run gen:api
|
||||
38
scripts/verify-codegen.sh
Executable file
38
scripts/verify-codegen.sh
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
cd "${REPO_ROOT}"
|
||||
|
||||
GENERATED_PATHS=(
|
||||
"openapi.json"
|
||||
"ui/litellm-dashboard/src/lib/http/schema.d.ts"
|
||||
)
|
||||
|
||||
"${REPO_ROOT}/scripts/update-codegen.sh"
|
||||
|
||||
if [[ -z "$(git status --porcelain -- "${GENERATED_PATHS[@]}")" ]]; then
|
||||
echo "Generated files are in sync with the proxy."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
|
||||
echo "::error file=openapi.json::Generated files are out of sync with the proxy. Run scripts/update-codegen.sh and commit the result."
|
||||
fi
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "Generated files are out of sync with the proxy."
|
||||
echo "A backend route or response model changed without regenerating them."
|
||||
echo ""
|
||||
git status --short -- "${GENERATED_PATHS[@]}"
|
||||
git diff --stat -- "${GENERATED_PATHS[@]}"
|
||||
echo ""
|
||||
echo "The regenerated files are already in your working tree. Review and commit them,"
|
||||
echo "or run scripts/update-codegen.sh yourself to reproduce this."
|
||||
} >&2
|
||||
|
||||
exit 1
|
||||
|
|
@ -2,7 +2,7 @@ Never put LiteLLM tokens or API keys in `localStorage`. `localStorage` survives
|
|||
|
||||
When you fix lint violations that are grandfathered in `eslint-suppressions.json`, run `eslint . --prune-suppressions` and commit the updated baseline so the gate ratchets down instead of leaving a stale suppression
|
||||
|
||||
`src/lib/http/schema.d.ts` is generated from the proxy's OpenAPI spec; never hand-edit it. After changing a backend route or response model that the dashboard consumes, run `npm run gen:api` and commit the result (CI `Check UI API Types Sync` enforces this)
|
||||
`openapi.json` at the repo root and `src/lib/http/schema.d.ts` are both generated from the proxy's FastAPI app; never hand-edit either. After changing any backend route or response model, run `scripts/update-codegen.sh` from the repo root and commit both files. `scripts/verify-codegen.sh` is the same generator plus a `git status` check, and it runs on every PR as `Check UI API Types Sync`, so you can reproduce a CI failure locally with it
|
||||
|
||||
Tests come in three tiers, named by the standard definitions. `Foo.test.tsx` is a unit test: one module, collaborators replaced by doubles, no multi-component tree, and it should run in milliseconds. `Foo.integration.test.tsx` renders a real component tree with real children and only stubs the network boundary; it costs seconds per case, so it earns its place by proving wiring that a unit test cannot reach. Browser-level tests live in `tests/e2e/ui/` as Playwright specs against a live proxy
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Regenerates src/lib/http/schema.d.ts from the proxy's OpenAPI spec.
|
||||
* Regenerates openapi.json and src/lib/http/schema.d.ts from the proxy's spec.
|
||||
*
|
||||
* Two hops, because the backend is the source of truth: the FastAPI app emits
|
||||
* the spec from its route decorators (app.openapi()), then openapi-typescript
|
||||
|
|
@ -7,20 +7,21 @@
|
|||
* the spec is read straight off the app object, so this runs in CI without a
|
||||
* database or proxy boot.
|
||||
*
|
||||
* Both outputs are committed. openapi.json is the checked-in API contract that
|
||||
* diff tooling compares a branch against, and it is indented so a changed route
|
||||
* shows up as readable lines in review rather than one rewritten blob.
|
||||
*
|
||||
* The Python interpreter must have litellm installed. Override which one via
|
||||
* LITELLM_PYTHON (CI passes "uv run --no-sync python"); defaults to python3.
|
||||
*/
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const dashboardDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolve(dashboardDir, "..", "..");
|
||||
const outPath = join(dashboardDir, "src", "lib", "http", "schema.d.ts");
|
||||
const specDir = mkdtempSync(join(tmpdir(), "litellm-openapi-"));
|
||||
const specPath = join(specDir, "openapi.json");
|
||||
const specPath = join(repoRoot, "openapi.json");
|
||||
|
||||
const python = (process.env.LITELLM_PYTHON ?? "python3").split(" ");
|
||||
// The dashboard calls internal UI routes that the public /openapi.json hides via
|
||||
|
|
@ -45,19 +46,15 @@ const dumpSpec = [
|
|||
" if isinstance(node, list):",
|
||||
" return [normalize(v) for v in node]",
|
||||
" return node",
|
||||
"with open(sys.argv[1], 'w') as f: json.dump(normalize(app.openapi()), f, sort_keys=True)",
|
||||
"with open(sys.argv[1], 'w') as f: json.dump(normalize(app.openapi()), f, sort_keys=True, indent=2); f.write('\\n')",
|
||||
].join("\n");
|
||||
|
||||
try {
|
||||
execFileSync(python[0], [...python.slice(1), "-c", dumpSpec, specPath], {
|
||||
cwd: repoRoot,
|
||||
stdio: "inherit",
|
||||
});
|
||||
execFileSync(python[0], [...python.slice(1), "-c", dumpSpec, specPath], {
|
||||
cwd: repoRoot,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
execFileSync(join(dashboardDir, "node_modules", ".bin", "openapi-typescript"), [specPath, "-o", outPath], {
|
||||
cwd: dashboardDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
} finally {
|
||||
rmSync(specDir, { recursive: true, force: true });
|
||||
}
|
||||
execFileSync(join(dashboardDir, "node_modules", ".bin", "openapi-typescript"), [specPath, "-o", outPath], {
|
||||
cwd: dashboardDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue