diff --git a/.github/RELEASE_PROCESS.md b/.github/RELEASE_PROCESS.md new file mode 100644 index 00000000..a42a8b9a --- /dev/null +++ b/.github/RELEASE_PROCESS.md @@ -0,0 +1,55 @@ +# Release Process + +This repository uses a fully automated release process based on semantic versioning and GitHub Actions. + +## How It Works + +### 1. Pull Request Testing + +When you create a pull request that changes files in the `packages/` directory: + +- The `pullrequest.yml` workflow automatically runs +- It builds and tests all packages +- It performs a dry-run of semantic-release to verify version changes +- A summary shows what packages would be published when merged + +This ensures that releases will work correctly once merged to main. + +### 2. Automatic Release on Merge + +When a pull request is merged to the `main` branch: + +- The `release.yml` workflow automatically runs +- It builds and tests all packages +- It identifies which packages have changes +- For each changed package: + - Determines the next version based on conventional commits + - Publishes to npm if there's a version bump + - Creates a GitHub release with changelog + +## Conventional Commits + +This repo follows the [Conventional Commits](https://www.conventionalcommits.org/) standard to determine version bumps: + +- `feat:` - Creates a minor version bump (0.1.0 → 0.2.0) +- `fix:` - Creates a patch version bump (0.1.0 → 0.1.1) +- `docs:`, `style:`, `refactor:`, `perf:`, `test:`, `build:`, `ci:` - Creates a patch bump +- `chore(deps):` - Updates to dependencies create a patch bump +- `BREAKING CHANGE:` in commit body - Creates a major version bump (0.1.0 → 1.0.0) + +## Troubleshooting + +If a release fails: + +1. Check the GitHub Actions logs for errors +2. Fix any issues in a new PR +3. When merged to main, the release will run again automatically + +## Testing Release Process + +To test the release process without publishing packages: + +1. Create a PR with your changes +2. The PR workflow will run semantic-release in dry-run mode +3. Review the workflow logs to see what would be released +4. Only when merged to main will actual publishing occur \ No newline at end of file diff --git a/.github/release-template.json b/.github/release-template.json index 0e899626..27af4107 100644 --- a/.github/release-template.json +++ b/.github/release-template.json @@ -1,15 +1,23 @@ { "branches": ["main"], "plugins": [ - "@semantic-release/commit-analyzer", + ["@semantic-release/commit-analyzer", { + "preset": "angular", + "releaseRules": [ + {"type": "feat", "release": "minor"}, + {"type": "fix", "release": "patch"}, + {"type": "docs", "release": "patch"}, + {"type": "style", "release": "patch"}, + {"type": "refactor", "release": "patch"}, + {"type": "perf", "release": "patch"}, + {"type": "test", "release": "patch"}, + {"type": "build", "release": "patch"}, + {"type": "ci", "release": "patch"}, + {"type": "chore", "scope": "deps", "release": "patch"} + ] + }], "@semantic-release/release-notes-generator", "@semantic-release/npm", - ["@semantic-release/github", { - "assets": [] - }], - ["@semantic-release/git", { - "assets": ["package.json"], - "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" - }] + "@semantic-release/github" ] } \ No newline at end of file diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml deleted file mode 100644 index 6e0bef7e..00000000 --- a/.github/workflows/npm-publish.yml +++ /dev/null @@ -1,135 +0,0 @@ -name: Publish Packages to npm - -on: - push: - branches: - - main - paths: - - 'packages/**' - workflow_dispatch: - inputs: - package: - description: 'Package to publish (leave empty to detect changes)' - required: false - type: string - force_publish: - description: 'Force publish even without version change' - required: false - type: boolean - default: false - -jobs: - detect-changes: - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Find changed packages - id: changed-packages - if: ${{ github.event.inputs.package == '' }} - run: | - # Get all packages - PACKAGES=$(find packages -type f -name "package.json" -not -path "*/node_modules/*" | grep -o 'packages/[^/]*' | sort | uniq) - - # Get last successful run commit - git fetch origin - LAST_COMMIT=$(git rev-parse HEAD^) - - # Initialize array for changed packages - CHANGED=() - - # Check each package - for PKG in $PACKAGES; do - if git diff --name-only $LAST_COMMIT HEAD | grep -q "^$PKG/"; then - echo "$PKG has changes" - PKG_NAME=$(basename $PKG) - CHANGED+=("$PKG_NAME") - fi - done - - # Convert to JSON array for matrix directly using jq - if [ ${#CHANGED[@]} -eq 0 ]; then - echo "No changes detected in any package" - echo "matrix=[]" >> $GITHUB_OUTPUT - else - JSON_ARRAY=$(printf '%s\n' "${CHANGED[@]}" | jq -R . | jq -c -s .) - echo "Changed packages: $JSON_ARRAY" - echo "matrix=$JSON_ARRAY" >> $GITHUB_OUTPUT - fi - - - name: Use manually specified package - id: manual-package - if: ${{ github.event.inputs.package != '' }} - run: | - PACKAGE="${{ github.event.inputs.package }}" - echo "Publishing package: $PACKAGE" - echo "matrix=[\"$PACKAGE\"]" >> $GITHUB_OUTPUT - - - name: Set matrix - id: set-matrix - run: | - if [[ "${{ github.event.inputs.package }}" != "" ]]; then - echo "matrix=${{ steps.manual-package.outputs.matrix }}" >> $GITHUB_OUTPUT - elif [[ "${{ steps.changed-packages.outputs.matrix }}" == "[]" || -z "${{ steps.changed-packages.outputs.matrix }}" ]]; then - echo "No changes detected in any package" - echo "matrix=[]" >> $GITHUB_OUTPUT - else - echo "matrix=${{ steps.changed-packages.outputs.matrix }}" >> $GITHUB_OUTPUT - fi - - publish: - needs: detect-changes - if: ${{ needs.detect-changes.outputs.matrix != '[]' }} - runs-on: ubuntu-latest - strategy: - matrix: - package: ${{ fromJson(needs.detect-changes.outputs.matrix) }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - registry-url: 'https://registry.npmjs.org' - cache: 'yarn' - - - name: Install dependencies - run: yarn install --frozen-lockfile - - - name: Build package - run: | - cd packages/${{ matrix.package }} - yarn build - - - name: Release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - FORCE_PUBLISH: ${{ github.event.inputs.force_publish }} - run: | - cd packages/${{ matrix.package }} - # Check if .releaserc.json exists, create if not - if [ ! -f .releaserc.json ]; then - echo "Creating .releaserc.json from template" - cp ../../.github/release-template.json .releaserc.json - fi - # Install semantic-release if not in package.json - if ! grep -q "semantic-release" package.json; then - yarn add --dev semantic-release @semantic-release/git - fi - - # Add force publish flag if specified - if [ "$FORCE_PUBLISH" == "true" ]; then - echo "Running with force publish flag" - npx semantic-release --no-ci - else - npx semantic-release \ No newline at end of file diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml new file mode 100644 index 00000000..6e8bead8 --- /dev/null +++ b/.github/workflows/pullrequest.yml @@ -0,0 +1,85 @@ +name: Pull Request + +on: + pull_request: + paths: + - 'packages/**' + - '.github/workflows/**' + +jobs: + test-and-verify: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Build + run: yarn build + + - name: Test + run: yarn test + + - name: Find changed packages + id: changed_packages + run: | + CHANGED_DIRS=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -o 'packages/[^/]*' | sort | uniq) + echo "Changed packages: $CHANGED_DIRS" + echo "dirs=$CHANGED_DIRS" >> $GITHUB_OUTPUT + + - name: Dry run semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Get the space-separated list of changed directories + CHANGED_PACKAGES="${{ steps.changed_packages.outputs.dirs }}" + + # Create a release template if it doesn't exist + if [ ! -f .github/release-template.json ]; then + echo '{"branches": ["main"], "plugins": [["@semantic-release/commit-analyzer", { "preset": "angular" }], "@semantic-release/release-notes-generator", "@semantic-release/npm", "@semantic-release/github"]}' > .github/release-template.json + fi + + # Perform dry run for each changed package + for PKG in $CHANGED_PACKAGES; do + echo "Testing release for package: $PKG" + cd $PKG + + # Check if .releaserc.json exists, create if not + if [ ! -f .releaserc.json ]; then + echo "Creating .releaserc.json from template" + cp ../../.github/release-template.json .releaserc.json + fi + + # Run semantic-release dry run to verify it will work when merged + npx semantic-release --dry-run + + cd ../../ + done + + - name: Package information summary + env: + CHANGED_PACKAGES: ${{ steps.changed_packages.outputs.dirs }} + run: | + echo "## Package Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + for PKG in $CHANGED_PACKAGES; do + echo "### ${PKG}" >> $GITHUB_STEP_SUMMARY + cd $PKG + CURRENT_VERSION=$(node -p "require('./package.json').version") + echo "- Current version: ${CURRENT_VERSION}" >> $GITHUB_STEP_SUMMARY + cd ../../ + done + + echo "" >> $GITHUB_STEP_SUMMARY + echo "This PR is safe to merge. Once merged to main, packages will be published automatically." >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..ba8ceb98 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,98 @@ +name: Release + +on: + push: + branches: + - main + paths: + - 'packages/**' + +jobs: + release: + permissions: + contents: write + packages: write + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Build packages + run: yarn build + + - name: Test packages + run: yarn test + + - name: Detect changed packages + id: changed_packages + run: | + CHANGED_PACKAGES=$(find packages -type f -name "package.json" -not -path "*/node_modules/*" | grep -o 'packages/[^/]*' | sort | uniq) + echo "packages=$CHANGED_PACKAGES" >> $GITHUB_OUTPUT + + # Format for summary + echo "## Packages to be released" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + for PKG in $CHANGED_PACKAGES; do + echo "- $PKG" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + + - name: Release with semantic-release + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CHANGED_PACKAGES: ${{ steps.changed_packages.outputs.packages }} + run: | + # Release each changed package + RELEASE_SUMMARY="" + for PKG in $CHANGED_PACKAGES; do + echo "Publishing package: $PKG" + cd $PKG + + # Get current version + CURRENT_VERSION=$(node -p "require('./package.json').version") + + # Check if .releaserc.json exists, create if not + if [ ! -f .releaserc.json ]; then + echo "Creating .releaserc.json from template" + cp ../../.github/release-template.json .releaserc.json + fi + + # Run semantic-release for the package + RELEASE_OUTPUT=$(npx semantic-release 2>&1 || true) + echo "$RELEASE_OUTPUT" + + # Extract new version if available + if echo "$RELEASE_OUTPUT" | grep -q "Publishing version"; then + NEW_VERSION=$(echo "$RELEASE_OUTPUT" | grep "Publishing version" | sed -E 's/.*Publishing version ([0-9]+\.[0-9]+\.[0-9]+).*/\1/') + echo "Successfully published $PKG@$NEW_VERSION (was $CURRENT_VERSION)" + RELEASE_SUMMARY="$RELEASE_SUMMARY\n### $PKG\n- Published: v$NEW_VERSION (was v$CURRENT_VERSION)\n" + else + echo "No new version to release for $PKG" + RELEASE_SUMMARY="$RELEASE_SUMMARY\n### $PKG\n- No release needed (current: v$CURRENT_VERSION)\n" + fi + + cd ../../ + done + + # Output summary + echo -e "$RELEASE_SUMMARY" >> $GITHUB_STEP_SUMMARY + + - name: Output release status + run: | + echo "✅ Release process completed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Packages have been published to npm." >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/test-publish.yml b/.github/workflows/test-publish.yml deleted file mode 100644 index 4e5580a8..00000000 --- a/.github/workflows/test-publish.yml +++ /dev/null @@ -1,135 +0,0 @@ -name: Test Package Publishing - -on: - pull_request: - paths: - - 'packages/**' - - '.github/workflows/test-publish.yml' - workflow_dispatch: - inputs: - package: - description: 'Package to test (leave empty to detect changes)' - required: false - type: string - -jobs: - detect-changes: - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Find changed packages - id: changed-packages - if: ${{ github.event.inputs.package == '' }} - run: | - # Get all packages - PACKAGES=$(find packages -type f -name "package.json" -not -path "*/node_modules/*" | grep -o 'packages/[^/]*' | sort | uniq) - - # For pull requests, compare with base branch - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - BASE_SHA="${{ github.event.pull_request.base.sha }}" - else - # For manual runs, compare with parent commit - git fetch origin - BASE_SHA=$(git rev-parse HEAD^) - fi - - # Initialize array for changed packages - CHANGED=() - - # Check each package - for PKG in $PACKAGES; do - if git diff --name-only $BASE_SHA HEAD | grep -q "^$PKG/"; then - echo "$PKG has changes" - PKG_NAME=$(basename $PKG) - CHANGED+=("$PKG_NAME") - fi - done - - # Convert to JSON array for matrix directly using jq - if [ ${#CHANGED[@]} -eq 0 ]; then - echo "No changes detected in any package" - echo "matrix=[]" >> $GITHUB_OUTPUT - else - JSON_ARRAY=$(printf '%s\n' "${CHANGED[@]}" | jq -R . | jq -c -s .) - echo "Changed packages: $JSON_ARRAY" - echo "matrix=$JSON_ARRAY" >> $GITHUB_OUTPUT - fi - - - name: Use manually specified package - id: manual-package - if: ${{ github.event.inputs.package != '' }} - run: | - PACKAGE="${{ github.event.inputs.package }}" - echo "Testing package: $PACKAGE" - echo "matrix=[\"$PACKAGE\"]" >> $GITHUB_OUTPUT - - - name: Set matrix - id: set-matrix - run: | - if [[ "${{ github.event.inputs.package }}" != "" ]]; then - echo "matrix=${{ steps.manual-package.outputs.matrix }}" >> $GITHUB_OUTPUT - elif [[ "${{ steps.changed-packages.outputs.matrix }}" == "[]" || -z "${{ steps.changed-packages.outputs.matrix }}" ]]; then - echo "No changes detected in any package" - echo "matrix=[]" >> $GITHUB_OUTPUT - else - echo "matrix=${{ steps.changed-packages.outputs.matrix }}" >> $GITHUB_OUTPUT - fi - - test-publish: - needs: detect-changes - if: ${{ needs.detect-changes.outputs.matrix != '[]' }} - runs-on: ubuntu-latest - strategy: - matrix: - package: ${{ fromJson(needs.detect-changes.outputs.matrix) }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - registry-url: 'https://registry.npmjs.org' - cache: 'yarn' - - - name: Install dependencies - run: yarn install --frozen-lockfile - - - name: Build package - run: | - cd packages/${{ matrix.package }} - yarn build - - - name: Dry run semantic-release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd packages/${{ matrix.package }} - # Check if .releaserc.json exists, create if not - if [ ! -f .releaserc.json ]; then - echo "Creating .releaserc.json from template" - cp ../../.github/release-template.json .releaserc.json - fi - # Install semantic-release if not in package.json - if ! grep -q "semantic-release" package.json; then - yarn add --dev semantic-release @semantic-release/git - fi - # Run in dry-run mode to test release process without publishing - npx semantic-release --dry-run - - - name: Pack npm package - run: | - cd packages/${{ matrix.package }} - npm pack - echo "Package would be published with command: npm publish" - echo "Created tarball:" - ls *.tgz \ No newline at end of file diff --git a/README.md b/README.md index 40a851f7..c6a7de81 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,16 @@ This project is in pre-alpha and actively evolving. Current focus areas: 📚 Coming Soon! Comprehensive documentation and guides are under development. +## Release Process + +This repository uses an automated release workflow following semantic versioning: + +1. **Pull Request Testing** - When you create a PR, it automatically runs tests and a semantic-release dry run +2. **Automated Publishing** - When merged to main, changed packages are automatically published to npm +3. **Versioning** - Package versions are determined by [Conventional Commits](https://www.conventionalcommits.org/) standards + +For detailed information about our release process, see [.github/RELEASE_PROCESS.md](.github/RELEASE_PROCESS.md). + ## Contributing We welcome contributions! Our vision is to create a collaborative ecosystem where AI and human developers work together. Soon, we'll have an AI agent to audit and govern contributions based on our shared vision.