Skip to content

Update omp dependency #402

Update omp dependency

Update omp dependency #402

Workflow file for this run

name: Update omp dependency
# The upstream repository cannot dispatch a workflow in this repository, so the
# hourly check reads its latest stable release. workflow_dispatch is useful for
# validating a release immediately after it is published.
on:
schedule:
- cron: "17 * * * *"
workflow_dispatch:
pull_request:
types:
- closed
permissions:
actions: write
contents: write
pull-requests: write
concurrency:
group: update-omp
cancel-in-progress: false
jobs:
update:
name: Open omp dependency PR
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Check out main
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
# Must satisfy engines.bun (>=1.3.14): the omp SDK is published as
# TypeScript importing `bun:` builtins, so verifying the bump on an
# older Bun proves nothing about the release this repo actually ships.
bun-version: "1.3.14"
- name: Read latest upstream release
id: release
uses: actions/github-script@v7
with:
github-token: ${{ github.token }}
script: |
const { data } = await github.rest.repos.getLatestRelease({
owner: "can1357",
repo: "oh-my-pi",
});
const version = String(data.tag_name || "").replace(/^v/, "");
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
core.setFailed(`Unexpected upstream release tag: ${data.tag_name}`);
return;
}
core.setOutput("version", version);
core.setOutput("tag", data.tag_name);
core.setOutput("url", data.html_url);
- name: Skip when the dependency is current
id: manifest
env:
OMP_VERSION: ${{ steps.release.outputs.version }}
shell: bash
run: |
current="$(node -p "require('./package.json').dependencies['@oh-my-pi/pi-coding-agent']" | sed -E 's/^[^0-9]*//')"
echo "current=$current" >> "$GITHUB_OUTPUT"
if [[ "$current" == "$OMP_VERSION" ]]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "omp $OMP_VERSION is already declared in package.json"
exit 0
fi
echo "changed=true" >> "$GITHUB_OUTPUT"
- name: Wait for the matching npm package
id: npm
if: steps.manifest.outputs.changed == 'true'
env:
OMP_VERSION: ${{ steps.release.outputs.version }}
shell: bash
run: |
for attempt in {1..10}; do
published="$(npm view "@oh-my-pi/pi-coding-agent@${OMP_VERSION}" version --silent 2>/dev/null || true)"
if [[ "$published" == "$OMP_VERSION" ]]; then
echo "published=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "@oh-my-pi/pi-coding-agent@$OMP_VERSION is not on npm yet (attempt $attempt/10)"
sleep 30
done
# Upstream routinely tags minutes before the npm publish lands. The
# hourly schedule is the retry, so a miss here is a "come back later",
# not a failure worth reddening every run until someone notices.
echo "published=false" >> "$GITHUB_OUTPUT"
echo "::notice::omp $OMP_VERSION is tagged upstream but not on npm yet; the next scheduled run will retry."
- name: Decide whether to prepare the update
id: plan
shell: bash
env:
CHANGED: ${{ steps.manifest.outputs.changed }}
PUBLISHED: ${{ steps.npm.outputs.published }}
run: |
if [[ "$CHANGED" == "true" && "$PUBLISHED" == "true" ]]; then
echo "proceed=true" >> "$GITHUB_OUTPUT"
else
echo "proceed=false" >> "$GITHUB_OUTPUT"
fi
- name: Update omp dependency ranges
if: steps.plan.outputs.proceed == 'true'
env:
OMP_VERSION: ${{ steps.release.outputs.version }}
run: |
node <<'NODE'
const fs = require('node:fs');
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const names = [
'@oh-my-pi/pi-agent-core',
'@oh-my-pi/pi-ai',
'@oh-my-pi/pi-coding-agent',
'@oh-my-pi/pi-tui',
'@oh-my-pi/pi-utils',
];
for (const name of names) {
if (!packageJson.dependencies?.[name]) throw new Error(`Missing dependency ${name}`);
packageJson.dependencies[name] = `~${process.env.OMP_VERSION}`;
}
fs.writeFileSync('package.json', `${JSON.stringify(packageJson, null, 2)}\n`);
NODE
- name: Bump the omp-web version
id: package
if: steps.plan.outputs.proceed == 'true'
env:
OMP_VERSION: ${{ steps.release.outputs.version }}
OMP_PREVIOUS: ${{ steps.manifest.outputs.current }}
run: |
node <<'NODE'
const fs = require('node:fs');
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(packageJson.version);
if (!match) {
throw new Error(`Unexpected omp-web version: ${packageJson.version}`);
}
const [major, minor, patch] = match.slice(1).map(Number);
// omp-web serves omp's own SDK in-process, so a new omp major moves
// the surface under omp-web's users too. That earns a minor bump; a
// routine refresh within the same major stays a patch.
const ompMajor = (version) => /^(\d+)\./.exec(version || '')?.[1];
const nextMajor = ompMajor(process.env.OMP_VERSION);
const previousMajor = ompMajor(process.env.OMP_PREVIOUS);
if (!nextMajor) throw new Error(`Unexpected omp version: ${process.env.OMP_VERSION}`);
const nextVersion = nextMajor === previousMajor
? `${major}.${minor}.${patch + 1}`
: `${major}.${minor + 1}.0`;
packageJson.version = nextVersion;
fs.writeFileSync('package.json', `${JSON.stringify(packageJson, null, 2)}\n`);
fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${nextVersion}\n`);
NODE
- name: Refresh Bun lockfile
if: steps.plan.outputs.proceed == 'true'
run: bun install --lockfile-only
# A breaking upstream release is the normal reason this fails, and that is
# exactly when the update is most worth surfacing. Record the outcome
# instead of aborting, so the run still opens a PR carrying the bump.
- name: Verify the updated dependency graph
id: verify
if: steps.plan.outputs.proceed == 'true'
continue-on-error: true
run: |
bun install --frozen-lockfile
bun run typecheck
bun test
- name: Report the verification outcome
id: report
if: steps.plan.outputs.proceed == 'true'
shell: bash
env:
OMP_VERSION: ${{ steps.release.outputs.version }}
VERIFY_OUTCOME: ${{ steps.verify.outcome }}
run: |
if [[ "$VERIFY_OUTCOME" == "success" ]]; then
note="The workflow refreshed \`bun.lock\`, then ran \`bun run typecheck\` and \`bun test\` against the new SDK. Both passed."
echo "draft=false" >> "$GITHUB_OUTPUT"
else
note=":warning: \`bun run typecheck\` or \`bun test\` failed against omp \`$OMP_VERSION\`, so this PR is a **draft**: the release needs source changes in omp-web before it can merge. The failing output is in [the workflow run](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})."
echo "draft=true" >> "$GITHUB_OUTPUT"
fi
{
echo "note<<EOF"
echo "$note"
echo "EOF"
} >> "$GITHUB_OUTPUT"
echo "$note" >> "$GITHUB_STEP_SUMMARY"
- name: Open or update the dependency PR
if: steps.plan.outputs.proceed == 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ github.token }}
branch: automation/omp-${{ steps.release.outputs.version }}
delete-branch: true
draft: ${{ steps.report.outputs.draft }}
commit-message: "chore: update omp to v${{ steps.release.outputs.version }}"
title: "chore: update omp to v${{ steps.release.outputs.version }}"
body: |
Updates all `@oh-my-pi/*` runtime packages to `~${{ steps.release.outputs.version }}`.
Upstream release: [${{ steps.release.outputs.tag }}](${{ steps.release.outputs.url }})
When merged, this PR will release `omp-web@${{ steps.package.outputs.version }}` with a note for `oh-my-pi v${{ steps.release.outputs.version }}`.
${{ steps.report.outputs.note }}
release:
name: Release omp-web
if: >-
github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == github.event.repository.default_branch &&
github.event.pull_request.head.repo.full_name == github.repository &&
startsWith(github.event.pull_request.head.ref, 'automation/omp-')
runs-on: ubuntu-latest
steps:
- name: Check out merged main
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
fetch-depth: 1
- name: Read release metadata
id: metadata
shell: bash
run: |
node <<'NODE'
const fs = require('node:fs');
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const packageVersion = packageJson.version;
if (!/^\d+\.\d+\.\d+$/.test(packageVersion)) {
throw new Error(`Unexpected omp-web version: ${packageVersion}`);
}
const names = [
'@oh-my-pi/pi-agent-core',
'@oh-my-pi/pi-ai',
'@oh-my-pi/pi-coding-agent',
'@oh-my-pi/pi-tui',
'@oh-my-pi/pi-utils',
];
const versions = names.map((name) => {
const range = packageJson.dependencies?.[name];
const match = /^~(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(range || '');
if (!match) {
throw new Error(`Unexpected ${name} range: ${range}`);
}
return match[1];
});
if (new Set(versions).size !== 1) {
throw new Error(`@oh-my-pi/* packages do not share one version: ${versions.join(', ')}`);
}
fs.appendFileSync(
process.env.GITHUB_OUTPUT,
`package_version=${packageVersion}\nomp_version=${versions[0]}\n`,
);
NODE
- name: Create GitHub release
id: github_release
env:
GH_TOKEN: ${{ github.token }}
PACKAGE_VERSION: ${{ steps.metadata.outputs.package_version }}
OMP_VERSION: ${{ steps.metadata.outputs.omp_version }}
TARGET_SHA: ${{ github.event.pull_request.merge_commit_sha }}
run: |
tag="v${PACKAGE_VERSION}"
if gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "Release $tag already exists"
echo "created=false" >> "$GITHUB_OUTPUT"
exit 0
fi
gh release create "$tag" \
--repo "$GITHUB_REPOSITORY" \
--target "$TARGET_SHA" \
--title "omp-web $tag" \
--notes "Updated oh-my-pi to v${OMP_VERSION}."
echo "created=true" >> "$GITHUB_OUTPUT"
- name: Start npm publish for the release tag
if: steps.github_release.outputs.created == 'true'
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: v${{ steps.metadata.outputs.package_version }}
run: gh workflow run publish-npm.yml --repo "$GITHUB_REPOSITORY" --ref "$RELEASE_TAG" -f ref="$RELEASE_TAG"