feat(cli): enable lazycodex codex installs
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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" },
|
||||||
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user