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
3 changes: 0 additions & 3 deletions .github/release-please/.release-please-manifest.json

This file was deleted.

8 changes: 0 additions & 8 deletions .github/release-please/release-please-config.json

This file was deleted.

21 changes: 0 additions & 21 deletions .github/workflows/release-please.yml

This file was deleted.

114 changes: 114 additions & 0 deletions .github/workflows/release-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Release PR

on:
push:
branches: [main]
workflow_dispatch:
inputs:
version:
description: "Override version (e.g. v0.0.3). Leave blank to auto-bump."
required: false
type: string

permissions: {}

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

jobs:
release-pr:
if: ${{ github.event_name == 'workflow_dispatch' || !contains(github.event.head_commit.message, 'chore(release):') }}
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
fetch-depth: 0
fetch-tags: true
persist-credentials: false

- name: Compute next version
id: version
env:
OVERRIDE: ${{ inputs.version }}
run: |
set -euo pipefail
if [ -n "${OVERRIDE:-}" ]; then
if ! printf '%s' "$OVERRIDE" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::Invalid version override '$OVERRIDE' (expected vMAJOR.MINOR.PATCH)"
exit 1
fi
next="$OVERRIDE"
echo "Using override: $next"
else
latest=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1 || true)
if [ -z "$latest" ]; then
next="v0.0.1"
else
v="${latest#v}"
major="${v%%.*}"
rest="${v#*.}"
minor="${rest%%.*}"
patch="${rest#*.}"
next="v${major}.${minor}.$((patch + 1))"
fi
echo "Latest tag: ${latest:-none}"
fi
echo "next=$next" >> "$GITHUB_OUTPUT"
echo "Next version: $next"

- name: Ensure changelog exists
run: touch CHANGELOG.md

- name: Generate changelog
uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4.8.0
id: git-cliff
with:
config: cliff.toml
github_token: ${{ secrets.GITHUB_TOKEN }}
args: --unreleased --tag ${{ steps.version.outputs.next }} --prepend CHANGELOG.md
env:
OUTPUT: RELEASE_NOTES.md

- name: Check for releasable changes
id: check
run: |
set -euo pipefail
if grep -qE '^- ' RELEASE_NOTES.md; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
echo "Changes found:"
cat RELEASE_NOTES.md
else
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "Nothing to release since the last tag. Skipping."
fi

- name: Write VERSION
if: steps.check.outputs.has_changes == 'true'
env:
NEXT: ${{ steps.version.outputs.next }}
run: printf '%s\n' "$NEXT" > VERSION

- name: Open release PR
if: steps.check.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
with:
token: ${{ secrets.GITHUB_TOKEN }}
sign-commits: true
base: main
branch: release/${{ steps.version.outputs.next }}
title: "chore(release): ${{ steps.version.outputs.next }}"
commit-message: "chore(release): ${{ steps.version.outputs.next }}"
labels: release
delete-branch: true
add-paths: |
CHANGELOG.md
VERSION
body: |
Automated release PR for **${{ steps.version.outputs.next }}**.

Review the changelog, edit if needed, then merge.
Merging creates the tag and publishes the GitHub Release.
61 changes: 61 additions & 0 deletions .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Publish Release

on:
pull_request:
types: [closed]

permissions: {}

jobs:
publish:
if: >
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
startsWith(github.event.pull_request.head.ref, 'release/') &&
contains(github.event.pull_request.labels.*.name, 'release')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
ref: main
fetch-depth: 0
persist-credentials: false

- name: Read version
id: version
run: |
set -euo pipefail
tag="$(head -n1 VERSION | tr -d '[:space:]')"
if ! printf '%s' "$tag" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::Invalid VERSION content '$tag' (expected vMAJOR.MINOR.PATCH)"
exit 1
fi
echo "tag=$tag" >> "$GITHUB_OUTPUT"

- name: Extract latest changelog section
run: |
awk '/^## /{ if (found) exit; found=1; next } found' CHANGELOG.md > RELEASE_NOTES.md
cat RELEASE_NOTES.md

- name: Create tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.version.outputs.tag }}
run: |
set -euo pipefail
sha="$(git rev-parse HEAD)"
gh api "repos/${GITHUB_REPOSITORY}/git/refs" \
-f "ref=refs/tags/${TAG}" \
-f "sha=${sha}"

- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.version.outputs.tag }}
run: |
set -euo pipefail
gh release create "$TAG" \
--title "$TAG" \
--notes-file RELEASE_NOTES.md
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

49 changes: 49 additions & 0 deletions cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
[changelog]
header = """
# Changelog

All notable changes to the Security Frameworks are documented here.
"""

body = """
{% macro remote_url() %}https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}{% endmacro %}
{% if version %}
## {{ version | trim_start_matches(pat="v") }} ({{ timestamp | date(format="%Y-%m-%d") }})
{% else %}
## Unreleased
{% endif %}
{% for group, commits in commits | filter(attribute="merge_commit", value=false) | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
{%- if commit.remote.pr_title -%}
{%- set entry = commit.remote.pr_title -%}
{%- else -%}
{%- set entry = commit.message | split(pat="\n") | first -%}
{%- endif %}
- {{ entry | trim }}{% if commit.remote.pr_number %} ([#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }})){% endif %}{% if commit.remote.username %} by @{{ commit.remote.username }}{% endif %}
{%- endfor %}
{% endfor %}
"""

trim = true

[git]
conventional_commits = false
filter_unconventional = false
filter_commits = false
sort_commits = "oldest"

commit_parsers = [
{ message = "^Merge pull request", skip = true },
{ message = "^Merge branch", skip = true },
{ message = "^chore\\(release\\)", skip = true },

{ field = "github.pr_labels", pattern = "dependencies", skip = true },

{ field = "github.pr_labels", pattern = "content:add", group = "<!-- 0 --> New content" },
{ field = "github.pr_labels", pattern = "content:update", group = "<!-- 1 --> Updated content" },
{ field = "github.pr_labels", pattern = "enhancement", group = "<!-- 2 --> Enhancements" },
{ field = "github.pr_labels", pattern = "certifications", group = "<!-- 3 --> Certifications" },

{ message = ".*", group = "<!-- 9 --> Other" },
]
5 changes: 5 additions & 0 deletions docs/pages/changelog.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

Release notes for the Security Frameworks, generated from [GitHub Releases](https://github.com/security-alliance/frameworks/releases).

::changelog
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "workspace",
"version": "1.0.0",
"version": "0.0.1",
"description": "Official repository to the Security Frameworks by SEAL. This repository contains the entire structure and contents of the frameworks. Feel free to suggest from new categories to grammar corrections. Collaboration is open to everyone. **This is a work in progress.**",
"main": "index.js",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion utils/generate-folder-indexes.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ function loadSidebarConfig(branchName) {
.replace(/^(?:import[^\n]*\n)+/, '')
.replace(/export default defineConfig\(config\)\s*;?\s*$/, 'return defineConfig(config);')
.replace(/\bas const\b/g, '')
.replace(/head\(\{[^}]*\}\s*:\s*\{[^}]*\}\)\s*\{[\s\S]*?\n \},/, '')
.replace(/head\([^)]*\)\s*\{[\s\S]*?\n \},/, '')
.replace(/new Set<[^>]+>\(/g, 'new Set(')
.replace(/function\s+(\w+)\(([^)]*)\)\s*:\s*[^\s{]+/g, (_, name, params) => {
const cleaned = params.replace(/:\s*[^,)]+/g, '');
Expand Down
46 changes: 38 additions & 8 deletions vocs.config.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { createElement, Fragment } from 'react'
import { defineConfig } from 'vocs/config'
import { readFileSync } from 'node:fs'
import { Changelog, defineConfig } from 'vocs/config'

const MAIN_SITE_URL = 'https://frameworks.securityalliance.org'
function siteVersion(): string {
try {
const raw = readFileSync('VERSION', 'utf8').trim()
if (raw) return raw.startsWith('v') ? raw : `v${raw}`
} catch {}
return 'v0.0.0'
}

const isMainBranch = process.env.CF_PAGES_BRANCH === 'main'

const config = {
srcDir: 'docs',
renderStrategy: 'full-static' as const,
head({ path }: { path: string }) {
const cleanPath = path.replace(/\/index\.html$/, '').replace(/\.html$/, '').replace(/\/$/, '')
if (!isMainBranch && devOnlyLinks.has(cleanPath)) return createElement(Fragment)
const canonicalUrl = `${MAIN_SITE_URL}${cleanPath || '/'}`
return createElement('link', { rel: 'canonical', href: canonicalUrl })
head(path: string) { const cleanPath = path.replace(/\/index\.html$/, '').replace(/\.html$/, '').replace(/\/$/, '')
return { canonical: `https://frameworks.securityalliance.org${cleanPath || '/'}` }
},
banner: {
content: 'This is a work in progress and not a release. We are looking for volunteers. See [Issues](https://github.com/security-alliance/frameworks/issues) and [Contribution](https://github.com/security-alliance/frameworks/blob/develop/docs/pages/contribute/contributing.mdx) to know how to collaborate.',
Expand All @@ -28,6 +31,19 @@ const config = {
iconUrl: 'https://frameworks-static.s3.us-east-2.amazonaws.com/images/logo/favicon.svg',
ogImageUrl: 'https://frameworks-static.s3.us-east-2.amazonaws.com/images/logo/frameworks-full-cropped.png',
checkDeadlinks: "warn" as const,
changelog:
typeof Changelog !== 'undefined'
? Changelog.github({ repo: 'security-alliance/frameworks' })
: undefined,
topNav: [
{
text: siteVersion(),
items: [
{ text: 'Changelog', link: '/changelog' },
{ text: 'Contributing', link: '/contribute/contributing' },
],
},
],
sidebar: [
{
text: 'Introduction',
Expand Down Expand Up @@ -701,6 +717,20 @@ function collectDevLinks(items: any[], parentIsDev = false): Set<string> {

const devOnlyLinks = collectDevLinks(config.sidebar)

if (!isMainBranch && devOnlyLinks.size > 0) {
const devRoutesLiteral = JSON.stringify([...devOnlyLinks])
// @ts-expect-error - head is rebuilt from source so the dev-route list is
config.head = new Function(
'path',
`
const devOnlyLinks = new Set(${devRoutesLiteral});
const cleanPath = path.replace(/\\/index\\.html$/, '').replace(/\\.html$/, '').replace(/\\/$/, '');
if (devOnlyLinks.has(cleanPath)) return { canonical: false };
return { canonical: 'https://frameworks.securityalliance.org' + (cleanPath || '/') };
`,
)
}

if (isMainBranch) {
config.sidebar = filterDevItems(config.sidebar)
}
Expand Down
Loading