chore(guards): add durable markers so fixes aren't silently dropped
Introduce AGENTS.md (root, primary agent instruction file) documenting six hard-won fixes with explicit DO NOT / WHY, plus executable enforcement so a future AI cannot delete or reintroduce them: 1. package-lock.json must be generated with npm 10 (Docker's npm 10.9.8). npm 11 drops the top-level @emnapi/core + @emnapi/runtime entries npm 10 needs, breaking the tag-triggered Docker build at `npm ci` (happened on v1.0.14). Add scripts/verify-lockfile-npm10.mjs + .npmrc + a Dockerfile fail-fast check + a CI step + tests/unit/lockfile-npm10-guard.test.js. Also re-fix the lockfile itself (regenerated with npm 10.9.8). 2. Tests must never write to the real ~/.9router DB (isolateDataDir). 3. Hidden providers must not leak into Usage (usageProviders !p.hidden). 4. codebuddy-intl connection test + OAuth identity. 5. Fork-only features that must survive upstream syncs. 6. Upstream sync procedure. Each marker cross-references AGENTS.md and the covering test. CLAUDE.md now points to AGENTS.md at the top. Verified: build ok, guard script passes, full suite leaves the real DB count unchanged (38), 0 new regressions.
This commit is contained in:
@@ -24,6 +24,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
|
||||||
|
# Guard: the committed package-lock.json must be npm-10-compatible (the
|
||||||
|
# Docker image ships npm 10.9.8). Fails fast with a fix hint instead of a
|
||||||
|
# cryptic `npm ci` EUSAGE error. See AGENTS.md §1.
|
||||||
|
- name: Verify lockfile is npm-10-compatible
|
||||||
|
run: node scripts/verify-lockfile-npm10.mjs
|
||||||
|
|
||||||
- uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
|
- uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0
|
||||||
|
|
||||||
- name: Log in to GHCR
|
- name: Log in to GHCR
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# MIBP fork — npm behavior pin.
|
||||||
|
#
|
||||||
|
# DO NOT DELETE. See AGENTS.md §1.
|
||||||
|
#
|
||||||
|
# The Docker image (node:22-alpine, pinned by digest) ships npm 10.9.8 and runs
|
||||||
|
# `npm ci` against the committed package-lock.json. Regenerating the lockfile
|
||||||
|
# with npm 11+ drops the top-level @emnapi/core + @emnapi/runtime entries npm 10
|
||||||
|
# needs, which breaks the tag-triggered Docker build ("Build and Push Docker
|
||||||
|
# Image") at `npm ci`.
|
||||||
|
#
|
||||||
|
# Regenerate the lockfile with npm 10 only:
|
||||||
|
# npx -y npm@10.9.8 install --package-lock-only
|
||||||
|
# node scripts/verify-lockfile-npm10.mjs
|
||||||
|
#
|
||||||
|
# Keep install output quiet; never let audit/fund noise hide a lockfile error.
|
||||||
|
audit=false
|
||||||
|
fund=false
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
# AGENTS.md — MIBP fork guardrails
|
||||||
|
|
||||||
|
> **READ THIS BEFORE EDITING.** This is the **MIBP fork** of `decolua/9router`
|
||||||
|
> (`github.com/mhiqrambg/9router-mibp-version`). Upstream is tracked as the
|
||||||
|
> `upstream` git remote. This file records hard-won fixes that are easy to
|
||||||
|
> silently delete or reintroduce. Each entry has a **DO NOT** and a **WHY**.
|
||||||
|
>
|
||||||
|
> If you are about to change something listed here, stop and read the whole
|
||||||
|
> entry first. If you believe an entry is obsolete, say so explicitly and get
|
||||||
|
> confirmation before removing it — do **not** quietly drop it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. `package-lock.json` MUST be generated with npm 10 (Docker's npm)
|
||||||
|
|
||||||
|
**DO NOT** regenerate `package-lock.json` with your global `npm` (npm 11+).
|
||||||
|
**DO NOT** delete `package-lock.json` or add it to `.gitignore`.
|
||||||
|
|
||||||
|
**WHY:** The Docker image pins `node:22-alpine` by digest (see `Dockerfile`
|
||||||
|
`ARG NODE_IMAGE`), which ships **npm 10.9.8**. The `Dockerfile` runs `npm ci`
|
||||||
|
against the committed lockfile. npm 11 **drops the top-level optional entries**
|
||||||
|
`@emnapi/core` and `@emnapi/runtime` that npm 10's platform-complete resolve
|
||||||
|
requires. The tag-triggered **"Build and Push Docker Image"** workflow then
|
||||||
|
fails at `npm ci`:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm error `npm ci` can only install packages when your package.json and package-lock.json are in sync.
|
||||||
|
npm error Missing: @emnapi/runtime@1.11.3 from lock file
|
||||||
|
npm error Missing: @emnapi/core@1.11.3 from lock file
|
||||||
|
```
|
||||||
|
|
||||||
|
This **already happened on tag `v1.0.14`** (run 35422006940) and on earlier
|
||||||
|
releases (`a2c6187a`, `470c8ca7`, `79294644`, `76b139b3`).
|
||||||
|
|
||||||
|
**Correct way to (re)generate the lockfile:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx -y npm@10.9.8 install --package-lock-only
|
||||||
|
node scripts/verify-lockfile-npm10.mjs # must print ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
**Enforcement:** `scripts/verify-lockfile-npm10.mjs` fails (exit 1) if the
|
||||||
|
top-level `@emnapi/core` / `@emnapi/runtime` entries are missing. Run it after
|
||||||
|
any dependency change; CI runs it before the Docker build.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Tests must NEVER write to the real database (`~/.9router`)
|
||||||
|
|
||||||
|
**DO NOT** remove `tests/setup/isolateDataDir.js` from `tests/vitest.config.js`
|
||||||
|
`setupFiles`. **DO NOT** delete `tests/unit/test-data-dir-isolation.test.js`.
|
||||||
|
|
||||||
|
**WHY:** Route-level tests (e.g. `zed-live-models.test.js`,
|
||||||
|
`zed-native-auth.test.js`) call the real `createProviderConnection`, which
|
||||||
|
persists to `$DATA_DIR/db/data.sqlite`. With `DATA_DIR` unset — the default for
|
||||||
|
`npx vitest run` — that resolved to the developer's **real** `~/.9router` DB and
|
||||||
|
appended fake connections on every run: `zed-live-*@example.com`,
|
||||||
|
`guard-*@example.com`, `zed "Account N"` (token `decrypted-token-xyz`), and the
|
||||||
|
fake provider `kimchi-nope`. These then showed up in the Usage dashboard. 42
|
||||||
|
such rows had to be cleaned out of the live DB once.
|
||||||
|
|
||||||
|
**Isolation contract:**
|
||||||
|
- Default: `DATA_DIR` → throwaway temp dir (auto-removed on exit).
|
||||||
|
- `RUN_REAL=1` or an explicit `DATA_DIR` → opt out (used by `*.real.test.js`
|
||||||
|
suites that intentionally read live credentials).
|
||||||
|
|
||||||
|
**Verify a change is safe:** run the suite, then confirm the real DB is
|
||||||
|
untouched:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node -e 'const D=require("better-sqlite3");const db=new D(process.env.HOME+"/.9router/db/data.sqlite",{readonly:true});console.log(db.prepare("SELECT COUNT(*) c FROM providerConnections").get().c)'
|
||||||
|
```
|
||||||
|
|
||||||
|
The count must be identical before and after `npx vitest run`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Hidden providers must not leak into the Usage page
|
||||||
|
|
||||||
|
**DO NOT** remove the `!p.hidden` filter in `src/shared/utils/usageProviders.js`
|
||||||
|
(`buildUsageProviderList`). **DO NOT** inline the old unfiltered
|
||||||
|
`Object.values(FREE_PROVIDERS).filter(p => p.noAuth && ...)` logic back into
|
||||||
|
`src/shared/components/UsageStats.js`.
|
||||||
|
|
||||||
|
**WHY:** The Usage page auto-adds every `noAuth` free provider so connectionless
|
||||||
|
providers (e.g. `opencode`) still appear. It must also honor the registry
|
||||||
|
`hidden` flag, matching the Providers page (`providers/page.js` filters
|
||||||
|
`!info.hidden`). Without it, `devin-cli` and `mimo-free` (both
|
||||||
|
`category:"free"`, `noAuth:true`, `hidden:true`) appear in Usage with zero
|
||||||
|
connections and zero traffic.
|
||||||
|
|
||||||
|
**Covered by:** `tests/unit/usage-provider-list.test.js`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. `codebuddy-intl` connection test + OAuth identity
|
||||||
|
|
||||||
|
**DO NOT** remove the `"codebuddy-intl"` entry from `OAUTH_TEST_CONFIG` in
|
||||||
|
`src/app/api/providers/[id]/test/testUtils.js`.
|
||||||
|
**DO NOT** remove `email` / `displayName` from `codebuddy-intl`'s `mapTokens`
|
||||||
|
(`src/lib/oauth/providers/codebuddy-intl.js`).
|
||||||
|
**DO NOT** remove `backfillCodeBuddyIntlIdentity` or its calls in
|
||||||
|
`GET /api/providers` and `/api/providers/client`.
|
||||||
|
|
||||||
|
**WHY (two bugs, both fixed 2026-09-19):**
|
||||||
|
1. **Test Connection** returned `"Provider test not supported"` because
|
||||||
|
`codebuddy-intl` was missing from `OAUTH_TEST_CONFIG`, so
|
||||||
|
`testOAuthConnection` bailed before probing. It now probes the Keycloak
|
||||||
|
realm's `userinfo` endpoint (URL derived from the token's `iss` claim).
|
||||||
|
2. **OAuth logins** were named `"Account N"` with no email. The access token is
|
||||||
|
a Keycloak JWT carrying `email`/`name`; `mapTokens` now extracts them, and a
|
||||||
|
run-once backfill self-heals pre-existing rows.
|
||||||
|
|
||||||
|
**Covered by:** `tests/unit/codebuddy-intl-connection.test.js`,
|
||||||
|
`tests/unit/codebuddy-intl-backfill.test.js`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Fork-only features — NEVER drop during an upstream sync
|
||||||
|
|
||||||
|
When merging `upstream/master`, these fork additions must survive conflict
|
||||||
|
resolution. If a merge conflict touches them, **resolve fork-priority** and
|
||||||
|
re-verify after.
|
||||||
|
|
||||||
|
| Area | Key files / markers |
|
||||||
|
|---|---|
|
||||||
|
| **Freebuff provider** | `open-sse/executors/freebuff.js`, `open-sse/providers/registry/freebuff.js`, `open-sse/services/usage/freebuff.js`, `src/lib/oauth/providers/freebuff.js`, `public/providers/freebuff.png`, and its entries in `open-sse/executors/index.js` + `open-sse/providers/registry/index.js` |
|
||||||
|
| **Proxy-pool fitness** | `open-sse/services/proxyPoolFitness.js`, `open-sse/services/poolGeo.js`, `src/lib/network/poolEgressProbe.js`, `src/lib/network/stateSweeper.js`, `src/app/(dashboard)/dashboard/proxy-fitness/`, `src/app/api/proxy-pools/**` |
|
||||||
|
| **Docker hardening** | `Dockerfile`: digest-pinned `NODE_IMAGE`, tracked `package-lock.json`, `npm ci`, `HEALTHCHECK`. `.github/workflows/docker-publish.yml`. |
|
||||||
|
| **dompurify security override** | `package.json` `overrides.dompurify` + the direct `dompurify` dependency |
|
||||||
|
| **MIBP branding** | `README.md`, `docker-compose.yml`, `.env.example`, the `MIBP Edition` link in `src/app/(dashboard)/dashboard/profile/page.js` |
|
||||||
|
| **Cline free-tier models** | `open-sse/providers/registry/cline.js` `authModes: ["oauth","apikey"]` + `cline-free/*` models; `open-sse/shared/clineAuth.js` product headers |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Upstream sync procedure
|
||||||
|
|
||||||
|
- Remote layout: `origin` = this fork, `upstream` = `decolua/9router`.
|
||||||
|
- **Merge, do not rebase** — the fork already has merge-based history; rebasing
|
||||||
|
rewrites public history.
|
||||||
|
- Work on a branch (`sync/upstream-<ver>`), tag a rollback point
|
||||||
|
(`backup/pre-sync-<ver>`), then fast-forward `master`.
|
||||||
|
- After resolving conflicts: `npm run build`, `npx vitest run`, and the
|
||||||
|
baseline scripts (`tests/__baseline__/verify-providers.mjs`,
|
||||||
|
`verify-oauth-urls.mjs`, `verify-alias.mjs`).
|
||||||
|
- The suite is **not** expected to be all-green (see `CLAUDE.md`). Judge only
|
||||||
|
for **new** regressions vs. a pre-merge run; a handful of upstream test-drift
|
||||||
|
failures are known.
|
||||||
|
- Bump `package.json` version (fork cadence `1.0.x`); `CHANGELOG.md` mirrors
|
||||||
|
upstream and is **not** edited by the fork.
|
||||||
|
- Regenerate the lockfile with npm 10 (see §1) after any dependency change.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Do not re-add `package-lock.json` to `.gitignore`
|
||||||
|
|
||||||
|
**WHY:** Upstream does not track a lockfile, but this fork **must** — the
|
||||||
|
Docker build depends on `npm ci` against a committed, npm-10-compatible
|
||||||
|
lockfile. The fork explicitly removed `package-lock.json` from `.gitignore`
|
||||||
|
(commit `76b139b3`).
|
||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
> ⚠️ **MIBP fork — read [`AGENTS.md`](AGENTS.md) first.** It lists fixes that are
|
||||||
|
> easy to silently delete or reintroduce (npm-10 lockfile rule, test DB
|
||||||
|
> isolation, hidden-provider filtering, codebuddy-intl behavior, fork-only
|
||||||
|
> features that must survive upstream syncs). Do not remove anything listed
|
||||||
|
> there without explicit confirmation.
|
||||||
|
|
||||||
## What this is
|
## What this is
|
||||||
|
|
||||||
9Router (`9router-app`) — a local AI routing gateway + Next.js dashboard. It exposes one OpenAI-compatible endpoint (`/v1/*`) and routes traffic across 40+ upstream providers with format translation, model-combo fallback, multi-account fallback, OAuth/API-key credential management, token refresh, quota/usage tracking, and optional cloud sync.
|
9Router (`9router-app`) — a local AI routing gateway + Next.js dashboard. It exposes one OpenAI-compatible endpoint (`/v1/*`) and routes traffic across 40+ upstream providers with format translation, model-combo fallback, multi-account fallback, OAuth/API-key credential management, token refresh, quota/usage tracking, and optional cloud sync.
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# syntax=docker/dockerfile:1.7
|
# syntax=docker/dockerfile:1.7
|
||||||
# Pinned by digest so a base-image refresh cannot silently bump npm and break
|
# Pinned by digest so a base-image refresh cannot silently bump npm and break
|
||||||
# `npm ci` against the committed lockfile (see v1.0.9 npm ci EUSAGE failure).
|
# `npm ci` against the committed lockfile (see v1.0.9 npm ci EUSAGE failure).
|
||||||
|
# DO NOT unpin. This image ships npm 10.9.8 — the lockfile MUST be regenerated
|
||||||
|
# with npm 10 (`npx -y npm@10.9.8 install --package-lock-only`), never npm 11+.
|
||||||
|
# See AGENTS.md §1 and scripts/verify-lockfile-npm10.mjs.
|
||||||
ARG NODE_IMAGE=node:22-alpine@sha256:c610fcdfb1d5b4740dd70c284ed3cb16bb857e0f7166196e36a5501df7a3aa32
|
ARG NODE_IMAGE=node:22-alpine@sha256:c610fcdfb1d5b4740dd70c284ed3cb16bb857e0f7166196e36a5501df7a3aa32
|
||||||
FROM ${NODE_IMAGE} AS base
|
FROM ${NODE_IMAGE} AS base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -10,6 +13,10 @@ FROM base AS builder
|
|||||||
RUN apk --no-cache upgrade && apk --no-cache add python3 make g++ linux-headers
|
RUN apk --no-cache upgrade && apk --no-cache add python3 make g++ linux-headers
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
|
# Fail fast with an actionable message if the lockfile was regenerated with
|
||||||
|
# npm 11+ (drops the top-level @emnapi entries npm 10 requires). Without this,
|
||||||
|
# `npm ci` still fails but with a cryptic "Missing: @emnapi/..." EUSAGE error.
|
||||||
|
RUN node -e "const l=require('./package-lock.json');const p=l.packages||{};const miss=['node_modules/@emnapi/core','node_modules/@emnapi/runtime'].filter(k=>!p[k]);if(miss.length){console.error('LOCKFILE NOT npm-10-COMPATIBLE — missing: '+miss.join(', '));console.error('Regenerate with: npx -y npm@10.9.8 install --package-lock-only');console.error('See AGENTS.md §1.');process.exit(1)}"
|
||||||
RUN --mount=type=cache,target=/root/.npm \
|
RUN --mount=type=cache,target=/root/.npm \
|
||||||
npm ci
|
npm ci
|
||||||
|
|
||||||
|
|||||||
Generated
+47
-20
@@ -98,7 +98,6 @@
|
|||||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.7",
|
"@babel/code-frame": "^7.29.7",
|
||||||
"@babel/generator": "^7.29.7",
|
"@babel/generator": "^7.29.7",
|
||||||
@@ -325,7 +324,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/accessibility": "^3.1.1",
|
"@dnd-kit/accessibility": "^3.1.1",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
@@ -376,6 +374,29 @@
|
|||||||
"react": ">=16.8.0"
|
"react": ">=16.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emnapi/core": {
|
||||||
|
"version": "1.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz",
|
||||||
|
"integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/wasi-threads": "1.2.3",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
|
||||||
|
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emnapi/wasi-threads": {
|
"node_modules/@emnapi/wasi-threads": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
|
||||||
@@ -383,6 +404,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
@@ -1867,6 +1889,27 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||||
|
"version": "1.11.1",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/wasi-threads": "1.2.2",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.11.1",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2204,7 +2247,6 @@
|
|||||||
"integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==",
|
"integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.67.0",
|
"@typescript-eslint/scope-manager": "8.67.0",
|
||||||
"@typescript-eslint/types": "8.67.0",
|
"@typescript-eslint/types": "8.67.0",
|
||||||
@@ -2909,7 +2951,6 @@
|
|||||||
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -3391,7 +3432,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.11.12",
|
"baseline-browser-mapping": "^2.11.12",
|
||||||
"caniuse-lite": "^1.0.30001809",
|
"caniuse-lite": "^1.0.30001809",
|
||||||
@@ -3807,7 +3847,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
@@ -4435,7 +4474,6 @@
|
|||||||
"integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==",
|
"integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -4621,7 +4659,6 @@
|
|||||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.9",
|
"array-includes": "^3.1.9",
|
||||||
@@ -5559,7 +5596,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.16.tgz",
|
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.16.tgz",
|
||||||
"integrity": "sha512-Xs7H9rBc+kti1J6RueUvbEBkmOz7jqj11XYgf+YMXAYzu8EeE7hwZ9poLXdVfVnGmJu7QAf41T7H2KuF6QoK6Q==",
|
"integrity": "sha512-Xs7H9rBc+kti1J6RueUvbEBkmOz7jqj11XYgf+YMXAYzu8EeE7hwZ9poLXdVfVnGmJu7QAf41T7H2KuF6QoK6Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/immer"
|
"url": "https://opencollective.com/immer"
|
||||||
@@ -6853,7 +6889,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.56.0.tgz",
|
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.56.0.tgz",
|
||||||
"integrity": "sha512-sXboRm3BeBeLm938eaiyLMe0OxzfXIlZvbv4ir/jVgQy1zDhWjgmny0WoN45fuDKhCCQsYMbBJrv/A6jd8aCUg==",
|
"integrity": "sha512-sXboRm3BeBeLm938eaiyLMe0OxzfXIlZvbv4ir/jVgQy1zDhWjgmny0WoN45fuDKhCCQsYMbBJrv/A6jd8aCUg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dompurify": "3.4.8",
|
"dompurify": "3.4.8",
|
||||||
"marked": "14.0.0"
|
"marked": "14.0.0"
|
||||||
@@ -6939,7 +6974,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/next/-/next-16.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-16.3.1.tgz",
|
||||||
"integrity": "sha512-hsAp0i7Rh+/dhe7DGIeN2YlpLM1DP4MNxti9EtDMtqcO612X81MvvEj388/oTce9U1EcEIOWDlGq0zRwrBKvuA==",
|
"integrity": "sha512-hsAp0i7Rh+/dhe7DGIeN2YlpLM1DP4MNxti9EtDMtqcO612X81MvvEj388/oTce9U1EcEIOWDlGq0zRwrBKvuA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "16.3.1",
|
"@next/env": "16.3.1",
|
||||||
"@swc/helpers": "0.5.23",
|
"@swc/helpers": "0.5.23",
|
||||||
@@ -7696,7 +7730,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -7706,7 +7739,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
||||||
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -7718,15 +7750,13 @@
|
|||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/react-redux": {
|
"node_modules/react-redux": {
|
||||||
"version": "9.3.0",
|
"version": "9.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz",
|
||||||
"integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==",
|
"integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/use-sync-external-store": "^0.0.6",
|
"@types/use-sync-external-store": "^0.0.6",
|
||||||
"use-sync-external-store": "^1.4.0"
|
"use-sync-external-store": "^1.4.0"
|
||||||
@@ -7800,8 +7830,7 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/redux-thunk": {
|
"node_modules/redux-thunk": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@@ -8866,7 +8895,6 @@
|
|||||||
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -9584,7 +9612,6 @@
|
|||||||
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -14,7 +14,9 @@
|
|||||||
"build:bun": "bun --bun next build --webpack",
|
"build:bun": "bun --bun next build --webpack",
|
||||||
"start:bun": "bun ./.next/standalone/custom-server.js",
|
"start:bun": "bun ./.next/standalone/custom-server.js",
|
||||||
"cli:pack": "npm --prefix cli run pack:cli",
|
"cli:pack": "npm --prefix cli run pack:cli",
|
||||||
"cli:publish": "npm --prefix cli run publish:cli"
|
"cli:publish": "npm --prefix cli run publish:cli",
|
||||||
|
"verify:lockfile": "node scripts/verify-lockfile-npm10.mjs",
|
||||||
|
"lockfile:regen": "npx -y npm@10.9.8 install --package-lock-only"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
|||||||
Executable
+71
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* verify-lockfile-npm10.mjs
|
||||||
|
*
|
||||||
|
* GUARD — DO NOT DELETE.
|
||||||
|
*
|
||||||
|
* Why this exists (root cause, 2026-09-19):
|
||||||
|
* The Docker image pins `node:22-alpine` by digest, which ships npm 10.9.8.
|
||||||
|
* The repo's `package-lock.json` is tracked (upstream does NOT track it) and
|
||||||
|
* the Dockerfile runs `npm ci`. If the lockfile is regenerated with npm 11+,
|
||||||
|
* npm 11 DROPS the top-level optional entries `@emnapi/core` and
|
||||||
|
* `@emnapi/runtime` that npm 10 requires for its platform-complete resolve.
|
||||||
|
* The tag-triggered "Build and Push Docker Image" workflow then fails at
|
||||||
|
* `npm ci` with:
|
||||||
|
* Missing: @emnapi/runtime@<v> from lock file
|
||||||
|
* Missing: @emnapi/core@<v> from lock file
|
||||||
|
* This already happened once on tag v1.0.14.
|
||||||
|
*
|
||||||
|
* Rule: ALWAYS regenerate package-lock.json with npm 10.x (the version Docker
|
||||||
|
* uses), never with the developer's global npm 11+.
|
||||||
|
*
|
||||||
|
* Correct: npx -y npm@10.9.8 install --package-lock-only
|
||||||
|
* Wrong: npm install # global npm 11+ silently breaks Docker
|
||||||
|
*
|
||||||
|
* This script asserts the lockfile still carries the entries npm 10 needs.
|
||||||
|
* Wire it into CI / pre-commit; exit 1 = lockfile will break the Docker build.
|
||||||
|
*
|
||||||
|
* Usage: node scripts/verify-lockfile-npm10.mjs
|
||||||
|
*/
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
|
||||||
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const lockPath = join(here, "..", "package-lock.json");
|
||||||
|
|
||||||
|
let lock;
|
||||||
|
try {
|
||||||
|
lock = JSON.parse(readFileSync(lockPath, "utf8"));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`❌ Cannot read ${lockPath}: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const packages = lock.packages || {};
|
||||||
|
const required = ["node_modules/@emnapi/core", "node_modules/@emnapi/runtime"];
|
||||||
|
const missing = required.filter((key) => !packages[key]);
|
||||||
|
|
||||||
|
if (missing.length > 0) {
|
||||||
|
console.error(
|
||||||
|
[
|
||||||
|
"❌ package-lock.json is NOT npm-10-compatible — the Docker build will fail at `npm ci`.",
|
||||||
|
"",
|
||||||
|
` Missing top-level entries: ${missing.join(", ")}`,
|
||||||
|
"",
|
||||||
|
" Cause: the lockfile was regenerated with npm 11+, which drops the",
|
||||||
|
" top-level @emnapi entries that npm 10 (node:22-alpine in the Docker image) requires.",
|
||||||
|
"",
|
||||||
|
" Fix (run from repo root):",
|
||||||
|
" npx -y npm@10.9.8 install --package-lock-only",
|
||||||
|
" Then re-run: node scripts/verify-lockfile-npm10.mjs",
|
||||||
|
"",
|
||||||
|
" Do NOT use your global `npm install` to regenerate the lockfile.",
|
||||||
|
].join("\n"),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`✅ package-lock.json is npm-10-compatible (found ${required.join(", ")}). Docker \`npm ci\` will succeed.`,
|
||||||
|
);
|
||||||
@@ -93,10 +93,13 @@ const OAUTH_TEST_CONFIG = {
|
|||||||
authPrefix: "Bearer ",
|
authPrefix: "Bearer ",
|
||||||
},
|
},
|
||||||
"codebuddy-cn": { tokenExists: true },
|
"codebuddy-cn": { tokenExists: true },
|
||||||
// CodeBuddy Intl access tokens are Keycloak JWTs (iss .../auth/realms/copilot);
|
// GUARD — DO NOT REMOVE. See AGENTS.md §4. Without this entry, Test Connection
|
||||||
// probe the realm's userinfo endpoint so a revoked/expired token is caught.
|
// returns "Provider test not supported". codebuddy-intl access tokens are
|
||||||
// Derive the realm URL from the token's `iss` claim, falling back to the
|
// Keycloak JWTs (iss .../auth/realms/copilot); probe the realm's userinfo
|
||||||
// known copilot realm. 200 = valid, 401 = invalid/revoked.
|
// endpoint so a revoked/expired token is caught. Derive the realm URL from the
|
||||||
|
// token's `iss` claim, falling back to the known copilot realm.
|
||||||
|
// 200 = valid, 401 = invalid/revoked.
|
||||||
|
// Covered by tests/unit/codebuddy-intl-connection.test.js.
|
||||||
"codebuddy-intl": {
|
"codebuddy-intl": {
|
||||||
buildUrl: (token) => {
|
buildUrl: (token) => {
|
||||||
const iss = decodeJwtPayload(token)?.iss;
|
const iss = decodeJwtPayload(token)?.iss;
|
||||||
|
|||||||
@@ -68,9 +68,10 @@ const codebuddyIntl = {
|
|||||||
accessToken: tokens.access_token,
|
accessToken: tokens.access_token,
|
||||||
refreshToken: tokens.refresh_token,
|
refreshToken: tokens.refresh_token,
|
||||||
expiresIn: tokens.expires_in || 86400,
|
expiresIn: tokens.expires_in || 86400,
|
||||||
// The CodeBuddy access token is a Keycloak JWT carrying email/name claims;
|
// GUARD — DO NOT REMOVE. See AGENTS.md §4. The CodeBuddy access token is a
|
||||||
// surface them so a fresh OAuth login is named by identity (and deduped on
|
// Keycloak JWT carrying email/name claims; surface them so a fresh OAuth
|
||||||
// re-login) instead of falling back to "Account N".
|
// login is named by identity (and deduped on re-login) instead of falling
|
||||||
|
// back to "Account N". Covered by tests/unit/codebuddy-intl-connection.test.js.
|
||||||
email: extractEmailFromAccessToken(tokens.access_token) || null,
|
email: extractEmailFromAccessToken(tokens.access_token) || null,
|
||||||
displayName: extractDisplayNameFromAccessToken(tokens.access_token) || null,
|
displayName: extractDisplayNameFromAccessToken(tokens.access_token) || null,
|
||||||
providerSpecificData: {},
|
providerSpecificData: {},
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// Provider list for the Usage page.
|
// Provider list for the Usage page.
|
||||||
//
|
//
|
||||||
|
// GUARD — DO NOT DELETE the `!p.hidden` filter below. See AGENTS.md §3.
|
||||||
|
//
|
||||||
// Two sources, deduped by provider id:
|
// Two sources, deduped by provider id:
|
||||||
// 1. Active LLM connections (one entry per provider).
|
// 1. Active LLM connections (one entry per provider).
|
||||||
// 2. noAuth free providers that need no connection (e.g. opencode).
|
// 2. noAuth free providers that need no connection (e.g. opencode).
|
||||||
@@ -7,6 +9,7 @@
|
|||||||
// Hidden providers are excluded — the Providers page filters `hidden`, so a
|
// Hidden providers are excluded — the Providers page filters `hidden`, so a
|
||||||
// hidden noAuth provider (devin-cli, mimo-free) must not leak into Usage with
|
// hidden noAuth provider (devin-cli, mimo-free) must not leak into Usage with
|
||||||
// zero connections and zero traffic.
|
// zero connections and zero traffic.
|
||||||
|
// Covered by tests/unit/usage-provider-list.test.js.
|
||||||
export function buildUsageProviderList({
|
export function buildUsageProviderList({
|
||||||
connections = [],
|
connections = [],
|
||||||
freeProviders = {},
|
freeProviders = {},
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
// they append test rows ("zed-live-*@example.com", "guard-*@example.com",
|
// they append test rows ("zed-live-*@example.com", "guard-*@example.com",
|
||||||
// "Account N") straight into the live DB.
|
// "Account N") straight into the live DB.
|
||||||
//
|
//
|
||||||
|
// GUARD — DO NOT DELETE this file or remove it from vitest.config.js setupFiles.
|
||||||
|
// See AGENTS.md §2. Covered by tests/unit/test-data-dir-isolation.test.js.
|
||||||
|
//
|
||||||
// DATA_DIR must be set before src/lib/dataDir.js is imported (it reads the env
|
// DATA_DIR must be set before src/lib/dataDir.js is imported (it reads the env
|
||||||
// at module-eval time), which is exactly what a vitest setupFile guarantees.
|
// at module-eval time), which is exactly what a vitest setupFile guarantees.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// GUARD — DO NOT DELETE. See AGENTS.md §1.
|
||||||
|
//
|
||||||
|
// The Docker image (node:22-alpine, pinned by digest) ships npm 10.9.8 and runs
|
||||||
|
// `npm ci` against the committed package-lock.json. Regenerating the lockfile
|
||||||
|
// with npm 11+ drops the top-level @emnapi/core + @emnapi/runtime entries npm 10
|
||||||
|
// requires, breaking the tag-triggered Docker build at `npm ci` (already
|
||||||
|
// happened on tag v1.0.14).
|
||||||
|
//
|
||||||
|
// Regenerate the lockfile with npm 10 only:
|
||||||
|
// npx -y npm@10.9.8 install --package-lock-only
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
|
||||||
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const lock = JSON.parse(readFileSync(join(here, "..", "..", "package-lock.json"), "utf8"));
|
||||||
|
|
||||||
|
describe("package-lock.json is npm-10-compatible (Docker npm ci)", () => {
|
||||||
|
it("keeps the top-level @emnapi/core entry npm 10 requires", () => {
|
||||||
|
expect(lock.packages?.["node_modules/@emnapi/core"]).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps the top-level @emnapi/runtime entry npm 10 requires", () => {
|
||||||
|
expect(lock.packages?.["node_modules/@emnapi/runtime"]).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stays on lockfileVersion 3", () => {
|
||||||
|
expect(lock.lockfileVersion).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user