Skip to content
Merged
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
63 changes: 49 additions & 14 deletions .github/workflows/node-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,36 @@ permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}-${{ github.event_name == 'pull_request' && github.event.action == 'edited' && github.event.changes.title == null && github.event.changes.base == null && 'metadata-only' || 'full' }}
cancel-in-progress: ${{ github.event_name == 'pull_request' && (github.event.action != 'edited' || github.event.changes.title != null || github.event.changes.base != null) }}

jobs:
validate-title:
name: validate pull request title
name: ${{ github.event_name == 'pull_request' && github.event.action == 'edited' && github.event.changes.title == null && github.event.changes.base == null && 'metadata-only / ' || '' }}validate pull request title
runs-on: ubuntu-latest
outputs:
run-full-ci: ${{ steps.scope.outputs.run-full-ci }}

steps:
- name: Decide whether full CI is needed
id: scope
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
EVENT_ACTION: ${{ github.event.action }}
TITLE_CHANGED: ${{ github.event.changes.title != null }}
BASE_CHANGED: ${{ github.event.changes.base != null }}
run: |
set -euo pipefail
run_full_ci=true
if [[ "$EVENT_NAME" == "pull_request" &&
"$EVENT_ACTION" == "edited" &&
"$TITLE_CHANGED" != "true" &&
"$BASE_CHANGED" != "true" ]]; then
run_full_ci=false
fi
printf 'run-full-ci=%s\n' "$run_full_ci" >> "$GITHUB_OUTPUT"

- name: Require a Conventional Commit pull request title
if: github.event_name == 'pull_request'
shell: bash
Expand All @@ -27,15 +48,16 @@ jobs:
run: |
set -euo pipefail
conventional_title='^([a-z][a-z0-9-]*)(\([a-z0-9][a-z0-9._/-]*\))?(!)?:[ ]([^[:space:]].*[^[:space:]]|[^[:space:]])$'
if [[ ! "$PR_TITLE" =~ $conventional_title ]]; then
if [[ "$PR_TITLE" == *$'\n'* || "$PR_TITLE" == *$'\r'* ||
! "$PR_TITLE" =~ $conventional_title ]]; then
echo "Pull request title must follow <type>[optional scope][!]: <description>." >&2
exit 1
fi

test:
name: ${{ matrix.os }} / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }}
name: ${{ github.event_name == 'pull_request' && github.event.action == 'edited' && github.event.changes.title == null && github.event.changes.base == null && 'metadata-only / ' || '' }}tests / ${{ matrix.os }} / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }}
needs: validate-title
if: always()
if: needs.validate-title.outputs.run-full-ci == 'true'
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
Expand All @@ -54,10 +76,6 @@ jobs:
node: "26"

steps:
- name: Require a valid pull request title
if: needs.validate-title.result != 'success'
run: exit 1

- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand Down Expand Up @@ -129,9 +147,25 @@ jobs:
shell: bash
run: pnpm run check:package ../../dist/*.tgz

required-test:
name: ${{ github.event_name == 'pull_request' && github.event.action == 'edited' && github.event.changes.title == null && github.event.changes.base == null && 'metadata-only / ' || '' }}${{ matrix.os }} / node-22
needs: [validate-title, test]
if: always() && !(github.event_name == 'pull_request' && github.event.action == 'edited' && github.event.changes.title == null && github.event.changes.base == null)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]

steps:
- name: Require every Unix coverage job
if: needs.validate-title.result != 'success' || needs.test.result != 'success'
run: exit 1

windows-test:
name: windows-latest / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }} / tests-${{ matrix.shard }}
name: ${{ github.event_name == 'pull_request' && github.event.action == 'edited' && github.event.changes.title == null && github.event.changes.base == null && 'metadata-only / ' || '' }}windows-latest / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }} / tests-${{ matrix.shard }}
needs: validate-title
if: needs.validate-title.outputs.run-full-ci == 'true'
runs-on: windows-latest
timeout-minutes: 20
strategy:
Expand Down Expand Up @@ -189,8 +223,9 @@ jobs:
run: bun test --timeout 120000 ./tests-ts/windows-machine-policy.test.ts

windows-verify:
name: windows-latest / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }} / verify
name: ${{ github.event_name == 'pull_request' && github.event.action == 'edited' && github.event.changes.title == null && github.event.changes.base == null && 'metadata-only / ' || '' }}windows-latest / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }} / verify
needs: validate-title
if: needs.validate-title.outputs.run-full-ci == 'true'
runs-on: windows-latest
timeout-minutes: 20
strategy:
Expand Down Expand Up @@ -228,9 +263,9 @@ jobs:
run: pnpm run check:package ../../dist/*.tgz

windows:
name: windows-latest / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }}
name: ${{ github.event_name == 'pull_request' && github.event.action == 'edited' && github.event.changes.title == null && github.event.changes.base == null && 'metadata-only / ' || '' }}windows-latest / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }}
runs-on: ubuntu-latest
if: always()
if: always() && !(github.event_name == 'pull_request' && github.event.action == 'edited' && github.event.changes.title == null && github.event.changes.base == null)
needs: [validate-title, windows-test, windows-verify]
strategy:
fail-fast: false
Expand Down
146 changes: 60 additions & 86 deletions .github/workflows/node-github-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,9 @@ jobs:
run: |
set -euo pipefail

release_workspace="$(mktemp -d)"
trap 'rm -rf "$release_workspace"' EXIT

verify_release_tag() {
local live_ref live_type live_object live_commit
if ! live_ref="$(
Expand Down Expand Up @@ -495,8 +498,8 @@ jobs:
)"
if [[ -n "$existing_release" ]]; then
asset_name="$(basename "$RELEASE_ARCHIVE")"
downloaded_assets="$(mktemp -d)"
trap 'rm -rf "$downloaded_assets"' EXIT
downloaded_assets="$release_workspace/downloaded-assets"
mkdir "$downloaded_assets"
gh release download "$RELEASE_TAG" \
--repo "$GITHUB_REPOSITORY" \
--pattern "$asset_name" \
Expand All @@ -514,19 +517,21 @@ jobs:
"$downloaded_archive"
fi

existing_notes=""
existing_notes_file="$release_workspace/existing-notes.md"
: > "$existing_notes_file"
if [[ -n "$existing_release" ]]; then
existing_notes="$(
printf '%s\n' "$existing_release" |
node --input-type=module --eval '
import { readFileSync } from "node:fs";
const release = JSON.parse(readFileSync(0, "utf8"));
if (release.body != null && typeof release.body !== "string") {
throw new Error("Existing release notes must be a string.");
}
process.stdout.write(release.body ?? "");
'
)"
printf '%s\n' "$existing_release" |
node --input-type=module --eval '
import { readFileSync, writeFileSync } from "node:fs";
const release = JSON.parse(readFileSync(0, "utf8"));
if (release.body != null && typeof release.body !== "string") {
throw new Error("Existing release notes must be a string.");
}
writeFileSync(
process.argv[1],
(release.body ?? "").replace(/(?:\r?\n)+$/u, ""),
);
' "$existing_notes_file"
fi

notes_args=(
Expand All @@ -541,73 +546,39 @@ jobs:
if [[ -n "$PREVIOUS_TAG" ]]; then
notes_args+=(-f "previous_tag_name=$PREVIOUS_TAG")
fi
generated_notes="$(gh api "${notes_args[@]}" --jq '.body')"
if [[ -z "$generated_notes" ]]; then
echo "Generated GitHub release notes must not be empty." >&2
exit 1
generated_notes_file="$release_workspace/generated-notes.md"
gh api "${notes_args[@]}" |
node --input-type=module --eval '
import { readFileSync, writeFileSync } from "node:fs";
const response = JSON.parse(readFileSync(0, "utf8"));
if (typeof response.body !== "string") {
throw new Error("Generated GitHub release notes must be a string.");
}
writeFileSync(process.argv[1], response.body);
' "$generated_notes_file"
release_note_args=()

tagged_notes_file="$release_workspace/tagged-notes.md"
if git show "$RELEASE_SHA:.github/release-notes.md" \
> "$tagged_notes_file" 2>/dev/null; then
release_note_args+=(
--tagged-notes-file "$tagged_notes_file"
)
fi

summary_start="<!-- codex-security-release-summary:start -->"
summary_end="<!-- codex-security-release-summary:end -->"
release_summary=""
if git cat-file -e "$RELEASE_SHA:.github/release-notes.md" 2>/dev/null; then
release_summary="$(
git show "$RELEASE_SHA:.github/release-notes.md"
)"
summary_header="${release_summary%%$'\n'*}"
summary_body="${release_summary#*$'\n'}"
expected_header="<!-- release-version: $RELEASE_VERSION -->"
if [[ "$summary_header" != "$expected_header" ||
"$summary_body" == "$release_summary" ||
-z "${summary_body//[[:space:]]/}" ]]; then
echo "Release notes must start with $expected_header and include a reviewed summary." >&2
exit 1
fi
elif [[ -n "$existing_notes" ]]; then
release_summary="$(
printf '%s\n' "$existing_notes" |
CODEX_SECURITY_SUMMARY_START="$summary_start" \
CODEX_SECURITY_SUMMARY_END="$summary_end" \
node --input-type=module --eval '
import { readFileSync } from "node:fs";
const notes = readFileSync(0, "utf8");
const start = process.env.CODEX_SECURITY_SUMMARY_START;
const end = process.env.CODEX_SECURITY_SUMMARY_END;
const startIndex = notes.indexOf(start);
const endIndex = notes.indexOf(end);
if (startIndex === -1 && endIndex === -1) process.exit(0);
if (
startIndex === -1 ||
endIndex <= startIndex ||
notes.indexOf(start, startIndex + start.length) !== -1 ||
notes.indexOf(end, endIndex + end.length) !== -1
) {
throw new Error(
"Existing release summary markers are malformed.",
);
}
const summary = notes
.slice(startIndex + start.length, endIndex)
.replace(/^\r?\n/u, "")
.replace(/\r?\n$/u, "");
if (!/\S/u.test(summary)) {
throw new Error("Existing release summary is empty.");
}
process.stdout.write(summary);
'
)"
if [[ -s "$existing_notes_file" ]]; then
release_note_args+=(
--existing-notes-file "$existing_notes_file"
)
fi

published_notes="$generated_notes"
if [[ -n "$release_summary" ]]; then
published_notes="$(
printf '%s\n%s\n%s\n\n%s' \
"$summary_start" \
"$release_summary" \
"$summary_end" \
"$generated_notes"
)"
fi
published_notes_file="$release_workspace/published-notes.md"
node sdk/typescript/scripts/release-automation.mjs \
compose-release-notes \
"$RELEASE_VERSION" \
"$generated_notes_file" \
${release_note_args[@]+"${release_note_args[@]}"} \
> "$published_notes_file"

if [[ -n "$existing_release" ]]; then
latest_response="$(
Expand Down Expand Up @@ -639,15 +610,18 @@ jobs:
currently_latest=true
fi

if [[ "$existing_notes" != "$published_notes" ||
notes_changed=false
if ! cmp -s "$existing_notes_file" "$published_notes_file"; then
notes_changed=true
fi
if [[ "$notes_changed" == true ||
"$currently_latest" != "$MAKE_LATEST" ]]; then
verify_release_tag
if [[ "$existing_notes" != "$published_notes" ]]; then
printf '%s\n' "$published_notes" |
gh release edit "$RELEASE_TAG" \
--repo "$GITHUB_REPOSITORY" \
--latest="$MAKE_LATEST" \
--notes-file -
if [[ "$notes_changed" == true ]]; then
gh release edit "$RELEASE_TAG" \
--repo "$GITHUB_REPOSITORY" \
--latest="$MAKE_LATEST" \
--notes-file "$published_notes_file"
echo "Updated existing GitHub Release with reviewed and generated notes."
else
gh release edit "$RELEASE_TAG" \
Expand All @@ -669,9 +643,9 @@ jobs:
--repo "$GITHUB_REPOSITORY"
--title "Codex Security $RELEASE_VERSION"
--verify-tag
--notes-file -
--notes-file "$published_notes_file"
--latest="$MAKE_LATEST"
)

verify_release_tag
printf '%s\n' "$published_notes" | gh release create "${release_args[@]}"
gh release create "${release_args[@]}"
19 changes: 7 additions & 12 deletions .github/workflows/node-release-cut.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
github.repository == 'openai/codex-security' &&
(github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'))
name: cut release tag
runs-on: ubuntu-latest
Expand Down Expand Up @@ -65,21 +66,15 @@ jobs:
fi

version="$(node sdk/typescript/scripts/release-automation.mjs version sdk/typescript/package.json)"
if ! release_summary="$(
git show "$RELEASE_SHA:.github/release-notes.md" 2>/dev/null
)"; then
release_notes_file="$(mktemp)"
trap 'rm -f "$release_notes_file"' EXIT
if ! git show "$RELEASE_SHA:.github/release-notes.md" \
> "$release_notes_file" 2>/dev/null; then
echo "The tagged commit must include .github/release-notes.md." >&2
exit 1
fi
summary_header="${release_summary%%$'\n'*}"
summary_body="${release_summary#*$'\n'}"
expected_header="<!-- release-version: $version -->"
if [[ "$summary_header" != "$expected_header" ||
"$summary_body" == "$release_summary" ||
-z "${summary_body//[[:space:]]/}" ]]; then
echo "Release notes must start with $expected_header and include a reviewed summary." >&2
exit 1
fi
node sdk/typescript/scripts/release-automation.mjs \
validate-release-notes "$version" "$release_notes_file" >/dev/null

if ! published_versions="$(
npm view @openai/codex-security versions \
Expand Down
15 changes: 13 additions & 2 deletions .github/workflows/node-release-labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,23 @@ jobs:
esac
}

title_sentinel="__CODEX_SECURITY_TITLE_END__"
current_title="$(
gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER" \
--jq '.title'
--jq '.title | @base64' |
node --input-type=module --eval '
import { readFileSync } from "node:fs";
process.stdout.write(
Buffer.from(readFileSync(0, "utf8"), "base64"),
);
' &&
printf '%s' "$title_sentinel"
)"
current_title="${current_title%"$title_sentinel"}"
conventional_title='^([a-z][a-z0-9-]*)(\([a-z0-9][a-z0-9._/-]*\))?(!)?:[ ]([^[:space:]].*[^[:space:]]|[^[:space:]])$'
if [[ ! "$current_title" =~ $conventional_title ]]; then
if [[ "$current_title" == *$'\n'* ||
"$current_title" == *$'\r'* ||
! "$current_title" =~ $conventional_title ]]; then
echo "Pull request title must follow <type>[optional scope][!]: <description>." >&2
exit 1
fi
Expand Down
Loading
Loading