Skip to content

Merge pull request #38 from keyqcloud/fix/version-literal-sync #39

Merge pull request #38 from keyqcloud/fix/version-literal-sync

Merge pull request #38 from keyqcloud/fix/version-literal-sync #39

Workflow file for this run

name: Release
# Tag-triggered. Builds minified bundles + source maps from kyte-source.js,
# uploads to S3, invalidates CloudFront, and creates a GitHub Release with
# notes extracted from CHANGELOG.md.
#
# The version comes from the tag (refs/tags/v2.0.0 → 2.0.0). CHANGELOG.md
# must have a `## <version>` section as the first H2 — the script extracts
# everything between that and the next `## ` as release notes.
on:
push:
tags:
- 'v*'
permissions:
contents: write # needed for softprops/action-gh-release
# GitHub force-migrates JavaScript actions to Node.js 24 on 2026-06-02.
# Opting in early so the migration is a no-op for us; covers the actions
# whose latest major (aws-actions/configure-aws-credentials@v4,
# softprops/action-gh-release@v2) doesn't yet have a Node-24-native bump.
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
release:
name: Build, deploy, and release
runs-on: ubuntu-latest
steps:
- name: Check out source code
uses: actions/checkout@v5
- name: Set up Node.js
uses: actions/setup-node@v5
with:
node-version: '20'
- name: Install terser
run: npm install -g terser
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
- name: Verify CHANGELOG.md matches tag
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
CHANGELOG_VERSION=$(awk '/^## /{print $2; exit}' CHANGELOG.md)
if [ "$CHANGELOG_VERSION" != "$VERSION" ]; then
echo "::error::Tag v$VERSION does not match top of CHANGELOG.md (## $CHANGELOG_VERSION)."
exit 1
fi
- name: Build minified bundles with source maps
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
YEAR=$(date +'%Y')
# Force the runtime VERSION literal to the tag. Single source of
# truth is the git tag (same as the copyright header below).
# release-please's x-release-please-version annotation keeps the
# committed source in sync; this is the belt-and-suspenders so the
# SHIPPED bundle can never drift from the tag even if that slips
# (which is exactly how v2.1.0 went out logging "2.0.2").
sed -i -E "s/(static VERSION *= *['\"])[0-9]+\.[0-9]+\.[0-9]+(['\"])/\1${VERSION}\2/" kyte-source.js
STAMPED=$(grep -oE "static VERSION *= *['\"][0-9.]+['\"]" kyte-source.js)
echo "Stamped: ${STAMPED}"
if ! echo "${STAMPED}" | grep -q "'${VERSION}'"; then
echo "::error::Failed to stamp VERSION literal to ${VERSION} (got: ${STAMPED})."
exit 1
fi
# Two builds:
# kyte.js — minified, with `//# sourceMappingURL=kyte.js.map`
# appended by terser; pairs with kyte.js.map for
# customer debugging.
# kyte.min.js — minified, no source-map reference; for callers
# that don't want the .map fetch (size-sensitive).
terser kyte-source.js \
-c -m \
--source-map "url='kyte.js.map'" \
-o kyte.js
terser kyte-source.js \
-c -m \
-o kyte.min.js
# Prepend copyright notice to both. Source map stays untouched —
# browsers tolerate a leading comment in the .js but not in JSON.
cat > copyright.txt <<EOF
/**
* Copyright 2020-${YEAR} KeyQ, Inc.
* MIT License — https://opensource.org/licenses/MIT
* KyteJS v${VERSION}
**/
EOF
cat copyright.txt kyte.js > tmp && mv tmp kyte.js
cat copyright.txt kyte.min.js > tmp && mv tmp kyte.min.js
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Upload stable bundles to S3
run: |
aws s3 cp kyte.js s3://${{ secrets.S3_BUCKET }}/kyte/js/stable/kyte.js --content-type application/javascript
aws s3 cp kyte.min.js s3://${{ secrets.S3_BUCKET }}/kyte/js/stable/kyte.min.js --content-type application/javascript
aws s3 cp kyte.js.map s3://${{ secrets.S3_BUCKET }}/kyte/js/stable/kyte.js.map --content-type application/json
- name: Upload version-pinned archive bundle to S3
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
aws s3 cp kyte.js "s3://${{ secrets.S3_BUCKET }}/kyte/js/archive/kyte-${VERSION}.js" --content-type application/javascript
aws s3 cp kyte.js.map "s3://${{ secrets.S3_BUCKET }}/kyte/js/archive/kyte-${VERSION}.js.map" --content-type application/json
- name: Invalidate CloudFront cache
run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} --paths '/*'
- name: Extract release notes from CHANGELOG.md
id: notes
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
# Capture everything between `## $VERSION` and the next `## ` line.
awk -v v="^## ${VERSION}$" '
$0 ~ v { capture=1; next }
/^## / && capture { exit }
capture { print }
' CHANGELOG.md > release-notes.md
# Sanity check: if release-notes.md is empty the GitHub Release
# would be blank. Fail loud rather than ship empty notes.
if [ ! -s release-notes.md ]; then
echo "::error::Could not extract release notes for v$VERSION from CHANGELOG.md"
exit 1
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: KyteJS v${{ steps.version.outputs.VERSION }}
body_path: release-notes.md
files: |
kyte.js
kyte.min.js
kyte.js.map
draft: false
prerelease: false