add github actions #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Auto Publish on Package Changes | ||
on: | ||
push: | ||
branches: | ||
- develop | ||
- main | ||
paths: | ||
- 'packages/**' | ||
- '.changeset/**' | ||
- 'package.json' | ||
- 'yarn.lock' | ||
workflow_dispatch: | ||
inputs: | ||
force_publish: | ||
description: 'Force publish even if no changes detected' | ||
required: false | ||
default: false | ||
type: boolean | ||
dry_run: | ||
description: 'Run in dry-run mode (no actual publishing)' | ||
required: false | ||
default: false | ||
type: boolean | ||
# Prevent concurrent runs of this workflow | ||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.ref }} | ||
cancel-in-progress: false | ||
jobs: | ||
detect-changes: | ||
name: Detect Package Changes | ||
runs-on: ubuntu-latest | ||
outputs: | ||
has-changes: ${{ steps.check-changes.outputs.has-changes }} | ||
packages-changed: ${{ steps.check-changes.outputs.packages-changed }} | ||
should-publish: ${{ steps.check-changes.outputs.should-publish }} | ||
steps: | ||
- name: Checkout Repository | ||
uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
- name: Setup Node.js 22.15.1 | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: 22.15.1 | ||
- name: Install Dependencies | ||
run: yarn install --frozen-lockfile | ||
- name: Check for Package Changes | ||
id: check-changes | ||
run: | | ||
set -e | ||
# Force publish if manual trigger with force flag | ||
if [ "${{ github.event.inputs.force_publish }}" = "true" ]; then | ||
echo "has-changes=true" >> $GITHUB_OUTPUT | ||
echo "packages-changed=force" >> $GITHUB_OUTPUT | ||
echo "should-publish=true" >> $GITHUB_OUTPUT | ||
echo "Force publish triggered manually" | ||
exit 0 | ||
fi | ||
# Check if any packages need to be published | ||
STATUS_OUTPUT=$(yarn changeset status --output=json 2>/dev/null || echo '{"releases":[]}') | ||
RELEASE_COUNT=$(echo "$STATUS_OUTPUT" | jq '.releases | length') | ||
if [ "$RELEASE_COUNT" -gt 0 ]; then | ||
echo "has-changes=true" >> $GITHUB_OUTPUT | ||
PACKAGES=$(echo "$STATUS_OUTPUT" | jq -r '.releases[].name' | tr '\n' ',' | sed 's/,$//') | ||
echo "packages-changed=$PACKAGES" >> $GITHUB_OUTPUT | ||
echo "should-publish=true" >> $GITHUB_OUTPUT | ||
echo "Found $RELEASE_COUNT package(s) with changes: $PACKAGES" | ||
else | ||
echo "has-changes=false" >> $GITHUB_OUTPUT | ||
echo "packages-changed=" >> $GITHUB_OUTPUT | ||
echo "should-publish=false" >> $GITHUB_OUTPUT | ||
echo "No packages found with version changes" | ||
fi | ||
build-and-test: | ||
name: Build and Test | ||
runs-on: ubuntu-latest | ||
needs: detect-changes | ||
if: needs.detect-changes.outputs.has-changes == 'true' | ||
steps: | ||
- name: Checkout Repository | ||
uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
- name: Setup Node.js 22.15.1 | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: 22.15.1 | ||
- name: Get yarn cache directory path | ||
id: yarn-cache-dir-path | ||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT | ||
- name: Cache yarn dependencies | ||
uses: actions/cache@v4 | ||
id: yarn-cache | ||
with: | ||
path: | | ||
${{ steps.yarn-cache-dir-path.outputs.dir }} | ||
node_modules | ||
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} | ||
restore-keys: | | ||
${{ runner.os }}-yarn- | ||
- name: Install Dependencies | ||
run: yarn install --frozen-lockfile | ||
- name: Run Tests | ||
run: yarn test | ||
- name: Run Linting | ||
run: yarn lint | ||
- name: Build Packages | ||
run: yarn build | ||
publish: | ||
name: Publish to NPM | ||
runs-on: ubuntu-latest | ||
needs: [detect-changes, build-and-test] | ||
if: needs.detect-changes.outputs.should-publish == 'true' && github.event.inputs.dry_run != 'true' | ||
environment: npm-publish | ||
steps: | ||
- name: Checkout Repository | ||
uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
# Use a PAT token that can push tags and trigger workflows | ||
token: ${{ secrets.GITHUB_TOKEN }} | ||
- name: Setup Node.js 22.15.1 | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: 22.15.1 | ||
registry-url: 'https://registry.npmjs.org' | ||
- name: Install Dependencies | ||
run: yarn install --frozen-lockfile | ||
- name: Build Packages | ||
run: yarn build | ||
- name: Configure Git User | ||
run: | | ||
git config --local user.email "[email protected]" | ||
git config --local user.name "GitHub Action" | ||
- name: Publish to NPM | ||
id: publish | ||
run: | | ||
echo "Publishing packages to NPM..." | ||
OUTPUT=$(yarn publish-changed 2>&1) | ||
echo "$OUTPUT" | ||
# Check if publish was successful | ||
if echo "$OUTPUT" | grep -q "Published"; then | ||
echo "publish-success=true" >> $GITHUB_OUTPUT | ||
echo "Packages published successfully" | ||
else | ||
echo "publish-success=false" >> $GITHUB_OUTPUT | ||
echo "Package publishing failed" | ||
exit 1 | ||
fi | ||
env: | ||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | ||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} | ||
- name: Push Git Tags | ||
if: steps.publish.outputs.publish-success == 'true' | ||
run: | | ||
echo "Pushing git tags..." | ||
# Configure git to use the token for authentication | ||
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git | ||
git push --follow-tags | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
- name: Create GitHub Release | ||
if: steps.publish.outputs.publish-success == 'true' | ||
uses: actions/github-script@v7 | ||
with: | ||
script: | | ||
const { execSync } = require('child_process'); | ||
try { | ||
// Get all tags and find the most recent one | ||
const tags = execSync('git tag --sort=-version:refname', { encoding: 'utf8' }).trim().split('\n'); | ||
const latestTag = tags[0]; | ||
if (!latestTag) { | ||
console.log('No tags found, skipping release creation'); | ||
return; | ||
} | ||
console.log(`Latest tag: ${latestTag}`); | ||
// Extract package name and version from tag | ||
// Handle both formats: @ag.ds-next/[email protected] or v1.30.0 | ||
let packageName, version; | ||
if (latestTag.includes('@') && latestTag.lastIndexOf('@') > 0) { | ||
// Format: @ag.ds-next/[email protected] | ||
const lastAtIndex = latestTag.lastIndexOf('@'); | ||
packageName = latestTag.substring(0, lastAtIndex); | ||
version = latestTag.substring(lastAtIndex + 1); | ||
} else if (latestTag.startsWith('v')) { | ||
// Format: v1.30.0 | ||
packageName = '@ag.ds-next/react'; // Default package name | ||
version = latestTag.substring(1); | ||
} else { | ||
// Other format | ||
packageName = '@ag.ds-next/react'; | ||
version = latestTag; | ||
} | ||
console.log(`Package: ${packageName}, Version: ${version}`); | ||
// Check if release already exists | ||
try { | ||
await github.rest.repos.getRelease({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
tag: latestTag | ||
}); | ||
console.log(`Release for ${latestTag} already exists, skipping creation`); | ||
return; | ||
} catch (error) { | ||
if (error.status !== 404) { | ||
throw error; | ||
} | ||
// Release doesn't exist, continue with creation | ||
} | ||
// Create release | ||
const release = await github.rest.repos.createRelease({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
tag_name: latestTag, | ||
name: `${packageName} v${version}`, | ||
body: `**${packageName}** version **${version}** has been released! | ||
## NPM Package | ||
- [View on NPM](https://www.npmjs.com/package/${packageName}/v/${version}) | ||
## Documentation | ||
- [Design System Website](https://design-system.agriculture.gov.au) | ||
- [Component Documentation](https://design-system.agriculture.gov.au/components) | ||
- [CHANGELOG](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/packages/react/CHANGELOG.md) | ||
## Installation | ||
\`\`\`bash | ||
yarn add ${packageName}@${version} | ||
# or | ||
npm install ${packageName}@${version} | ||
\`\`\` | ||
## Update to Latest | ||
\`\`\`bash | ||
yarn add ${packageName}@latest | ||
# or | ||
npm install ${packageName}@latest | ||
\`\`\` | ||
*This release was automatically created by GitHub Actions.*`, | ||
draft: false, | ||
prerelease: false | ||
}); | ||
console.log(`Created GitHub release: ${release.data.html_url}`); | ||
} catch (error) { | ||
console.log(`Failed to create GitHub release: ${error.message}`); | ||
// Don't fail the workflow if release creation fails | ||
} | ||
dry-run: | ||
name: Dry Run (Preview Changes) | ||
runs-on: ubuntu-latest | ||
needs: [detect-changes, build-and-test] | ||
if: github.event.inputs.dry_run == 'true' && needs.detect-changes.outputs.has-changes == 'true' | ||
steps: | ||
- name: Checkout Repository | ||
uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
- name: Setup Node.js 22.15.1 | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: 22.15.1 | ||
- name: Install Dependencies | ||
run: yarn install --frozen-lockfile | ||
- name: Build Packages | ||
run: yarn build | ||
- name: Preview Publish Changes | ||
run: | | ||
echo "Dry run mode - showing what would be published:" | ||
yarn changeset status --verbose | ||
echo "" | ||
echo "Packages that would be published:" | ||
yarn changeset status --output=json | jq -r '.releases[] | "• \(.name): \(.oldVersion) → \(.newVersion)"' | ||
notify-success: | ||
name: Notify Success | ||
runs-on: ubuntu-latest | ||
needs: [detect-changes, publish, dry-run] | ||
if: always() && (needs.publish.result == 'success' || needs.dry-run.result == 'success') | ||
steps: | ||
- name: Comment on Success | ||
uses: actions/github-script@v7 | ||
with: | ||
script: | | ||
const packages = '${{ needs.detect-changes.outputs.packages-changed }}'; | ||
const isDryRun = '${{ needs.dry-run.result }}' === 'success'; | ||
const body = isDryRun ? | ||
`🔍 **Dry run completed successfully!** | ||
**Packages that would be published:** ${packages} | ||
*This was a dry run - no packages were actually published. To publish for real, run the workflow without the dry_run option.* | ||
*Automated by GitHub Actions*` : | ||
`🚀 **Successfully published packages to NPM!** | ||
**Published:** ${packages} | ||
**Links:** | ||
- [NPM Registry](https://www.npmjs.com/package/@ag.ds-next/react) | ||
- [Release Notes](https://github.com/${{ github.repository }}/releases) | ||
- [Design System Docs](https://design-system.agriculture.gov.au) | ||
*Automated by GitHub Actions*`; | ||
await github.rest.repos.createCommitComment({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
commit_sha: context.sha, | ||
body: body | ||
}); | ||
notify-failure: | ||
name: Notify Failure | ||
runs-on: ubuntu-latest | ||
needs: [detect-changes, build-and-test, publish, dry-run] | ||
if: always() && (failure() || cancelled()) | ||
steps: | ||
- name: Notify Failure | ||
uses: actions/github-script@v7 | ||
with: | ||
script: | | ||
const isDryRun = '${{ github.event.inputs.dry_run }}' === 'true'; | ||
const failedJob = '${{ needs.build-and-test.result }}' === 'failure' ? 'build-and-test' : | ||
'${{ needs.publish.result }}' === 'failure' ? 'publish' : | ||
'${{ needs.dry-run.result }}' === 'failure' ? 'dry-run' : 'unknown'; | ||
const title = isDryRun ? 'Dry run failed' : 'NPM package publication failed'; | ||
await github.rest.repos.createCommitComment({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
commit_sha: context.sha, | ||
body: `**${title}** | ||
**Failed job:** ${failedJob} | ||
**Check the workflow logs:** [View Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) | ||
**Possible causes:** | ||
- Build or test failures | ||
- NPM authentication issues | ||
- Network connectivity problems | ||
- Package version conflicts | ||
- Changesets configuration issues | ||
*Please investigate and retry the publication manually if needed.*` | ||
}); |