fix: publish lazycodex as release alias

This commit is contained in:
YeonGyu-Kim
2026-05-30 23:34:13 +09:00
parent d508b9b52b
commit 090e780974
4 changed files with 132 additions and 129 deletions
+33 -74
View File
@@ -22,8 +22,8 @@ on:
required: false required: false
type: boolean type: boolean
default: false default: false
publish_lazycodex: sync_lazycodex_marketplace:
description: "Publish lazycodex npm alias and sync Codex marketplace" description: "Sync the LazyCodex Codex marketplace repository"
required: false required: false
type: boolean type: boolean
default: false default: false
@@ -35,9 +35,6 @@ 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
@@ -81,7 +78,7 @@ jobs:
contents: read contents: read
steps: steps:
- name: Require LazyCodex sync token - name: Require LazyCodex sync token
if: inputs.publish_lazycodex == true if: inputs.sync_lazycodex_marketplace == true
env: env:
LAZYCODEX_SYNC_TOKEN: ${{ secrets.LAZYCODEX_SYNC_TOKEN }} LAZYCODEX_SYNC_TOKEN: ${{ secrets.LAZYCODEX_SYNC_TOKEN }}
run: | run: |
@@ -94,7 +91,6 @@ jobs:
env: env:
REPO: code-yeongyu/oh-my-openagent REPO: code-yeongyu/oh-my-openagent
WORKFLOW_FILE: publish.yml WORKFLOW_FILE: publish.yml
PUBLISH_LAZYCODEX: ${{ inputs.publish_lazycodex }}
run: | run: |
OIDC_TOKEN=$(curl -sH "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \ OIDC_TOKEN=$(curl -sH "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=npm:registry.npmjs.org" \ "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=npm:registry.npmjs.org" \
@@ -106,10 +102,7 @@ jobs:
fi fi
PLATFORMS=(darwin-arm64 darwin-x64 darwin-x64-baseline linux-x64 linux-x64-baseline linux-arm64 linux-x64-musl linux-x64-musl-baseline linux-arm64-musl windows-x64 windows-x64-baseline) PLATFORMS=(darwin-arm64 darwin-x64 darwin-x64-baseline linux-x64 linux-x64-baseline linux-arm64 linux-x64-musl linux-x64-musl-baseline linux-arm64-musl windows-x64 windows-x64-baseline)
ALL_PACKAGES=(oh-my-opencode oh-my-openagent) ALL_PACKAGES=(oh-my-opencode oh-my-openagent lazycodex)
if [ "${PUBLISH_LAZYCODEX}" = "true" ]; then
ALL_PACKAGES+=(lazycodex)
fi
for plat in "${PLATFORMS[@]}"; do for plat in "${PLATFORMS[@]}"; do
ALL_PACKAGES+=("oh-my-opencode-${plat}") ALL_PACKAGES+=("oh-my-opencode-${plat}")
ALL_PACKAGES+=("oh-my-openagent-${plat}") ALL_PACKAGES+=("oh-my-openagent-${plat}")
@@ -135,45 +128,20 @@ jobs:
done done
if [ ${#FAILED[@]} -gt 0 ]; then if [ ${#FAILED[@]} -gt 0 ]; then
HARD_FAILED=() {
SOFT_FAILED=() echo
for pkg in "${FAILED[@]}"; do echo "::error::Trusted publisher not configured for ${#FAILED[@]} required package(s)."
if [ "${pkg}" = "lazycodex" ]; then echo "::error::Configure each below at the URL with these values:"
SOFT_FAILED+=("${pkg}") echo "::error:: Provider: GitHub Actions"
else echo "::error:: Organization: code-yeongyu"
HARD_FAILED+=("${pkg}") echo "::error:: Repository: ${REPO}"
fi echo "::error:: Workflow filename: ${WORKFLOW_FILE}"
done echo
for pkg in "${FAILED[@]}"; do
if [ ${#HARD_FAILED[@]} -gt 0 ]; then echo "::error:: https://www.npmjs.com/package/${pkg}/access"
{ done
echo } >&2
echo "::error::Trusted publisher not configured for ${#HARD_FAILED[@]} required package(s)." exit 1
echo "::error::Configure each below at the URL with these values:"
echo "::error:: Provider: GitHub Actions"
echo "::error:: Organization: code-yeongyu"
echo "::error:: Repository: ${REPO}"
echo "::error:: Workflow filename: ${WORKFLOW_FILE}"
echo
for pkg in "${HARD_FAILED[@]}"; do
echo "::error:: https://www.npmjs.com/package/${pkg}/access"
done
} >&2
exit 1
fi
if [ ${#SOFT_FAILED[@]} -gt 0 ]; then
{
echo
echo "::warning::Trusted publisher is not configured for lazycodex yet."
echo "::warning::First publish can require a one-time manual npm publish to claim the name."
echo "::warning::After claiming, configure GitHub Actions trusted publishing at:"
echo "::warning:: https://www.npmjs.com/package/lazycodex/access"
echo "::warning:: Organization: code-yeongyu"
echo "::warning:: Repository: ${REPO}"
echo "::warning:: Workflow filename: ${WORKFLOW_FILE}"
}
fi
fi fi
echo echo
@@ -313,13 +281,14 @@ jobs:
fi fi
- name: Check if lazycodex already published - name: Check if lazycodex already published
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/${LAZYCODEX_VERSION}") STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/lazycodex/${VERSION}")
if [ "$STATUS" = "200" ]; then if [ "$STATUS" = "200" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT" echo "skip=true" >> "$GITHUB_OUTPUT"
echo "✓ lazycodex@${LAZYCODEX_VERSION} already published" echo "✓ lazycodex@${VERSION} already published"
else else
echo "skip=false" >> "$GITHUB_OUTPUT" echo "skip=false" >> "$GITHUB_OUTPUT"
fi fi
@@ -328,7 +297,7 @@ jobs:
if: >- if: >-
steps.check.outputs.skip != 'true' || steps.check.outputs.skip != 'true' ||
steps.check-openagent.outputs.skip != 'true' || steps.check-openagent.outputs.skip != 'true' ||
(inputs.publish_lazycodex == true && steps.check-lazycodex.outputs.skip != 'true') steps.check-lazycodex.outputs.skip != 'true'
env: env:
VERSION: ${{ needs.release-metadata.outputs.version }} VERSION: ${{ needs.release-metadata.outputs.version }}
run: | run: |
@@ -346,14 +315,14 @@ jobs:
if: >- if: >-
steps.check.outputs.skip != 'true' || steps.check.outputs.skip != 'true' ||
steps.check-openagent.outputs.skip != 'true' || steps.check-openagent.outputs.skip != 'true' ||
(inputs.publish_lazycodex == true && steps.check-lazycodex.outputs.skip != 'true') steps.check-lazycodex.outputs.skip != 'true'
run: bun run build run: bun run build
- name: Strip token auth from .npmrc to force OIDC - name: Strip token auth from .npmrc to force OIDC
if: >- if: >-
steps.check.outputs.skip != 'true' || steps.check.outputs.skip != 'true' ||
steps.check-openagent.outputs.skip != 'true' || steps.check-openagent.outputs.skip != 'true' ||
(inputs.publish_lazycodex == true && steps.check-lazycodex.outputs.skip != 'true') steps.check-lazycodex.outputs.skip != 'true'
run: | run: |
for f in .npmrc "$HOME/.npmrc"; do for f in .npmrc "$HOME/.npmrc"; do
if [ -f "$f" ]; then if [ -f "$f" ]; then
@@ -372,7 +341,7 @@ jobs:
if [ -n "$DIST_TAG" ]; then if [ -n "$DIST_TAG" ]; then
npm publish --access public --provenance --tag "$DIST_TAG" --loglevel verbose npm publish --access public --provenance --tag "$DIST_TAG" --loglevel verbose
else else
npm publish --access public --provenance --loglevel verbose npm publish --access public --provenance --tag latest --loglevel verbose
fi fi
- name: Publish oh-my-openagent - name: Publish oh-my-openagent
@@ -405,36 +374,26 @@ jobs:
git checkout -- package.json git checkout -- package.json
- name: Publish lazycodex - name: Publish lazycodex
if: inputs.publish_lazycodex == true && steps.check-lazycodex.outputs.skip != 'true' if: steps.check-lazycodex.outputs.skip != 'true'
continue-on-error: true
env: env:
OMO_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 lazycodex_version "$LAZYCODEX_VERSION" --arg omo_version "$OMO_VERSION" ' jq --arg omo_version "$OMO_VERSION" '
.name = "lazycodex" | .name = "lazycodex" |
.version = $lazycodex_version | .version = $omo_version |
.optionalDependencies = (.optionalDependencies | to_entries | map(.value = $omo_version) | 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
if [ -n "$DIST_TAG" ]; then if [ -n "$DIST_TAG" ]; then
npm publish --access public --provenance --tag "$DIST_TAG" --loglevel verbose npm publish --access public --provenance --tag "$DIST_TAG" --loglevel verbose
else else
npm publish --access public --provenance --loglevel verbose npm publish --access public --provenance --tag latest --loglevel verbose
fi
PUBLISH_STATUS=$?
set -e
if [ "$PUBLISH_STATUS" -ne 0 ]; then
echo "::warning::lazycodex publish failed. If this is the first publish, claim it once manually from a trusted environment (with NPM_AUTH_TOKEN) using npm publish."
echo "::warning::Then configure GitHub Actions trusted publishing at https://www.npmjs.com/package/lazycodex/access with org=code-yeongyu repo=oh-my-openagent workflow=publish.yml."
exit 0
fi fi
- name: Restore package.json after lazycodex publish attempt - name: Restore package.json after lazycodex publish attempt
if: always() && inputs.publish_lazycodex == true && steps.check-lazycodex.outputs.skip != 'true' if: always() && steps.check-lazycodex.outputs.skip != 'true'
run: | run: |
git checkout -- package.json git checkout -- package.json
@@ -572,7 +531,7 @@ jobs:
fi fi
- name: Checkout LazyCodex marketplace - name: Checkout LazyCodex marketplace
if: inputs.publish_lazycodex == true if: inputs.sync_lazycodex_marketplace == true
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
repository: code-yeongyu/lazycodex repository: code-yeongyu/lazycodex
@@ -581,7 +540,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Sync LazyCodex Codex marketplace - name: Sync LazyCodex Codex marketplace
if: inputs.publish_lazycodex == true if: inputs.sync_lazycodex_marketplace == true
env: env:
VERSION: ${{ needs.release-metadata.outputs.version }} VERSION: ${{ needs.release-metadata.outputs.version }}
run: | run: |
+5 -34
View File
@@ -6,7 +6,6 @@ import { spawnSync } from "node:child_process";
import { readFileSync } from "node:fs"; import { readFileSync } from "node:fs";
import { createRequire } from "node:module"; import { createRequire } from "node:module";
import { basename } from "node:path"; import { basename } from "node:path";
import { fileURLToPath } from "node:url";
import { import {
getPlatformPackageCandidates, getPlatformPackageCandidates,
getBinaryPath, getBinaryPath,
@@ -97,10 +96,13 @@ function getWrapperPackageName() {
* (e.g. `lazycodex` defaults to the Codex install flow). * (e.g. `lazycodex` defaults to the Codex install flow).
* @returns {string} * @returns {string}
*/ */
function getInvocationName() { function getInvocationName(wrapperPackageName) {
if (process.env.OMO_INVOCATION_NAME) { if (process.env.OMO_INVOCATION_NAME) {
return process.env.OMO_INVOCATION_NAME; return process.env.OMO_INVOCATION_NAME;
} }
if (getPackageBareName(wrapperPackageName) === "lazycodex") {
return "lazycodex";
}
const argv1 = process.argv[1] ?? ""; const argv1 = process.argv[1] ?? "";
if (!argv1) { if (!argv1) {
return "oh-my-opencode"; return "oh-my-opencode";
@@ -108,42 +110,11 @@ function getInvocationName() {
return basename(argv1, ".js").replace(/\.exe$/, ""); return basename(argv1, ".js").replace(/\.exe$/, "");
} }
function shouldRunBundledLazyCodexCli(packageName, invocationName) {
return getPackageBareName(packageName) === "lazycodex" && invocationName === "lazycodex";
}
function runBundledLazyCodexCli(invocationName) {
const cliPath = fileURLToPath(new URL("../dist/cli/index.js", import.meta.url));
const result = spawnSync("bun", [cliPath, ...process.argv.slice(2)], {
stdio: "inherit",
env: {
...process.env,
OMO_INVOCATION_NAME: invocationName,
},
});
if (result.error) {
console.error("\nlazycodex: Failed to execute bundled CLI with Bun.");
console.error("Install Bun or run through `bunx lazycodex`.");
console.error(`Error: ${result.error.message}\n`);
process.exit(2);
}
if (result.signal) {
process.exit(getSignalExitCode(result.signal));
}
process.exit(result.status ?? 1);
}
function main() { function main() {
const { platform, arch } = process; const { platform, arch } = process;
const libcFamily = getLibcFamily(); const libcFamily = getLibcFamily();
const wrapperPackageName = getWrapperPackageName(); const wrapperPackageName = getWrapperPackageName();
const invocationName = getInvocationName(); const invocationName = getInvocationName(wrapperPackageName);
if (shouldRunBundledLazyCodexCli(wrapperPackageName, invocationName)) {
runBundledLazyCodexCli(invocationName);
}
const packageBaseName = resolvePlatformPackageBaseName(wrapperPackageName); const packageBaseName = resolvePlatformPackageBaseName(wrapperPackageName);
const avx2Supported = supportsAvx2(); const avx2Supported = supportsAvx2();
+71 -9
View File
@@ -1,9 +1,12 @@
/// <reference types="bun-types" />
import { afterEach, describe, expect, test } from "bun:test"; import { afterEach, describe, expect, test } from "bun:test";
import { spawnSync } from "node:child_process"; import { spawnSync } from "node:child_process";
import { chmod, cp, mkdir, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises"; import { chmod, cp, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { dirname, join } from "node:path"; import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { getPlatformPackageCandidates } from "./platform.js";
const testRoots: string[] = []; const testRoots: string[] = [];
@@ -12,7 +15,7 @@ afterEach(async () => {
}); });
describe("lazycodex bin wrapper", () => { describe("lazycodex bin wrapper", () => {
test("runs the bundled Bun CLI instead of stale OMO platform binaries", async () => { test("runs the platform binary so npx lazycodex does not require Bun", async () => {
// #given // #given
const fixture = await createLazyCodexFixture(); const fixture = await createLazyCodexFixture();
const nodePath = Bun.which("node") ?? "node"; const nodePath = Bun.which("node") ?? "node";
@@ -23,7 +26,7 @@ describe("lazycodex bin wrapper", () => {
env: { env: {
...process.env, ...process.env,
CAPTURE_DIR: fixture.captureDir, CAPTURE_DIR: fixture.captureDir,
PATH: `${fixture.fakeBinDir}:${process.env.PATH ?? ""}`, PATH: fixture.fakeBinDir,
}, },
}); });
@@ -31,13 +34,12 @@ describe("lazycodex bin wrapper", () => {
expect(result.status).toBe(23); expect(result.status).toBe(23);
expect((await readFile(join(fixture.captureDir, "env"), "utf8")).trim()).toBe("lazycodex"); expect((await readFile(join(fixture.captureDir, "env"), "utf8")).trim()).toBe("lazycodex");
expect((await readFile(join(fixture.captureDir, "args"), "utf8")).trim().split("\n")).toEqual([ expect((await readFile(join(fixture.captureDir, "args"), "utf8")).trim().split("\n")).toEqual([
await realpath(fixture.bundledCli),
"install", "install",
"--no-tui", "--no-tui",
]); ]);
}); });
test("runs the bundled Bun CLI when published under an npm scope", async () => { test("runs the platform binary when published under an npm scope", async () => {
// #given // #given
const fixture = await createLazyCodexFixture({ packageName: "@code-yeongyu/lazycodex" }); const fixture = await createLazyCodexFixture({ packageName: "@code-yeongyu/lazycodex" });
const nodePath = Bun.which("node") ?? "node"; const nodePath = Bun.which("node") ?? "node";
@@ -48,7 +50,31 @@ describe("lazycodex bin wrapper", () => {
env: { env: {
...process.env, ...process.env,
CAPTURE_DIR: fixture.captureDir, CAPTURE_DIR: fixture.captureDir,
PATH: `${fixture.fakeBinDir}:${process.env.PATH ?? ""}`, PATH: fixture.fakeBinDir,
},
});
// #then
expect(result.status).toBe(23);
expect((await readFile(join(fixture.captureDir, "env"), "utf8")).trim()).toBe("lazycodex");
expect((await readFile(join(fixture.captureDir, "args"), "utf8")).trim().split("\n")).toEqual([
"install",
"--no-tui",
]);
});
test("routes npm shim execution from the lazycodex package to the Codex installer", async () => {
// #given
const fixture = await createLazyCodexFixture({ wrapperFileName: "oh-my-opencode.js" });
const nodePath = Bun.which("node") ?? "node";
// #when
const result = spawnSync(nodePath, [fixture.wrapperBin, "install", "--no-tui"], {
encoding: "utf8",
env: {
...process.env,
CAPTURE_DIR: fixture.captureDir,
PATH: fixture.fakeBinDir,
}, },
}); });
@@ -56,14 +82,13 @@ describe("lazycodex bin wrapper", () => {
expect(result.status).toBe(23); expect(result.status).toBe(23);
expect((await readFile(join(fixture.captureDir, "env"), "utf8")).trim()).toBe("lazycodex"); expect((await readFile(join(fixture.captureDir, "env"), "utf8")).trim()).toBe("lazycodex");
expect((await readFile(join(fixture.captureDir, "args"), "utf8")).trim().split("\n")).toEqual([ expect((await readFile(join(fixture.captureDir, "args"), "utf8")).trim().split("\n")).toEqual([
await realpath(fixture.bundledCli),
"install", "install",
"--no-tui", "--no-tui",
]); ]);
}); });
}); });
async function createLazyCodexFixture(options: { packageName?: string } = {}) { async function createLazyCodexFixture(options: { packageName?: string; wrapperFileName?: string } = {}) {
const root = await mkdtemp(join(tmpdir(), "lazycodex-bin-wrapper-")); const root = await mkdtemp(join(tmpdir(), "lazycodex-bin-wrapper-"));
testRoots.push(root); testRoots.push(root);
@@ -76,10 +101,16 @@ async function createLazyCodexFixture(options: { packageName?: string } = {}) {
await mkdir(fakeBinDir, { recursive: true }); await mkdir(fakeBinDir, { recursive: true });
await mkdir(captureDir, { recursive: true }); await mkdir(captureDir, { recursive: true });
await cp(fileURLToPath(new URL("./oh-my-opencode.js", import.meta.url)), join(binDir, "lazycodex")); const wrapperFileName = options.wrapperFileName ?? "lazycodex";
const wrapperBin = join(binDir, wrapperFileName);
await cp(fileURLToPath(new URL("./oh-my-opencode.js", import.meta.url)), wrapperBin);
if (wrapperFileName !== "lazycodex") {
await symlink(wrapperFileName, join(binDir, "lazycodex"));
}
await cp(fileURLToPath(new URL("./platform.js", import.meta.url)), join(binDir, "platform.js")); await cp(fileURLToPath(new URL("./platform.js", import.meta.url)), join(binDir, "platform.js"));
await writeFile(join(root, "package.json"), JSON.stringify({ name: options.packageName ?? "lazycodex", type: "module" })); await writeFile(join(root, "package.json"), JSON.stringify({ name: options.packageName ?? "lazycodex", type: "module" }));
await writeFile(distCli, "#!/usr/bin/env bun\n"); await writeFile(distCli, "#!/usr/bin/env bun\n");
await writePlatformPackages(root);
const fakeBun = join(fakeBinDir, "bun"); const fakeBun = join(fakeBinDir, "bun");
await writeFile( await writeFile(
@@ -99,5 +130,36 @@ async function createLazyCodexFixture(options: { packageName?: string } = {}) {
captureDir, captureDir,
fakeBinDir, fakeBinDir,
lazycodexBin: join(binDir, "lazycodex"), lazycodexBin: join(binDir, "lazycodex"),
wrapperBin,
}; };
} }
async function writePlatformPackages(root: string): Promise<void> {
const packages = getPlatformPackageCandidates({
platform: process.platform,
arch: process.arch,
libcFamily: process.platform === "linux" ? "glibc" : undefined,
packageBaseName: "oh-my-opencode",
});
for (const packageName of packages) {
const binaryPath = join(root, "node_modules", packageName, "bin", process.platform === "win32" ? "oh-my-opencode.exe" : "oh-my-opencode");
await mkdir(dirname(binaryPath), { recursive: true });
await writeFile(
binaryPath,
[
"#!/bin/sh",
"printf '%s\\n' \"$OMO_INVOCATION_NAME\" > \"$CAPTURE_DIR/env\"",
"printf '%s\\n' \"$@\" > \"$CAPTURE_DIR/args\"",
"exit 23",
"",
].join("\n"),
);
await chmod(binaryPath, 0o755);
}
if (process.platform === "linux") {
const detectLibcPath = join(root, "node_modules", "detect-libc", "index.js");
await mkdir(dirname(detectLibcPath), { recursive: true });
await writeFile(detectLibcPath, 'exports.familySync = () => "glibc";\n');
}
}
+23 -12
View File
@@ -155,7 +155,7 @@ describe("test workflows", () => {
expect(marketplacePushSkipsWhenClean, "marketplace sync must skip push when rerun has no changes").toBe(true) expect(marketplacePushSkipsWhenClean, "marketplace sync must skip push when rerun has no changes").toBe(true)
}) })
test("keeps LazyCodex deployment behind an explicit publish flag", () => { test("publishes the LazyCodex npm alias on every release while keeping marketplace sync explicit", () => {
// #given // #given
const workflow = readFileSync(publishWorkflowPath, "utf8") const workflow = readFileSync(publishWorkflowPath, "utf8")
@@ -163,8 +163,8 @@ describe("test workflows", () => {
const keepsCodexPluginVersionIndependent = 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/.codex-plugin/plugin.json") &&
!workflow.includes("jq --arg v \"$VERSION\" '.version = $v' packages/omo-codex/plugin/package.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("sync_lazycodex_marketplace:") &&
workflow.includes('description: "Publish lazycodex npm alias and sync Codex marketplace"') && workflow.includes('description: "Sync the LazyCodex Codex marketplace repository"') &&
workflow.includes("default: false") workflow.includes("default: false")
const syncsLazycodexMarketplace = workflow.includes("bun run script/sync-lazycodex-marketplace.ts") const syncsLazycodexMarketplace = workflow.includes("bun run script/sync-lazycodex-marketplace.ts")
const syncBuildsMcpDists = const syncBuildsMcpDists =
@@ -175,10 +175,18 @@ describe("test workflows", () => {
workflow.includes("bun run --cwd packages/omo-codex/plugin build") && workflow.includes("bun run --cwd packages/omo-codex/plugin build") &&
workflow.indexOf("bun run --cwd packages/omo-codex/plugin build") < workflow.indexOf("bun run script/sync-lazycodex-marketplace.ts") workflow.indexOf("bun run --cwd packages/omo-codex/plugin build") < workflow.indexOf("bun run script/sync-lazycodex-marketplace.ts")
const pushesLazycodexMarketplace = workflow.includes("code-yeongyu/lazycodex") const pushesLazycodexMarketplace = workflow.includes("code-yeongyu/lazycodex")
const gatesLazycodexNpmPublish = workflow.includes("name: Publish lazycodex") && const publishLazycodexStep = workflow.slice(
workflow.includes("if: inputs.publish_lazycodex == true && steps.check-lazycodex.outputs.skip != 'true'") workflow.indexOf("name: Publish lazycodex"),
workflow.indexOf("name: Restore package.json after lazycodex publish attempt"),
)
const alwaysChecksLazycodexNpm = workflow.includes("name: Check if lazycodex already published") &&
workflow.includes('https://registry.npmjs.org/lazycodex/${VERSION}')
const publishesLazycodexNpm = publishLazycodexStep.includes("name: Publish lazycodex") &&
publishLazycodexStep.includes("if: steps.check-lazycodex.outputs.skip != 'true'") &&
publishLazycodexStep.includes("npm publish --access public --provenance --tag latest --loglevel verbose") &&
!publishLazycodexStep.includes("continue-on-error: true")
const gatesLazycodexMarketplaceSync = workflow.includes("name: Sync LazyCodex Codex marketplace") && const gatesLazycodexMarketplaceSync = workflow.includes("name: Sync LazyCodex Codex marketplace") &&
workflow.includes("if: inputs.publish_lazycodex == true") workflow.includes("if: inputs.sync_lazycodex_marketplace == true")
const tokenRequirementBeforePublish = workflow.indexOf("name: Require LazyCodex sync token") < const tokenRequirementBeforePublish = workflow.indexOf("name: Require LazyCodex sync token") <
workflow.indexOf("publish-main:") workflow.indexOf("publish-main:")
const requiresLazycodexSyncToken = workflow.includes("LAZYCODEX_SYNC_TOKEN: ${{ secrets.LAZYCODEX_SYNC_TOKEN }}") && const requiresLazycodexSyncToken = workflow.includes("LAZYCODEX_SYNC_TOKEN: ${{ secrets.LAZYCODEX_SYNC_TOKEN }}") &&
@@ -187,13 +195,14 @@ describe("test workflows", () => {
// #then // #then
expect(keepsCodexPluginVersionIndependent, "LazyCodex plugin metadata must keep its own 0.1.0 version").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 marketplace sync 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)
expect(syncBuildsCodexPlugin, "release must build the aggregate Codex plugin before LazyCodex marketplace sync").toBe(true) expect(syncBuildsCodexPlugin, "release must build the aggregate Codex plugin before LazyCodex marketplace sync").toBe(true)
expect(pushesLazycodexMarketplace, "release must target the LazyCodex repository").toBe(true) expect(pushesLazycodexMarketplace, "release must target the LazyCodex repository").toBe(true)
expect(gatesLazycodexNpmPublish, "lazycodex npm publish must require publish_lazycodex=true").toBe(true) expect(alwaysChecksLazycodexNpm, "release must always check lazycodex using the release version").toBe(true)
expect(gatesLazycodexMarketplaceSync, "LazyCodex marketplace push must require publish_lazycodex=true").toBe(true) expect(publishesLazycodexNpm, "lazycodex npm publish must be part of the normal release, tag stable releases as latest, and fail loudly").toBe(true)
expect(gatesLazycodexMarketplaceSync, "LazyCodex marketplace push must require sync_lazycodex_marketplace=true").toBe(true)
expect(requiresLazycodexSyncToken, "release must require a cross-repo token for LazyCodex push").toBe(true) expect(requiresLazycodexSyncToken, "release must require a cross-repo token for LazyCodex push").toBe(true)
}) })
@@ -203,8 +212,10 @@ 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 lazycodexStepPinsWrapperVersion = workflow.includes('LAZYCODEX_VERSION: "0.1.0"') && const lazycodexStepUsesReleaseVersion =
workflow.includes(".version = $lazycodex_version |") !workflow.includes('LAZYCODEX_VERSION: "0.1.0"') &&
workflow.includes(".name = \"lazycodex\" |") &&
workflow.includes(".version = $omo_version |")
const lazycodexStepUsesOmoPlatformVersion = workflow.includes( const lazycodexStepUsesOmoPlatformVersion = workflow.includes(
".optionalDependencies = (.optionalDependencies | to_entries | map(.value = $omo_version) | from_entries)", ".optionalDependencies = (.optionalDependencies | to_entries | map(.value = $omo_version) | from_entries)",
) )
@@ -213,7 +224,7 @@ describe("test workflows", () => {
platformResolver.includes("lazycodex") && platformResolver.includes("oh-my-opencode") platformResolver.includes("lazycodex") && platformResolver.includes("oh-my-opencode")
// #then // #then
expect(lazycodexStepPinsWrapperVersion, "lazycodex publish step must pin wrapper metadata to 0.1.0").toBe(true) expect(lazycodexStepUsesReleaseVersion, "lazycodex publish step must use the release version so unpublished versions are not reused").toBe(true)
expect(lazycodexStepUsesOmoPlatformVersion, "lazycodex must depend on the matching OMO platform packages").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)