feat(cli): enable lazycodex codex installs

This commit is contained in:
YeonGyu-Kim
2026-05-30 21:32:57 +09:00
parent 675d828077
commit a789d6c2a5
14 changed files with 47 additions and 192 deletions
+10 -13
View File
@@ -35,6 +35,9 @@ permissions:
id-token: write id-token: write
actions: write actions: write
env:
LAZYCODEX_VERSION: "0.1.0"
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -312,13 +315,11 @@ jobs:
- name: Check if lazycodex already published - name: Check if lazycodex already published
if: inputs.publish_lazycodex == true if: inputs.publish_lazycodex == true
id: check-lazycodex id: check-lazycodex
env:
VERSION: ${{ needs.release-metadata.outputs.version }}
run: | run: |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/lazycodex/${VERSION}") STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/lazycodex/${LAZYCODEX_VERSION}")
if [ "$STATUS" = "200" ]; then if [ "$STATUS" = "200" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT" echo "skip=true" >> "$GITHUB_OUTPUT"
echo "✓ lazycodex@${VERSION} already published" echo "✓ lazycodex@${LAZYCODEX_VERSION} already published"
else else
echo "skip=false" >> "$GITHUB_OUTPUT" echo "skip=false" >> "$GITHUB_OUTPUT"
fi fi
@@ -340,8 +341,6 @@ jobs:
done done
jq --arg v "$VERSION" '.optionalDependencies = (.optionalDependencies | to_entries | map(.value = $v) | from_entries)' package.json > tmp.json && mv tmp.json package.json jq --arg v "$VERSION" '.optionalDependencies = (.optionalDependencies | to_entries | map(.value = $v) | from_entries)' package.json > tmp.json && mv tmp.json package.json
jq --arg v "$VERSION" '.version = $v' packages/omo-codex/plugin/.codex-plugin/plugin.json > tmp.json && mv tmp.json packages/omo-codex/plugin/.codex-plugin/plugin.json
jq --arg v "$VERSION" '.version = $v' packages/omo-codex/plugin/package.json > tmp.json && mv tmp.json packages/omo-codex/plugin/package.json
- name: Build main package - name: Build main package
if: >- if: >-
@@ -409,14 +408,14 @@ jobs:
if: inputs.publish_lazycodex == true && steps.check-lazycodex.outputs.skip != 'true' if: inputs.publish_lazycodex == true && steps.check-lazycodex.outputs.skip != 'true'
continue-on-error: true continue-on-error: true
env: env:
VERSION: ${{ needs.release-metadata.outputs.version }} OMO_VERSION: ${{ needs.release-metadata.outputs.version }}
DIST_TAG: ${{ needs.release-metadata.outputs.dist_tag }} DIST_TAG: ${{ needs.release-metadata.outputs.dist_tag }}
NPM_CONFIG_PROVENANCE: true NPM_CONFIG_PROVENANCE: true
run: | run: |
jq --arg v "$VERSION" ' jq --arg lazycodex_version "$LAZYCODEX_VERSION" --arg omo_version "$OMO_VERSION" '
.name = "lazycodex" | .name = "lazycodex" |
.version = $v | .version = $lazycodex_version |
.optionalDependencies = (.optionalDependencies | to_entries | map(.value = $v) | from_entries) .optionalDependencies = (.optionalDependencies | to_entries | map(.value = $omo_version) | from_entries)
' package.json > tmp.json && mv tmp.json package.json ' package.json > tmp.json && mv tmp.json package.json
set +e set +e
@@ -526,8 +525,6 @@ jobs:
done done
jq --arg v "$VERSION" '.optionalDependencies = (.optionalDependencies | to_entries | map(.value = $v) | from_entries)' package.json > tmp.json && mv tmp.json package.json jq --arg v "$VERSION" '.optionalDependencies = (.optionalDependencies | to_entries | map(.value = $v) | from_entries)' package.json > tmp.json && mv tmp.json package.json
jq --arg v "$VERSION" '.version = $v' packages/omo-codex/plugin/.codex-plugin/plugin.json > tmp.json && mv tmp.json packages/omo-codex/plugin/.codex-plugin/plugin.json
jq --arg v "$VERSION" '.version = $v' packages/omo-codex/plugin/package.json > tmp.json && mv tmp.json packages/omo-codex/plugin/package.json
- name: Commit version bump - name: Commit version bump
if: steps.release-state.outputs.release_commit_exists != 'true' if: steps.release-state.outputs.release_commit_exists != 'true'
@@ -536,7 +533,7 @@ jobs:
run: | run: |
git config user.email "github-actions[bot]@users.noreply.github.com" git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
git add package.json packages/oh-my-opencode-*/package.json packages/omo-codex/plugin/.codex-plugin/plugin.json packages/omo-codex/plugin/package.json git add package.json packages/oh-my-opencode-*/package.json
git diff --cached --quiet || git commit -m "release: v${VERSION}" git diff --cached --quiet || git commit -m "release: v${VERSION}"
- name: Create release tag - name: Create release tag
+11 -6
View File
@@ -160,7 +160,9 @@ describe("test workflows", () => {
const workflow = readFileSync(publishWorkflowPath, "utf8") const workflow = readFileSync(publishWorkflowPath, "utf8")
// #when // #when
const appliesCodexPluginVersion = workflow.includes("packages/omo-codex/plugin/.codex-plugin/plugin.json") const keepsCodexPluginVersionIndependent =
!workflow.includes("jq --arg v \"$VERSION\" '.version = $v' packages/omo-codex/plugin/.codex-plugin/plugin.json") &&
!workflow.includes("jq --arg v \"$VERSION\" '.version = $v' packages/omo-codex/plugin/package.json")
const flagDefaultsOff = workflow.includes("publish_lazycodex:") && const flagDefaultsOff = workflow.includes("publish_lazycodex:") &&
workflow.includes('description: "Publish lazycodex npm alias and sync Codex marketplace"') && workflow.includes('description: "Publish lazycodex npm alias and sync Codex marketplace"') &&
workflow.includes("default: false") workflow.includes("default: false")
@@ -184,7 +186,7 @@ describe("test workflows", () => {
tokenRequirementBeforePublish tokenRequirementBeforePublish
// #then // #then
expect(appliesCodexPluginVersion, "release must version the Codex plugin manifest before marketplace sync").toBe(true) expect(keepsCodexPluginVersionIndependent, "LazyCodex plugin metadata must keep its own 0.1.0 version").toBe(true)
expect(flagDefaultsOff, "LazyCodex deployment must default to disabled").toBe(true) expect(flagDefaultsOff, "LazyCodex deployment must default to disabled").toBe(true)
expect(syncsLazycodexMarketplace, "release must sync the LazyCodex marketplace bundle").toBe(true) expect(syncsLazycodexMarketplace, "release must sync the LazyCodex marketplace bundle").toBe(true)
expect(syncBuildsMcpDists, "release must build bundled MCP dists before LazyCodex marketplace sync").toBe(true) expect(syncBuildsMcpDists, "release must build bundled MCP dists before LazyCodex marketplace sync").toBe(true)
@@ -201,15 +203,18 @@ describe("test workflows", () => {
const platformResolver = readFileSync(new URL("../bin/platform.js", import.meta.url), "utf8") const platformResolver = readFileSync(new URL("../bin/platform.js", import.meta.url), "utf8")
// #when // #when
const lazycodexStepOnlyRenamesWrapper = workflow.includes('.name = "lazycodex" |') && const lazycodexStepPinsWrapperVersion = workflow.includes('LAZYCODEX_VERSION: "0.1.0"') &&
workflow.includes('.version = $v |') && workflow.includes(".version = $lazycodex_version |")
workflow.includes('.optionalDependencies = (.optionalDependencies | to_entries | map(.value = $v) | from_entries)') const lazycodexStepUsesOmoPlatformVersion = workflow.includes(
".optionalDependencies = (.optionalDependencies | to_entries | map(.value = $omo_version) | from_entries)",
)
const lazycodexStepDoesNotRenameOptionalDeps = !workflow.includes('sub("^oh-my-opencode-"; "lazycodex-")') const lazycodexStepDoesNotRenameOptionalDeps = !workflow.includes('sub("^oh-my-opencode-"; "lazycodex-")')
const shimMapsLazycodexToPublishedPlatformFamily = const shimMapsLazycodexToPublishedPlatformFamily =
platformResolver.includes("lazycodex") && platformResolver.includes("oh-my-opencode") platformResolver.includes("lazycodex") && platformResolver.includes("oh-my-opencode")
// #then // #then
expect(lazycodexStepOnlyRenamesWrapper, "lazycodex publish step should only rename wrapper metadata").toBe(true) expect(lazycodexStepPinsWrapperVersion, "lazycodex publish step must pin wrapper metadata to 0.1.0").toBe(true)
expect(lazycodexStepUsesOmoPlatformVersion, "lazycodex must depend on the matching OMO platform packages").toBe(true)
expect(lazycodexStepDoesNotRenameOptionalDeps, "lazycodex publish step must keep optionalDependencies on published platform packages").toBe(true) expect(lazycodexStepDoesNotRenameOptionalDeps, "lazycodex publish step must keep optionalDependencies on published platform packages").toBe(true)
expect(shimMapsLazycodexToPublishedPlatformFamily, "platform resolver must map lazycodex to the real published platform package family").toBe(true) expect(shimMapsLazycodexToPublishedPlatformFamily, "platform resolver must map lazycodex to the real published platform package family").toBe(true)
}) })
-7
View File
@@ -61,21 +61,14 @@ function stubOpenCodeSuccess(): void {
describe("runCliInstaller platform branching", () => { describe("runCliInstaller platform branching", () => {
const consoleLogMock = mock(() => {}) const consoleLogMock = mock(() => {})
const consoleLog = console.log const consoleLog = console.log
const originalPublishLazycodex = process.env.OMO_PUBLISH_LAZYCODEX
beforeEach(() => { beforeEach(() => {
consoleLogMock.mockClear() consoleLogMock.mockClear()
console.log = consoleLogMock console.log = consoleLogMock
process.env.OMO_PUBLISH_LAZYCODEX = "true"
}) })
afterEach(() => { afterEach(() => {
console.log = consoleLog console.log = consoleLog
if (originalPublishLazycodex === undefined) {
delete process.env.OMO_PUBLISH_LAZYCODEX
} else {
process.env.OMO_PUBLISH_LAZYCODEX = originalPublishLazycodex
}
mock.restore() mock.restore()
}) })
-7
View File
@@ -9,7 +9,6 @@ describe("runCliInstaller", () => {
const mockConsoleError = mock(() => {}) const mockConsoleError = mock(() => {})
const originalConsoleLog = console.log const originalConsoleLog = console.log
const originalConsoleError = console.error const originalConsoleError = console.error
const originalPublishLazycodex = process.env.OMO_PUBLISH_LAZYCODEX
beforeEach(() => { beforeEach(() => {
console.log = mockConsoleLog console.log = mockConsoleLog
@@ -21,11 +20,6 @@ describe("runCliInstaller", () => {
afterEach(() => { afterEach(() => {
console.log = originalConsoleLog console.log = originalConsoleLog
console.error = originalConsoleError console.error = originalConsoleError
if (originalPublishLazycodex === undefined) {
delete process.env.OMO_PUBLISH_LAZYCODEX
} else {
process.env.OMO_PUBLISH_LAZYCODEX = originalPublishLazycodex
}
mock.restore() mock.restore()
}) })
@@ -134,7 +128,6 @@ describe("runCliInstaller", () => {
it("skips OpenCode checks and writes for platform=codex", async () => { it("skips OpenCode checks and writes for platform=codex", async () => {
// given // given
process.env.OMO_PUBLISH_LAZYCODEX = "true"
const detectSpy = spyOn(configManager, "detectCurrentConfig") const detectSpy = spyOn(configManager, "detectCurrentConfig")
const installedSpy = spyOn(configManager, "isOpenCodeInstalled") const installedSpy = spyOn(configManager, "isOpenCodeInstalled")
const versionSpy = spyOn(configManager, "getOpenCodeVersion") const versionSpy = spyOn(configManager, "getOpenCodeVersion")
+1 -5
View File
@@ -6,7 +6,6 @@ import { doctor } from "./doctor"
import { refreshModelCapabilities } from "./refresh-model-capabilities" import { refreshModelCapabilities } from "./refresh-model-capabilities"
import { createMcpOAuthCommand } from "./mcp-oauth" import { createMcpOAuthCommand } from "./mcp-oauth"
import { boulder } from "./boulder" import { boulder } from "./boulder"
import { isLazycodexPublishingEnabled } from "./lazycodex-feature-flag"
import type { InstallArgs } from "./types" import type { InstallArgs } from "./types"
import type { RunOptions } from "./run" import type { RunOptions } from "./run"
import type { GetLocalVersionOptions } from "./get-local-version/types" import type { GetLocalVersionOptions } from "./get-local-version/types"
@@ -33,14 +32,11 @@ type InstallCommandOptions = {
readonly skipAuth?: boolean readonly skipAuth?: boolean
} }
type Environment = Readonly<Record<string, string | undefined>>
export function resolveInstallArgs( export function resolveInstallArgs(
options: InstallCommandOptions, options: InstallCommandOptions,
invocationName: string | undefined = process.env.OMO_INVOCATION_NAME, invocationName: string | undefined = process.env.OMO_INVOCATION_NAME,
env: Environment = process.env,
): InstallArgs { ): InstallArgs {
const defaultPlatform = invocationName === "lazycodex" && isLazycodexPublishingEnabled(env) ? "codex" : undefined const defaultPlatform = invocationName === "lazycodex" ? "codex" : undefined
return { return {
tui: options.tui !== false, tui: options.tui !== false,
@@ -7,7 +7,6 @@ import { argsToConfig } from "../install-validators"
describe("lazycodex install routing", () => { describe("lazycodex install routing", () => {
const originalInvocationName = process.env.OMO_INVOCATION_NAME const originalInvocationName = process.env.OMO_INVOCATION_NAME
const originalPublishLazycodex = process.env.OMO_PUBLISH_LAZYCODEX
afterEach(() => { afterEach(() => {
if (originalInvocationName === undefined) { if (originalInvocationName === undefined) {
@@ -15,38 +14,11 @@ describe("lazycodex install routing", () => {
} else { } else {
process.env.OMO_INVOCATION_NAME = originalInvocationName process.env.OMO_INVOCATION_NAME = originalInvocationName
} }
if (originalPublishLazycodex === undefined) {
delete process.env.OMO_PUBLISH_LAZYCODEX
} else {
process.env.OMO_PUBLISH_LAZYCODEX = originalPublishLazycodex
}
}) })
test("leaves lazycodex invocation unresolved when lazycodex publishing is disabled", () => { test("defaults platform to codex when invoked as lazycodex", () => {
// given // given
process.env.OMO_INVOCATION_NAME = "lazycodex" process.env.OMO_INVOCATION_NAME = "lazycodex"
delete process.env.OMO_PUBLISH_LAZYCODEX
// when
const args = resolveInstallArgs({
tui: false,
claude: "no",
gemini: "no",
copilot: "no",
})
const config = argsToConfig(args)
// then
expect(args.platform).toBeUndefined()
expect(config.hasCodex).toBe(false)
expect(config.hasOpenCode).toBe(true)
})
test("defaults platform to codex when invoked as lazycodex with lazycodex publishing enabled", () => {
// given
process.env.OMO_INVOCATION_NAME = "lazycodex"
process.env.OMO_PUBLISH_LAZYCODEX = "true"
// when // when
const args = resolveInstallArgs({ const args = resolveInstallArgs({
@@ -63,10 +35,9 @@ describe("lazycodex install routing", () => {
expect(config.hasOpenCode).toBe(false) expect(config.hasOpenCode).toBe(false)
}) })
test("respects explicit --platform=both when lazycodex publishing is enabled", () => { test("respects explicit --platform=both", () => {
// given // given
process.env.OMO_INVOCATION_NAME = "lazycodex" process.env.OMO_INVOCATION_NAME = "lazycodex"
process.env.OMO_PUBLISH_LAZYCODEX = "true"
// when // when
const args = resolveInstallArgs({ const args = resolveInstallArgs({
+1 -12
View File
@@ -59,24 +59,13 @@ describe("install platform resolution", () => {
expect(args.platform).toBe("opencode") expect(args.platform).toBe("opencode")
}) })
test("leaves lazycodex install unresolved when lazycodex publishing is disabled", () => { test("defaults lazycodex install to codex platform", () => {
// given // given
const invocationName = "lazycodex" const invocationName = "lazycodex"
// when // when
const args = resolveInstallArgs({ tui: true }, invocationName) const args = resolveInstallArgs({ tui: true }, invocationName)
// then
expect(args.platform).toBeUndefined()
})
test("defaults lazycodex install to codex platform when lazycodex publishing is enabled", () => {
// given
const invocationName = "lazycodex"
// when
const args = resolveInstallArgs({ tui: true }, invocationName, { OMO_PUBLISH_LAZYCODEX: "true" })
// then // then
expect(args.platform).toBe("codex") expect(args.platform).toBe("codex")
}) })
+5 -21
View File
@@ -130,33 +130,19 @@ describe("validateNonTuiArgs", () => {
expect(result.errors).toContain("--copilot is required (values: no, yes)") expect(result.errors).toContain("--copilot is required (values: no, yes)")
}) })
test("rejects codex-only non-TUI installs when lazycodex publishing is disabled", () => { test("allows codex-only non-TUI installs", () => {
// #given // #given
const args: InstallArgs = { tui: false, platform: "codex" } const args: InstallArgs = { tui: false, platform: "codex" }
// #when // #when
const result = validateNonTuiArgs(args) const result = validateNonTuiArgs(args)
// #then
expect(result.valid).toBe(false)
expect(result.errors).toContain(
"Codex platform install is disabled. Set OMO_PUBLISH_LAZYCODEX=true to enable LazyCodex publish/install.",
)
})
test("allows codex-only non-TUI installs with lazycodex publishing enabled", () => {
// #given
const args: InstallArgs = { tui: false, platform: "codex" }
// #when
const result = validateNonTuiArgs(args, { OMO_PUBLISH_LAZYCODEX: "true" })
// #then // #then
expect(result.valid).toBe(true) expect(result.valid).toBe(true)
expect(result.errors).toEqual([]) expect(result.errors).toEqual([])
}) })
test("rejects platform=both when lazycodex publishing is disabled", () => { test("allows platform=both without a LazyCodex publish flag", () => {
// #given // #given
const args = createArgs({ platform: "both" }) const args = createArgs({ platform: "both" })
@@ -164,10 +150,8 @@ describe("validateNonTuiArgs", () => {
const result = validateNonTuiArgs(args) const result = validateNonTuiArgs(args)
// #then // #then
expect(result.valid).toBe(false) expect(result.valid).toBe(true)
expect(result.errors).toContain( expect(result.errors).toEqual([])
"Codex platform install is disabled. Set OMO_PUBLISH_LAZYCODEX=true to enable LazyCodex publish/install.",
)
}) })
test("rejects OpenCode flags for codex-only non-TUI installs", () => { test("rejects OpenCode flags for codex-only non-TUI installs", () => {
@@ -175,7 +159,7 @@ describe("validateNonTuiArgs", () => {
const args = createArgs({ platform: "codex", claude: "yes" }) const args = createArgs({ platform: "codex", claude: "yes" })
// #when // #when
const result = validateNonTuiArgs(args, { OMO_PUBLISH_LAZYCODEX: "true" }) const result = validateNonTuiArgs(args)
// #then // #then
expect(result.valid).toBe(false) expect(result.valid).toBe(false)
+1 -14
View File
@@ -7,11 +7,6 @@ import type {
InstallConfig, InstallConfig,
InstallPlatform, InstallPlatform,
} from "./types" } from "./types"
import {
LAZYCODEX_DISABLED_MESSAGE,
isLazycodexPublishingEnabled,
platformRequiresLazycodex,
} from "./lazycodex-feature-flag"
export const SYMBOLS = { export const SYMBOLS = {
check: color.green("[OK]"), check: color.green("[OK]"),
@@ -24,7 +19,6 @@ export const SYMBOLS = {
} }
const ANSI_COLOR_PATTERN = new RegExp("\u001b\\[[0-9;]*m", "g") const ANSI_COLOR_PATTERN = new RegExp("\u001b\\[[0-9;]*m", "g")
type Environment = Readonly<Record<string, string | undefined>>
function formatProvider(name: string, enabled: boolean, detail?: string): string { function formatProvider(name: string, enabled: boolean, detail?: string): string {
const status = enabled ? SYMBOLS.check : color.dim("○") const status = enabled ? SYMBOLS.check : color.dim("○")
@@ -125,19 +119,12 @@ export function printBox(content: string, title?: string): void {
console.log() console.log()
} }
export function validateNonTuiArgs( export function validateNonTuiArgs(args: InstallArgs): { valid: boolean; errors: string[] } {
args: InstallArgs,
env: Environment = process.env,
): { valid: boolean; errors: string[] } {
const errors: string[] = [] const errors: string[] = []
const platform = resolvePlatform(args) const platform = resolvePlatform(args)
const hasOpenCode = platform === "opencode" || platform === "both" const hasOpenCode = platform === "opencode" || platform === "both"
const hasCodexOnly = platform === "codex" const hasCodexOnly = platform === "codex"
if (platformRequiresLazycodex(platform) && !isLazycodexPublishingEnabled(env)) {
errors.push(LAZYCODEX_DISABLED_MESSAGE)
}
if (hasOpenCode && args.claude === undefined) { if (hasOpenCode && args.claude === undefined) {
errors.push("--claude is required (values: no, yes, max20)") errors.push("--claude is required (values: no, yes, max20)")
} else if (args.claude !== undefined && !["no", "yes", "max20"].includes(args.claude)) { } else if (args.claude !== undefined && !["no", "yes", "max20"].includes(args.claude)) {
-15
View File
@@ -1,15 +0,0 @@
import type { InstallPlatform } from "./types"
export const LAZYCODEX_PUBLISH_FLAG = "OMO_PUBLISH_LAZYCODEX"
export const LAZYCODEX_DISABLED_MESSAGE =
"Codex platform install is disabled. Set OMO_PUBLISH_LAZYCODEX=true to enable LazyCodex publish/install."
type Environment = Readonly<Record<string, string | undefined>>
export function isLazycodexPublishingEnabled(env: Environment = process.env): boolean {
return env[LAZYCODEX_PUBLISH_FLAG] === "true"
}
export function platformRequiresLazycodex(platform: InstallPlatform | undefined): boolean {
return platform === "codex" || platform === "both"
}
+12 -8
View File
@@ -46,12 +46,12 @@ describe("promptInstallPlatform", () => {
mock.restore() mock.restore()
}) })
test("offers OpenCode, Codex, and Both choices when lazycodex publishing is enabled", async () => { test("offers OpenCode, Codex, and Both choices", async () => {
// given // given
const selectSpy = spyOn(p, "select").mockResolvedValue("opencode") const selectSpy = spyOn(p, "select").mockResolvedValue("opencode")
// when // when
const value = await prompts.promptInstallPlatform("opencode", true) const value = await prompts.promptInstallPlatform("opencode")
// then // then
expect(value).toBe("opencode") expect(value).toBe("opencode")
@@ -66,19 +66,23 @@ describe("promptInstallPlatform", () => {
}) })
}) })
test("hides Codex platform choices when lazycodex publishing is disabled", async () => { test("preserves Codex as the initial platform", async () => {
// given // given
const selectSpy = spyOn(p, "select").mockResolvedValue("opencode") const selectSpy = spyOn(p, "select").mockResolvedValue("codex")
// when // when
const value = await prompts.promptInstallPlatform("codex", false) const value = await prompts.promptInstallPlatform("codex")
// then // then
expect(value).toBe("opencode") expect(value).toBe("codex")
expect(selectSpy).toHaveBeenCalledTimes(1) expect(selectSpy).toHaveBeenCalledTimes(1)
expect(selectSpy.mock.calls[0]?.[0]).toMatchObject({ expect(selectSpy.mock.calls[0]?.[0]).toMatchObject({
initialValue: "opencode", initialValue: "codex",
options: [{ value: "opencode" }], options: [
{ value: "opencode" },
{ value: "codex" },
{ value: "both" },
],
}) })
}) })
}) })
+3 -11
View File
@@ -7,7 +7,6 @@ import type {
InstallPlatform, InstallPlatform,
} from "./types" } from "./types"
import { detectedToInitialValues } from "./install-validators" import { detectedToInitialValues } from "./install-validators"
import { isLazycodexPublishingEnabled } from "./lazycodex-feature-flag"
async function selectOrCancel<TValue extends Readonly<string | boolean | number>>(params: { async function selectOrCancel<TValue extends Readonly<string | boolean | number>>(params: {
message: string message: string
@@ -30,24 +29,17 @@ async function selectOrCancel<TValue extends Readonly<string | boolean | number>
export async function promptInstallPlatform( export async function promptInstallPlatform(
initialValue: InstallPlatform = "opencode", initialValue: InstallPlatform = "opencode",
lazycodexEnabled = isLazycodexPublishingEnabled(),
): Promise<InstallPlatform | null> { ): Promise<InstallPlatform | null> {
const options: Option<InstallPlatform>[] = [ const options: Option<InstallPlatform>[] = [
{ value: "opencode", label: "OpenCode", hint: "Install OpenCode plugin only" }, { value: "opencode", label: "OpenCode", hint: "Install OpenCode plugin only" },
{ value: "codex", label: "Codex", hint: "Install Codex harness adapter only" },
{ value: "both", label: "Both", hint: "Install OpenCode plugin and Codex adapter" },
] ]
if (lazycodexEnabled) {
options.push(
{ value: "codex", label: "Codex", hint: "Install Codex harness adapter only" },
{ value: "both", label: "Both", hint: "Install OpenCode plugin and Codex adapter" },
)
}
const safeInitialValue = lazycodexEnabled || initialValue === "opencode" ? initialValue : "opencode"
return selectOrCancel<InstallPlatform>({ return selectOrCancel<InstallPlatform>({
message: "Which platform do you want to install?", message: "Which platform do you want to install?",
options, options,
initialValue: safeInitialValue, initialValue,
}) })
} }
+1 -32
View File
@@ -19,7 +19,6 @@ function createMockSpinner(): ReturnType<typeof p.spinner> {
describe("runTuiInstaller", () => { describe("runTuiInstaller", () => {
const originalIsStdinTty = process.stdin.isTTY const originalIsStdinTty = process.stdin.isTTY
const originalIsStdoutTty = process.stdout.isTTY const originalIsStdoutTty = process.stdout.isTTY
const originalPublishLazycodex = process.env.OMO_PUBLISH_LAZYCODEX
beforeEach(() => { beforeEach(() => {
Object.defineProperty(process.stdin, "isTTY", { configurable: true, value: true }) Object.defineProperty(process.stdin, "isTTY", { configurable: true, value: true })
@@ -29,11 +28,6 @@ describe("runTuiInstaller", () => {
afterEach(() => { afterEach(() => {
Object.defineProperty(process.stdin, "isTTY", { configurable: true, value: originalIsStdinTty }) Object.defineProperty(process.stdin, "isTTY", { configurable: true, value: originalIsStdinTty })
Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: originalIsStdoutTty }) Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: originalIsStdoutTty })
if (originalPublishLazycodex === undefined) {
delete process.env.OMO_PUBLISH_LAZYCODEX
} else {
process.env.OMO_PUBLISH_LAZYCODEX = originalPublishLazycodex
}
}) })
it("blocks installation when OpenCode is below the minimum version", async () => { it("blocks installation when OpenCode is below the minimum version", async () => {
@@ -57,7 +51,6 @@ describe("runTuiInstaller", () => {
hasKimiForCoding: false, hasKimiForCoding: false,
hasOpencodeGo: false, hasOpencodeGo: false,
hasVercelAiGateway: false, hasVercelAiGateway: false,
codexAutonomous: false,
}), }),
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true), spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.3.9"), spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.3.9"),
@@ -83,29 +76,6 @@ describe("runTuiInstaller", () => {
outroSpy.mockRestore() outroSpy.mockRestore()
}) })
it("blocks codex platform when lazycodex publishing is disabled", async () => {
// given
delete process.env.OMO_PUBLISH_LAZYCODEX
const platformSpy = spyOn(tuiInstallPrompts, "promptInstallPlatform").mockResolvedValue("codex")
const promptConfigSpy = spyOn(tuiInstallPrompts, "promptInstallConfig")
const logErrorSpy = spyOn(p.log, "error").mockImplementation(() => undefined)
const outroSpy = spyOn(p, "outro").mockImplementation(() => undefined)
// when
const result = await runTuiInstaller({ tui: true, platform: "codex" }, "3.16.0")
// then
expect(result).toBe(1)
expect(platformSpy).toHaveBeenCalled()
expect(promptConfigSpy).not.toHaveBeenCalled()
expect(logErrorSpy).toHaveBeenCalled()
platformSpy.mockRestore()
promptConfigSpy.mockRestore()
logErrorSpy.mockRestore()
outroSpy.mockRestore()
})
it("proceeds when OpenCode meets the minimum version", async () => { it("proceeds when OpenCode meets the minimum version", async () => {
// given // given
const restoreSpies = [ const restoreSpies = [
@@ -132,7 +102,6 @@ describe("runTuiInstaller", () => {
hasKimiForCoding: false, hasKimiForCoding: false,
hasOpencodeGo: false, hasOpencodeGo: false,
hasVercelAiGateway: false, hasVercelAiGateway: false,
codexAutonomous: false,
}), }),
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true), spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"), spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"),
@@ -175,7 +144,6 @@ describe("runTuiInstaller", () => {
it("skips OpenCode checks and writes when platform is codex", async () => { it("skips OpenCode checks and writes when platform is codex", async () => {
// given // given
process.env.OMO_PUBLISH_LAZYCODEX = "true"
const restoreSpies = [ const restoreSpies = [
spyOn(p, "spinner").mockReturnValue(createMockSpinner()), spyOn(p, "spinner").mockReturnValue(createMockSpinner()),
spyOn(p, "intro").mockImplementation(() => undefined), spyOn(p, "intro").mockImplementation(() => undefined),
@@ -200,6 +168,7 @@ describe("runTuiInstaller", () => {
hasKimiForCoding: false, hasKimiForCoding: false,
hasOpencodeGo: false, hasOpencodeGo: false,
hasVercelAiGateway: false, hasVercelAiGateway: false,
codexAutonomous: false,
}), }),
] ]
const detectConfigSpy = spyOn(configManager, "detectCurrentConfig") const detectConfigSpy = spyOn(configManager, "detectCurrentConfig")
-10
View File
@@ -13,11 +13,6 @@ import { detectedToInitialValues, formatConfigSummary, SYMBOLS } from "./install
import { getUnsupportedOpenCodeVersionMessage } from "./minimum-opencode-version" import { getUnsupportedOpenCodeVersionMessage } from "./minimum-opencode-version"
import { promptInstallConfig, promptInstallPlatform } from "./tui-install-prompts" import { promptInstallConfig, promptInstallPlatform } from "./tui-install-prompts"
import { runCodexInstaller } from "./install-codex" import { runCodexInstaller } from "./install-codex"
import {
LAZYCODEX_DISABLED_MESSAGE,
isLazycodexPublishingEnabled,
platformRequiresLazycodex,
} from "./lazycodex-feature-flag"
import { STAR_REPOSITORIES, formatGitHubStarCommand } from "./star-request" import { STAR_REPOSITORIES, formatGitHubStarCommand } from "./star-request"
export async function runTuiInstaller(args: InstallArgs, version: string): Promise<number> { export async function runTuiInstaller(args: InstallArgs, version: string): Promise<number> {
@@ -28,11 +23,6 @@ export async function runTuiInstaller(args: InstallArgs, version: string): Promi
const selectedPlatform = await promptInstallPlatform(args.platform ?? "opencode") const selectedPlatform = await promptInstallPlatform(args.platform ?? "opencode")
if (!selectedPlatform) return 1 if (!selectedPlatform) return 1
if (platformRequiresLazycodex(selectedPlatform) && !isLazycodexPublishingEnabled()) {
p.log.error(LAZYCODEX_DISABLED_MESSAGE)
p.outro(color.red("Installation blocked."))
return 1
}
const hasOpenCode = selectedPlatform === "opencode" || selectedPlatform === "both" const hasOpenCode = selectedPlatform === "opencode" || selectedPlatform === "both"
const detected = hasOpenCode const detected = hasOpenCode