refactor(flags): migrate icon discovery runtime flag (#27609)
This commit is contained in:
@@ -52,7 +52,6 @@ export const Flag = {
|
|||||||
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: Config.boolean("OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER").pipe(
|
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: Config.boolean("OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER").pipe(
|
||||||
Config.withDefault(false),
|
Config.withDefault(false),
|
||||||
),
|
),
|
||||||
OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY"),
|
|
||||||
OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT:
|
OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT:
|
||||||
copy === undefined ? process.platform === "win32" : truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT"),
|
copy === undefined ? process.platform === "win32" : truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT"),
|
||||||
OPENCODE_ENABLE_EXA: truthy("OPENCODE_ENABLE_EXA") || OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA"),
|
OPENCODE_ENABLE_EXA: truthy("OPENCODE_ENABLE_EXA") || OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA"),
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export class Service extends ConfigService.Service<Service>()("@opencode/Runtime
|
|||||||
experimentalPlanMode: enabledByExperimental("OPENCODE_EXPERIMENTAL_PLAN_MODE"),
|
experimentalPlanMode: enabledByExperimental("OPENCODE_EXPERIMENTAL_PLAN_MODE"),
|
||||||
experimentalEventSystem: enabledByExperimental("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"),
|
experimentalEventSystem: enabledByExperimental("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"),
|
||||||
experimentalWorkspaces: enabledByExperimental("OPENCODE_EXPERIMENTAL_WORKSPACES"),
|
experimentalWorkspaces: enabledByExperimental("OPENCODE_EXPERIMENTAL_WORKSPACES"),
|
||||||
|
experimentalIconDiscovery: enabledByExperimental("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY"),
|
||||||
bashDefaultTimeoutMs: positiveInteger("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS"),
|
bashDefaultTimeoutMs: positiveInteger("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS"),
|
||||||
client: Config.string("OPENCODE_CLIENT").pipe(Config.withDefault("cli")),
|
client: Config.string("OPENCODE_CLIENT").pipe(Config.withDefault("cli")),
|
||||||
}) {}
|
}) {}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
|||||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||||
import { NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
|
import { NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@/effect/service-use"
|
||||||
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
|
||||||
const log = Log.create({ service: "project" })
|
const log = Log.create({ service: "project" })
|
||||||
|
|
||||||
@@ -130,7 +131,7 @@ type GitResult = { code: number; text: string; stderr: string }
|
|||||||
export const layer: Layer.Layer<
|
export const layer: Layer.Layer<
|
||||||
Service,
|
Service,
|
||||||
never,
|
never,
|
||||||
AppFileSystem.Service | Path.Path | ChildProcessSpawner.ChildProcessSpawner | Bus.Service
|
AppFileSystem.Service | Path.Path | ChildProcessSpawner.ChildProcessSpawner | Bus.Service | RuntimeFlags.Service
|
||||||
> = Layer.effect(
|
> = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
@@ -138,6 +139,7 @@ export const layer: Layer.Layer<
|
|||||||
const pathSvc = yield* Path.Path
|
const pathSvc = yield* Path.Path
|
||||||
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
||||||
const bus = yield* Bus.Service
|
const bus = yield* Bus.Service
|
||||||
|
const flags = yield* RuntimeFlags.Service
|
||||||
|
|
||||||
const git = Effect.fnUntraced(
|
const git = Effect.fnUntraced(
|
||||||
function* (args: string[], opts?: { cwd?: string }) {
|
function* (args: string[], opts?: { cwd?: string }) {
|
||||||
@@ -282,7 +284,7 @@ export const layer: Layer.Layer<
|
|||||||
time: { created: Date.now(), updated: Date.now() },
|
time: { created: Date.now(), updated: Date.now() },
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Flag.OPENCODE_EXPERIMENTAL_ICON_DISCOVERY) yield* discover(existing).pipe(Effect.ignore, Effect.forkIn(scope))
|
if (flags.experimentalIconDiscovery) yield* discover(existing).pipe(Effect.ignore, Effect.forkIn(scope))
|
||||||
|
|
||||||
const result: Info = {
|
const result: Info = {
|
||||||
...existing,
|
...existing,
|
||||||
@@ -505,6 +507,7 @@ export const defaultLayer = layer.pipe(
|
|||||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||||
Layer.provide(AppFileSystem.defaultLayer),
|
Layer.provide(AppFileSystem.defaultLayer),
|
||||||
Layer.provide(NodePath.layer),
|
Layer.provide(NodePath.layer),
|
||||||
|
Layer.provide(RuntimeFlags.defaultLayer),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const use = serviceUse(Service)
|
export const use = serviceUse(Service)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ describe("RuntimeFlags", () => {
|
|||||||
expect(flags.experimentalPlanMode).toBe(true)
|
expect(flags.experimentalPlanMode).toBe(true)
|
||||||
expect(flags.experimentalEventSystem).toBe(true)
|
expect(flags.experimentalEventSystem).toBe(true)
|
||||||
expect(flags.experimentalWorkspaces).toBe(true)
|
expect(flags.experimentalWorkspaces).toBe(true)
|
||||||
|
expect(flags.experimentalIconDiscovery).toBe(true)
|
||||||
expect(flags.client).toBe("desktop")
|
expect(flags.client).toBe("desktop")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -53,6 +54,7 @@ describe("RuntimeFlags", () => {
|
|||||||
expect(flags.disableDefaultPlugins).toBe(true)
|
expect(flags.disableDefaultPlugins).toBe(true)
|
||||||
expect(flags.disableClaudeCodeSkills).toBe(false)
|
expect(flags.disableClaudeCodeSkills).toBe(false)
|
||||||
expect(flags.enableExa).toBe(false)
|
expect(flags.enableExa).toBe(false)
|
||||||
|
expect(flags.experimentalIconDiscovery).toBe(false)
|
||||||
expect(flags.experimentalOxfmt).toBe(false)
|
expect(flags.experimentalOxfmt).toBe(false)
|
||||||
expect(flags.bashDefaultTimeoutMs).toBe(1_000)
|
expect(flags.bashDefaultTimeoutMs).toBe(1_000)
|
||||||
expect(flags.enableExperimentalModels).toBe(false)
|
expect(flags.enableExperimentalModels).toBe(false)
|
||||||
@@ -60,6 +62,32 @@ describe("RuntimeFlags", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("experimentalIconDiscovery defaults to false", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const flags = yield* readFlags.pipe(Effect.provide(fromConfig({})))
|
||||||
|
|
||||||
|
expect(flags.experimentalIconDiscovery).toBe(false)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("experimentalIconDiscovery reads OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const flags = yield* readFlags.pipe(
|
||||||
|
Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: "true" })),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(flags.experimentalIconDiscovery).toBe(true)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("experimentalIconDiscovery inherits OPENCODE_EXPERIMENTAL", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL: "true" })))
|
||||||
|
|
||||||
|
expect(flags.experimentalIconDiscovery).toBe(true)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("experimentalOxfmt defaults to false", () =>
|
it.effect("experimentalOxfmt defaults to false", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const flags = yield* readFlags.pipe(Effect.provide(fromConfig({})))
|
const flags = yield* readFlags.pipe(Effect.provide(fromConfig({})))
|
||||||
@@ -147,6 +175,7 @@ describe("RuntimeFlags", () => {
|
|||||||
expect(flags.disableDefaultPlugins).toBe(false)
|
expect(flags.disableDefaultPlugins).toBe(false)
|
||||||
expect(flags.disableClaudeCodeSkills).toBe(false)
|
expect(flags.disableClaudeCodeSkills).toBe(false)
|
||||||
expect(flags.enableExa).toBe(false)
|
expect(flags.enableExa).toBe(false)
|
||||||
|
expect(flags.experimentalIconDiscovery).toBe(false)
|
||||||
expect(flags.experimentalOxfmt).toBe(false)
|
expect(flags.experimentalOxfmt).toBe(false)
|
||||||
expect(flags.bashDefaultTimeoutMs).toBeUndefined()
|
expect(flags.bashDefaultTimeoutMs).toBeUndefined()
|
||||||
expect(flags.client).toBe("cli")
|
expect(flags.client).toBe("cli")
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { NodePath } from "@effect/platform-node"
|
|||||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||||
import { testEffect } from "../lib/effect"
|
import { testEffect } from "../lib/effect"
|
||||||
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
|
||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
|
|
||||||
@@ -69,12 +70,39 @@ function projectLayerWithFailure(failArg: string) {
|
|||||||
Layer.provide(Bus.defaultLayer),
|
Layer.provide(Bus.defaultLayer),
|
||||||
Layer.provide(AppFileSystem.defaultLayer),
|
Layer.provide(AppFileSystem.defaultLayer),
|
||||||
Layer.provide(NodePath.layer),
|
Layer.provide(NodePath.layer),
|
||||||
|
Layer.provide(RuntimeFlags.defaultLayer),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function projectLayerWithRuntimeFlags(flags: Parameters<typeof RuntimeFlags.layer>[0]) {
|
||||||
|
return Project.layer.pipe(
|
||||||
|
Layer.provide(Bus.defaultLayer),
|
||||||
|
Layer.provide(AppFileSystem.defaultLayer),
|
||||||
|
Layer.provide(NodePath.layer),
|
||||||
|
Layer.provide(RuntimeFlags.layer(flags)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const failureIt = (failArg: string) =>
|
const failureIt = (failArg: string) =>
|
||||||
testEffect(Layer.mergeAll(projectLayerWithFailure(failArg), CrossSpawnSpawner.defaultLayer))
|
testEffect(Layer.mergeAll(projectLayerWithFailure(failArg), CrossSpawnSpawner.defaultLayer))
|
||||||
|
|
||||||
|
const iconDiscoveryIt = testEffect(
|
||||||
|
Layer.provideMerge(
|
||||||
|
projectLayerWithRuntimeFlags({ experimentalIconDiscovery: true }),
|
||||||
|
CrossSpawnSpawner.defaultLayer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
function waitForProjectIcon(id: ProjectID, attempts = 50): Effect.Effect<Project.Info> {
|
||||||
|
return Effect.gen(function* () {
|
||||||
|
const project = Project.get(id)
|
||||||
|
if (project?.icon?.url) return project
|
||||||
|
if (attempts <= 0) throw new Error(`Project icon was not discovered: ${id}`)
|
||||||
|
yield* Effect.sleep("10 millis")
|
||||||
|
return yield* waitForProjectIcon(id, attempts - 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
describe("Project.fromDirectory", () => {
|
describe("Project.fromDirectory", () => {
|
||||||
it.live("should handle git repository with no commits", () =>
|
it.live("should handle git repository with no commits", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
@@ -284,6 +312,20 @@ describe("Project.fromDirectory with worktrees", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("Project.discover", () => {
|
describe("Project.discover", () => {
|
||||||
|
iconDiscoveryIt.live("discovers favicon from fromDirectory when enabled", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const tmp = yield* tmpdirScoped({ git: true })
|
||||||
|
const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
|
||||||
|
yield* Effect.promise(() => Bun.write(path.join(tmp, "favicon.png"), pngData))
|
||||||
|
|
||||||
|
const { project } = yield* run((svc) => svc.fromDirectory(tmp))
|
||||||
|
const updated = yield* waitForProjectIcon(project.id)
|
||||||
|
|
||||||
|
expect(updated.icon?.url).toStartWith("data:")
|
||||||
|
expect(updated.icon?.url).toContain("base64")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.live("should discover favicon.png in root", () =>
|
it.live("should discover favicon.png in root", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const tmp = yield* tmpdirScoped({ git: true })
|
const tmp = yield* tmpdirScoped({ git: true })
|
||||||
|
|||||||
Reference in New Issue
Block a user