Skip to content

chore: test workflow #1

chore: test workflow

chore: test workflow #1

Workflow file for this run

name: Mirror Releases to S3
on:
release:
types: [published] # Auto trigger when a release is published
workflow_dispatch: # Manual trigger
inputs:
tag_name:
description: "Release tag (e.g. v0.6.9). Takes precedence if both provided."
required: false
type: string
release_name:
description: "Release name (e.g. Jan 0.6.9). Used when tag_name is not provided."
required: false
type: string
push: # Auto trigger on push for a specific test branch
branches:
- feat/release-to-aws-s3 # ← change to your PR branch name
jobs:
mirror:
runs-on: ubuntu-latest
permissions:
contents: read
env:
CDN_HOST: catalog.jan.ai # CDN domain pointing to S3 (CloudFront/Cloudflare/R2)
BUCKET: ${{ secrets.CATALOG_AWS_S3_BUCKET_NAME }}
AWS_REGION: ${{ secrets.CATALOG_AWS_REGION }}
MAX_HISTORY: "20"
# Hardcoded test values used only on push (optional)
TEST_TAG: b4951 # ← put a real existing tag to test
TEST_NAME: "" # or a release name if you prefer to match by name
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Resolve release (automatic/manual/push-test)
id: rel
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
// Inputs from workflow_dispatch
const tagInput = core.getInput('tag_name');
const nameInput = core.getInput('release_name');
// Env for push-test
const testTag = process.env.TEST_TAG && process.env.TEST_TAG.trim() ? process.env.TEST_TAG.trim() : '';
const testName = process.env.TEST_NAME && process.env.TEST_NAME.trim() ? process.env.TEST_NAME.trim() : '';
// Resolution order:
// 1) tag_name input
// 2) TEST_TAG env (for push test)
// 3) release_name input
// 4) TEST_NAME env (for push test)
// 5) context.payload.release (release event)
let release = null;
let chosen = null;
async function getByTag(tag) {
const r = await github.rest.repos.getReleaseByTag({ owner, repo, tag });
return r.data;
}
async function getByName(name) {
const rels = await github.paginate(github.rest.repos.listReleases, { owner, repo, per_page: 100 });
return rels.find(r => (r.name || r.tag_name) === name) || null;
}
try {
if (tagInput) {
chosen = `tag_input:${tagInput}`;
release = await getByTag(tagInput);
} else if (testTag) {
chosen = `test_tag:${testTag}`;
release = await getByTag(testTag);
} else if (nameInput) {
chosen = `name_input:${nameInput}`;
release = await getByName(nameInput);
} else if (testName) {
chosen = `test_name:${testName}`;
release = await getByName(testName);
} else if (context.payload.release) {
chosen = 'payload_release_event';
release = context.payload.release;
} else {
core.setFailed('No release reference provided: set tag_name, TEST_TAG, release_name, TEST_NAME, or trigger from a release event.');
return;
}
} catch (e) {
core.setFailed(`Failed to resolve release (${chosen}). ${e.message}`);
return;
}
if (!release) {
core.setFailed(`Release not found (${chosen}).`);
return;
}
const tag = release.tag_name;
const name = release.name || tag;
const assets = (release.assets || []).map(a => ({
name: a.name,
size: a.size,
gh_url: a.browser_download_url
}));
core.info(`Resolved release via ${chosen}: tag=${tag}, name=${name}, assets=${assets.length}`);
core.setOutput('tag', tag);
core.setOutput('name', name);
core.setOutput('published_at', release.published_at || new Date().toISOString());
core.setOutput('assets', JSON.stringify(assets));
- name: Prepare assets directory
run: mkdir -p out meta
- name: Download assets from GitHub
run: |
echo '${{ steps.rel.outputs.assets }}' \
| jq -r '.[].gh_url' \
| while read url; do
f="out/$(basename "$url")"
echo "Downloading $url -> $f"
curl -L --fail -o "$f" "$url"
done
- name: Build/merge releases.json (keep max N entries)
env:
TAG: ${{ steps.rel.outputs.tag }}
NAME: ${{ steps.rel.outputs.name }}
PUBLISHED_AT: ${{ steps.rel.outputs.published_at }}
CDN_HOST: ${{ env.CDN_HOST }}
MAX_HISTORY: ${{ env.MAX_HISTORY }}
run: |
test -f meta/releases.json || echo '[]' > meta/releases.json
node - <<'NODE'
const fs = require('fs')
const path = require('path')
const TAG = process.env.TAG
const NAME = process.env.NAME || TAG
const PUBLISHED_AT = process.env.PUBLISHED_AT || new Date().toISOString()
const CDN = process.env.CDN_HOST
const MAX = parseInt(process.env.MAX_HISTORY || '20', 10)
const files = fs.readdirSync('out')
const sizeOf = f => fs.statSync(path.join('out', f)).size
const entry = {
tag_name: TAG,
name: NAME,
published_at: PUBLISHED_AT,
assets: files.map(f => ({
name: f,
size: sizeOf(f),
browser_download_url: `https://${CDN}/releases/${TAG}/${f}`
}))
}
const p = 'meta/releases.json'
const arr = JSON.parse(fs.readFileSync(p, 'utf8'))
// Update or insert at the top
const idx = arr.findIndex(x => x.tag_name === TAG)
if (idx >= 0) arr.splice(idx, 1)
arr.unshift(entry)
// Trim history to MAX entries
while (arr.length > MAX) arr.pop()
fs.writeFileSync(p, JSON.stringify(arr, null, 2))
NODE
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.CATALOG_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.CATALOG_AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Upload assets to S3
env:
TAG: ${{ steps.rel.outputs.tag }}
BUCKET: ${{ env.BUCKET }}
run: |
echo "Uploading assets for tag $TAG to s3://$BUCKET/releases/$TAG/"
aws s3 sync out "s3://$BUCKET/releases/$TAG/"
- name: Update 'latest' alias in S3
env:
TAG: ${{ steps.rel.outputs.tag }}
BUCKET: ${{ env.BUCKET }}
run: |
echo "Updating 'latest' alias to point to tag $TAG"
aws s3 rm "s3://$BUCKET/releases/latest/" --recursive || true
aws s3 sync "s3://$BUCKET/releases/$TAG/" "s3://$BUCKET/releases/latest/"
- name: Upload releases.json
env:
BUCKET: ${{ env.BUCKET }}
run: |
echo "Uploading releases.json"
aws s3 cp meta/releases.json "s3://$BUCKET/releases/releases.json" \
--content-type 'application/json' --cache-control 'no-store'