From 82a9e60798b56bd2b482f2ae69d32a8204d9bed8 Mon Sep 17 00:00:00 2001 From: MarioCadenas Date: Fri, 14 Aug 2026 15:11:06 +0200 Subject: [PATCH] fix(cli): classify doctor field origin via shared computeOriginFromField MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit doctor's resolve-targets.isUserSuppliedEnv hand-rolled the field-origin cascade and never handled the cli origin, so a resolve-based (cli-origin) field — whether stamped or derived from the field shape — was misreported as a missing user-supplied env var. Derive the effective origin via the shared computeOriginFromField (trusting a stamped `origin`, else the field shape) and treat a field as user-supplied only when that origin is "user". Stacked on feat/registry-cli, which introduces computeOriginFromField. Signed-off-by: MarioCadenas --- .../commands/doctor/resolve-targets.test.ts | 50 ++++++++++++++++++- .../cli/commands/doctor/resolve-targets.ts | 13 ++--- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/packages/shared/src/cli/commands/doctor/resolve-targets.test.ts b/packages/shared/src/cli/commands/doctor/resolve-targets.test.ts index 3988c82c9..6b3c8d53f 100644 --- a/packages/shared/src/cli/commands/doctor/resolve-targets.test.ts +++ b/packages/shared/src/cli/commands/doctor/resolve-targets.test.ts @@ -160,7 +160,9 @@ describe("targetsFromManifestFile", () => { expect(targets).toEqual([]); }); - it("excludes value-default and platform-injected fields from envVars", () => { + it("keeps only user-supplied fields, excluding cli/platform/static ones", () => { + // A synced manifest stamps `origin`. Only the user-supplied field should + // reach envVars; the cli-, platform-, and static-origin fields are excluded. const targets = targetsFromManifestFile( writeManifest({ version: "2.0", @@ -175,6 +177,10 @@ describe("targetsFromManifestFile", () => { alias: "Postgres", permission: "CAN_CONNECT_AND_CREATE", fields: { + instanceName: { + env: "LAKEBASE_INSTANCE_NAME", + origin: "user", + }, endpointPath: { env: "LAKEBASE_ENDPOINT", origin: "cli" }, host: { env: "PGHOST", @@ -203,7 +209,47 @@ describe("targetsFromManifestFile", () => { }), ); const pg = targets.find((t) => t.type === "postgres"); - expect(pg?.envVars).toEqual(["LAKEBASE_ENDPOINT"]); + expect(pg?.envVars).toEqual(["LAKEBASE_INSTANCE_NAME"]); + }); + + it("excludes cli-origin fields, whether stamped or derived from `resolve`", () => { + // A stamped `origin: "cli"` (synced manifest) and an unstamped field with + // `resolve` (authored manifest) must both be treated as non-user, so + // doctor never reports them as MISSING user env vars. + const targets = targetsFromManifestFile( + writeManifest({ + version: "2.0", + plugins: { + lakebase: { + requiredByTemplate: true, + resources: { + required: [ + { + type: "postgres", + resourceKey: "pg", + alias: "Postgres", + permission: "CAN_CONNECT_AND_CREATE", + fields: { + // synced manifest: origin stamped by `plugin sync` + stampedCli: { env: "LAKEBASE_ENDPOINT", origin: "cli" }, + // authored manifest: no stamped origin, resolve → cli + derivedCli: { + env: "LAKEBASE_HOST", + resolve: "postgres:host", + }, + // genuine user-supplied field survives + user: { env: "LAKEBASE_INSTANCE_NAME" }, + }, + }, + ], + optional: [], + }, + }, + }, + }), + ); + const pg = targets.find((t) => t.type === "postgres"); + expect(pg?.envVars).toEqual(["LAKEBASE_INSTANCE_NAME"]); }); it("resolves fieldValues from a static `value` when the env var is unset", () => { diff --git a/packages/shared/src/cli/commands/doctor/resolve-targets.ts b/packages/shared/src/cli/commands/doctor/resolve-targets.ts index 35e56cdb8..f48a8b700 100644 --- a/packages/shared/src/cli/commands/doctor/resolve-targets.ts +++ b/packages/shared/src/cli/commands/doctor/resolve-targets.ts @@ -7,6 +7,7 @@ import fs from "node:fs"; import path from "node:path"; +import { computeOriginFromField } from "../../../schemas/manifest"; import type { ResourceTarget } from "./types"; import { errorMessage } from "./utils"; @@ -17,6 +18,8 @@ interface ManifestField { /** Static default value baked into the manifest. */ value?: string; origin?: "user" | "platform" | "static" | "cli"; + /** Resolver key (cli origin), e.g. "postgres:host". */ + resolve?: string; /** Only generated into the local .env; the platform injects it at deploy. */ localOnly?: boolean; } @@ -47,13 +50,11 @@ interface TemplateManifest { plugins?: Record; } -/** A field's env var is the developer's to supply only when it has no static - * default and isn't platform-injected at deploy time. */ +/** A field's env var is the developer's to supply only when its effective + * origin is `"user"` — a stamped `origin`, else derived from the field shape. */ function isUserSuppliedEnv(field: ManifestField): boolean { - if (field.value !== undefined) return false; - if (field.origin === "platform" || field.origin === "static") return false; - if (field.localOnly) return false; - return true; + const origin = field.origin ?? computeOriginFromField(field); + return origin === "user"; } /** Env vars the config layer should presence-check (i.e. user-supplied ones). */