diff --git a/docker/build/darwin-arm64.Dockerfile b/docker/build/darwin-arm64.Dockerfile index 71c0ec23f7..9b81734d3b 100644 --- a/docker/build/darwin-arm64.Dockerfile +++ b/docker/build/darwin-arm64.Dockerfile @@ -43,6 +43,12 @@ RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \ export SKIP_WASM_BUILD=1 && \ pnpm install --ignore-scripts && \ VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build:engine -F @rivetkit/engine-frontend; \ + elif [ "$BUILD_TARGET" = "rivetkit-napi" ]; then \ + export NODE_OPTIONS="--max-old-space-size=8192" && \ + export SKIP_NAPI_BUILD=1 && \ + export SKIP_WASM_BUILD=1 && \ + pnpm install --ignore-scripts && \ + npx turbo build:inspector-ui -F @rivetkit/engine-frontend; \ fi RUN --mount=type=cache,id=cargo-registry-darwin-arm64,target=/usr/local/cargo/registry,sharing=locked \ diff --git a/docker/build/darwin-x64.Dockerfile b/docker/build/darwin-x64.Dockerfile index 2d2433b434..c6df3fdfa2 100644 --- a/docker/build/darwin-x64.Dockerfile +++ b/docker/build/darwin-x64.Dockerfile @@ -43,6 +43,12 @@ RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \ export SKIP_WASM_BUILD=1 && \ pnpm install --ignore-scripts && \ VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build:engine -F @rivetkit/engine-frontend; \ + elif [ "$BUILD_TARGET" = "rivetkit-napi" ]; then \ + export NODE_OPTIONS="--max-old-space-size=8192" && \ + export SKIP_NAPI_BUILD=1 && \ + export SKIP_WASM_BUILD=1 && \ + pnpm install --ignore-scripts && \ + npx turbo build:inspector-ui -F @rivetkit/engine-frontend; \ fi RUN --mount=type=cache,id=cargo-registry-darwin-x64,target=/usr/local/cargo/registry,sharing=locked \ diff --git a/docker/build/linux-arm64-gnu.Dockerfile b/docker/build/linux-arm64-gnu.Dockerfile index b4bf311e39..e762ae5d52 100644 --- a/docker/build/linux-arm64-gnu.Dockerfile +++ b/docker/build/linux-arm64-gnu.Dockerfile @@ -30,6 +30,12 @@ RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \ export SKIP_WASM_BUILD=1 && \ pnpm install --ignore-scripts && \ VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build:engine -F @rivetkit/engine-frontend; \ + elif [ "$BUILD_TARGET" = "rivetkit-napi" ]; then \ + export NODE_OPTIONS="--max-old-space-size=8192" && \ + export SKIP_NAPI_BUILD=1 && \ + export SKIP_WASM_BUILD=1 && \ + pnpm install --ignore-scripts && \ + npx turbo build:inspector-ui -F @rivetkit/engine-frontend; \ fi RUN --mount=type=cache,id=cargo-registry-linux-arm64-gnu,target=/usr/local/cargo/registry,sharing=locked \ diff --git a/docker/build/linux-arm64-musl.Dockerfile b/docker/build/linux-arm64-musl.Dockerfile index eabcf6ee2b..06929dd39f 100644 --- a/docker/build/linux-arm64-musl.Dockerfile +++ b/docker/build/linux-arm64-musl.Dockerfile @@ -36,6 +36,12 @@ RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \ export SKIP_WASM_BUILD=1 && \ pnpm install --ignore-scripts && \ VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build:engine -F @rivetkit/engine-frontend; \ + elif [ "$BUILD_TARGET" = "rivetkit-napi" ]; then \ + export NODE_OPTIONS="--max-old-space-size=8192" && \ + export SKIP_NAPI_BUILD=1 && \ + export SKIP_WASM_BUILD=1 && \ + pnpm install --ignore-scripts && \ + npx turbo build:inspector-ui -F @rivetkit/engine-frontend; \ fi RUN --mount=type=cache,id=cargo-registry-linux-arm64-musl,target=/usr/local/cargo/registry,sharing=locked \ diff --git a/docker/build/linux-x64-gnu.Dockerfile b/docker/build/linux-x64-gnu.Dockerfile index 32761c180a..c04fec715b 100644 --- a/docker/build/linux-x64-gnu.Dockerfile +++ b/docker/build/linux-x64-gnu.Dockerfile @@ -39,6 +39,12 @@ RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \ export SKIP_WASM_BUILD=1 && \ pnpm install --ignore-scripts && \ VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build:engine -F @rivetkit/engine-frontend; \ + elif [ "$BUILD_TARGET" = "rivetkit-napi" ]; then \ + export NODE_OPTIONS="--max-old-space-size=8192" && \ + export SKIP_NAPI_BUILD=1 && \ + export SKIP_WASM_BUILD=1 && \ + pnpm install --ignore-scripts && \ + npx turbo build:inspector-ui -F @rivetkit/engine-frontend; \ fi # Build binary. diff --git a/docker/build/linux-x64-musl.Dockerfile b/docker/build/linux-x64-musl.Dockerfile index 19bcb67aea..6959b30757 100644 --- a/docker/build/linux-x64-musl.Dockerfile +++ b/docker/build/linux-x64-musl.Dockerfile @@ -35,6 +35,12 @@ RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \ export SKIP_WASM_BUILD=1 && \ pnpm install --ignore-scripts && \ VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build:engine -F @rivetkit/engine-frontend; \ + elif [ "$BUILD_TARGET" = "rivetkit-napi" ]; then \ + export NODE_OPTIONS="--max-old-space-size=8192" && \ + export SKIP_NAPI_BUILD=1 && \ + export SKIP_WASM_BUILD=1 && \ + pnpm install --ignore-scripts && \ + npx turbo build:inspector-ui -F @rivetkit/engine-frontend; \ fi RUN --mount=type=cache,id=cargo-registry-linux-x64-musl,target=/usr/local/cargo/registry,sharing=locked \ diff --git a/docker/build/windows-x64.Dockerfile b/docker/build/windows-x64.Dockerfile index d55ecfe59e..45f7f00eeb 100644 --- a/docker/build/windows-x64.Dockerfile +++ b/docker/build/windows-x64.Dockerfile @@ -43,6 +43,12 @@ RUN if [ "$BUILD_TARGET" = "engine" ] && [ "$BUILD_FRONTEND" = "true" ]; then \ export SKIP_WASM_BUILD=1 && \ pnpm install --ignore-scripts && \ VITE_APP_API_URL="${VITE_APP_API_URL}" VITE_FEATURE_FLAGS="${VITE_FEATURE_FLAGS}" npx turbo build:engine -F @rivetkit/engine-frontend; \ + elif [ "$BUILD_TARGET" = "rivetkit-napi" ]; then \ + export NODE_OPTIONS="--max-old-space-size=8192" && \ + export SKIP_NAPI_BUILD=1 && \ + export SKIP_WASM_BUILD=1 && \ + pnpm install --ignore-scripts && \ + npx turbo build:inspector-ui -F @rivetkit/engine-frontend; \ fi RUN --mount=type=cache,id=cargo-registry-windows-x64,target=/usr/local/cargo/registry,sharing=locked \ diff --git a/frontend/src/components/actors/actor-inspector-context.tsx b/frontend/src/components/actors/actor-inspector-context.tsx index a209b5227d..782565ce62 100644 --- a/frontend/src/components/actors/actor-inspector-context.tsx +++ b/frontend/src/components/actors/actor-inspector-context.tsx @@ -190,6 +190,14 @@ export function isVersionAtLeast( if (!parsed || !minParsed) { return false; } + // `0.0.0` is the dev/preview placeholder (e.g. `0.0.0-.` from + // pkg.pr.new or `0.0.0-main.` snapshots). These are built from the + // latest source, so treat them as newer than any release gate. Released + // prereleases like `2.3.0-rc.1` keep comparing by major.minor.patch + // (`parseSemver` already drops the prerelease suffix), so they pass too. + if (parsed.major === 0 && parsed.minor === 0 && parsed.patch === 0) { + return true; + } return compareSemver(parsed, minParsed) >= 0; } diff --git a/rivetkit-typescript/packages/rivetkit-napi/scripts/build.mjs b/rivetkit-typescript/packages/rivetkit-napi/scripts/build.mjs index 749b402053..dfa8539c3a 100644 --- a/rivetkit-typescript/packages/rivetkit-napi/scripts/build.mjs +++ b/rivetkit-typescript/packages/rivetkit-napi/scripts/build.mjs @@ -16,6 +16,14 @@ if (process.env.SKIP_NAPI_BUILD === "1") { process.exit(0); } +// The per-actor inspector UI (frontend/dist/inspector-ui, embedded into +// rivetkit-core by its build.rs) must be built before this napi build runs. +// It is NOT built here: rivetkit-core's embed needs rivetkit/inspector-tab, +// which is downstream of this package in the build graph, so building it from +// the napi build would invert the dependency order. CI builds it via +// `turbo build:inspector-ui` in docker/build/*.Dockerfile before `napi build`; +// for local builds run `pnpm -F @rivetkit/engine-frontend build:inspector-ui` +// (or `turbo build:inspector-ui`) first. const cmd = ["build", "--platform", ...extraFlags]; console.log(`[rivetkit-napi/build] running: napi ${cmd.join(" ")}`); execFileSync("napi", cmd, { stdio: "inherit" }); diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/native.ts b/rivetkit-typescript/packages/rivetkit/src/registry/native.ts index a0a9e2af83..a91ddca709 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/native.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/native.ts @@ -4756,12 +4756,29 @@ export async function buildServeConfig( serverlessMaxStartPayloadBytes: config.serverless.maxStartPayloadBytes, }; - if (config.startEngine) { + // Always best-effort resolve the npm-installed engine binary and hand its + // path to the core. The core alone decides whether to actually spawn a local + // engine (its `should_manage_engine`, based on the endpoint + spawn mode), so + // JS must not duplicate that decision here. Only JS knows the npm + // `node_modules` layout, so it resolves the path; if no binary is available + // (remote-only install, unsupported platform, optional deps skipped), leave + // it unset and let the core report `engine.binary_unavailable` if it actually + // needs one. + try { const { getEnginePath } = await loadEngineCli(); serveConfig.engineBinaryPath = getEnginePath(); - serveConfig.engineHost = config.engineHost; - serveConfig.enginePort = config.enginePort; + } catch (error) { + // The npm-installed engine binary could not be resolved. The core still + // decides whether it needs to spawn a local engine; if it does, it will + // fail with engine.binary_unavailable (auto-download is off in the napi + // runtime). Warn so the cause is actionable. + logger().warn({ + msg: "could not resolve a local engine binary; if a local engine must be spawned it will fail with engine.binary_unavailable — set RIVET_ENGINE_BINARY_PATH or install the @rivetkit/engine-cli platform package", + error: stringifyError(error), + }); } + serveConfig.engineHost = config.engineHost; + serveConfig.enginePort = config.enginePort; if (config.test?.enabled) { serveConfig.inspectorTestToken = getEnvUniversal("_RIVET_TEST_INSPECTOR_TOKEN") ?? "token"; diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/runtime.ts b/rivetkit-typescript/packages/rivetkit/src/registry/runtime.ts index 35f8e4748d..606815ddba 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/runtime.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/runtime.ts @@ -1,5 +1,7 @@ +import { stringifyError } from "@/common/utils"; import type { SqliteNativeMetrics } from "@/common/database/config"; import type { RegistryConfig } from "./config"; +import { logger } from "./log"; declare const handleBrand: unique symbol; @@ -599,11 +601,25 @@ export async function buildServeConfig( serverlessMaxStartPayloadBytes: config.serverless.maxStartPayloadBytes, }; - if (config.startEngine) { + // Always best-effort resolve the engine binary path and hand it to the core. + // The core alone decides whether to actually spawn a local engine, so JS must + // not duplicate that decision here. `loadEnginePath` throws when no binary is + // available (remote-only install, unsupported platform, optional deps + // skipped); in that case leave it unset and let the core report + // `engine.binary_unavailable` only if it actually needs one. + try { serveConfig.engineBinaryPath = await loadEnginePath(); - serveConfig.engineHost = config.engineHost; - serveConfig.enginePort = config.enginePort; + } catch (error) { + // The engine binary could not be resolved. The core still decides whether + // it needs to spawn a local engine; if it does, it will fail with + // engine.binary_unavailable (auto-download is off in the napi runtime). + logger().warn({ + msg: "could not resolve a local engine binary; if a local engine must be spawned it will fail with engine.binary_unavailable — set RIVET_ENGINE_BINARY_PATH or install the @rivetkit/engine-cli platform package", + error: stringifyError(error), + }); } + serveConfig.engineHost = config.engineHost; + serveConfig.enginePort = config.enginePort; if (config.test?.enabled) { serveConfig.inspectorTestToken = process.env._RIVET_TEST_INSPECTOR_TOKEN ?? "token"; diff --git a/scripts/publish/src/ci/bin.ts b/scripts/publish/src/ci/bin.ts index 9a46b8514d..17fc5ea3c4 100644 --- a/scripts/publish/src/ci/bin.ts +++ b/scripts/publish/src/ci/bin.ts @@ -47,15 +47,21 @@ const RUST_CRATES = [ "rivet-error", "rivet-metrics", "rivet-util-serde", + // rivet-envoy-protocol must precede rivet-depot-client, which pins it as an + // exact dependency. Publishing depot-client first makes cargo fail to + // resolve rivet-envoy-protocol because it is not yet on crates.io. + "rivet-envoy-protocol", "rivet-depot-client-types", "rivet-depot-client", - "rivet-envoy-protocol", "rivetkit-shared-types", "rivet-envoy-client", "rivetkit-actor-persist", "rivetkit-client-protocol", "rivetkit-inspector-protocol", "rivetkit-client", + // rivetkit-core has an optional dependency on rivetkit-engine-process, which + // cargo still requires to be resolvable on crates.io at publish time. + "rivetkit-engine-process", "rivetkit-core", "rivetkit", ] as const; diff --git a/scripts/publish/src/lib/npm.ts b/scripts/publish/src/lib/npm.ts index 3215ad65a9..9efcc1b0db 100644 --- a/scripts/publish/src/lib/npm.ts +++ b/scripts/publish/src/lib/npm.ts @@ -385,8 +385,12 @@ export async function publishAll( counts.failed === 0 && counts.alreadyExists === packages.length ) { - throw new Error( - `release mode: all ${packages.length} packages already published at this version. Did you forget to bump the version?`, + // This usually means a forgotten version bump, but it also happens on a + // legitimate re-run after npm fully published and a later pipeline step + // (crates.io, git tag, GitHub release) failed. Warn instead of failing so + // the re-run can reach those downstream steps; they are idempotent. + log.warn( + `release mode: all ${packages.length} packages already published at this version. Continuing (assuming a re-run); ensure the version was bumped.`, ); } diff --git a/scripts/publish/src/lib/version.ts b/scripts/publish/src/lib/version.ts index e98e3ac5e1..fd58d289b6 100644 --- a/scripts/publish/src/lib/version.ts +++ b/scripts/publish/src/lib/version.ts @@ -60,6 +60,7 @@ const PUBLISHED_RUST_WORKSPACE_DEPS = new Set([ "rivetkit-inspector-protocol", "rivetkit-client", "rivetkit-core", + "rivetkit-engine-process", ]); export interface BumpOptions { diff --git a/scripts/publish/src/local/cut-release.ts b/scripts/publish/src/local/cut-release.ts index b909cf5abb..1d95e18863 100644 --- a/scripts/publish/src/local/cut-release.ts +++ b/scripts/publish/src/local/cut-release.ts @@ -24,6 +24,7 @@ import { Command } from "commander"; import { $ } from "execa"; import { scoped } from "../lib/logger.js"; import { + bumpCargoVersions, bumpPackageJsons, getLatestGitVersion, listRecentVersions, @@ -137,6 +138,14 @@ async function main() { log.info("updating source files (Cargo.toml, examples)"); await updateSourceFiles(repoRoot, version); + // Bump the Cargo.toml workspace dependency pins (the `version = "=X"` + // exact pins on internal crates). updateSourceFiles only rewrites the + // [workspace.package] version, so without this the internal crate pins + // stay on the previous version and the Rust/wasm build fails to resolve. + // Always writes (like updateSourceFiles); dry-run still mutates source + // files and only skips the commit/push/trigger tail. + await bumpCargoVersions(repoRoot, version); + // 6. Rewrite package.json version fields via discovery. Uses versionOnly // mode so `workspace:*` dep specs are preserved — the lockfile depends on // them. CI runs the full publish-time bump (with dep rewriting + diff --git a/turbo.json b/turbo.json index a4cf1ca17e..3cd0039cf6 100644 --- a/turbo.json +++ b/turbo.json @@ -25,6 +25,18 @@ "outputs": ["dist/**"], "env": ["BASE_URL", "VITE_APP_*", "VITE_FEATURE_FLAGS"] }, + "build:inspector-ui": { + "dependsOn": ["^build"], + "inputs": [ + "src/**", + "apps/inspector-ui/**", + "scripts/**", + "vite.inspector-ui.config.ts", + "tsconfig.json", + "package.json" + ], + "outputs": ["dist/inspector-ui/**", "dist/inspector-tab/**"] + }, "build:ladle": { "dependsOn": ["^build"], "inputs": [