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
57 changes: 35 additions & 22 deletions .github/workflows/publish-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@ on:
description: Full reviewed commit SHA on next, explicitly approved by Omer
required: true
type: string
dist_tag:
description: Approved npm distribution tag
type: choice
options: [dev, next, rc, alpha, beta, latest]
default: dev
required: true
permissions:
id-token: write
contents: read
concurrency:
group: release
Expand All @@ -23,18 +16,23 @@ jobs:
if: github.ref == 'refs/heads/next'
environment: release
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
env:
APPROVED_COMMIT: ${{ inputs.commit }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.commit }}
ref: next
fetch-depth: 0
- name: Verify approved commit belongs to next
env:
APPROVED_COMMIT: ${{ inputs.commit }}
persist-credentials: false
- name: Verify and select the exact approved commit
run: |
[[ "$APPROVED_COMMIT" =~ ^[0-9a-f]{40}$ ]]
git merge-base --is-ancestor "$APPROVED_COMMIT" origin/next
git switch --detach "$APPROVED_COMMIT"
test "$(git rev-parse HEAD)" = "$APPROVED_COMMIT"
git merge-base --is-ancestor HEAD origin/next
- uses: pnpm/action-setup@v6
with:
version: 9.15.4
Expand All @@ -43,31 +41,46 @@ jobs:
node-version: 22
registry-url: https://registry.npmjs.org/
- run: pnpm install --frozen-lockfile
- run: pnpm audit --audit-level low
- run: pnpm build
- run: pnpm lint
- run: pnpm test
- run: pnpm test:e2e
- name: Check npm authorization and package metadata
- name: Verify beta metadata, immutable tags, and retry ownership
env:
DIST_TAG: ${{ inputs.dist_tag }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
case "$DIST_TAG" in dev|next|rc|alpha|beta|latest) ;; *) exit 1 ;; esac
test -n "$NODE_AUTH_TOKEN"
npm_user="$(npm whoami --registry=https://registry.npmjs.org/)"
package_access="$(npm access list packages "$npm_user" --json)"
for manifest in packages/*/package.json; do
if jq -e '.private == true' "$manifest" > /dev/null; then continue; fi
jq -e '.repository.url == "https://github.com/codotech/contractual.git" and .publishConfig.access == "public" and (.version | test("^[0-9]+\\.[0-9]+\\.[0-9]+-beta\\.[0-9]+$"))' "$manifest" > /dev/null
package_name="$(jq -r '.name' "$manifest")"
jq -e '.repository.url == "https://github.com/codotech/contractual.git" and .publishConfig.access == "public"' "$manifest" > /dev/null
version="$(jq -r '.version' "$manifest")"
tag="$package_name@$version"
jq -e --arg name "$package_name" '.[$name] == "read-write"' <<< "$package_access" > /dev/null
if [ "$DIST_TAG" = latest ]; then
jq -e '.version | contains("-") | not' "$manifest" > /dev/null
if git show-ref --verify --quiet "refs/tags/$tag"; then
test "$(git rev-parse "refs/tags/$tag^{commit}")" = "$APPROVED_COMMIT"
fi
echo "$package_name: publishing preflight passed ($DIST_TAG)"
if npm view "$package_name@$version" --json > "$RUNNER_TEMP/registry-package.json"; then
jq -e --arg sha "$APPROVED_COMMIT" '.gitHead == $sha' "$RUNNER_TEMP/registry-package.json" > /dev/null
test "$(npm view "$package_name" dist-tags.beta)" = "$version"
else
jq -e '.error.code == "E404"' "$RUNNER_TEMP/registry-package.json" > /dev/null
fi
echo "$tag: publishing preflight passed (beta)"
done
- name: Publish approved versions (does not create Git tags)
- name: Publish only to beta, never latest
env:
DIST_TAG: ${{ inputs.dist_tag }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: pnpm exec lerna publish from-package --yes --dist-tag "$DIST_TAG" --no-git-reset
run: pnpm exec lerna publish from-package --yes --dist-tag beta --git-head "$APPROVED_COMMIT" --no-git-reset
- name: Verify published metadata (tagging remains a separate approval)
run: |
for manifest in packages/*/package.json; do
if jq -e '.private == true' "$manifest" > /dev/null; then continue; fi
package_name="$(jq -r '.name' "$manifest")"
version="$(jq -r '.version' "$manifest")"
npm view "$package_name@$version" --json | jq -e --arg sha "$APPROVED_COMMIT" '.gitHead == $sha' > /dev/null
test "$(npm view "$package_name" dist-tags.beta)" = "$version"
done
26 changes: 13 additions & 13 deletions .github/workflows/release-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ on:
workflow_dispatch:
inputs:
release_type:
description: Approved release type
description: Use preminor to enter beta, prerelease for the next beta
type: choice
options: [prerelease, graduate]
default: prerelease
options: [preminor, prerelease]
default: preminor
required: true
preid:
description: Prerelease identifier
type: choice
options: [dev, next, rc, alpha, beta]
default: dev
options: [beta]
default: beta
required: true
permissions:
contents: write
Expand All @@ -30,6 +30,7 @@ jobs:
with:
ref: next
fetch-depth: 0
persist-credentials: false
- uses: pnpm/action-setup@v6
with:
version: 9.15.4
Expand All @@ -45,12 +46,11 @@ jobs:
RELEASE_TYPE: ${{ inputs.release_type }}
PREID: ${{ inputs.preid }}
run: |
test "$PREID" = beta
case "$RELEASE_TYPE" in preminor|prerelease) ;; *) exit 1 ;; esac
git switch -c "chore/release-${GITHUB_RUN_ID}"
if [ "$RELEASE_TYPE" = graduate ]; then
pnpm exec lerna version --conventional-graduate --yes --no-git-tag-version --no-push --no-commit-hooks --allow-branch "chore/release-${GITHUB_RUN_ID}"
else
pnpm exec lerna version --conventional-commits --conventional-prerelease --preid "$PREID" --yes --no-git-tag-version --no-push --no-commit-hooks --allow-branch "chore/release-${GITHUB_RUN_ID}"
fi
pnpm exec lerna version "$RELEASE_TYPE" --preid "$PREID" --conventional-commits --yes --no-git-tag-version --no-push --no-commit-hooks --ignore-scripts --allow-branch "chore/release-${GITHUB_RUN_ID}"
pnpm install --lockfile-only --ignore-scripts
- name: Open a release PR
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -62,6 +62,6 @@ jobs:
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add packages pnpm-lock.yaml
git commit -m 'chore: prepare package versions'
git push origin "HEAD:refs/heads/chore/release-${GITHUB_RUN_ID}"
gh pr create --base next --head "chore/release-${GITHUB_RUN_ID}" --title 'chore: prepare package versions' --body 'Review package versions and validation before merging. This PR does not tag or publish. Publishing requires a separate explicit approval from Omer. Because this PR uses GITHUB_TOKEN, a maintainer must reopen it to trigger PR checks.'
git -c core.hooksPath=/dev/null commit -m 'chore(*): prepare beta packages'
git -c credential.helper='!gh auth git-credential' push origin "HEAD:refs/heads/chore/release-${GITHUB_RUN_ID}"
gh pr create --base next --head "chore/release-${GITHUB_RUN_ID}" --title 'chore(*): prepare beta packages' --body 'Review package versions and validation before merging. This PR does not tag or publish. Publishing requires a separate explicit approval from Omer. Approve the bot PR workflow runs if GitHub requests it.'
22 changes: 9 additions & 13 deletions .github/workflows/release-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ on:
workflow_dispatch:
inputs:
release_type:
description: Release type to preview locally
description: Use preminor to enter beta, prerelease for the next beta
type: choice
options: [prerelease, graduate, auto]
default: auto
options: [preminor, prerelease]
default: preminor
required: true
preid:
description: Prerelease identifier
type: choice
options: [dev, next, rc, alpha, beta]
default: next
options: [beta]
default: beta
required: true
permissions:
contents: read
Expand Down Expand Up @@ -40,14 +40,10 @@ jobs:
RELEASE_TYPE: ${{ inputs.release_type }}
PREID: ${{ inputs.preid }}
run: |
case "$PREID" in dev|next|rc|alpha|beta) ;; *) exit 1 ;; esac
args=(--yes --no-git-tag-version --no-push --no-commit-hooks --allow-branch next)
case "$RELEASE_TYPE" in
prerelease) pnpm exec lerna version prerelease --preid "$PREID" "${args[@]}" ;;
graduate) pnpm exec lerna version --conventional-graduate "${args[@]}" ;;
auto) pnpm exec lerna version --conventional-commits "${args[@]}" ;;
*) exit 1 ;;
esac
test "$PREID" = beta
case "$RELEASE_TYPE" in preminor|prerelease) ;; *) exit 1 ;; esac
pnpm exec lerna version "$RELEASE_TYPE" --preid "$PREID" --conventional-commits --yes --no-git-tag-version --no-push --no-commit-hooks --ignore-scripts --allow-branch next
pnpm install --lockfile-only --ignore-scripts
- name: Summarize local preview
run: |
echo '## Proposed versions (preview only)' >> "$GITHUB_STEP_SUMMARY"
Expand Down
50 changes: 49 additions & 1 deletion .github/workflows/security-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,52 @@ jobs:
- uses: actions/setup-node@v7
with:
node-version: 22
- run: pnpm audit --audit-level low
- name: Install dependencies for complete audit paths
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Audit dependencies (two approved exceptions expire October 22)
env:
AUDIT_EXCEPTIONS_EXPIRE_AT: '2026-10-22T05:37:00Z'
run: |
# Omer approved these two temporary risks on 2026-10-08, not a release.
# braces: CLI glob size/depth guards mitigate input; tooling remains affected.
# http-cache-semantics: reviewed Lerna callers use private caches on fresh runners.
# Remove exceptions when patched; expiry never rolls forward automatically.
report="$RUNNER_TEMP/dependency-audit.json"
audit_status=0
pnpm audit --json > "$report" || audit_status=$?
cat "$report"
# Registry errors, muted findings, unexpected statuses/schema must fail closed.
jq -e --argjson status "$audit_status" '
type == "object" and (has("error") | not) and .muted == [] and
(.advisories | type == "object") and
(.metadata.vulnerabilities | keys == ["critical", "high", "info", "low", "moderate"]) and
(.metadata.vulnerabilities | all(.[]; type == "number" and . >= 0)) and
(([.metadata.vulnerabilities[]] | add) == (.advisories | length)) and
(($status == 0 and (.advisories | length) == 0) or
($status == 1 and (.advisories | length) > 0)) and
(.advisories | all(.[];
(.module_name | type == "string") and
(.findings | type == "array" and length > 0) and
(.findings | all(.[]; (.version | type == "string") and
(.paths | type == "array" and length > 0) and
(.paths | all(.[]; type == "string"))))))
' "$report" > /dev/null
jq --arg expires "$AUDIT_EXCEPTIONS_EXPIRE_AT" --argjson now "$(date +%s)" '
[.advisories[] | select(
($now < ($expires | fromdateiso8601) and
((.github_advisory_id == "GHSA-vfj7-8cjw-p6xm" and
.module_name == "braces" and all(.findings[]; .version == "3.0.3")) or
(.github_advisory_id == "GHSA-ch52-4w7c-c8xp" and
.module_name == "http-cache-semantics" and
all(.findings[]; .version == "4.2.0" and
all(.paths[]; startswith(". > lerna@9.0.7 > ")))))) | not)]
' "$report" > "$RUNNER_TEMP/blocking-advisories.json"
if ! jq -e 'length == 0' "$RUNNER_TEMP/blocking-advisories.json" > /dev/null; then
cat "$RUNNER_TEMP/blocking-advisories.json"
echo "::error::Audit contains unapproved findings or the temporary exceptions have expired."
exit 1
fi
if jq -e '.advisories | length > 0' "$report" > /dev/null; then
echo "::warning::Audit passed with approved risk exceptions until $AUDIT_EXCEPTIONS_EXPIRE_AT; dependencies remain affected."
echo "Audit: temporary risk accepted for the reported braces/http-cache-semantics findings until $AUDIT_EXCEPTIONS_EXPIRE_AT. Not release approval." >> "$GITHUB_STEP_SUMMARY"
fi
44 changes: 30 additions & 14 deletions .github/workflows/tag-global.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
tag_name:
description: 'Tag Name (e.g. v1.0.0)'
description: 'Approved immutable beta tag (e.g. v0.2.0-beta.0)'
required: true
commit:
description: 'Full reviewed commit SHA on next, explicitly approved by Omer'
Expand All @@ -14,6 +14,10 @@ on:
permissions:
contents: write

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

jobs:
tag-and-push:
if: github.ref == 'refs/heads/next'
Expand All @@ -25,36 +29,48 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v7
with:
ref: ${{ inputs.commit }}
ref: next
fetch-depth: 0
persist-credentials: false

- name: Verify approved commit
- name: Verify approved commit and beta tag
env:
APPROVED_COMMIT: ${{ inputs.commit }}
TAG_NAME: ${{ inputs.tag_name }}
run: |
[[ "$APPROVED_COMMIT" =~ ^[0-9a-f]{40}$ ]]
[[ "$TAG_NAME" =~ ^(v|@contractual/[a-z.-]+@)[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+$ ]]
git check-ref-format "refs/tags/$TAG_NAME"
git merge-base --is-ancestor "$APPROVED_COMMIT" origin/next
git switch --detach "$APPROVED_COMMIT"
test "$(git rev-parse HEAD)" = "$APPROVED_COMMIT"
git merge-base --is-ancestor HEAD origin/next
if [[ "$TAG_NAME" == @contractual/* ]]; then
package_name="${TAG_NAME%@*}"
version="${TAG_NAME##*@}"
jq -se --arg name "$package_name" --arg version "$version" 'any(.[]; .name == $name and .version == $version)' packages/*/package.json > /dev/null
fi
if git show-ref --verify --quiet "refs/tags/$TAG_NAME"; then
test "$(git rev-parse "refs/tags/$TAG_NAME^{commit}")" = "$APPROVED_COMMIT"
fi

- name: Config Git
run: |
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
git config --global user.name "${{ github.actor }}"
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'

- name: Tag and Push
env:
GH_TOKEN: ${{ github.token }}
TAG_NAME: ${{ inputs.tag_name }}
APPROVED_COMMIT: ${{ inputs.commit }}
run: |
git check-ref-format "refs/tags/$TAG_NAME"
git tag -a "$TAG_NAME" -m "Tag Version $TAG_NAME"
git push origin "refs/tags/$TAG_NAME"
if ! git show-ref --verify --quiet "refs/tags/$TAG_NAME"; then
git tag -a "$TAG_NAME" "$APPROVED_COMMIT" -m "Tag Version $TAG_NAME"
fi
git -c credential.helper='!gh auth git-credential' push origin "refs/tags/$TAG_NAME"

- name: Generate Summary
env:
TAG_NAME: ${{ inputs.tag_name }}
run: |
echo "## Tag Created" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Tag:** \`$TAG_NAME\`" >> $GITHUB_STEP_SUMMARY
echo "**Commit:** \`$(git rev-parse HEAD)\`" >> $GITHUB_STEP_SUMMARY
echo "**Branch:** \`$(git rev-parse --abbrev-ref HEAD)\`" >> $GITHUB_STEP_SUMMARY
echo "Tagged $TAG_NAME at $(git rev-parse HEAD). No packages were published." >> "$GITHUB_STEP_SUMMARY"
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,14 @@
"vite@>=7.0.0 <7.3.5": "^7.3.5",
"esbuild@>=0.27.3 <0.28.1": "0.28.1",
"tar@<7.5.21": "7.5.21",
"brace-expansion@>=5.0.0 <5.0.9": "5.0.9",
"brace-expansion@>=5.0.0 <5.0.12": "5.0.12",
"js-yaml@>=4.0.0 <4.3.2": "4.3.2",
"axios@>=1.0.0 <1.18.0": "1.18.0",
"axios@>=1.0.0 <1.20.0": "1.20.0",
"pacote@>=21.0.0 <21.5.1": "21.5.1",
"smol-toml@<1.7.1": "1.7.1"
"smol-toml@<1.9.0": "1.9.0",
"nx@>=22.0.0 <22.7.10": "22.7.10",
"source-map-js@>=1.0.0 <1.2.2": "1.2.2",
"postcss-selector-parser@<7.1.6": "7.1.6"
}
}
}
Loading
Loading