@app-vitals/agent-runtime 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Dockerfile CHANGED
@@ -54,7 +54,10 @@ FROM oven/bun:1.4.4-slim@sha256:1846adf22496bd711036aae8be147e57bdebbaea2e5264e3
54
54
  # package set as full `chromium` on this OS.
55
55
  # oven/bun:1.4.4-slim is Debian trixie (13) — see node-source stage comment above
56
56
  # — hence the t64-suffixed package names (Debian's 64-bit time_t transition).
57
- RUN apt-get update && apt-get install -y --no-install-recommends \
57
+ # Upgrade apt/libapt-pkg7.0 past the base image's 3.0.3 (Trivy HIGH CVE-2026-109518, fixed in 3.0.4).
58
+ RUN apt-get update \
59
+ && apt-get install -y --only-upgrade apt libapt-pkg7.0 \
60
+ && apt-get install -y --no-install-recommends \
58
61
  git \
59
62
  curl \
60
63
  ca-certificates \
@@ -129,13 +132,20 @@ RUN curl -fsSL -o /tmp/mise-install.sh https://mise.run \
129
132
  # deb.nodesource.com (see that stage's comment).
130
133
  COPY --from=node-source /usr/local/bin/node /usr/local/bin/node
131
134
  COPY --from=node-source /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/npm
135
+ # The npm copied from node-source bundles vulnerable undici/brace-expansion, so
136
+ # upgrade it in place before installing anything with it. npm 12 blocks install
137
+ # scripts by default, so Claude Code's postinstall (native binary) is allowed explicitly.
138
+ # renovate: datasource=npm depName=npm
139
+ ARG NPM_VERSION=12.2.2
132
140
  # renovate: datasource=npm depName=@anthropic-ai/claude-code
133
141
  ARG CLAUDE_CODE_VERSION=2.1.292
134
142
  # The trailing check fails the build if the installed CLI doesn't report exactly
135
143
  # the pinned version (e.g. a registry/tag mismatch), so a drifted image can't ship.
136
144
  RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
137
145
  && ln -s /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx \
138
- && npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION} \
146
+ && npm install -g npm@${NPM_VERSION} \
147
+ && test "$(npm --version)" = "${NPM_VERSION}" \
148
+ && npm install -g --allow-scripts=@anthropic-ai/claude-code @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION} \
139
149
  && INSTALLED="$(claude --version)" \
140
150
  && echo "claude --version: ${INSTALLED}" \
141
151
  && case "${INSTALLED}" in \
@@ -150,6 +160,32 @@ RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
150
160
  # from process.env, so spawned claude processes inherit it.
151
161
  ENV DISABLE_AUTOUPDATER=1
152
162
 
163
+ # ─── Production dependencies ──────────────────────────────────────────────────
164
+ # Production-only install: the runtime stage copies node_modules from here, so
165
+ # devDependencies (semantic-release → handlebars, the TypeScript native `tsc`
166
+ # binary, a nested npm, biome) never ship in the image. Same manifests as the
167
+ # build stage; admin/prisma is needed because admin's postinstall runs
168
+ # `prisma generate`.
169
+
170
+ FROM base AS prod-deps
171
+
172
+ WORKDIR /app
173
+
174
+ COPY package.json bunfig.toml bun.lock* ./
175
+ COPY runtime/package.json ./runtime/
176
+ COPY admin/package.json ./admin/
177
+ COPY admin/prisma ./admin/prisma/
178
+ COPY admin/prisma.config.ts ./admin/
179
+ COPY lib/package.json ./lib/
180
+ COPY patches/ ./patches/
181
+
182
+ # typescript is only an optional peer of prisma / @prisma/client, but bun still
183
+ # links it (and its native tsc binary) into the store because the lockfile
184
+ # carries it; nothing at runtime loads it, so drop it.
185
+ RUN if [ -f bun.lock ]; then bun install --frozen-lockfile --production; else bun install --production; fi \
186
+ && rm -rf node_modules/.bun/typescript@* node_modules/.bun/@typescript+* \
187
+ node_modules/.bun/node_modules/typescript node_modules/.bun/node_modules/@typescript
188
+
153
189
  # ─── Build stage ──────────────────────────────────────────────────────────────
154
190
 
155
191
  FROM base AS build
@@ -215,7 +251,7 @@ ENV HOME=/home/bun
215
251
  # routes push through signed-push.
216
252
  ENV PATH="/home/bun/.local/share/mise/shims:/app/scripts/bin:${PATH}"
217
253
 
218
- COPY --from=build --chown=1000:1000 /app/node_modules ./node_modules
254
+ COPY --from=prod-deps --chown=1000:1000 /app/node_modules ./node_modules
219
255
  COPY --from=build --chown=1000:1000 /app/runtime ./runtime
220
256
  COPY --from=build --chown=1000:1000 /app/admin ./admin
221
257
  COPY --from=build --chown=1000:1000 /app/lib ./lib
@@ -14,7 +14,13 @@
14
14
  */
15
15
 
16
16
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
17
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
17
+ import {
18
+ mkdirSync,
19
+ mkdtempSync,
20
+ readFileSync,
21
+ rmSync,
22
+ writeFileSync,
23
+ } from "node:fs";
18
24
  import { tmpdir } from "node:os";
19
25
  import { join } from "node:path";
20
26
 
@@ -146,4 +152,37 @@ describe.skipIf(!BASE_IMAGE)("agent base-image contract", () => {
146
152
  "/app/scripts/bin/git",
147
153
  ]);
148
154
  }, 60_000);
155
+
156
+ test("runtime image ships no devDependency-only packages and keeps the Prisma client", () => {
157
+ const run = docker([
158
+ "run",
159
+ "--rm",
160
+ "--entrypoint",
161
+ "bash",
162
+ DERIVED_TAG,
163
+ "-c",
164
+ 'test -d /app/admin/prisma/client && test -z "$(find /app/node_modules/.bun -maxdepth 1 \\( -name "handlebars@*" -o -name "typescript@*" -o -name "@typescript+*" -o -name "semantic-release@*" -o -name "@biomejs+*" -o -name "npm@*" \\))"',
165
+ ]);
166
+ expect(run.exitCode, run.stdout + run.stderr).toBe(0);
167
+ }, 60_000);
168
+ });
169
+
170
+ // Static guard (no Docker needed): the runtime stage must take node_modules
171
+ // from the production-only stage, never the build stage's full install.
172
+ describe("runtime Dockerfile", () => {
173
+ const dockerfile = readFileSync(
174
+ join(import.meta.dir, "Dockerfile"),
175
+ "utf8",
176
+ );
177
+
178
+ test("copies node_modules from the production-only stage", () => {
179
+ expect(dockerfile).toMatch(
180
+ /^COPY --from=prod-deps\b.*\/app\/node_modules \.\/node_modules$/m,
181
+ );
182
+ expect(dockerfile).not.toMatch(
183
+ /^COPY --from=build\b.*\/app\/node_modules\b/m,
184
+ );
185
+ expect(dockerfile).toMatch(/^FROM base AS prod-deps$/m);
186
+ expect(dockerfile).toMatch(/bun install --frozen-lockfile --production/);
187
+ });
149
188
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@app-vitals/agent-runtime",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/app-vitals/agent.git",
@@ -40,8 +40,8 @@
40
40
  "./workspace-paths": "./workspace-paths.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@app-vitals/agent-admin": "1.2.2",
44
- "@app-vitals/agent-lib": "1.2.2",
43
+ "@app-vitals/agent-admin": "1.2.4",
44
+ "@app-vitals/agent-lib": "1.2.4",
45
45
  "@octokit/auth-app": "^8.2.0",
46
46
  "@sentry/bun": "^10.63.0",
47
47
  "@slack/bolt": "^5.0.0",