Skip to content

Commit

Permalink
module release CI setup (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
bgentry authored Aug 28, 2024
1 parent d65285f commit af74763
Show file tree
Hide file tree
Showing 8 changed files with 337 additions and 0 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
dist
Dockerfile
node_modules
packager
File renamed without changes.
180 changes: 180 additions & 0 deletions .github/workflows/package-and-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
name: Package and Release

on:
workflow_call:
inputs:
after-version:
required: false
type: string
module-base:
required: true
type: string
module-dir:
required: true
type: string
storage-bucket:
required: true
type: string
version-tag:
required: true
type: string
secrets:
r2-access-key-id:
description: "Cloudflare R2 access key ID passed from caller workflow"
required: true
r2-endpoint-url:
description: "Cloudflare R2 jurisdiction-specific endpoint URL"
required: true
r2-secret-access-key:
description: "A secret access key passed from the caller workflow"
required: true

jobs:
package:
name: Package
runs-on: ubuntu-latest
env:
AWS_DEFAULT_REGION: auto
AWS_ACCESS_KEY_ID: ${{ secrets.r2-access-key-id }}
AWS_ENDPOINT_URL: ${{ secrets.r2-endpoint-url }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.r2-secret-access-key }}
OUTPUT_DIR: /tmp/mod-zip-result

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Install Node.js
uses: actions/setup-node@v4
with:
cache: "npm"
cache-dependency-path: package-lock.json
node-version: "20.12.2"

- name: Install dependencies
run: npm install
shell: sh

- name: Build JS 🏗️
run: npm exec vite build

- name: Setup Go
uses: actions/setup-go@v5
with:
check-latest: true
go-version-file: "packager/go.mod"
cache-dependency-path: |
packager/go.sum
- name: Display Go version
run: go version

- name: Install dependencies
working-directory: ./packager
run: |
echo "::group::go get"
go get -t ./...
echo "::endgroup::"
- name: Build packager
working-directory: ./packager
run: go install .

- name: Fetch tags
run: git fetch --tags -f origin

- name: Determine module names and version details
id: module_info
run: |
BASE_MODULE_NAME=${{ inputs.module-base }}
MODULE_NAME=$(go list -m)
echo "base_module_name=${BASE_MODULE_NAME}" >> $GITHUB_OUTPUT
echo "module_name=${MODULE_NAME}" >> $GITHUB_OUTPUT
if [ "$MODULE_NAME" == "$BASE_MODULE_NAME" ]; then
TAG_PREFIX=""
else
TAG_PREFIX=${MODULE_NAME#$BASE_MODULE_NAME/}/
fi
echo "tag_prefix=${TAG_PREFIX}" >> $GITHUB_OUTPUT
VERSION_TAG=${{ inputs.version-tag }}
echo "version_tag=${VERSION_TAG}" >> $GITHUB_OUTPUT
VERSION_NUMBER=${VERSION_TAG#$TAG_PREFIX}
echo "version_number=${VERSION_NUMBER}" >> $GITHUB_OUTPUT
working-directory: ${{ inputs.module-dir }}

- name: Extract timestamp
id: extract_timestamp
run: echo "timestamp=$(git log -1 --format=%ct ${{ steps.module_info.outputs.version_tag }})" >> $GITHUB_OUTPUT

- name: print stuff
run: |
echo "base module: ${{ steps.module_info.outputs.base_module_name }}"
echo "module: ${{ steps.module_info.outputs.module_name }}"
echo "tag_prefix: ${{ steps.module_info.outputs.tag_prefix }}"
echo "timestamp: ${{ steps.extract_timestamp.outputs.timestamp }}"
echo "version_tag: ${{ steps.module_info.outputs.version_tag }}"
echo "version_number: ${{ steps.module_info.outputs.version_number }}"
- name: Remove all undesirable files
# Remove all files that we don't want to end up in the final mod release .zip file, including:
# * node_modules
# * UI source files, .json, .js
run: |
rm -r .eslintrc.cjs .github .storybook .stylelintignore .vscode node_modules package.json package-lock.json public src
find . -type f -name '*.json' -exec rm -f {} +
- name: Package module
# if: startsWith(github.ref, 'refs/tags/')
run: |
packager \
-dir ${{ inputs.module-dir }} \
-output ${{ env.OUTPUT_DIR }} \
-mod ${{ steps.module_info.outputs.module_name }} \
-timestamp ${{ steps.extract_timestamp.outputs.timestamp }} \
-version ${{ steps.module_info.outputs.version_number }}
- name: Extract sorted version list and write files
id: version_list
run: |
AFTER_VERSION="${{ inputs.after-version }}"
TAG_PREFIX="${{ steps.module_info.outputs.tag_prefix }}"
TAG_MATCHER="${TAG_PREFIX}v*"
if [ -n "$AFTER_VERSION" ]; then
VERSION_LIST=$(git tag -l "$TAG_MATCHER" | sed "s#^$TAG_PREFIX##" | sort -V | awk -v threshold="$AFTER_VERSION" '$0 > threshold')
else
VERSION_LIST=$(git tag -l "$TAG_MATCHER" | sed "s#^$TAG_PREFIX##" | sort -V)
fi
LATEST_VERSION=$(echo "$VERSION_LIST" | tail -n 1)
echo "Latest version: $LATEST_VERSION"
echo "All versions:"
echo "$VERSION_LIST"
if [ -z "$VERSION_LIST" ]; then
echo "Error: Version list is empty."
exit 1
fi
if [ -z "$LATEST_VERSION" ]; then
echo "Error: Latest version is empty."
exit 1
fi
echo "latest_version=${LATEST_VERSION}" >> $GITHUB_OUTPUT
echo "$VERSION_LIST" > ${{ env.OUTPUT_DIR }}/${{ steps.module_info.outputs.module_name }}/@v/list
- name: List results
run: |
tree ${{ env.OUTPUT_DIR }}
ls -la ${{ env.OUTPUT_DIR }}/${{ steps.module_info.outputs.module_name }}
ls -la ${{ env.OUTPUT_DIR }}/${{ steps.module_info.outputs.module_name }}/@v
cat ${{ env.OUTPUT_DIR }}/${{ steps.module_info.outputs.module_name }}/@v/list
- name: Sync files to R2
run: |
aws s3 cp ${{ env.OUTPUT_DIR }}/${{ steps.module_info.outputs.module_name }}/@v/ s3://${{ inputs.storage-bucket }}/${{ steps.module_info.outputs.module_name }}/@v/ --recursive
aws s3 cp s3://${{ inputs.storage-bucket }}/${{ steps.module_info.outputs.module_name }}/@v/${{ steps.version_list.outputs.latest_version }}.info s3://${{ inputs.storage-bucket }}/${{ steps.module_info.outputs.module_name }}/@latest
27 changes: 27 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Release

on:
push:
branches:
- master
- "*"
tags:
# Additional packages must be added both here AND in the job list below:
- "v*"

jobs:
release_riverui:
uses: ./.github/workflows/package-and-release.yaml
if: startsWith(github.ref, 'refs/tags/v')
with:
after-version: v0.4.0
module-base: riverqueue.com/riverui
module-dir: .
storage-bucket: ${{ vars.RELEASE_STORAGE_BUCKET }}
version-tag: ${{ github.ref_name}}
permissions:
contents: read
secrets:
r2-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }}
r2-endpoint-url: ${{ secrets.R2_ENDPOINT_URL }}
r2-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.env
.env.*
!.env.example
/packager/packager
.tool-versions
/riverui

Expand Down
7 changes: 7 additions & 0 deletions packager/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module riverqueue.com/riverqueue/packager

go 1.23

toolchain go1.23.0

require golang.org/x/mod v0.18.0
4 changes: 4 additions & 0 deletions packager/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
117 changes: 117 additions & 0 deletions packager/packager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package main

import (
"encoding/json"
"errors"
"flag"
"log"
"os"
"path/filepath"
"time"

"golang.org/x/mod/module"
"golang.org/x/mod/semver"
"golang.org/x/mod/zip"
)

func main() {
if err := createBundle(); err != nil {
log.Fatal(err)
}
}

func createBundle() error {
var (
dir string
mod string
outputDirPath string
timestampInt int64
versionString string
)
flag.StringVar(&dir, "dir", "", "dir to package up")
flag.StringVar(&mod, "mod", "", "module name, i.e. github.com/user/repo")
flag.Int64Var(&timestampInt, "timestamp", 0, "timestamp of the version")
flag.StringVar(&outputDirPath, "output", "", "output directory path, which will include a fully nested directory structure for the module name")
flag.StringVar(&versionString, "version", "", "version of the module")

flag.Parse()

if dir == "" {
return errors.New("dir is required")
}
if outputDirPath == "" {
return errors.New("output is required")
}
if mod == "" {
return errors.New("mod is required")
}
if timestampInt == 0 {
return errors.New("timestamp is required")
}
if versionString == "" {
return errors.New("version is required")
}

if !semver.IsValid(versionString) {
return errors.New("version is not valid")
}

// Convert the timestamp to a time.Time object:
timestamp := time.Unix(timestampInt, 0).UTC()

nestedOutputDir := filepath.Join(outputDirPath, mod)
vOutputDir := filepath.Join(nestedOutputDir, "@v")

if err := os.MkdirAll(vOutputDir, 0o700); err != nil {
return err
}

version := module.Version{
Path: mod,
Version: versionString,
}

modFilename := version.Version + ".mod"
zipFilename := version.Version + ".zip"

modFileContents, err := os.ReadFile(filepath.Join(dir, "go.mod"))
if err != nil {
return err
}

if err := os.WriteFile(filepath.Join(vOutputDir, modFilename), modFileContents, 0o644); err != nil {
return err
}

f, err := os.OpenFile(filepath.Join(vOutputDir, zipFilename), os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()

if err := zip.CreateFromDir(f, version, dir); err != nil {
return err
}

info := Info{
Version: version.Version,
Time: timestamp,
}

infoFile, err := os.Create(filepath.Join(vOutputDir, version.Version+".info"))
if err != nil {
return err
}
defer infoFile.Close()

if err := json.NewEncoder(infoFile).Encode(info); err != nil {
return err
}

return nil
}

type Info struct {
Version string // version string
Time time.Time // commit time
}

0 comments on commit af74763

Please sign in to comment.