Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/node-release-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: node-release-pr

on:
push:
branches: [main]
workflow_dispatch:
inputs:
dry_run:
description: Preview the release PR without changing GitHub
type: boolean
default: true

permissions:
contents: read
pull-requests: read

concurrency:
group: node-release-pr
cancel-in-progress: false

jobs:
reconcile:
if: github.repository == 'openai/codex-security' && github.ref == 'refs/heads/main'
name: maintain draft release PR
runs-on: ubuntu-latest
env:
RELEASE_PR_DRY_RUN: ${{ (github.event_name == 'workflow_dispatch' && inputs.dry_run) || (github.event_name != 'workflow_dispatch' && vars.RELEASE_PR_ENABLED != 'true') }}

steps:
- name: Checkout main, never the release PR
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: main
fetch-depth: 0
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: "22.13.0"

- name: Create repository-scoped release App token
id: app-token
if: env.RELEASE_PR_DRY_RUN == 'false'
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-contents: write
permission-pull-requests: write

- name: Reconcile the rolling release proposal
env:
GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }}
run: node sdk/typescript/scripts/release-pr.mjs
116 changes: 116 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,122 @@ Use `release` and `test` only for changes that do not affect package users. A
maintainer can apply `skip-release-notes` to exclude another internal change.
That manual label takes precedence over the title category.

## Version policy before 1.0

While the package is on `0.x`, ordinary changes, including features, use a
patch release. Breaking changes use a minor release and reset the patch to
zero. For example, changes after `0.1.23` propose `0.1.24`, or `0.2.0` if any
included change is breaking. The category of a feature remains **Features**;
the category does not imply a minor version bump.

The release PR updater recognizes breaking changes from a `!` in a
Conventional Commit title, a `BREAKING CHANGE:` or `BREAKING-CHANGE:` footer,
or the `breaking-change` label. `skip-release-notes` affects visibility, not
version impact. All merges and direct commits after the release boundary
count toward the next version, including documentation and internal changes.
Review this policy before enabling automation for `1.x`.

## Rolling release PRs

`node-release-pr` runs after pushes to `main` and can be dispatched manually.
It defaults to a read-only preview. It does not merge, tag, publish, or change
the existing publication gates.

The updater keeps one draft proposal on `release/next-<base-version>` and
recomputes its version from all changes since the current package version
first reached `main`. Squash-merge release PRs, as required by this
repository's enabled merge method, so the whole proposal lands as one
release boundary commit. A later breaking change changes the version on the
same PR. Each update incorporates the latest `main` and appends a commit;
the updater never force-pushes. A concurrent commit causes it to reread and
retry. It requests Codex review when the proposal files change. Updates that
only incorporate `main` keep CI current without repeating the same proposal
review. New suggestions for human-owned notes still appear in a comment.
Before marking the proposal ready, check CI and request a final Codex review
if the last review targets an older head.

If a run reports that GitHub has not exposed the updated PR head, manually
rerun the updater with **dry_run** disabled after the PR catches up. This
allows any deferred review request or note suggestions to be posted. Verify
review on the current head before marking the proposal ready.

When the release version merges, the next draft can open immediately, even
while publication is still running. Until another change reaches `main`,
that draft leaves the package version unchanged. Do not mark an empty draft
ready or merge it. Publication of the previous version still has to complete
and pass the verification steps below.

The updater leaves another open `release:` PR targeting `main`, including a
manually prepared release, untouched and does not open a duplicate. Finish
or close that PR before enabling the new flow. Closing an automated proposal
pauses its cycle; reopen it to resume. Retargeting it away from `main` also
pauses updates; restore its `main` base before resuming. Marking the proposal
ready pauses updates, preserving the reviewed version and notes. To resume,
convert it back to a draft and rerun the updater. Do this before merging if
`main` has advanced, then review the updated proposal. The updater rechecks
these conditions before advancing the branch. Changes to other files on the
release branch, or to package fields other than the version, also pause the
updater so those edits cannot be lost. These intentional pauses return
`action: "held"` and leave the workflow successful. Preserve or merge the
additional changes, then rerun the updater to resume.

### Editing the draft notes

The committed `.github/release-notes.md` is authoritative. The updater
drafts highlights from merged titles and lists marked breaking changes for
migration review; it does not infer migration instructions from source code.
Review and refine these suggestions before releasing.

- Edit the highlights or upgrade notes on the release PR branch. Keep the
surrounding `release-section` comments if you want to retain section
boundaries. The bot refreshes a section only while it matches the last
generated draft. An edit or deletion makes that section human-owned, and
later updates preserve it. Custom prose outside the sections is preserved.
- Later suggestions appear in a new bot comment on the same PR. They do not
replace human-owned notes. The bot updates the version header and PR title
but never rewrites the PR description. Review version-specific links in
human-owned prose when the proposed version changes.
- `.github/release-pr-state.json` records the cycle and section ownership.
To explicitly regenerate a section, add `"reset": true` to that section's
state entry. The next run consumes the reset and resumes automatic updates.
Restore any damaged section markers before resetting. Do not delete the
state file to reset ownership. A section with missing ownership metadata
is preserved until explicitly reset; missing state does not authorize
replacing manual notes.
- Deleting the notes file is preserved too. Restore reviewed notes, or
explicitly reset the sections, before merging; publication requires the
versioned notes file.

### Preview and enable

With an authenticated `gh` CLI, a local checkout can preview the plan with:

```bash
RELEASE_PR_DRY_RUN=true node sdk/typescript/scripts/release-pr.mjs
```

The preview may fetch missing Git objects locally, but performs no GitHub
writes. Its JSON output includes the proposed files and any reason the
updater would pause. After the workflow is on `main`, use its **Run workflow**
form with **dry_run** enabled to test the hosted read-only path.

To test writes, configure a GitHub App installed on this repository
with **Contents: write** and **Pull requests: write**, set the
`RELEASE_APP_CLIENT_ID` repository variable and `RELEASE_APP_PRIVATE_KEY`
secret, then manually dispatch the workflow with **dry_run** disabled.
This permits a single write run while automatic updates remain disabled.
Review the resulting draft, its hosted CI, and the Codex review request.
The workflow requests an App token scoped to this repository so CI on the
bot's PR does not need the approval required for PR events created by
`GITHUB_TOKEN`. See [GitHub's token documentation](https://docs.github.com/en/actions/concepts/security/github_token).

After the manual write run is verified, set the `RELEASE_PR_ENABLED`
repository variable to `true` to allow updates after pushes to `main`.
Remove it or set it to `false` to return push-triggered runs to previews.
Manual runs always honor their **dry_run** input, which defaults to a preview;
disabling automatic updates does not prevent an explicit manual write run.
Generated PRs leave the disclosure attestations unchecked for maintainer review.

## Prepare a release

1. Choose the next stable version and update `sdk/typescript/package.json`.
Expand Down
15 changes: 8 additions & 7 deletions sdk/typescript/scripts/release-automation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,18 @@ function stableReleaseTagVersion(tag) {
return version;
}

export function assertStableVersion(version) {
if (typeof version !== "string" || !stableVersion.test(version)) {
throw new Error("Release package must have a stable X.Y.Z version.");
}
return version;
}

export function releaseVersion(packageJson) {
if (packageJson?.name !== packageName) {
throw new Error("Release package must be @openai/codex-security.");
}
if (
typeof packageJson.version !== "string" ||
!stableVersion.test(packageJson.version)
) {
throw new Error("Release package must have a stable X.Y.Z version.");
}
return packageJson.version;
return assertStableVersion(packageJson.version);
}

function hasReviewedText(value) {
Expand Down
Loading
Loading