From 90fb32be30f12ea8c552980ddba8269ae9e45cf9 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Wed, 10 Jun 2026 20:32:35 +1000 Subject: [PATCH] fix(core): accept deprecated reference config key (#31659) --- .opencode/opencode.jsonc | 4 ++- packages/core/src/v1/config/config.ts | 3 ++ packages/core/src/v1/config/migrate.ts | 3 +- packages/core/test/config/config.test.ts | 38 ++++++++++++++++++++ packages/opencode/test/config/config.test.ts | 20 +++++++++++ packages/sdk/js/src/v2/gen/types.gen.ts | 3 ++ 6 files changed, 69 insertions(+), 2 deletions(-) diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc index b0f7d5944..ae68a477d 100644 --- a/.opencode/opencode.jsonc +++ b/.opencode/opencode.jsonc @@ -2,7 +2,9 @@ "$schema": "https://opencode.ai/config.json", "provider": {}, "permission": {}, - "references": { + // TODO: flip back to `references` once a release containing the v1 `reference` migration ships. + // The release pipeline runs the latest published opencode against this file, which only knows `reference`. + "reference": { "effect": { "repository": "github.com/Effect-TS/effect-smol", "description": "Use for Effect v4 and effect-smol implementation details", diff --git a/packages/core/src/v1/config/config.ts b/packages/core/src/v1/config/config.ts index 5c520846d..2e773f71e 100644 --- a/packages/core/src/v1/config/config.ts +++ b/packages/core/src/v1/config/config.ts @@ -45,6 +45,9 @@ export const Info = Schema.Struct({ references: Schema.optional(ConfigReference.Info).annotate({ description: "Named git or local directory references", }), + reference: Schema.optional(ConfigReference.Info).annotate({ + description: "@deprecated Use 'references' field instead. Named git or local directory references", + }), watcher: Schema.optional(Schema.Struct({ ignore: Schema.optional(Schema.mutable(Schema.Array(Schema.String))) })), snapshot: Schema.optional(Schema.Boolean).annotate({ description: diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index 19bf48aa7..3b4f13868 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -12,6 +12,7 @@ const keys = new Set([ "logLevel", "server", "command", + "reference", "snapshot", "plugin", "autoshare", @@ -62,7 +63,7 @@ export function migrate(info: typeof ConfigV1.Info.Type) { skills: info.skills && [...(info.skills.paths ?? []), ...(info.skills.urls ?? [])], commands: info.command, instructions: info.instructions, - references: info.references, + references: info.references ?? info.reference, plugins: info.plugin?.map((plugin) => typeof plugin === "string" ? plugin : { package: plugin[0], options: plugin[1] }, ), diff --git a/packages/core/test/config/config.test.ts b/packages/core/test/config/config.test.ts index 5f62cbce6..6275d8fed 100644 --- a/packages/core/test/config/config.test.ts +++ b/packages/core/test/config/config.test.ts @@ -70,7 +70,9 @@ describe("Config", () => { Effect.sync(() => { expect(ConfigMigrateV1.isV1({ snapshot: false })).toBe(true) expect(ConfigMigrateV1.isV1({ snapshot: false, agents: {} })).toBe(true) + expect(ConfigMigrateV1.isV1({ reference: {} })).toBe(true) expect(ConfigMigrateV1.isV1({ shell: "/bin/zsh", model: "anthropic/claude" })).toBe(false) + expect(ConfigMigrateV1.isV1({ references: {} })).toBe(false) }), ) @@ -431,6 +433,42 @@ describe("Config", () => { ), ) + it.live("migrates the deprecated reference key into references", () => + Effect.acquireRelease( + Effect.promise(() => tmpdir()), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ).pipe( + Effect.flatMap((tmp) => + Effect.gen(function* () { + yield* Effect.promise(() => + fs.writeFile( + path.join(tmp.path, "opencode.json"), + JSON.stringify({ + reference: { + local: { path: "../library" }, + sdk: { repository: "github.com/example/sdk", branch: "main" }, + shorthand: "github.com/example/docs", + }, + }), + ), + ) + + return yield* Effect.gen(function* () { + const config = yield* Config.Service + const documents = (yield* config.entries()).filter((entry) => entry.type === "document") + + expect(documents).toHaveLength(1) + expect(documents[0]?.info.references).toEqual({ + local: { path: "../library" }, + sdk: { repository: "github.com/example/sdk", branch: "main" }, + shorthand: "github.com/example/docs", + }) + }).pipe(Effect.provide(testLayer(tmp.path))) + }), + ), + ), + ) + it.live("migrates v1 configuration when a v1-only key is present", () => Effect.acquireRelease( Effect.promise(() => tmpdir()), diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index c016431bc..f4a0cd4a2 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -722,6 +722,26 @@ it.instance("migrates mode field to agent field", () => }), ) +it.instance("accepts the deprecated reference field", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* writeConfigEffect(test.directory, { + $schema: "https://opencode.ai/config.json", + reference: { + local: { path: "../library" }, + sdk: { repository: "github.com/example/sdk", branch: "main" }, + shorthand: "github.com/example/docs", + }, + }) + const config = yield* Config.use.get() + expect(config.reference).toEqual({ + local: { path: "../library" }, + sdk: { repository: "github.com/example/sdk", branch: "main" }, + shorthand: "github.com/example/docs", + }) + }), +) + it.instance("loads config from .opencode directory", () => Effect.gen(function* () { const test = yield* TestInstance diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 9e9c74c6e..7f5e765a4 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1931,6 +1931,9 @@ export type Config = { references?: { [key: string]: string | ConfigV2ReferenceGit | ConfigV2ReferenceLocal } + reference?: { + [key: string]: string | ConfigV2ReferenceGit | ConfigV2ReferenceLocal + } watcher?: { ignore?: Array }