From f13462be9d26bf2b6a690db3294f188d92b3d7e9 Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 15:52:31 -0700 Subject: [PATCH 01/16] Create ChangelogManager, use in workflows, update docs --- .github/changelog_manager.rb | 196 +++++++++ .github/spec/changelog_manager_spec.rb | 413 ++++++++++++++++++ .github/spec/fixtures/CHANGELOG_sample.md | 15 + .github/spec/fixtures/v20111101_package.json | 33 ++ .github/spec/fixtures/v20250224_package.json | 32 ++ .github/workflows/generate.yml | 24 +- .../workflows/generate_publish_release.yml | 59 +-- CHANGELOG.md | 6 +- docs/Adding-a-New-API-Version.md | 297 +++++++++---- docs/Changelog-Manager.md | 82 ++++ docs/Workflow-and-Configuration-Reference.md | 36 +- 11 files changed, 1024 insertions(+), 169 deletions(-) create mode 100644 .github/changelog_manager.rb create mode 100644 .github/spec/changelog_manager_spec.rb create mode 100644 .github/spec/fixtures/CHANGELOG_sample.md create mode 100644 .github/spec/fixtures/v20111101_package.json create mode 100644 .github/spec/fixtures/v20250224_package.json create mode 100644 docs/Changelog-Manager.md diff --git a/.github/changelog_manager.rb b/.github/changelog_manager.rb new file mode 100644 index 0000000..0268206 --- /dev/null +++ b/.github/changelog_manager.rb @@ -0,0 +1,196 @@ +require 'json' +require 'date' + +class ChangelogManager + CHANGELOG_PATH = 'CHANGELOG.md'.freeze + BASE_PATH = '.'.freeze + TODAY = Date.today.freeze + + # API versions in priority order (newest first) + # This ensures most recent API version entries appear before older API versions in the log + API_VERSION_ORDER = ['v20250224', 'v20111101'].freeze + + class << self + # CLI entry point: validates arguments and updates changelog + # Called from GitHub Actions workflows + # + # @param versions_arg [String, nil] Versions from ARGV[0] + # @return [true] Returns true on success + # @raise SystemExit If validation fails + def run(versions_arg) + # Validate versions argument + validate_versions(versions_arg) + update(versions_arg) + puts "✅ CHANGELOG updated successfully" + end + + # Public interface: update CHANGELOG with new version entries + # + # @param versions [String, Array] Version(s) to update. String format: "v20250224,v20111101" + # @return [true] Returns true on success + # @raise [StandardError] If versions not found or changelog not readable + def update(versions) + versions_array = normalize_versions(versions) + + # Check changelog exists first before doing any processing + unless File.exist?(CHANGELOG_PATH) + raise "Changelog not found at #{CHANGELOG_PATH}" + end + + # Read version numbers from each version's package.json + version_data = versions_array.map do |api_version| + version_number = read_package_version(api_version) + raise "Could not read version from #{api_version}/package.json" if version_number.nil? || version_number.empty? + [api_version, version_number] + end + + # Sort by API_VERSION_ORDER to ensure consistent ordering + sorted_data = sort_versions(version_data) + + # Read existing changelog + current_changelog = File.read(CHANGELOG_PATH) + + # Build changelog entries for each version + entries = sorted_data.map { |api_version, version_num| build_entry(api_version, version_num, TODAY) } + + # Insert entries at the top of changelog (after header) + updated_changelog = insert_entries(current_changelog, entries) + + # Write back to file + File.write(CHANGELOG_PATH, updated_changelog) + + true + end + + private + + def validate_versions(versions_arg) + if versions_arg.nil? || versions_arg.empty? + puts "Usage: ruby changelog_manager.rb " + puts "Example: ruby changelog_manager.rb 'v20250224,v20111101'" + puts "Supported versions: #{API_VERSION_ORDER.join(', ')}" + exit 1 + end + + if has_invalid_versions?(versions_arg) + puts "❌ Error: Invalid versions. Supported versions: #{API_VERSION_ORDER.join(', ')}" + exit 1 + end + end + + def has_invalid_versions?(versions_arg) + versions_array = versions_arg.split(',').map(&:strip) + invalid_versions = versions_array - API_VERSION_ORDER + invalid_versions.any? + end + + # Normalize versions parameter to array + # @param versions [String, Array] + # @return [Array] Array of version strings + def normalize_versions(versions) + case versions + when String + versions.split(',').map(&:strip) + when Array + versions.map(&:to_s) + else + raise "Versions must be String or Array, got #{versions.class}" + end + end + + # Read version number from a specific API version's package.json + # @param api_version [String] e.g., "v20250224" + # @return [String] Version number from package.json + def read_package_version(api_version) + package_json_path = File.join(BASE_PATH, api_version, 'package.json') + + unless File.exist?(package_json_path) + raise "Package file not found at #{package_json_path}" + end + + package_json = JSON.parse(File.read(package_json_path)) + package_json['version'] + end + + # Sort versions by API_VERSION_ORDER + # @param version_data [Array] Array of [api_version, version_number] pairs + # @return [Array] Sorted array of [api_version, version_number] pairs + def sort_versions(version_data) + version_data.sort_by do |api_version, _| + order_index = API_VERSION_ORDER.index(api_version) + order_index || Float::INFINITY + end + end + + # Build a single changelog entry + # @param api_version [String] e.g., "v20250224" + # @param version_number [String] e.g., "3.2.0" + # @param date [Date] Entry date + # @return [String] Formatted changelog entry + def build_entry(api_version, version_number, date) + date_str = date.strftime('%Y-%m-%d') + last_change_date = extract_last_change_date(api_version) + + # Format the message with last change date if found + if last_change_date + last_change_str = last_change_date.strftime('%Y-%m-%d') + message = "Updated #{api_version} API specification to most current version. Please check full [API changelog](https://docs.mx.com/resources/changelog/platform) for any changes made between #{last_change_str} and #{date_str}." + else + message = "Updated #{api_version} API specification to most current version. Please check full [API changelog](https://docs.mx.com/resources/changelog/platform) for any changes." + end + + <<~ENTRY + ## [#{version_number}] - #{date_str} (#{api_version} API) + #{message} + ENTRY + end + + # Extract the date of the last change for a given API version from the changelog + # Finds the first entry in the changelog that mentions the api_version + # @param api_version [String] e.g., "v20250224" + # @return [Date, nil] Date of last change or nil if not found + def extract_last_change_date(api_version) + return nil unless File.exist?(CHANGELOG_PATH) + + File.readlines(CHANGELOG_PATH).each do |line| + # Look for lines like: ## [2.0.0] - 2025-01-15 (v20111101 API) + if line.match?(/## \[\d+\.\d+\.\d+\]\s*-\s*(\d{4}-\d{2}-\d{2})\s*\(#{Regexp.escape(api_version)}\s+API\)/) + # Extract the date from the line + match = line.match(/(\d{4}-\d{2}-\d{2})/) + return Date.parse(match[1]) if match + end + end + + nil + end + + # Insert entries into changelog after the header section + # Finds the first ## entry and inserts new entries before it + # + # @param changelog [String] Current changelog content + # @param entries [Array] Entries to insert + # @return [String] Updated changelog + def insert_entries(changelog, entries) + lines = changelog.split("\n") + + # Find the line number of the first version entry (first line starting with ##) + first_entry_index = lines.find_index { |line| line.start_with?('## [') } + + if first_entry_index.nil? + raise "Could not find existing changelog entries. Expected format: ## [version]" + end + + # Extract header (everything before first entry) + header = lines[0...first_entry_index] + + # Get the rest (from first entry onwards) + rest = lines[first_entry_index..] + + # Combine: header + new entries + rest + (header + entries.map { |e| e.rstrip } + [''] + rest).join("\n") + end + end +end + +# CLI Interface - allows usage from GitHub Actions +ChangelogManager.run(ARGV[0]) if __FILE__ == $0 diff --git a/.github/spec/changelog_manager_spec.rb b/.github/spec/changelog_manager_spec.rb new file mode 100644 index 0000000..007de00 --- /dev/null +++ b/.github/spec/changelog_manager_spec.rb @@ -0,0 +1,413 @@ +require 'rspec' +require 'json' +require 'date' +require 'fileutils' + +# Load the class to test +require_relative '../changelog_manager' + +describe ChangelogManager do + let(:spec_dir) { File.expand_path('..', __FILE__) } + let(:fixtures_dir) { File.join(spec_dir, 'fixtures') } + let(:temp_dir) { File.join(spec_dir, 'tmp') } + + before(:each) do + # Create temp directory for test files + FileUtils.mkdir_p(temp_dir) + + # Stub constants to use temp directory for tests + stub_const('ChangelogManager::CHANGELOG_PATH', File.join(temp_dir, 'CHANGELOG.md')) + stub_const('ChangelogManager::BASE_PATH', temp_dir) + stub_const('ChangelogManager::TODAY', Date.new(2025, 01, 28)) + end + + after(:each) do + # Clean up temp directory + FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir) + end + + describe '.update with single version' do + it 'updates changelog with single version entry' do + # Setup + changelog_path = File.join(temp_dir, 'CHANGELOG.md') + package_dir = File.join(temp_dir, 'v20250224') + + FileUtils.mkdir_p(package_dir) + FileUtils.cp( + File.join(fixtures_dir, 'CHANGELOG_sample.md'), + changelog_path + ) + FileUtils.cp( + File.join(fixtures_dir, 'v20250224_package.json'), + File.join(package_dir, 'package.json') + ) + + # Execute + result = ChangelogManager.update('v20250224') + + # Verify + expect(result).to be true + + updated_content = File.read(changelog_path) + expect(updated_content).to include('## [3.0.0] - 2025-01-28 (v20250224 API)') + expect(updated_content).to include('Updated v20250224 API specification to most current version') + expect(updated_content).to include('[API changelog]') + + # Ensure it appears before existing entries + v20250224_pos = updated_content.index('[3.0.0]') + v20111101_pos = updated_content.index('[2.0.0]') + expect(v20250224_pos).to be < v20111101_pos + end + end + + describe '.update with multiple versions' do + it 'updates changelog with entries from multiple versions' do + # Setup + changelog_path = File.join(temp_dir, 'CHANGELOG.md') + v20250224_dir = File.join(temp_dir, 'v20250224') + v20111101_dir = File.join(temp_dir, 'v20111101') + + FileUtils.mkdir_p(v20250224_dir) + FileUtils.mkdir_p(v20111101_dir) + + FileUtils.cp( + File.join(fixtures_dir, 'CHANGELOG_sample.md'), + changelog_path + ) + FileUtils.cp( + File.join(fixtures_dir, 'v20250224_package.json'), + File.join(v20250224_dir, 'package.json') + ) + FileUtils.cp( + File.join(fixtures_dir, 'v20111101_package.json'), + File.join(v20111101_dir, 'package.json') + ) + + # Execute + result = ChangelogManager.update('v20250224,v20111101') + + # Verify + expect(result).to be true + + updated_content = File.read(changelog_path) + + # Both versions should be present + expect(updated_content).to include('## [3.0.0] - 2025-01-28 (v20250224 API)') + expect(updated_content).to include('## [2.0.0] - 2025-01-28 (v20111101 API)') + expect(updated_content).to include('Updated v20250224 API specification to most current version') + expect(updated_content).to include('Updated v20111101 API specification to most current version') + + # v20250224 should come BEFORE v20111101 (sorting) + v20250224_pos = updated_content.index('[3.0.0]') + v20111101_pos = updated_content.index('[2.0.0]') + expect(v20250224_pos).to be < v20111101_pos + end + end + + describe '.update with array versions' do + it 'accepts versions as an array' do + # Setup + changelog_path = File.join(temp_dir, 'CHANGELOG.md') + package_dir = File.join(temp_dir, 'v20250224') + + FileUtils.mkdir_p(package_dir) + FileUtils.cp( + File.join(fixtures_dir, 'CHANGELOG_sample.md'), + changelog_path + ) + FileUtils.cp( + File.join(fixtures_dir, 'v20250224_package.json'), + File.join(package_dir, 'package.json') + ) + + # Execute with array instead of string + result = ChangelogManager.update(['v20250224']) + + # Verify + expect(result).to be true + updated_content = File.read(changelog_path) + expect(updated_content).to include('## [3.0.0] - 2025-01-28 (v20250224 API)') + end + end + + describe '.update sorting behavior' do + it 'always places v20250224 before v20111101' do + # Setup + changelog_path = File.join(temp_dir, 'CHANGELOG.md') + v20250224_dir = File.join(temp_dir, 'v20250224') + v20111101_dir = File.join(temp_dir, 'v20111101') + + FileUtils.mkdir_p(v20250224_dir) + FileUtils.mkdir_p(v20111101_dir) + + FileUtils.cp( + File.join(fixtures_dir, 'CHANGELOG_sample.md'), + changelog_path + ) + FileUtils.cp( + File.join(fixtures_dir, 'v20250224_package.json'), + File.join(v20250224_dir, 'package.json') + ) + FileUtils.cp( + File.join(fixtures_dir, 'v20111101_package.json'), + File.join(v20111101_dir, 'package.json') + ) + + # Execute with reversed input order to verify sorting + result = ChangelogManager.update('v20111101,v20250224') + + # Verify + expect(result).to be true + updated_content = File.read(changelog_path) + + # Despite input order, v20250224 should come first + v20250224_pos = updated_content.index('[3.0.0]') + v20111101_pos = updated_content.index('[2.0.0]') + expect(v20250224_pos).to be < v20111101_pos + end + end + + describe '.update with date range behavior' do + it 'includes date range when prior entry exists for API version' do + # Setup + changelog_path = File.join(temp_dir, 'CHANGELOG.md') + package_dir = File.join(temp_dir, 'v20111101') + + FileUtils.mkdir_p(package_dir) + FileUtils.cp( + File.join(fixtures_dir, 'CHANGELOG_sample.md'), + changelog_path + ) + FileUtils.cp( + File.join(fixtures_dir, 'v20111101_package.json'), + File.join(package_dir, 'package.json') + ) + + # Execute - updating v20111101 which has an entry dated 2025-01-15 in the fixture + result = ChangelogManager.update('v20111101') + + # Verify + expect(result).to be true + updated_content = File.read(changelog_path) + + # Should include the date range message + expect(updated_content).to include('between 2025-01-15 and 2025-01-28') + end + + it 'shows no prior date when entry has no previous version' do + # Setup + changelog_path = File.join(temp_dir, 'CHANGELOG.md') + package_dir = File.join(temp_dir, 'v20250224') + + FileUtils.mkdir_p(package_dir) + FileUtils.cp( + File.join(fixtures_dir, 'CHANGELOG_sample.md'), + changelog_path + ) + FileUtils.cp( + File.join(fixtures_dir, 'v20250224_package.json'), + File.join(package_dir, 'package.json') + ) + + # Execute - v20250224 has no prior entry in the fixture + result = ChangelogManager.update('v20250224') + + # Verify + expect(result).to be true + updated_content = File.read(changelog_path) + + # Should include fallback message without date range + expect(updated_content).to include('Updated v20250224 API specification to most current version. Please check full [API changelog]') + # Should NOT have a "between" clause + expect(updated_content).not_to match(/between \d{4}-\d{2}-\d{2} and \d{4}-\d{2}-\d{2}.*v20250224/) + end + + it 'uses correct dates in range for multiple version updates' do + # Setup + changelog_path = File.join(temp_dir, 'CHANGELOG.md') + v20250224_dir = File.join(temp_dir, 'v20250224') + v20111101_dir = File.join(temp_dir, 'v20111101') + + FileUtils.mkdir_p(v20250224_dir) + FileUtils.mkdir_p(v20111101_dir) + + FileUtils.cp( + File.join(fixtures_dir, 'CHANGELOG_sample.md'), + changelog_path + ) + FileUtils.cp( + File.join(fixtures_dir, 'v20250224_package.json'), + File.join(v20250224_dir, 'package.json') + ) + FileUtils.cp( + File.join(fixtures_dir, 'v20111101_package.json'), + File.join(v20111101_dir, 'package.json') + ) + + # Execute + result = ChangelogManager.update('v20250224,v20111101') + + # Verify + expect(result).to be true + updated_content = File.read(changelog_path) + + # v20111101 should have date range (prior entry on 2025-01-15) + expect(updated_content).to include('between 2025-01-15 and 2025-01-28') + + # v20250224 should NOT have date range (no prior entry) + v20250224_section = updated_content[/## \[3\.0\.0\].*?(?=##|\z)/m] + expect(v20250224_section).not_to match(/between.*v20250224/) + end + end + + describe '.update error handling' do + it 'raises error when changelog not found' do + # Execute with non-existent changelog + expect { + ChangelogManager.update('v20250224') + }.to raise_error(/Changelog not found/) + end + + it 'raises error when package.json not found' do + # Setup + changelog_path = File.join(temp_dir, 'CHANGELOG.md') + FileUtils.cp( + File.join(fixtures_dir, 'CHANGELOG_sample.md'), + changelog_path + ) + + # Execute without creating version directory + expect { + ChangelogManager.update('v20250224') + }.to raise_error(/Package file not found/) + end + + it 'raises error when package.json is malformed' do + # Setup + changelog_path = File.join(temp_dir, 'CHANGELOG.md') + package_dir = File.join(temp_dir, 'v20250224') + + FileUtils.mkdir_p(package_dir) + FileUtils.cp( + File.join(fixtures_dir, 'CHANGELOG_sample.md'), + changelog_path + ) + File.write( + File.join(package_dir, 'package.json'), + 'invalid json {]' + ) + + # Execute + expect { + ChangelogManager.update('v20250224') + }.to raise_error(JSON::ParserError) + end + + it 'raises error when version is not in package.json' do + # Setup + changelog_path = File.join(temp_dir, 'CHANGELOG.md') + package_dir = File.join(temp_dir, 'v20250224') + + FileUtils.mkdir_p(package_dir) + FileUtils.cp( + File.join(fixtures_dir, 'CHANGELOG_sample.md'), + changelog_path + ) + File.write( + File.join(package_dir, 'package.json'), + JSON.generate({ name: '@mx-platform/node' }) # Missing version + ) + + # Execute + expect { + ChangelogManager.update('v20250224') + }.to raise_error(/Could not read version/) + end + + it 'raises error when changelog has no existing entries' do + # Setup + changelog_path = File.join(temp_dir, 'CHANGELOG.md') + package_dir = File.join(temp_dir, 'v20250224') + + FileUtils.mkdir_p(package_dir) + File.write( + changelog_path, + "# Changelog\n\nNo entries here" + ) + FileUtils.cp( + File.join(fixtures_dir, 'v20250224_package.json'), + File.join(package_dir, 'package.json') + ) + + # Execute + expect { + ChangelogManager.update('v20250224') + }.to raise_error(/Could not find existing changelog entries/) + end + end + + + + describe '.run (CLI entry point)' do + it 'validates and updates successfully with valid versions' do + # Setup + changelog_path = File.join(temp_dir, 'CHANGELOG.md') + package_dir = File.join(temp_dir, 'v20250224') + + FileUtils.mkdir_p(package_dir) + FileUtils.cp( + File.join(fixtures_dir, 'CHANGELOG_sample.md'), + changelog_path + ) + FileUtils.cp( + File.join(fixtures_dir, 'v20250224_package.json'), + File.join(package_dir, 'package.json') + ) + + # Execute + expect { + ChangelogManager.run('v20250224') + }.to output(/✅ CHANGELOG updated successfully/).to_stdout + + # Verify changelog was updated + updated_content = File.read(changelog_path) + expect(updated_content).to include('## [3.0.0] - 2025-01-28 (v20250224 API)') + end + + it 'exits with error when versions argument is nil' do + expect { + ChangelogManager.run(nil) + }.to output(/Usage: ruby changelog_manager.rb/).to_stdout.and raise_error(SystemExit) + end + + it 'exits with error when versions argument is empty string' do + expect { + ChangelogManager.run('') + }.to output(/Usage: ruby changelog_manager.rb/).to_stdout.and raise_error(SystemExit) + end + + it 'exits with error when version is invalid' do + expect { + ChangelogManager.run('v99999999') + }.to output(/❌ Error: Invalid versions/).to_stdout.and raise_error(SystemExit) + end + + it 'exits with error when any version in list is invalid' do + expect { + ChangelogManager.run('v20250224,v99999999') + }.to output(/❌ Error: Invalid versions/).to_stdout.and raise_error(SystemExit) + end + + it 'outputs supported versions when argument is missing' do + expect { + ChangelogManager.run(nil) + }.to output(/Supported versions: v20250224, v20111101/).to_stdout.and raise_error(SystemExit) + end + + it 'outputs supported versions when version is invalid' do + expect { + ChangelogManager.run('v99999999') + }.to output(/Supported versions: v20250224, v20111101/).to_stdout.and raise_error(SystemExit) + end + end +end diff --git a/.github/spec/fixtures/CHANGELOG_sample.md b/.github/spec/fixtures/CHANGELOG_sample.md new file mode 100644 index 0000000..38841ce --- /dev/null +++ b/.github/spec/fixtures/CHANGELOG_sample.md @@ -0,0 +1,15 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.0.0] - 2025-01-15 (v20111101 API) +Updated v20111101 API specification. +[See full API changelog](https://docs.mx.com/resources/changelog/platform) + +## [1.12.1] - 2025-11-25 + +### Fixed +- Updated package template to fix dependency regression diff --git a/.github/spec/fixtures/v20111101_package.json b/.github/spec/fixtures/v20111101_package.json new file mode 100644 index 0000000..d915312 --- /dev/null +++ b/.github/spec/fixtures/v20111101_package.json @@ -0,0 +1,33 @@ +{ + "name": "mx-platform-node", + "version": "2.0.0", + "description": "A Node library for the MX Platform API.", + "author": "MX", + "keywords": [ + "mx", + "mx.com", + "mx platform api", + "mx-platform-node" + ], + "license": "MIT", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "files": [ + "dist/", + "CHANGELOG.md", + "README.md", + "MIGRATION.md" + ], + "scripts": { + "build": "tsc --outDir dist/", + "prepare": "npm run build" + }, + "_comment": "IMPORTANT: Keep these dependency versions in sync with security updates. If package.json is manually updated for security fixes, this template MUST also be updated to prevent automated generation from overwriting the fixes.", + "dependencies": { + "axios": "^1.6.8" + }, + "devDependencies": { + "@types/node": "^20.12.7", + "typescript": "^5.4.5" + } +} \ No newline at end of file diff --git a/.github/spec/fixtures/v20250224_package.json b/.github/spec/fixtures/v20250224_package.json new file mode 100644 index 0000000..1666c1a --- /dev/null +++ b/.github/spec/fixtures/v20250224_package.json @@ -0,0 +1,32 @@ +{ + "name": "mx-platform-node", + "version": "3.0.0", + "description": "A Node.js SDK for the MX Platform API (v20250224)", + "apiVersion": "v20250224", + "author": "MX", + "keywords": [ + "mx", + "mx.com", + "mx platform api", + "mx-platform-node" + ], + "license": "MIT", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "files": [ + "dist/", + "*.md" + ], + "scripts": { + "build": "tsc --outDir dist/", + "prepare": "npm run build" + }, + "_comment": "IMPORTANT: Keep these dependency versions in sync with security updates. If package.json is manually updated for security fixes, this template MUST also be updated to prevent automated generation from overwriting the fixes.", + "dependencies": { + "axios": "^1.6.8" + }, + "devDependencies": { + "@types/node": "^20.12.7", + "typescript": "^5.4.5" + } +} \ No newline at end of file diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index b0448a1..64e58db 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -98,29 +98,7 @@ jobs: -t ./openapi/templates \ -o ./${{ github.event.inputs.api_version }} - name: Update CHANGELOG - run: | - VERSION=$(jq -r '.version' ./${{ github.event.inputs.api_version }}/package.json) - DATE=$(date +%Y-%m-%d) - - # Find the line number of the first version entry (first line starting with ##) - FIRST_ENTRY_LINE=$(grep -n "^##" CHANGELOG.md | head -1 | cut -d: -f1) - - # Extract header (everything before first entry) - head -n $((FIRST_ENTRY_LINE - 1)) CHANGELOG.md > /tmp/new_changelog.txt - - # Add new entry - cat >> /tmp/new_changelog.txt << EOF - - ## [$VERSION] - $DATE (${{ github.event.inputs.api_version }} API) - Updated ${{ github.event.inputs.api_version }} API specification. - [See full API changelog](https://docs.mx.com/resources/changelog/platform) - EOF - - # Add rest of file (from first entry onwards) - tail -n +$FIRST_ENTRY_LINE CHANGELOG.md >> /tmp/new_changelog.txt - - # Replace original - mv /tmp/new_changelog.txt CHANGELOG.md + run: ruby .github/changelog_manager.rb ${{ github.event.inputs.api_version }} - name: Copy documentation run: | cp LICENSE ./${{ github.event.inputs.api_version }}/LICENSE diff --git a/.github/workflows/generate_publish_release.yml b/.github/workflows/generate_publish_release.yml index 4211d57..443ed6a 100644 --- a/.github/workflows/generate_publish_release.yml +++ b/.github/workflows/generate_publish_release.yml @@ -83,11 +83,6 @@ jobs: -c ${{ matrix.config_file }} \ -t ./openapi/templates \ -o ./${{ matrix.api_version }} - - name: Copy documentation - run: | - cp LICENSE ./${{ matrix.api_version }}/LICENSE - cp CHANGELOG.md ./${{ matrix.api_version }}/CHANGELOG.md - cp MIGRATION.md ./${{ matrix.api_version }}/MIGRATION.md - name: Upload artifacts uses: actions/upload-artifact@v3 with: @@ -118,57 +113,25 @@ jobs: - name: Update CHANGELOG run: | GENERATED_VERSIONS="${{ steps.track_versions.outputs.generated_versions }}" - DATE=$(date +%Y-%m-%d) # Only update if something was generated if [ -z "$GENERATED_VERSIONS" ]; then + echo "No versions generated, skipping changelog update" exit 0 fi - # Initialize version variables as empty - V20111101_VERSION="" - V20250224_VERSION="" - - # Read versions only for versions that were actually generated - for VERSION in $GENERATED_VERSIONS; do - if [ "$VERSION" = "v20111101" ]; then - V20111101_VERSION=$(jq -r '.version' ./v20111101/package.json 2>/dev/null) - elif [ "$VERSION" = "v20250224" ]; then - V20250224_VERSION=$(jq -r '.version' ./v20250224/package.json 2>/dev/null) + # Convert space-separated versions to comma-separated for changelog_manager.rb + VERSIONS_CSV=$(echo "$GENERATED_VERSIONS" | tr ' ' ',') + ruby .github/changelog_manager.rb "$VERSIONS_CSV" + - name: Copy documentation + run: | + for dir in ./v20111101 ./v20250224; do + if [ -d "$dir" ]; then + cp LICENSE "$dir/LICENSE" + cp CHANGELOG.md "$dir/CHANGELOG.md" + cp MIGRATION.md "$dir/MIGRATION.md" fi done - - # Find the line number of the first version entry (first line starting with ##) - FIRST_ENTRY_LINE=$(grep -n "^##" CHANGELOG.md | head -1 | cut -d: -f1) - - # Extract header (everything before first entry) - head -n $((FIRST_ENTRY_LINE - 1)) CHANGELOG.md > /tmp/new_changelog.txt - - # Build and add changelog entries ONLY for versions that were actually generated - # v20250224 first (newer API version), then v20111101 - if [ ! -z "$V20250224_VERSION" ]; then - cat >> /tmp/new_changelog.txt << EOF - - ## [$V20250224_VERSION] - $DATE (v20250224 API) - Updated v20250224 API specification. - [See full API changelog](https://docs.mx.com/resources/changelog/platform) - EOF - fi - - if [ ! -z "$V20111101_VERSION" ]; then - cat >> /tmp/new_changelog.txt << EOF - - ## [$V20111101_VERSION] - $DATE (v20111101 API) - Updated v20111101 API specification. - [See full API changelog](https://docs.mx.com/resources/changelog/platform) - EOF - fi - - # Add rest of file (from first entry onwards) - tail -n +$FIRST_ENTRY_LINE CHANGELOG.md >> /tmp/new_changelog.txt - - # Replace original - mv /tmp/new_changelog.txt CHANGELOG.md - name: Checkout master run: git checkout master - name: Create commit diff --git a/CHANGELOG.md b/CHANGELOG.md index 330816d..7a0fd47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2.0.0] - 1/7/2026 +## [2.0.0] - 2026-01-07 ### Changed - **Versioning Correction:** Re-released as v2.0.0 to properly indicate breaking changes that were inadvertently introduced in v1.10.1 @@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Restructured API classes from single `MxPlatformApi` to domain-specific classes -## [1.12.1] - 11/25/2025 +## [1.12.1] - 2025-11-25 ### Fixed - Updated package template (`package.mustache`) to fix recurring dependency regression @@ -42,7 +42,7 @@ These versions (v1.10.1 through v1.12.0) contain the breaking API restructure bu **If you are on any of these versions:** Please upgrade to v2.0.0. -## [1.10.0] - 11/5/2025 +## [1.10.0] - 2025-11-05 ### Note - Last stable version with unified `MxPlatformApi` class diff --git a/docs/Adding-a-New-API-Version.md b/docs/Adding-a-New-API-Version.md index e61e7dc..4936a78 100644 --- a/docs/Adding-a-New-API-Version.md +++ b/docs/Adding-a-New-API-Version.md @@ -12,12 +12,14 @@ When the OpenAPI repository releases a new API version, adding it to mx-platform-node requires four main steps: 1. Create a configuration file for the new API version -2. Update workflow files to include the new version in the matrix -3. Coordinate with the OpenAPI repository on payload format +2. Update workflow files to include the new version in all required locations +3. Update documentation to reflect the new version 4. Verify the setup works correctly The process is designed to be self-contained and non-breaking—existing versions continue to work regardless of whether you've added new ones. +**Prerequisite**: The new API version OAS file must exist in the [openapi repository](https://github.com/mxenabled/openapi) following the existing file naming convention: `openapi/v.yml` (e.g., `openapi/v20300101.yml`). + --- ## Step 1: Create Configuration File @@ -62,120 +64,232 @@ Should output valid parsed YAML without errors. ## Step 2: Update Workflow Files -### 2.1 Update on-push-master.yml +You must update three workflow files in the `.github/workflows/` directory. Each file has multiple locations that require the new version entry. + +### 2.1 Update generate.yml + +This workflow enables manual SDK generation via GitHub Actions. + +**Location 1: Workflow dispatch options** + +In the `on.workflow_dispatch.inputs.api_version.options` section, add the new version to the dropdown list: + +```yaml +api_version: + description: "API version to generate" + required: true + type: choice + options: + - v20111101 + - v20250224 + - v20300101 # NEW +``` + +**Location 2: Semantic versioning validation** + +In the `Validate` job's validation step, add a new conditional check for your version: + +```yaml +if [ "$API_VERSION" = "v20111101" ] && [ "$MAJOR_VERSION" != "2" ]; then + echo "❌ Semantic versioning error: v20111101 must have major version 2, found $MAJOR_VERSION" + exit 1 +fi + +if [ "$API_VERSION" = "v20250224" ] && [ "$MAJOR_VERSION" != "3" ]; then + echo "❌ Semantic versioning error: v20250224 must have major version 3, found $MAJOR_VERSION" + exit 1 +fi + +if [ "$API_VERSION" = "v20300101" ] && [ "$MAJOR_VERSION" != "4" ]; then + echo "❌ Semantic versioning error: v20300101 must have major version 4, found $MAJOR_VERSION" + exit 1 +fi +``` + +This ensures the major version in your config file matches the expected value for that API version. + +### 2.2 Update generate_publish_release.yml + +This workflow is automatically triggered by the OpenAPI repository to generate and publish SDKs for all versions in parallel. + +**Location 1: Version-to-config mapping** + +In the `Setup` job's `Set up matrix` step, add an `elif` branch to map your new version to its config file: + +```yaml +if [ "$VERSION" = "v20111101" ]; then + CONFIG="openapi/config-v20111101.yml" +elif [ "$VERSION" = "v20250224" ]; then + CONFIG="openapi/config-v20250224.yml" +elif [ "$VERSION" = "v20300101" ]; then + CONFIG="openapi/config-v20300101.yml" +fi +``` + +This dynamically builds the matrix that determines which config file each version uses during generation. -Add the new version to the matrix strategy: +**Location 2: Add version to ChangelogManager priority order** -**File**: `.github/workflows/on-push-master.yml` +In `.github/changelog_manager.rb`, add your new version to the `API_VERSION_ORDER` array in the correct priority position (newest API version first): + +```ruby +API_VERSION_ORDER = ['v20300101', 'v20250224', 'v20111101'].freeze +``` + +This ensures when multiple versions are generated, changelog entries appear in order by API version (newest first), following standard changelog conventions. + +**No other changes needed for CHANGELOG updates** — the `ChangelogManager` class automatically: +- Reads version numbers from each API's `package.json` +- Validates versions are in `API_VERSION_ORDER` +- Extracts date ranges from existing entries +- Inserts properly formatted entries at the top of the changelog + +### 2.3 Update on-push-master.yml + +This workflow automatically triggers publish and release jobs when version directories are pushed to master. + +**Location 1: Path trigger** + +In the `on.push.paths` section, add a new path for your version: + +```yaml +on: + push: + branches: [master] + paths: + - 'v20111101/**' + - 'v20250224/**' + - 'v20300101/**' # NEW +``` + +This ensures the workflow triggers when changes to your version directory are pushed to master. + +**Location 2: Publish job matrix** + +In the `publish` job's strategy matrix, add your version entry: -**Find this section**: ```yaml strategy: matrix: version: - api_version: v20111101 - npm_version: 2 - api_version: v20250224 - npm_version: 3 + - api_version: v20300101 # NEW + fail-fast: false ``` -**Add new entry**: +**Location 3: Release job matrix** + +In the `release` job's strategy matrix, add your version entry (mirror the publish matrix): + ```yaml strategy: matrix: version: - api_version: v20111101 - npm_version: 2 - api_version: v20250224 - npm_version: 3 - api_version: v20300101 # NEW - npm_version: 4 + fail-fast: false ``` -### 2.2 Update Path Triggers +### 2.4 Verify Workflow Syntax -In the same file, add the new path trigger: +Check that your YAML is valid for all three modified files: -**Find this section**: -```yaml -on: - push: - branches: [master] - paths: - - 'v20111101/**' - - 'v20250224/**' +```bash +ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/generate.yml'))" +ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/generate_publish_release.yml'))" +ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/on-push-master.yml'))" ``` -**Add new path**: -```yaml -on: - push: - branches: [master] - paths: - - 'v20111101/**' - - 'v20250224/**' - - 'v20300101/**' # NEW +All commands should output valid parsed YAML without errors. + +--- + +## Step 3: Update Documentation + +Documentation files need to be updated to reflect the new API version availability. These files provide visibility to users about which versions are available and how to migrate between them. + +### 3.1 Update Root README.md + +Update the API versions table to include your new version. + +**Location: API versions table** + +In the "Which API Version Do You Need?" section, add a row for your version: + +```markdown +| API Version | npm Package | Documentation | +|---|---|---| +| **v20111101** | `mx-platform-node@^2` | [v20111101 SDK README](./v20111101/README.md) | +| **v20250224** | `mx-platform-node@^3` | [v20250224 SDK README](./v20250224/README.md) | +| **v20300101** | `mx-platform-node@^4` | [v20300101 SDK README](./v20300101/README.md) | ``` -This ensures that when changes to `v20300101/` are pushed to master, the publish and release workflows automatically trigger. +**Location: Installation section** -### 2.3 Verify Workflow Syntax +Also add an installation example for your version in the Installation section: -Check that your YAML is valid: ```bash -ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/on-push-master.yml'))" +# For v20300101 API +npm install mx-platform-node@^4 ``` ---- +### 3.2 Update MIGRATION.md -## Step 3: Coordinate with OpenAPI Repository +Add a new migration section for users upgrading from the previous API version to your new version. -The OpenAPI repository must be updated to send the new API version in the `repository_dispatch` event payload. +**New section to add** (before the existing v20111101→v20250224 migration section): -### What Needs to Change in openapi repo +```markdown +## Upgrading from v20250224 (v3.x) to v20300101 (v4.x) -When openapi repository wants to trigger generation for the new version, it should send: +The v20300101 API is now available, and v4.0.0 of this SDK provides support as an independent major version. -```json -{ - "api_versions": "v20111101,v20250224,v20300101" -} +### Installation + +The two API versions are published as separate major versions of the same npm package: + +**For v20250224 API:** +```bash +npm install mx-platform-node@^3 ``` -**Example curl command** (what openapi repo would use): +**For v20300101 API:** ```bash -curl -X POST \ - https://api.github.com/repos/mxenabled/mx-platform-node/dispatches \ - -H "Authorization: token $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - -d '{ - "event_type": "generate_sdk", - "client_payload": { - "api_versions": "v20111101,v20250224,v20300101" - } - }' +npm install mx-platform-node@^4 ``` -### Backward Compatibility +### Migration Path + +1. **Review API Changes**: Consult the [MX Platform API Migration Guide](https://docs.mx.com/api-reference/platform-api/overview/migration) for breaking changes and new features +2. **Update Package**: Update your `package.json` to use `mx-platform-node@^4` +3. **Update Imports**: Both APIs have similar structure, but review type definitions for any breaking changes +4. **Run Tests**: Validate your code works with the new SDK version +5. **Deploy**: Update production once validated + +### Benefits of TypeScript -If the OpenAPI repository doesn't send the new version in the payload: -- `generate_publish_release.yml` defaults to `v20111101` only -- Existing versions continue to work unchanged -- New version won't generate until explicitly included in the payload +Since this is a TypeScript SDK, the compiler will help catch most compatibility issues at compile time when you update to v4.x. +``` + +### 3.3 Update README.mustache Template -This is intentional—allows phased rollout without breaking existing workflows. +In `openapi/templates/README.mustache`, update the "Available API Versions" section to include your version. -### Transition Plan +**Location: Available API Versions section** -**Phase 1**: New config exists but openapi repo doesn't send the version -- System works with v20111101 and v20250224 only -- New version `v20300101/` directory doesn't get created -- No errors or issues +Add a new line for your version in the list: -**Phase 2**: OpenAPI repo updated to send new version -- Next `generate_publish_release.yml` run includes all three versions -- `v20300101/` directory created automatically -- All three versions published to npm in parallel +```markdown +## Available API Versions + +- **{{npmName}}@2.x.x** - [v20111101 API](https://docs.mx.com/api-reference/platform-api/v20111101/reference/mx-platform-api/) +- **{{npmName}}@3.x.x** - [v20250224 API](https://docs.mx.com/api-reference/platform-api/reference/mx-platform-api/) +- **{{npmName}}@4.x.x** - [v20300101 API](https://docs.mx.com/api-reference/platform-api/reference/mx-platform-api/) +``` + +**Note**: The template uses Mustache variables (`{{npmName}}`), so it will automatically populate the correct package name. This list is static and won't change based on the variables, so you must manually update it. --- @@ -245,12 +359,23 @@ After merging the PR, pushing to master with changes in `v20300101/` should auto Use this checklist to verify you've completed all steps: +- [ ] Confirmed new API version OAS file exists in openapi repository at `openapi/v20300101.yml` - [ ] Created `openapi/config-v20300101.yml` with correct syntax - [ ] Major version in config is unique and sequential (4.0.0 for v20300101) -- [ ] Updated `.github/workflows/on-push-master.yml` matrix with new version -- [ ] Updated `.github/workflows/on-push-master.yml` paths with `v20300101/**` -- [ ] Verified workflow YAML syntax is valid -- [ ] Coordinated with OpenAPI repository on payload changes +- [ ] Updated `.github/workflows/generate.yml` with new version in dropdown options +- [ ] Updated `.github/workflows/generate.yml` with semantic versioning validation +- [ ] Updated `.github/workflows/generate_publish_release.yml` with version-to-config mapping in Setup job +- [ ] Updated `.github/workflows/generate_publish_release.yml` with version variable initialization in Commit-and-Push job +- [ ] Updated `.github/workflows/generate_publish_release.yml` with version package.json reading logic in Commit-and-Push job +- [ ] Updated `.github/workflows/generate_publish_release.yml` with CHANGELOG entry generation in Commit-and-Push job +- [ ] Updated `.github/workflows/on-push-master.yml` path triggers with `v20300101/**` +- [ ] Updated `.github/workflows/on-push-master.yml` publish job matrix with new version +- [ ] Updated `.github/workflows/on-push-master.yml` release job matrix with new version +- [ ] Verified workflow YAML syntax is valid for all three modified files +- [ ] Updated root `README.md` with new API version table entry +- [ ] Updated root `README.md` with installation example for new version +- [ ] Updated `MIGRATION.md` with new migration section +- [ ] Updated `openapi/templates/README.mustache` Available API Versions section - [ ] Ran `generate.yml` manual test with new version - [ ] Verified generated `package.json` has correct version and apiVersion - [ ] Verified PR would be created with correct branch name format @@ -261,25 +386,37 @@ Use this checklist to verify you've completed all steps: ## Troubleshooting +### OAS file not found in openapi repository +**Cause**: The new API version spec file doesn't exist in the openapi repository +**Solution**: Verify the file exists at `https://github.com/mxenabled/openapi/blob/master/openapi/v20300101.yml` + ### Config file not found during generation **Cause**: Filename doesn't match API version **Solution**: Verify config file is named exactly `openapi/config-v20300101.yml` ### New version doesn't appear in generate.yml dropdown -**Cause**: Config file syntax error or not recognized -**Solution**: Verify YAML syntax with `ruby -e "require 'yaml'; puts YAML.load(File.read('openapi/config-v20300101.yml'))"` +**Cause**: Config file not added to workflow options or YAML syntax error +**Solution**: Verify the version is listed in the `on.workflow_dispatch.inputs.api_version.options` section and YAML syntax is valid + +### Semantic versioning validation fails +**Cause**: Validation check missing for new version or major version mismatch +**Solution**: Ensure the validation check for your version is added to generate.yml and the major version in your config matches the expected value ### Generated version is 2.x.x or 3.x.x instead of 4.0.0 **Cause**: Wrong major version in config file **Solution**: Update `npmVersion: 4.0.0` in config file to use unique major version +### generate_publish_release.yml doesn't recognize new version +**Cause**: Version-to-config mapping missing or CHANGELOG variable/logic not added +**Solution**: Verify all four locations in generate_publish_release.yml are updated: mapping, variable initialization, package.json reading, and CHANGELOG entry generation + ### on-push-master.yml doesn't trigger after merge -**Cause**: Path trigger syntax incorrect -**Solution**: Verify path is exactly `v20300101/**` with forward slashes +**Cause**: Path trigger syntax incorrect or matrix not updated +**Solution**: Verify path is exactly `v20300101/**` with forward slashes and both publish and release matrix entries are present ### Existing versions break after adding new version -**Cause**: Matrix syntax error or bad YAML -**Solution**: Verify on-push-master.yml YAML is valid; test existing workflows still work +**Cause**: Matrix syntax error, missing conditional, or bad YAML +**Solution**: Verify all workflow files have valid YAML syntax; test existing workflows still work --- diff --git a/docs/Changelog-Manager.md b/docs/Changelog-Manager.md new file mode 100644 index 0000000..a2c3c13 --- /dev/null +++ b/docs/Changelog-Manager.md @@ -0,0 +1,82 @@ +# Changelog Manager + +## Purpose + +Manages automatic CHANGELOG.md updates whenever API versions are generated and published. The root CHANGELOG.md is shared across the entire project, but each API version is independently updated. This class handles extracting version information from each API's package.json and inserting properly formatted entries at the top of the changelog. + +## Context + +This repo supports multiple API versions (v20250224, v20111101) that are generated from OpenAPI specs and published separately. When a new version is generated, we need to: +1. Extract the new version number from that API's package.json +2. Add an entry to the root CHANGELOG.md showing this update +3. Include a date range showing what changed since the last update to that API version + +The `ChangelogManager` class centralizes this logic rather than embedding it in build scripts. + +## Usage + +### From Command Line + +```bash +# Called from GitHub Actions workflows +ruby .github/changelog_manager.rb v20250224,v20111101 +``` + +The script uses today's date for the changelog entry and looks for existing entries to determine the date range. + +### As a Ruby Class + +```ruby +ChangelogManager.update('v20250224,v20111101') +ChangelogManager.update(['v20250224']) +``` + +## How It Works + +1. Validates that the provided versions are supported (v20250224, v20111101) +2. Reads the package.json from each version to get the version number +3. Searches the existing changelog for prior entries for each API version +4. **Sorts versions by priority** (newest first, v20250224 before v20111101) so that entries with higher version numbers appear at the top of the changelog, following standard changelog conventions. This ordering is consistent regardless of the order versions are passed in. +5. Creates changelog entries with: + - The new version number and today's date + - A reference to the API changelog + - A date range showing changes since the last update (if a prior entry exists) +6. Inserts new entries at the top of the changelog + +### Example with Multiple Versions + +When updating both versions at once: +```bash +ruby .github/changelog_manager.rb v20111101,v20250224 +``` + +The entries are inserted in version order (v20250224 first, v20111101 second), even though v20111101 was listed first. This is because v20250224 has a higher version number (3.2.0 vs 2.5.3) and should appear at the top per changelog conventions. + +Result: +```markdown +## [3.2.0] - 2025-01-28 (v20250224 API) +Updated v20250224 API specification to most current version... + +## [2.5.3] - 2025-01-28 (v20111101 API) +Updated v20111101 API specification to most current version... +``` + +### Example Output + +With a prior entry: +```markdown +## [3.2.0] - 2025-01-28 (v20250224 API) +Updated v20250224 API specification to most current version. Please check full [API changelog](https://docs.mx.com/resources/changelog/platform) for any changes made between 2025-01-15 and 2025-01-28. +``` + +Without a prior entry: +```markdown +## [3.2.0] - 2025-01-28 (v20250224 API) +Updated v20250224 API specification to most current version. Please check full [API changelog](https://docs.mx.com/resources/changelog/platform) for any changes. +``` + +## Location + +- **Class**: `.github/changelog_manager.rb` +- **Tests**: `.github/spec/changelog_manager_spec.rb` +- **Fixtures**: `.github/spec/fixtures/` diff --git a/docs/Workflow-and-Configuration-Reference.md b/docs/Workflow-and-Configuration-Reference.md index a2827e4..bb69f4d 100644 --- a/docs/Workflow-and-Configuration-Reference.md +++ b/docs/Workflow-and-Configuration-Reference.md @@ -93,24 +93,29 @@ strategy: - Used by CHANGELOG automation to add entries only for generated versions 3. **Update CHANGELOG.md** - - Find first version entry: `grep -n "^##" CHANGELOG.md` - - Insert new entries after document header, before first existing entry - - Format: `## [X.Y.Z] - YYYY-MM-DD (API vXXXXXXXX)` - - Only add entries for versions that were actually generated - - Example: + - Call `ChangelogManager` with comma-separated list of generated versions + - Command: `ruby .github/changelog_manager.rb v20111101,v20250224` + - The class automatically: + - Reads version numbers from each API's `package.json` + - Sorts versions by priority (newer API versions first) + - Extracts date range from existing entries + - Inserts new entries at top of changelog with proper formatting + - See [Changelog-Manager.md](Changelog-Manager.md) for full documentation + - Example result: ```markdown # Changelog - ## [2.0.1] - 2026-01-27 (v20111101 API) - Updated v20111101 API specification. - ## [3.0.1] - 2026-01-27 (v20250224 API) - Updated v20250224 API specification. + Updated v20250224 API specification... + + ## [2.0.1] - 2026-01-27 (v20111101 API) + Updated v20111101 API specification... ``` -4. **Copy CHANGELOG to Version Directories** - - Copy root `CHANGELOG.md` to each version directory - - Each npm package includes changelog for users +4. **Copy Documentation to Version Directories** + - After CHANGELOG update, copy root documentation to each version directory + - Files: `LICENSE`, `CHANGELOG.md`, `MIGRATION.md` → `v20111101/` and `v20250224/` + - Each npm package includes current changelog for users #### Step 4: Commit All Changes to Master @@ -210,9 +215,10 @@ Developer manually clicks "Run workflow" on `generate.yml` in GitHub Actions UI #### Step 5: Update CHANGELOG.md -- Insert new entry after document header, before first existing entry -- Format: `## [X.Y.Z] - YYYY-MM-DD (API vXXXXXXXX)` -- Only selected version's CHANGELOG entry added +- Call `ChangelogManager` with the selected API version +- Command: `ruby .github/changelog_manager.rb v20111101` +- The class reads version from `package.json`, formats entry, and inserts at top of changelog +- See [Changelog-Manager.md](Changelog-Manager.md) for full documentation #### Step 6: Create Feature Branch From c30c0cf489ea28ca9237ea09640efa7cf3669088 Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 16:25:59 -0700 Subject: [PATCH 02/16] Add gemfile and workflow to run specs --- .github/workflows/run-specs.yml | 38 +++++++++++++++++++++++++++++++++ Gemfile | 3 +++ 2 files changed, 41 insertions(+) create mode 100644 .github/workflows/run-specs.yml create mode 100644 Gemfile diff --git a/.github/workflows/run-specs.yml b/.github/workflows/run-specs.yml new file mode 100644 index 0000000..492c9e7 --- /dev/null +++ b/.github/workflows/run-specs.yml @@ -0,0 +1,38 @@ +name: Run Specs + +on: + pull_request: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + ruby-version: ['3.0', '3.1', '3.2'] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby ${{ matrix.ruby-version }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + + - name: Install RSpec + run: gem install rspec + + - name: Run Ruby spec tests + run: rspec .github/spec --format progress --color + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: rspec-results-${{ matrix.ruby-version }} + path: | + coverage/ + rspec_results.xml + if-no-files-found: ignore diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7cbe966 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' + +gem 'rspec', '~> 3.12' From 2a47da04610ea8b1f6a39518f83f77397889675b Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 17:14:50 -0700 Subject: [PATCH 03/16] Update workflows to address errors/duplicate logic --- .../workflows/generate_publish_release.yml | 29 --------- .github/workflows/on-push-master.yml | 60 ++++++++++--------- .github/workflows/validate-template-sync.yml | 2 - 3 files changed, 33 insertions(+), 58 deletions(-) diff --git a/.github/workflows/generate_publish_release.yml b/.github/workflows/generate_publish_release.yml index 443ed6a..20eda21 100644 --- a/.github/workflows/generate_publish_release.yml +++ b/.github/workflows/generate_publish_release.yml @@ -146,32 +146,3 @@ jobs: run: git push origin master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - Publish-and-Release: - runs-on: ubuntu-latest - needs: [Setup, Commit-and-Push] - strategy: - matrix: ${{ fromJson(needs.Setup.outputs.matrix) }} - steps: - - name: Publish - uses: ./.github/workflows/publish.yml - with: - version_directory: ${{ matrix.api_version }} - secrets: inherit - - name: Release - uses: ./.github/workflows/release.yml - with: - version_directory: ${{ matrix.api_version }} - secrets: inherit - - name: Slack notification - uses: ravsamhq/notify-slack-action@v2 - if: always() - with: - status: ${{ job.status }} - token: ${{ secrets.GITHUB_TOKEN }} - notification_title: "{repo}: {workflow} workflow" - message_format: "{emoji} Generated and published ${{ matrix.api_version }}" - footer: "<{workflow_url}|View Workflow>" - notify_when: "failure" - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/on-push-master.yml b/.github/workflows/on-push-master.yml index 4c8a300..aeca3cb 100644 --- a/.github/workflows/on-push-master.yml +++ b/.github/workflows/on-push-master.yml @@ -27,35 +27,41 @@ jobs: echo "✅ No skip flag - proceeding with publish/release" fi - # Matrix-based publish and release: runs one job per version, - # conditionally based on path changes - # Each matrix iteration only executes if both conditions are true: - # 1. [skip-publish] flag is NOT present in commit message - # 2. Files in this version's directory were modified in the commit - publish: + # Publish and release for each version conditionally + # Only runs if [skip-publish] flag is NOT present AND files for that version were modified + + publish-v20111101: + runs-on: ubuntu-latest needs: check-skip-publish - strategy: - matrix: - version: - - api_version: v20111101 - - api_version: v20250224 - fail-fast: false - if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, matrix.version.api_version) - uses: ./.github/workflows/publish.yml + if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20111101') + uses: ./.github/workflows/publish.yml@master + with: + version_directory: v20111101 + secrets: inherit + + release-v20111101: + runs-on: ubuntu-latest + needs: [check-skip-publish, publish-v20111101] + if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20111101') + uses: ./.github/workflows/release.yml@master with: - version_directory: ${{ matrix.version.api_version }} + version_directory: v20111101 secrets: inherit - - release: - needs: [check-skip-publish, publish] - strategy: - matrix: - version: - - api_version: v20111101 - - api_version: v20250224 - fail-fast: false - if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, matrix.version.api_version) - uses: ./.github/workflows/release.yml + + publish-v20250224: + runs-on: ubuntu-latest + needs: [check-skip-publish, release-v20111101] + if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20250224') + uses: ./.github/workflows/publish.yml@master + with: + version_directory: v20250224 + secrets: inherit + + release-v20250224: + runs-on: ubuntu-latest + needs: [check-skip-publish, publish-v20250224] + if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20250224') + uses: ./.github/workflows/release.yml@master with: - version_directory: ${{ matrix.version.api_version }} + version_directory: v20250224 secrets: inherit diff --git a/.github/workflows/validate-template-sync.yml b/.github/workflows/validate-template-sync.yml index d4e4550..d1d9da0 100644 --- a/.github/workflows/validate-template-sync.yml +++ b/.github/workflows/validate-template-sync.yml @@ -5,8 +5,6 @@ on: paths: - '**/package.json' - 'openapi/templates/package.mustache' - push: - branches: [master] workflow_dispatch: jobs: From 6706d2b84a43428d4602e8e591b22882eeaf6cf1 Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 17:42:09 -0700 Subject: [PATCH 04/16] Update on push to use serial job and gate --- .github/workflows/on-push-master.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/on-push-master.yml b/.github/workflows/on-push-master.yml index aeca3cb..572ef30 100644 --- a/.github/workflows/on-push-master.yml +++ b/.github/workflows/on-push-master.yml @@ -48,9 +48,21 @@ jobs: version_directory: v20111101 secrets: inherit - publish-v20250224: + # Gate job to handle serial ordering when v20111101 is modified + # Uses always() to run even if release-v20111101 is skipped, allowing downstream jobs to proceed + # This ensures v20250224 can run when only v20250224 is modified, while still maintaining + # serial ordering when both versions are modified + gate-v20111101-complete: runs-on: ubuntu-latest needs: [check-skip-publish, release-v20111101] + if: always() && needs.check-skip-publish.outputs.skip_publish == 'false' + steps: + - name: Gate complete - ready for v20250224 + run: echo "v20111101 release workflow complete (or skipped)" + + publish-v20250224: + runs-on: ubuntu-latest + needs: [check-skip-publish, gate-v20111101-complete] if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20250224') uses: ./.github/workflows/publish.yml@master with: From 30b218dfc7d808b33d913c24273c64705cf97381 Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 17:42:33 -0700 Subject: [PATCH 05/16] Update all docs based on workflow changes --- docs/ARCHIVED/README.md | 15 + .../SDK-Generation-Publishing-Flow.md | 419 ++++++++++++++++++ docs/Adding-a-New-API-Version.md | 8 +- docs/Multi-Version-SDK-Flow.md | 19 +- docs/Troubleshooting-Guide.md | 35 ++ docs/Workflow-and-Configuration-Reference.md | 164 ++++--- 6 files changed, 584 insertions(+), 76 deletions(-) create mode 100644 docs/ARCHIVED/README.md create mode 100644 docs/ARCHIVED/SDK-Generation-Publishing-Flow.md diff --git a/docs/ARCHIVED/README.md b/docs/ARCHIVED/README.md new file mode 100644 index 0000000..d2ed5ae --- /dev/null +++ b/docs/ARCHIVED/README.md @@ -0,0 +1,15 @@ +# Archived Documentation + +This folder contains **legacy documentation** from earlier versions of the mx-platform-node repository. + +## Contents + +### SDK-Generation-Publishing-Flow.md +⚠️ **LEGACY** - Describes the single-version SDK system before multi-version support was added (pre-January 2026). + +**Do not use this as a reference for current workflows.** For current documentation, see: +- [Multi-Version-SDK-Flow.md](../Multi-Version-SDK-Flow.md) - Current system overview +- [Workflow-and-Configuration-Reference.md](../Workflow-and-Configuration-Reference.md) - Current technical details +- [Adding-a-New-API-Version.md](../Adding-a-New-API-Version.md) - How to add new API versions + +This document is kept for **historical reference only** and may be useful for repositories that have not yet migrated to multi-version support. diff --git a/docs/ARCHIVED/SDK-Generation-Publishing-Flow.md b/docs/ARCHIVED/SDK-Generation-Publishing-Flow.md new file mode 100644 index 0000000..a932625 --- /dev/null +++ b/docs/ARCHIVED/SDK-Generation-Publishing-Flow.md @@ -0,0 +1,419 @@ +# SDK Generation, Publishing, and Release Flow (LEGACY - Single-Version) + +> ⚠️ **ARCHIVED DOCUMENTATION** - This document describes the **single-version SDK system** that was in use before multi-version support was added. +> +> **For current documentation**, see: +> - [Multi-Version-SDK-Flow.md](Multi-Version-SDK-Flow.md) - Current system overview +> - [Workflow-and-Configuration-Reference.md](Workflow-and-Configuration-Reference.md) - Current technical details +> +> This document is kept for **historical reference** and may be useful for repositories that have not yet migrated to multi-version support. + +**Document Purpose**: This document explains how the Node.js SDK was automatically generated, published to npm, and released in the single-version system. It covers both automatic triggers (from the OpenAPI repository) and manual generation flows (for development and testing). + +**Last Updated**: January 20, 2026 (ARCHIVED) +**Repository**: mx-platform-node +**Author**: DevExperience Team +**Status**: Legacy - Superseded by multi-version flows + +--- + +## Overview + +The mx-platform-node repository has a fully automated pipeline for generating TypeScript SDKs from OpenAPI specifications, publishing them to npm, and creating releases on GitHub. The process is triggered in two ways: + +1. **Automatic Flow** - When OpenAPI specifications change in the upstream `openapi` repository +2. **Manual Flow** - When developers manually trigger generation for development, testing, or version bumps + +Both flows use the same underlying generation logic but differ in how they handle commits, publishing, and release creation. + +--- + +## Flow 1: Automatic Generation (Repository Dispatch) + +### Trigger +OpenAPI specifications in the upstream `openapi` repository change → Repository sends `repository_dispatch` event to `mx-platform-node` → `generate_publish_release.yml` workflow is triggered + +### Current Implementation + +**Workflow**: `.github/workflows/generate_publish_release.yml` + +This is the **production flow** that automatically generates, publishes, and releases SDKs when upstream APIs change. + +#### Step 1: Generate SDK from OpenAPI Spec +- **Input**: OpenAPI specification from `https://raw.githubusercontent.com/mxenabled/openapi/master/openapi/mx_platform_api.yml` +- **Output Directory**: `./latest/` +- **Configuration**: Uses `./openapi/config.yml` to control generation settings + - Package name: `mx-platform-node` + - **Package version source**: `npmVersion` field in `./openapi/config.yml` (the source of truth) + - **Version bump**: `client_payload.version` from repository dispatch (e.g., "patch", "minor") tells `version.rb` which component to increment + - Flow: `client_payload.version` (bump instruction) → `version.rb` reads config → updates `npmVersion` in config → `package.mustache` uses updated `npmVersion` to create `package.json` +- **Templates**: Uses `./openapi/templates/` (package.mustache, README.mustache, etc.) +- **Process**: + 1. Clean repository using `clean.rb` (removes old generated files) + 2. Bump version: `version.rb` reads current `npmVersion` from config, increments based on `client_payload.version`, writes updated version back to config + 3. Run OpenAPI Generator to create TypeScript-Axios SDK (uses updated config with new `npmVersion`) + 4. Copy documentation files (LICENSE, CHANGELOG.md, MIGRATION.md) to generated directory + +#### Step 2: Commit Changes to Master +- **Git Config**: Uses `devexperience` bot account +- **Commit Message**: `"Generated version X.Y.Z - This commit was automatically created by a GitHub Action..."` +- **Target Branch**: Directly commits to `master` (no PR created) + +#### Step 3: Publish to npm +- **Trigger**: After successful commit, dispatches `publish_sdk` repository_dispatch event +- **Workflow**: `.github/workflows/publish.yml` (triggered via repository_dispatch) +- **Process**: + 1. Navigate to `./latest/` directory + 2. Install dependencies: `npm install` + 3. Publish to npm registry with `--tag next` + 4. Uses NPM_AUTH_TOKEN secret for authentication + +#### Step 4: Create GitHub Release +- **Trigger**: After successful publish, dispatches `release_sdk` repository_dispatch event +- **Workflow**: `.github/workflows/release.yml` (triggered via repository_dispatch) +- **Process**: + 1. Read version from `./latest/package.json` + 2. Create GitHub release tagged with version (e.g., `v2.0.5`) + 3. Slack notification on failure + +--- + +## Flow 2: Manual Generation (Workflow Dispatch) + +### Trigger +Developer manually clicks "Run workflow" on `generate.yml` in GitHub Actions UI + +### User Inputs +The workflow accepts two parameters: +- **version_level**: Which version component to bump (major, minor, or patch) +- Default: patch + +### Current Implementation + +**Workflow**: `.github/workflows/generate.yml` + +This flow is used for: +- Development and testing +- Creating PRs with proposed SDK updates +- Manual version bumping before merging + +#### Step 1: Version Bumping +- **Script**: `ruby .github/version.rb ` +- **Input File**: `./openapi/config.yml` +- **Process**: + 1. Read current version from config file + 2. Increment major/minor/patch based on input + 3. Write updated version back to config file + 4. Output new version for next steps +- **Example**: + - Current: 2.0.5 → Run with "minor" → New: 2.1.0 + +#### Step 2: Clean Repository +- **Script**: `ruby .github/clean.rb` +- **Process**: + 1. Delete all generated files from previous generation + 2. Protect certain directories from deletion (`.git`, `.github`, `openapi`, `LICENSE`, etc.) + 3. Keeps source configuration and workflow files intact + +#### Step 3: Generate SDK +- **Input**: OpenAPI specification from `https://raw.githubusercontent.com/mxenabled/openapi/master/openapi/mx_platform_api.yml` +- **Output Directory**: `./latest/` +- **Configuration**: Uses `./openapi/config.yml` +- **Process**: + 1. Install OpenAPI Generator CLI globally + 2. Run generator with TypeScript-Axios language + 3. Copy documentation files to output directory + +#### Step 4: Create Feature Branch +- **Branch Name**: `openapi-generator-X.Y.Z` (version from step 1) +- **Process**: + 1. Create new branch from current checkout + 2. Stage all changes with `git add .` + 3. Commit with version number in message + +#### Step 5: Create Pull Request +- **Command**: `gh pr create -f` (uses commit message for PR title/body) +- **Destination**: Targets default branch (master) +- **Review**: PR is created and awaits manual review + merge + +#### Step 6: Trigger Publishing (After Merge) +- **Trigger**: When PR is merged to `master`, `on-push-master.yml` workflow activates +- **Condition**: Triggered only if files matching `latest/**` were modified +- **Workflows Called**: + 1. `./.github/workflows/publish.yml` + 2. `./.github/workflows/release.yml` +- **Result**: Same publishing and releasing as automatic flow + +--- + + + +## Supporting Scripts + +### version.rb +**Purpose**: Increment version numbers in configuration files + +**Usage**: `ruby .github/version.rb [config_file]` + +**Important Nuance - npmVersion as Source of Truth**: + +The `npmVersion` field in the config file is the **authoritative source of truth** for the package version. Here's how the version flows through the system: + +1. **Config File** (Source of Truth) + - `openapi/config.yml` contains `npmVersion: 2.0.0` + - This is the persistent, stored version number + - Lives in Git and is checked in with each update + +2. **version.rb Script** (Updates Source of Truth) + - Reads the current `npmVersion` from the config file + - Receives bump instruction from caller: "patch", "minor", or "major" + - Calculates new version: 2.0.0 → 2.0.1 (patch), 2.1.0 (minor), or 3.0.0 (major) + - **Writes updated npmVersion back to config file** (persists to Git) + - Outputs new version to stdout (for workflow logging) + +3. **package.mustache Template** (Uses Source of Truth) + - Contains placeholder: `"version": "{{npmVersion}}"` + - OpenAPI Generator replaces `{{npmVersion}}` with value from config file + - Generates `package.json` with the correct version number + +4. **Result** + - The generated `package.json` always has the correct version + - Version comes entirely from the config file + - No hardcoding in workflows or templates + +**Important Distinction**: +- `client_payload.version` from repository_dispatch is a **bump instruction** (e.g., "patch") +- `npmVersion` in config file is the **actual version number** (e.g., "2.0.0") +- These are different things! The bump instruction is used to calculate the new version number. + +**Behavior**: +- Reads YAML config file +- Parses current version (major.minor.patch) +- Increments requested component +- Resets lower components to 0 (e.g., 2.1.5 → 3.0.0 when major bumped) +- Writes updated config file +- Outputs new version to stdout + +**Config File Parameter**: Optional, defaults to `./openapi/config.yml` + +**Examples**: +```bash +ruby .github/version.rb patch # Bumps config.yml patch version (2.0.0 → 2.0.1) +ruby .github/version.rb minor openapi/config-v20111101.yml # Bumps config-v20111101 minor version (2.0.0 → 2.1.0) +ruby .github/version.rb major openapi/config-v20250224.yml # Bumps config-v20250224 major version (3.0.0 → 4.0.0) +``` + +### clean.rb +**Purpose**: Remove generated SDK files before regeneration + +**Behavior**: +- Walks through repository root directory +- Deletes all files/directories except those in ALLOW_LIST +- Protected directories: `.git`, `.github`, `.openapi-generator-ignore`, `openapi`, `LICENSE`, `README.md`, `CHANGELOG.md`, `MIGRATION.md` +- Prevents accidental deletion of configuration and workflow files + +**Part 5 Note**: When multi-version support is activated, protected list will include: `v20111101`, `v20250224`, and remove `latest` + +**Part 5 Note**: When multi-version support is activated, protected list will include: `v20111101`, `v20250224`, and remove `latest` + +--- + +## Configuration Files + +### openapi/config.yml + +```yaml +--- +generatorName: typescript-axios +npmName: mx-platform-node +npmVersion: 2.0.0 +supportsES6: true +.openapi-generator-ignore: true +``` + +**Used by**: +- `generate.yml` (manual generation) +- `generate_publish_release.yml` (automatic generation) +- `version.rb` script + +### openapi/templates/ +**Purpose**: Customized templates for package generation + +**Files**: +- `package.mustache`: Controls package.json generation + - **Key Feature**: Includes `"files"` field to explicitly control what gets published to npm + - Controls package name, version, scripts, dependencies +- `README.mustache`: Controls README.md generation + +--- + +## Path-Based Triggers + +### on-push-master.yml +**Purpose**: Automatically trigger publish and release workflows when SDK code changes are pushed to master + +```yaml +on: + push: + branches: [master] + paths: + - 'latest/**' # Only trigger on changes in the latest/ directory +``` + +**This prevents**: +- Enhancement PRs (docs only) from triggering publish +- README updates from triggering releases +- Workflow file changes from triggering publish + +--- + +## Workflow Sequences + +The following sequence diagrams show the timeline and interactions for each flow, making it clear when workflows trigger and what happens at each stage. + +### Flow 1: Automatic Generation (Repository Dispatch) + +```mermaid +sequenceDiagram + participant OpenAPI as OpenAPI
Repository + participant GH as GitHub
Actions + participant Gen as generate_publish
_release.yml + participant npm as npm
Registry + participant GHRel as GitHub
Releases + + OpenAPI->>GH: mx_platform_api.yml changes
(repository_dispatch event) + GH->>+Gen: Trigger workflow + Gen->>Gen: 1. Clean repo
(clean.rb) + Gen->>Gen: 2. Bump version
(version.rb) + Gen->>Gen: 3. Generate SDK
(OpenAPI Generator) + Gen->>Gen: 4. Copy docs
(LICENSE, CHANGELOG, MIGRATION) + Gen->>Gen: 5. Commit to master
(devexperience bot) + Gen->>npm: Dispatch publish_sdk event + Gen->>GHRel: Dispatch release_sdk event + deactivate Gen + + rect rgba(0, 255, 0, .1) + note right of npm: Parallel Publishing & Release + npm->>npm: publish.yml:
npm publish --tag next
from ./latest/ + GHRel->>GHRel: release.yml:
gh release create
with version tag + end + + npm-->>GH: ✅ Published + GHRel-->>GH: ✅ Released +``` + +### Flow 2: Manual Generation (Workflow Dispatch) + +```mermaid +sequenceDiagram + participant Dev as Developer + participant GH as GitHub
Actions + participant Gen as generate.yml + participant Review as Code
Review + participant Trigger as on-push-
master.yml + participant npm as npm
Registry + participant GHRel as GitHub
Releases + + Dev->>GH: Run generate.yml
(version_level: major/minor/patch) + GH->>+Gen: Trigger workflow + Gen->>Gen: 1. Bump version
(version.rb) + Gen->>Gen: 2. Clean repo
(clean.rb) + Gen->>Gen: 3. Generate SDK
(OpenAPI Generator) + Gen->>Gen: 4. Copy docs + Gen->>Gen: 5. Create feature branch
(openapi-generator-X.Y.Z) + Gen->>Gen: 6. Create Pull Request + deactivate Gen + + Review->>Review: 👤 Code Review + Review->>Review: Approve & Merge
to master + + GH->>+Trigger: on-push-master.yml
(path: latest/**) + Trigger->>Trigger: ✅ Path matched + Trigger->>npm: workflow_call publish.yml + Trigger->>GHRel: workflow_call release.yml + deactivate Trigger + + rect rgba(0, 255, 0, .1) + note right of npm: Parallel Publishing & Release + npm->>npm: publish.yml:
npm publish --tag next
from ./latest/ + GHRel->>GHRel: release.yml:
gh release create
with version tag + end + + npm-->>GH: ✅ Published + GHRel-->>GH: ✅ Released +``` + +--- + +--- + +## Environment Variables & Secrets Used + +### Required Secrets (`.github/secrets`) + +| Secret | Used In | Purpose | +|--------|---------|---------| +| `NPM_AUTH_TOKEN` | publish.yml | Authenticate to npm registry for publishing | +| `GITHUB_TOKEN` | All workflows | GitHub API access (auto-provided, but sometimes explicitly referenced) | +| `SLACK_WEBHOOK_URL` | All workflows | Send failure notifications to Slack | +| `PAPI_SDK_APP_ID` | generate_publish_release.yml | GitHub App ID for custom token generation | +| `PAPI_SDK_INSTALLATION_ID` | generate_publish_release.yml | GitHub App installation ID | +| `PAPI_SDK_PRIVATE_KEY` | generate_publish_release.yml | GitHub App private key | + +### Environment Setup + +- **Node**: v20.x +- **Ruby**: 3.1 +- **OpenAPI Generator**: Latest version (installed via npm during workflow) + +--- + +## Current Implementation Status + +### ✅ Fully Implemented +- Automatic generation from OpenAPI spec changes (Flow 1) +- Manual generation for PRs with version bumping (Flow 2) +- Path-based triggers for publish/release +- npm publishing with semantic versioning +- GitHub releases +- Slack notifications +- v2.0.0 stable release confirmed + +### Key Architectural Decisions + +1. **Direct Commit vs PR**: + - Automatic flow (Flow 1) commits directly to master + - Manual flow (Flow 2) creates PR for review + +2. **Tag Strategy**: + - `--tag next`: Used during auto-publish (for staging validation) + - Can be promoted to `latest` tag after validation + +3. **Path-Based Triggers**: Only changes to SDK code (`latest/**`) trigger publish/release, not docs or workflows + +4. **Atomic Operations**: Publishing and releasing happen in sequence within same workflow, preventing version mismatches + +--- + +## Troubleshooting + +### NPM Publish Fails +- Check `NPM_AUTH_TOKEN` secret is valid +- Verify token has publish permissions for `mx-platform-node` package +- Check version isn't already published + +### Release Creation Fails +- Verify `GITHUB_TOKEN` has release creation permissions +- Check if tag already exists (gh release fails if tag exists) + +### Generation Produces No Changes +- Verify OpenAPI spec URL is accessible +- Check openapi-generator-cli installed correctly +- Verify config file exists and is valid YAML + +### publish.yml or release.yml Don't Trigger After Generate +- Check path filter in `on-push-master.yml` matches changed files +- Verify files were actually committed (`git log` to confirm) +- Check branch is `master` and not another branch + +--- diff --git a/docs/Adding-a-New-API-Version.md b/docs/Adding-a-New-API-Version.md index 4936a78..9c37fba 100644 --- a/docs/Adding-a-New-API-Version.md +++ b/docs/Adding-a-New-API-Version.md @@ -365,9 +365,7 @@ Use this checklist to verify you've completed all steps: - [ ] Updated `.github/workflows/generate.yml` with new version in dropdown options - [ ] Updated `.github/workflows/generate.yml` with semantic versioning validation - [ ] Updated `.github/workflows/generate_publish_release.yml` with version-to-config mapping in Setup job -- [ ] Updated `.github/workflows/generate_publish_release.yml` with version variable initialization in Commit-and-Push job -- [ ] Updated `.github/workflows/generate_publish_release.yml` with version package.json reading logic in Commit-and-Push job -- [ ] Updated `.github/workflows/generate_publish_release.yml` with CHANGELOG entry generation in Commit-and-Push job +- [ ] Updated `.github/changelog_manager.rb` with new version in `API_VERSION_ORDER` array - [ ] Updated `.github/workflows/on-push-master.yml` path triggers with `v20300101/**` - [ ] Updated `.github/workflows/on-push-master.yml` publish job matrix with new version - [ ] Updated `.github/workflows/on-push-master.yml` release job matrix with new version @@ -407,8 +405,8 @@ Use this checklist to verify you've completed all steps: **Solution**: Update `npmVersion: 4.0.0` in config file to use unique major version ### generate_publish_release.yml doesn't recognize new version -**Cause**: Version-to-config mapping missing or CHANGELOG variable/logic not added -**Solution**: Verify all four locations in generate_publish_release.yml are updated: mapping, variable initialization, package.json reading, and CHANGELOG entry generation +**Cause**: Version-to-config mapping missing in Setup job or ChangelogManager not updated +**Solution**: Verify two locations in generate_publish_release.yml are updated: (1) version-to-config mapping in Setup job, and (2) add version to API_VERSION_ORDER in changelog_manager.rb ### on-push-master.yml doesn't trigger after merge **Cause**: Path trigger syntax incorrect or matrix not updated diff --git a/docs/Multi-Version-SDK-Flow.md b/docs/Multi-Version-SDK-Flow.md index 5b9e319..3254d98 100644 --- a/docs/Multi-Version-SDK-Flow.md +++ b/docs/Multi-Version-SDK-Flow.md @@ -124,19 +124,30 @@ sequenceDiagram sequenceDiagram participant Push as Git Push participant Auto as on-push-
master.yml + participant skip as check-skip-
publish participant pub as publish.yml participant rel as release.yml + participant gate as gate-
v20111101 participant npm as npm participant GHRel as GitHub Push->>+Auto: Push to master
(v20111101/** changed) - Auto->>pub: Matrix: Publish v20111101 - pub->>npm: npm publish - Auto->>rel: Matrix: Release v20111101 - rel->>GHRel: Create tag + Auto->>skip: Check skip-publish flag + skip-->>Auto: skip_publish: false + Auto->>pub: publish-v20111101 + pub->>npm: npm publish v2.x.x + Auto->>rel: release-v20111101 + rel->>GHRel: Create tag v2.x.x + rel-->>gate: Release complete + gate->>gate: Gate always runs
(even if v20111101 skipped) + note right of gate: Unblocks v20250224
when only v20250224 changed + gate-->>Auto: Gate complete + Auto->>pub: publish-v20250224
(skipped - not modified) deactivate Auto ``` +**Key Feature**: The `gate-v20111101-complete` job uses `always()` to run even when intermediate jobs are skipped, ensuring v20250224 can publish when only v20250224 is modified. + --- ## Common Tasks diff --git a/docs/Troubleshooting-Guide.md b/docs/Troubleshooting-Guide.md index 030fe37..3bbab76 100644 --- a/docs/Troubleshooting-Guide.md +++ b/docs/Troubleshooting-Guide.md @@ -214,6 +214,41 @@ fatal: A release with this tag already exists --- +### Only One Version Doesn't Publish When Only That Version Changed + +**Symptom**: Merged a PR that only modified `v20250224/` files, but the publish job didn't run + +**Expected Behavior**: `publish-v20250224` should run when only v20250224 is modified + +**Root Cause**: Previous versions of the workflow had a dependency chain that broke when intermediate jobs were skipped. This has been fixed with the gate job pattern. + +**Current Implementation** (uses gate job pattern): +- `gate-v20111101-complete` uses GitHub Actions `always()` condition +- This job runs even when v20111101 jobs are skipped +- It unblocks downstream v20250224 jobs +- Result: Publishing works correctly whether one or both versions are modified + +**If You're Still Seeing This Issue**: +1. Verify you have the latest `on-push-master.yml`: + ```bash + grep -A 3 "gate-v20111101-complete" .github/workflows/on-push-master.yml + ``` +2. Confirm the gate job uses `always()` condition: + ```yaml + gate-v20111101-complete: + if: always() && needs.check-skip-publish.outputs.skip_publish == 'false' + ``` +3. Ensure `publish-v20250224` depends on the gate job: + ```yaml + publish-v20250224: + needs: [check-skip-publish, gate-v20111101-complete] + ``` +4. If not present, update workflow from latest template + +**Technical Details**: See [Workflow-and-Configuration-Reference.md](Workflow-and-Configuration-Reference.md#step-3-gate-job---unblock-v20250224-publishing) for full gate job implementation details. + +--- + ### Generation Produces Stale Spec Files **Symptom**: Generated SDK doesn't include changes that were in the OpenAPI spec diff --git a/docs/Workflow-and-Configuration-Reference.md b/docs/Workflow-and-Configuration-Reference.md index bb69f4d..6a29d47 100644 --- a/docs/Workflow-and-Configuration-Reference.md +++ b/docs/Workflow-and-Configuration-Reference.md @@ -120,42 +120,52 @@ strategy: #### Step 4: Commit All Changes to Master - **Git Config**: Uses `devexperience` bot account -- **Commit Message**: `"Generated Node.js SDKs [v20111101=2.0.1,v20250224=3.0.1]"` +- **Commit Message**: `"Generated SDK versions: v20111101,v20250224"` - **Files Committed**: Updated config files, generated SDK directories, updated CHANGELOG.md - **Target Branch**: Directly commits to `master` (no PR created for automatic flow) - **Atomic Operation**: All versions committed together in single commit -#### Step 5: Publish to npm (Parallel Matrix Execution) +#### Step 5: Automatic Publish and Release (via on-push-master.yml) -**Architecture**: Uses `workflow_call` to invoke `publish.yml` as reusable workflow +**Architecture**: After `Commit-and-Push` completes and pushes to master, the automatic `on-push-master.yml` workflow is triggered by GitHub's push event. -**Why workflow_call?** Repository dispatch events don't support input parameters. `workflow_call` allows passing `version_directory` to specify which version directory contains the SDK to publish. +**Why This Architecture?** +- Separates concerns: `generate_publish_release.yml` owns generation, `on-push-master.yml` owns publishing +- Enables consistent publish logic: All publishes (whether from automated generation or manual PR merge) go through the same workflow +- Prevents duplicate publishes: Manual generate.yml + PR merge only triggers publish once (via on-push-master.yml) -**Process**: -1. Call `publish.yml` with version-specific directory -2. Navigate to version directory (e.g., `v20111101/`) -3. Install dependencies: `npm install` -4. Publish to npm: `npm publish` (no tag for production) -5. Use `NPM_AUTH_TOKEN` secret for authentication +**Process** (handled by `on-push-master.yml`): +1. Check-skip-publish job detects if `[skip-publish]` flag is in commit message +2. For each version with path changes (v20111101/**, v20250224/**): + - Publish job: Call `publish.yml` with version-specific directory + - Release job: Call `release.yml` after publish completes +3. Path-based matrix execution ensures only modified versions are published -**Result**: -- `mx-platform-node@2.0.1` published to npm (v20111101 API) -- `mx-platform-node@3.0.1` published to npm (v20250224 API) -- Both major versions coexist on npm registry under same package name +**Serialization Chain** (for race condition prevention): +- v20111101 publish runs first (depends on check-skip-publish) +- v20111101 release runs second (depends on publish) +- **gate-v20111101-complete** runs (uses `always()`, runs even if v20111101 jobs are skipped) ⭐ **NEW - Handles conditional dependencies** +- v20250224 publish runs third (depends on gate job) ← **Serialized to prevent npm race conditions** +- v20250224 release runs fourth (depends on v20250224 publish) -#### Step 6: Create GitHub Releases (Parallel Matrix Execution) +**The Gate Job Pattern** (Solves Single-Version Publishing): +Previously, when only v20250224 changed, the workflow would fail because `publish-v20250224` depended on `release-v20111101`, which was skipped (v20111101 not modified). -**Same architecture as publish**: Uses `workflow_call` to invoke `release.yml` +Solution: The `gate-v20111101-complete` job uses GitHub Actions' `always()` condition, which runs regardless of whether upstream jobs succeeded, failed, or were skipped. This: +- ✅ Allows downstream jobs to proceed even when intermediate jobs are skipped +- ✅ Maintains serial ordering when both versions are modified +- ✅ Fixes the issue where only modifying v20250224 would cause the publish to hang -**Process**: -1. Read version from version-specific `package.json` -2. Create GitHub release with version-specific tag (e.g., `v2.0.1`, `v3.0.1`) -3. Release body includes API version and links to API documentation +**Scenario Examples**: +- **Both versions modified**: publish v20111101 → release v20111101 → gate (runs) → publish v20250224 → release v20250224 +- **Only v20250224 modified**: (v20111101 jobs skipped) → gate (always runs, unblocks downstream) → publish v20250224 → release v20250224 +- **Only v20111101 modified**: publish v20111101 → release v20111101 → gate (always runs) → publish v20250224 (skipped, not modified) → release v20250224 (skipped) **Result**: -- GitHub release `v2.0.1` created (v20111101 API) -- GitHub release `v3.0.1` created (v20250224 API) -- Both versions have separate release history +- Versions publish sequentially to npm (prevents registry conflicts) +- Each version has independent release history on GitHub +- Only affected versions are published (path-based filtering) +- Can be skipped with `[skip-publish]` flag in commit message --- @@ -259,7 +269,7 @@ Include `[skip-publish]` in commit message to prevent publish/release for this p **Workflow**: `.github/workflows/on-push-master.yml` -**Architectural Approach**: Matrix strategy with conditional execution per iteration eliminates code duplication while maintaining clear, independent version management. +**Architectural Approach**: Serial job chaining with gate job pattern ensures single-version and multi-version publishing both work correctly while preventing npm race conditions. #### Step 1: Check Skip-Publish Flag @@ -279,52 +289,67 @@ Include `[skip-publish]` in commit message to prevent publish/release for this p - Sets output: `skip_publish` = true/false - Used by subsequent jobs to determine execution -#### Step 2: Matrix-Based Publish Jobs +#### Step 2: Publish and Release v20111101 (First in Serial Chain) -**Matrix Definition**: -```yaml -strategy: - matrix: - version: - - api_version: v20111101 - npm_version: 2 - - api_version: v20250224 - npm_version: 3 -``` +**Jobs**: `publish-v20111101` and `release-v20111101` -**For Each Version**: +**publish-v20111101 executes when**: +- No `[skip-publish]` flag +- Files in `v20111101/**` were changed -**Job Executes When**: -- No upstream failures (`!cancelled()`) -- No `[skip-publish]` flag in commit message -- Files in this version's directory were changed +**release-v20111101 executes when**: +- No `[skip-publish]` flag +- Files in `v20111101/**` were changed +- **AND** `publish-v20111101` completes **Process**: -1. Call `publish.yml` with version-specific directory -2. Navigate to version directory -3. Install dependencies and publish to npm -4. Each version publishes independently with its own version number +1. Publish job calls `publish.yml` with `version_directory: v20111101` +2. Release job calls `release.yml` after publish completes -**Result**: Each version publishes independently, no race conditions, parallel execution when both versions changed +#### Step 3: Gate Job - Unblock v20250224 Publishing -#### Step 3: Matrix-Based Release Jobs +**Job**: `gate-v20111101-complete` -**Same Matrix Strategy as Publish** +```yaml +gate-v20111101-complete: + runs-on: ubuntu-latest + needs: [check-skip-publish, release-v20111101] + if: always() && needs.check-skip-publish.outputs.skip_publish == 'false' + steps: + - name: Gate complete - ready for v20250224 + run: echo "v20111101 release workflow complete (or skipped)" +``` + +**Key Feature**: Uses `always()` condition - runs even when `release-v20111101` is skipped + +**Why This Exists**: +- When only v20250224 is modified, `release-v20111101` is skipped, which would normally prevent downstream jobs from running +- The gate job always runs (via `always()` condition), allowing downstream jobs to proceed +- Enables correct behavior in all scenarios: single-version or multi-version changes + +**Gate Job Executes When**: +- No `[skip-publish]` flag in commit message +- Always runs regardless of whether v20111101 jobs were skipped + +#### Step 4: Publish and Release v20250224 (Second in Serial Chain) -**For Each Version**: +**Jobs**: `publish-v20250224` and `release-v20250224` -**Job Executes When**: -- Same conditions as publish (skip-publish flag, path match) -- **Plus**: Only after its corresponding publish job succeeds +**publish-v20250224 executes when**: +- No `[skip-publish]` flag +- Files in `v20250224/**` were changed +- **AND** `gate-v20111101-complete` completes (ensures serial ordering) -This ensures each version's release depends only on its own publish job, preventing race conditions. +**release-v20250224 executes when**: +- No `[skip-publish]` flag +- Files in `v20250224/**` were changed +- **AND** `publish-v20250224` completes **Process**: -1. Call `release.yml` with version-specific directory -2. Read version from that directory's `package.json` -3. Create GitHub release with version-specific tag +1. Publish job calls `publish.yml` with `version_directory: v20250224` +2. Release job calls `release.yml` after publish completes -**Result**: Each version released independently, ordered after its corresponding publish job +**Serial Chain Benefit**: Even though both versions could publish in parallel, the gate job ensures v20250224 waits for v20111101 release, preventing npm registry race conditions when both versions are modified. --- @@ -544,17 +569,22 @@ OpenAPI Repo: Commits change to v20111101.yml and v20250224.yml repository_dispatch: {"api_versions": "v20111101,v20250224"} ↓ generate_publish_release.yml: Triggered + ├─ Setup: Create matrix from api_versions ├─ Matrix[v20111101]: Clean, Bump, Generate (parallel) ├─ Matrix[v20250224]: Clean, Bump, Generate (parallel) ├─ Download artifacts ├─ Update CHANGELOG.md ├─ Commit to master - ├─ publish[v20111101]: npm publish (parallel) - ├─ publish[v20250224]: npm publish (parallel) - ├─ release[v20111101]: Create tag v2.0.1 (parallel) - ├─ release[v20250224]: Create tag v3.0.1 (parallel) + ├─ Push to master (triggers on-push-master.yml) ↓ -Result: Both versions published and released, CHANGELOG updated +on-push-master.yml: Triggered (push event) + ├─ check-skip-publish: Verify no [skip-publish] flag + ├─ publish-v20111101: npm publish (path filter matched) + ├─ release-v20111101: Create tag v2.0.1 (after publish) + ├─ publish-v20250224: npm publish (serialized, after v20111101 release) + ├─ release-v20250224: Create tag v3.0.1 (after publish) + ↓ +Result: Both versions published and released sequentially, CHANGELOG updated ``` ### Manual Flow Timeline @@ -563,16 +593,16 @@ Result: Both versions published and released, CHANGELOG updated Developer: Runs generate.yml (api_version, version_bump) ↓ generate.yml: Validate, Bump (if needed), Clean, Generate - ├─ Update CHANGELOG.md + ├─ Update CHANGELOG.md (via changelog_manager.rb) ├─ Create feature branch ├─ Create Pull Request ↓ -Code Review: Developer reviews and merges PR +Code Review: Developer reviews and merges PR to master ↓ -on-push-master.yml: Triggered - ├─ check-skip-publish: false - ├─ publish[matching_version]: npm publish - ├─ release[matching_version]: Create tag +on-push-master.yml: Triggered (push event) + ├─ check-skip-publish: false (no skip flag in merge commit) + ├─ publish[matching_version]: npm publish (path filter matches) + ├─ release[matching_version]: Create tag (after publish) ↓ Result: Selected version published and released ``` From ff0cc6b2f89ab70376cb5c2012470f244f1c0728 Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 21:49:39 -0700 Subject: [PATCH 06/16] Cleanup tests and changelog code --- .github/changelog_manager.rb | 47 +---- .github/spec/changelog_manager_spec.rb | 263 ++++++------------------- 2 files changed, 68 insertions(+), 242 deletions(-) diff --git a/.github/changelog_manager.rb b/.github/changelog_manager.rb index 0268206..85a4667 100644 --- a/.github/changelog_manager.rb +++ b/.github/changelog_manager.rb @@ -11,28 +11,15 @@ class ChangelogManager API_VERSION_ORDER = ['v20250224', 'v20111101'].freeze class << self - # CLI entry point: validates arguments and updates changelog - # Called from GitHub Actions workflows - # - # @param versions_arg [String, nil] Versions from ARGV[0] - # @return [true] Returns true on success - # @raise SystemExit If validation fails def run(versions_arg) - # Validate versions argument validate_versions(versions_arg) update(versions_arg) puts "✅ CHANGELOG updated successfully" end - # Public interface: update CHANGELOG with new version entries - # - # @param versions [String, Array] Version(s) to update. String format: "v20250224,v20111101" - # @return [true] Returns true on success - # @raise [StandardError] If versions not found or changelog not readable def update(versions) versions_array = normalize_versions(versions) - # Check changelog exists first before doing any processing unless File.exist?(CHANGELOG_PATH) raise "Changelog not found at #{CHANGELOG_PATH}" end @@ -44,16 +31,11 @@ def update(versions) [api_version, version_number] end - # Sort by API_VERSION_ORDER to ensure consistent ordering sorted_data = sort_versions(version_data) - - # Read existing changelog current_changelog = File.read(CHANGELOG_PATH) - # Build changelog entries for each version + # Build changelog entries for each version and updated changelog entries = sorted_data.map { |api_version, version_num| build_entry(api_version, version_num, TODAY) } - - # Insert entries at the top of changelog (after header) updated_changelog = insert_entries(current_changelog, entries) # Write back to file @@ -84,9 +66,6 @@ def has_invalid_versions?(versions_arg) invalid_versions.any? end - # Normalize versions parameter to array - # @param versions [String, Array] - # @return [Array] Array of version strings def normalize_versions(versions) case versions when String @@ -98,9 +77,6 @@ def normalize_versions(versions) end end - # Read version number from a specific API version's package.json - # @param api_version [String] e.g., "v20250224" - # @return [String] Version number from package.json def read_package_version(api_version) package_json_path = File.join(BASE_PATH, api_version, 'package.json') @@ -112,9 +88,7 @@ def read_package_version(api_version) package_json['version'] end - # Sort versions by API_VERSION_ORDER - # @param version_data [Array] Array of [api_version, version_number] pairs - # @return [Array] Sorted array of [api_version, version_number] pairs + def sort_versions(version_data) version_data.sort_by do |api_version, _| order_index = API_VERSION_ORDER.index(api_version) @@ -122,16 +96,10 @@ def sort_versions(version_data) end end - # Build a single changelog entry - # @param api_version [String] e.g., "v20250224" - # @param version_number [String] e.g., "3.2.0" - # @param date [Date] Entry date - # @return [String] Formatted changelog entry def build_entry(api_version, version_number, date) date_str = date.strftime('%Y-%m-%d') last_change_date = extract_last_change_date(api_version) - # Format the message with last change date if found if last_change_date last_change_str = last_change_date.strftime('%Y-%m-%d') message = "Updated #{api_version} API specification to most current version. Please check full [API changelog](https://docs.mx.com/resources/changelog/platform) for any changes made between #{last_change_str} and #{date_str}." @@ -147,8 +115,7 @@ def build_entry(api_version, version_number, date) # Extract the date of the last change for a given API version from the changelog # Finds the first entry in the changelog that mentions the api_version - # @param api_version [String] e.g., "v20250224" - # @return [Date, nil] Date of last change or nil if not found + # such as "v20250224" and returns date of last change or nil if not found def extract_last_change_date(api_version) return nil unless File.exist?(CHANGELOG_PATH) @@ -166,24 +133,16 @@ def extract_last_change_date(api_version) # Insert entries into changelog after the header section # Finds the first ## entry and inserts new entries before it - # - # @param changelog [String] Current changelog content - # @param entries [Array] Entries to insert - # @return [String] Updated changelog def insert_entries(changelog, entries) lines = changelog.split("\n") - # Find the line number of the first version entry (first line starting with ##) first_entry_index = lines.find_index { |line| line.start_with?('## [') } if first_entry_index.nil? raise "Could not find existing changelog entries. Expected format: ## [version]" end - # Extract header (everything before first entry) header = lines[0...first_entry_index] - - # Get the rest (from first entry onwards) rest = lines[first_entry_index..] # Combine: header + new entries + rest diff --git a/.github/spec/changelog_manager_spec.rb b/.github/spec/changelog_manager_spec.rb index 007de00..2b21090 100644 --- a/.github/spec/changelog_manager_spec.rb +++ b/.github/spec/changelog_manager_spec.rb @@ -2,23 +2,25 @@ require 'json' require 'date' require 'fileutils' - -# Load the class to test require_relative '../changelog_manager' describe ChangelogManager do let(:spec_dir) { File.expand_path('..', __FILE__) } let(:fixtures_dir) { File.join(spec_dir, 'fixtures') } let(:temp_dir) { File.join(spec_dir, 'tmp') } + let(:changelog_path) { File.join(temp_dir, 'CHANGELOG.md') } + let(:changelog_sample_fixture) { File.join(fixtures_dir, 'CHANGELOG_sample.md') } + let(:v20250224_package_fixture) { File.join(fixtures_dir, 'v20250224_package.json') } + let(:v20111101_package_fixture) { File.join(fixtures_dir, 'v20111101_package.json') } before(:each) do # Create temp directory for test files FileUtils.mkdir_p(temp_dir) # Stub constants to use temp directory for tests - stub_const('ChangelogManager::CHANGELOG_PATH', File.join(temp_dir, 'CHANGELOG.md')) + stub_const('ChangelogManager::CHANGELOG_PATH', changelog_path) stub_const('ChangelogManager::BASE_PATH', temp_dir) - stub_const('ChangelogManager::TODAY', Date.new(2025, 01, 28)) + stub_const('ChangelogManager::TODAY', Date.new(2026, 01, 28)) end after(:each) do @@ -26,30 +28,37 @@ FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir) end + # Helper methods for test setup + def setup_changelog + FileUtils.cp(changelog_sample_fixture, changelog_path) + end + + def setup_version_directory(version_name, package_fixture) + version_dir = File.join(temp_dir, version_name) + FileUtils.mkdir_p(version_dir) + FileUtils.cp(package_fixture, File.join(version_dir, 'package.json')) + version_dir + end + + def setup_version_with_changelog(version_name, package_fixture) + setup_changelog + setup_version_directory(version_name, package_fixture) + end + + def read_changelog + File.read(changelog_path) + end + describe '.update with single version' do it 'updates changelog with single version entry' do - # Setup - changelog_path = File.join(temp_dir, 'CHANGELOG.md') - package_dir = File.join(temp_dir, 'v20250224') + setup_version_with_changelog('v20250224', v20250224_package_fixture) - FileUtils.mkdir_p(package_dir) - FileUtils.cp( - File.join(fixtures_dir, 'CHANGELOG_sample.md'), - changelog_path - ) - FileUtils.cp( - File.join(fixtures_dir, 'v20250224_package.json'), - File.join(package_dir, 'package.json') - ) - - # Execute result = ChangelogManager.update('v20250224') - # Verify expect(result).to be true - updated_content = File.read(changelog_path) - expect(updated_content).to include('## [3.0.0] - 2025-01-28 (v20250224 API)') + updated_content = read_changelog + expect(updated_content).to include('## [3.0.0] - 2026-01-28 (v20250224 API)') expect(updated_content).to include('Updated v20250224 API specification to most current version') expect(updated_content).to include('[API changelog]') @@ -62,38 +71,19 @@ describe '.update with multiple versions' do it 'updates changelog with entries from multiple versions' do - # Setup - changelog_path = File.join(temp_dir, 'CHANGELOG.md') - v20250224_dir = File.join(temp_dir, 'v20250224') - v20111101_dir = File.join(temp_dir, 'v20111101') - - FileUtils.mkdir_p(v20250224_dir) - FileUtils.mkdir_p(v20111101_dir) + setup_changelog + setup_version_directory('v20250224', v20250224_package_fixture) + setup_version_directory('v20111101', v20111101_package_fixture) - FileUtils.cp( - File.join(fixtures_dir, 'CHANGELOG_sample.md'), - changelog_path - ) - FileUtils.cp( - File.join(fixtures_dir, 'v20250224_package.json'), - File.join(v20250224_dir, 'package.json') - ) - FileUtils.cp( - File.join(fixtures_dir, 'v20111101_package.json'), - File.join(v20111101_dir, 'package.json') - ) - - # Execute result = ChangelogManager.update('v20250224,v20111101') - # Verify expect(result).to be true - updated_content = File.read(changelog_path) + updated_content = read_changelog # Both versions should be present - expect(updated_content).to include('## [3.0.0] - 2025-01-28 (v20250224 API)') - expect(updated_content).to include('## [2.0.0] - 2025-01-28 (v20111101 API)') + expect(updated_content).to include('## [3.0.0] - 2026-01-28 (v20250224 API)') + expect(updated_content).to include('## [2.0.0] - 2026-01-28 (v20111101 API)') expect(updated_content).to include('Updated v20250224 API specification to most current version') expect(updated_content).to include('Updated v20111101 API specification to most current version') @@ -106,59 +96,27 @@ describe '.update with array versions' do it 'accepts versions as an array' do - # Setup - changelog_path = File.join(temp_dir, 'CHANGELOG.md') - package_dir = File.join(temp_dir, 'v20250224') - - FileUtils.mkdir_p(package_dir) - FileUtils.cp( - File.join(fixtures_dir, 'CHANGELOG_sample.md'), - changelog_path - ) - FileUtils.cp( - File.join(fixtures_dir, 'v20250224_package.json'), - File.join(package_dir, 'package.json') - ) + setup_version_with_changelog('v20250224', v20250224_package_fixture) - # Execute with array instead of string result = ChangelogManager.update(['v20250224']) - # Verify expect(result).to be true - updated_content = File.read(changelog_path) - expect(updated_content).to include('## [3.0.0] - 2025-01-28 (v20250224 API)') + updated_content = read_changelog + expect(updated_content).to include('## [3.0.0] - 2026-01-28 (v20250224 API)') end end describe '.update sorting behavior' do it 'always places v20250224 before v20111101' do - # Setup - changelog_path = File.join(temp_dir, 'CHANGELOG.md') - v20250224_dir = File.join(temp_dir, 'v20250224') - v20111101_dir = File.join(temp_dir, 'v20111101') - - FileUtils.mkdir_p(v20250224_dir) - FileUtils.mkdir_p(v20111101_dir) - - FileUtils.cp( - File.join(fixtures_dir, 'CHANGELOG_sample.md'), - changelog_path - ) - FileUtils.cp( - File.join(fixtures_dir, 'v20250224_package.json'), - File.join(v20250224_dir, 'package.json') - ) - FileUtils.cp( - File.join(fixtures_dir, 'v20111101_package.json'), - File.join(v20111101_dir, 'package.json') - ) + setup_changelog + setup_version_directory('v20250224', v20250224_package_fixture) + setup_version_directory('v20111101', v20111101_package_fixture) # Execute with reversed input order to verify sorting result = ChangelogManager.update('v20111101,v20250224') - # Verify expect(result).to be true - updated_content = File.read(changelog_path) + updated_content = read_changelog # Despite input order, v20250224 should come first v20250224_pos = updated_content.index('[3.0.0]') @@ -169,52 +127,24 @@ describe '.update with date range behavior' do it 'includes date range when prior entry exists for API version' do - # Setup - changelog_path = File.join(temp_dir, 'CHANGELOG.md') - package_dir = File.join(temp_dir, 'v20111101') + setup_version_with_changelog('v20111101', v20111101_package_fixture) - FileUtils.mkdir_p(package_dir) - FileUtils.cp( - File.join(fixtures_dir, 'CHANGELOG_sample.md'), - changelog_path - ) - FileUtils.cp( - File.join(fixtures_dir, 'v20111101_package.json'), - File.join(package_dir, 'package.json') - ) - - # Execute - updating v20111101 which has an entry dated 2025-01-15 in the fixture result = ChangelogManager.update('v20111101') - # Verify expect(result).to be true - updated_content = File.read(changelog_path) + updated_content = read_changelog # Should include the date range message - expect(updated_content).to include('between 2025-01-15 and 2025-01-28') + expect(updated_content).to include('between 2025-01-15 and 2026-01-28') end it 'shows no prior date when entry has no previous version' do - # Setup - changelog_path = File.join(temp_dir, 'CHANGELOG.md') - package_dir = File.join(temp_dir, 'v20250224') - - FileUtils.mkdir_p(package_dir) - FileUtils.cp( - File.join(fixtures_dir, 'CHANGELOG_sample.md'), - changelog_path - ) - FileUtils.cp( - File.join(fixtures_dir, 'v20250224_package.json'), - File.join(package_dir, 'package.json') - ) + setup_version_with_changelog('v20250224', v20250224_package_fixture) - # Execute - v20250224 has no prior entry in the fixture result = ChangelogManager.update('v20250224') - # Verify expect(result).to be true - updated_content = File.read(changelog_path) + updated_content = read_changelog # Should include fallback message without date range expect(updated_content).to include('Updated v20250224 API specification to most current version. Please check full [API changelog]') @@ -223,36 +153,17 @@ end it 'uses correct dates in range for multiple version updates' do - # Setup - changelog_path = File.join(temp_dir, 'CHANGELOG.md') - v20250224_dir = File.join(temp_dir, 'v20250224') - v20111101_dir = File.join(temp_dir, 'v20111101') + setup_changelog + setup_version_directory('v20250224', v20250224_package_fixture) + setup_version_directory('v20111101', v20111101_package_fixture) - FileUtils.mkdir_p(v20250224_dir) - FileUtils.mkdir_p(v20111101_dir) - - FileUtils.cp( - File.join(fixtures_dir, 'CHANGELOG_sample.md'), - changelog_path - ) - FileUtils.cp( - File.join(fixtures_dir, 'v20250224_package.json'), - File.join(v20250224_dir, 'package.json') - ) - FileUtils.cp( - File.join(fixtures_dir, 'v20111101_package.json'), - File.join(v20111101_dir, 'package.json') - ) - - # Execute result = ChangelogManager.update('v20250224,v20111101') - # Verify expect(result).to be true - updated_content = File.read(changelog_path) + updated_content = read_changelog # v20111101 should have date range (prior entry on 2025-01-15) - expect(updated_content).to include('between 2025-01-15 and 2025-01-28') + expect(updated_content).to include('between 2025-01-15 and 2026-01-28') # v20250224 should NOT have date range (no prior entry) v20250224_section = updated_content[/## \[3\.0\.0\].*?(?=##|\z)/m] @@ -283,63 +194,32 @@ end it 'raises error when package.json is malformed' do - # Setup - changelog_path = File.join(temp_dir, 'CHANGELOG.md') - package_dir = File.join(temp_dir, 'v20250224') - - FileUtils.mkdir_p(package_dir) - FileUtils.cp( - File.join(fixtures_dir, 'CHANGELOG_sample.md'), - changelog_path - ) - File.write( - File.join(package_dir, 'package.json'), - 'invalid json {]' - ) + setup_changelog + version_dir = setup_version_directory('v20250224', v20250224_package_fixture) + File.write(File.join(version_dir, 'package.json'), 'invalid json {]') - # Execute expect { ChangelogManager.update('v20250224') }.to raise_error(JSON::ParserError) end it 'raises error when version is not in package.json' do - # Setup - changelog_path = File.join(temp_dir, 'CHANGELOG.md') - package_dir = File.join(temp_dir, 'v20250224') - - FileUtils.mkdir_p(package_dir) - FileUtils.cp( - File.join(fixtures_dir, 'CHANGELOG_sample.md'), - changelog_path - ) + setup_changelog + version_dir = setup_version_directory('v20250224', v20250224_package_fixture) File.write( - File.join(package_dir, 'package.json'), - JSON.generate({ name: '@mx-platform/node' }) # Missing version + File.join(version_dir, 'package.json'), + JSON.generate({ name: '@mx-platform/node' }) ) - # Execute expect { ChangelogManager.update('v20250224') }.to raise_error(/Could not read version/) end it 'raises error when changelog has no existing entries' do - # Setup - changelog_path = File.join(temp_dir, 'CHANGELOG.md') - package_dir = File.join(temp_dir, 'v20250224') + File.write(changelog_path, "# Changelog\n\nNo entries here") + setup_version_directory('v20250224', v20250224_package_fixture) - FileUtils.mkdir_p(package_dir) - File.write( - changelog_path, - "# Changelog\n\nNo entries here" - ) - FileUtils.cp( - File.join(fixtures_dir, 'v20250224_package.json'), - File.join(package_dir, 'package.json') - ) - - # Execute expect { ChangelogManager.update('v20250224') }.to raise_error(/Could not find existing changelog entries/) @@ -350,28 +230,15 @@ describe '.run (CLI entry point)' do it 'validates and updates successfully with valid versions' do - # Setup - changelog_path = File.join(temp_dir, 'CHANGELOG.md') - package_dir = File.join(temp_dir, 'v20250224') - - FileUtils.mkdir_p(package_dir) - FileUtils.cp( - File.join(fixtures_dir, 'CHANGELOG_sample.md'), - changelog_path - ) - FileUtils.cp( - File.join(fixtures_dir, 'v20250224_package.json'), - File.join(package_dir, 'package.json') - ) + setup_version_with_changelog('v20250224', v20250224_package_fixture) - # Execute expect { ChangelogManager.run('v20250224') }.to output(/✅ CHANGELOG updated successfully/).to_stdout # Verify changelog was updated - updated_content = File.read(changelog_path) - expect(updated_content).to include('## [3.0.0] - 2025-01-28 (v20250224 API)') + updated_content = read_changelog + expect(updated_content).to include('## [3.0.0] - 2026-01-28 (v20250224 API)') end it 'exits with error when versions argument is nil' do From 460df9e9ec6b412c798370bbe3aa2eba5cee670d Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 21:50:20 -0700 Subject: [PATCH 07/16] Create validator for config file and api -> major version --- .github/config_validator.rb | 77 +++++++++++++++ .github/spec/config_validator_spec.rb | 130 ++++++++++++++++++++++++++ Gemfile.lock | 27 ++++++ 3 files changed, 234 insertions(+) create mode 100644 .github/config_validator.rb create mode 100644 .github/spec/config_validator_spec.rb create mode 100644 Gemfile.lock diff --git a/.github/config_validator.rb b/.github/config_validator.rb new file mode 100644 index 0000000..8c59d51 --- /dev/null +++ b/.github/config_validator.rb @@ -0,0 +1,77 @@ +require "yaml" + +# ConfigValidator validates SDK configuration files before generation +# Ensures semantic versioning rules are enforced and configs are properly structured +class ConfigValidator + SUPPORTED_VERSIONS = { + "v20111101" => 2, + "v20250224" => 3 + }.freeze + + def self.validate!(config_file, api_version) + new(config_file, api_version).validate! + end + + def initialize(config_file, api_version) + @config_file = config_file + @api_version = api_version + end + + def validate! + check_api_version_supported! + check_config_exists! + check_config_readable! + validate_semantic_versioning! + true + end + + private + + def check_api_version_supported! + unless SUPPORTED_VERSIONS.key?(@api_version) + supported = SUPPORTED_VERSIONS.keys.join(", ") + raise "Invalid API version: #{@api_version}. Supported versions: #{supported}" + end + end + + def check_config_exists! + unless File.exist?(@config_file) + raise "Config file not found: #{@config_file}" + end + end + + def check_config_readable! + begin + config = YAML.load(File.read(@config_file)) + # YAML.load can return a string if file contains only invalid YAML + # We need to ensure it returned a Hash (parsed YAML object) + unless config.is_a?(Hash) + raise "Config file does not contain valid YAML structure: #{@config_file}" + end + rescue Psych::SyntaxError => e + raise "Config file syntax error in #{@config_file}: #{e.message}" + rescue StandardError => e + raise "Could not read config file #{@config_file}: #{e.message}" + end + end + + def validate_semantic_versioning! + config = YAML.load(File.read(@config_file)) + + unless config.key?("npmVersion") + raise "Config missing npmVersion field: #{@config_file}" + end + + npm_version = config["npmVersion"].to_s.strip + major_version = npm_version.split(".")[0].to_i + + expected_major = SUPPORTED_VERSIONS[@api_version] + + if major_version != expected_major + raise "Semantic versioning error: #{@api_version} API must use npm major version #{expected_major}, " \ + "found #{major_version} in #{@config_file}\n" \ + "Current npmVersion: #{npm_version}\n" \ + "Update config with correct major version: #{expected_major}.x.x" + end + end +end diff --git a/.github/spec/config_validator_spec.rb b/.github/spec/config_validator_spec.rb new file mode 100644 index 0000000..bbe19ce --- /dev/null +++ b/.github/spec/config_validator_spec.rb @@ -0,0 +1,130 @@ +require 'rspec' +require_relative '../config_validator' + +describe ConfigValidator do + subject { ConfigValidator.validate!(path, api_version) } + + let(:path) { 'config-test.yml' } + let(:api_version) { 'v20111101' } + let(:yaml_content) { "---\nnpmVersion: 2.0.0\napiVersion: v20111101\n" } + let(:file_exists) { true } + + before do + allow(File).to receive(:exist?).with(path).and_return(file_exists) + allow(File).to receive(:read).with(path).and_return(yaml_content) + end + + + describe 'with valid configurations' do + let(:yaml_content) { "---\nnpmVersion: 2.0.0\napiVersion: v20111101\n" } + let(:api_version) { 'v20111101' } + + it 'validates v20111101 config with major version 2' do + expect(subject).to be true + end + + context 'with v20250224' do + let(:yaml_content) { "---\nnpmVersion: 3.0.0\napiVersion: v20250224\n" } + let(:api_version) { 'v20250224' } + + it 'validates v20250224 config with major version 3' do + expect(subject).to be true + end + end + + context 'with different minor and patch versions' do + context 'for v20111101' do + let(:yaml_content) { "---\nnpmVersion: 2.1.5\napiVersion: v20111101\n" } + let(:api_version) { 'v20111101' } + + it 'validates with minor.patch variations' do + expect(subject).to be true + end + end + + context 'for v20250224' do + let(:yaml_content) { "---\nnpmVersion: 3.2.1\napiVersion: v20250224\n" } + let(:api_version) { 'v20250224' } + + it 'validates with minor.patch variations' do + expect(subject).to be true + end + end + end + end + + describe 'with invalid API version' do + let(:api_version) { 'v99999999' } + + it 'raises error when API version is not supported' do + expect { subject }.to raise_error(/Invalid API version: v99999999/) + end + + it 'includes list of supported versions in error message' do + expect { subject }.to raise_error(/Supported versions: v20111101, v20250224/) + end + end + + describe 'with missing config file' do + let(:file_exists) { false } + + it 'raises error when config file does not exist' do + expect { subject }.to raise_error(/Config file not found: #{Regexp.escape(path)}/) + end + end + + describe 'with semantic versioning errors' do + let(:yaml_content) { "---\nnpmVersion: 3.0.0\napiVersion: v20111101\n" } + let(:api_version) { 'v20111101' } + + it 'raises descriptive error with all required details' do + expect { subject }.to raise_error(/Semantic versioning error.*must use npm major version 2.*found 3.*Update config with correct major version: 2\.x\.x/m) + end + end + + describe 'with malformed config file' do + let(:yaml_content) { "---\n invalid: yaml: invalid syntax:" } + + it 'raises error when YAML is syntactically invalid' do + expect { subject }.to raise_error(/Config file syntax error|does not contain valid YAML/) + end + end + + describe 'with missing npmVersion field' do + let(:yaml_content) { "---\ngeneratorName: typescript-axios\napiVersion: v20111101\n" } + let(:api_version) { 'v20111101' } + + it 'raises error when npmVersion is not in config' do + expect { subject }.to raise_error(/missing npmVersion field/) + end + end + + describe 'behavior across all supported versions' do + ConfigValidator::SUPPORTED_VERSIONS.each do |api_ver, expected_major| + describe "for #{api_ver}" do + let(:api_version) { api_ver } + + it 'allows any minor.patch combination for major version' do + [0, 1, 5, 10].each do |minor| + [0, 1, 5, 10].each do |patch| + version = "#{expected_major}.#{minor}.#{patch}" + + allow(File).to receive(:read).with(path).and_return("---\nnpmVersion: #{version}\napiVersion: #{api_ver}\n") + + expect { ConfigValidator.validate!(path, api_ver) }.not_to raise_error + end + end + end + + it 'rejects any other major version' do + wrong_major = expected_major == 2 ? 3 : 2 + version = "#{wrong_major}.0.0" + + allow(File).to receive(:read).with(path).and_return("---\nnpmVersion: #{version}\napiVersion: #{api_ver}\n") + + expect { ConfigValidator.validate!(path, api_ver) }.to raise_error(/Semantic versioning error/) + end + end + end + end +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..cba6872 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,27 @@ +GEM + remote: https://rubygems.org/ + specs: + diff-lcs (1.6.2) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.6) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.6) + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + rspec (~> 3.12) + +BUNDLED WITH + 2.6.9 From b1d50341380e8c869cf6f8def022640cdcc4e84f Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 22:13:27 -0700 Subject: [PATCH 08/16] Update config validator and includes in all docs --- .github/config_validator.rb | 3 + .github/workflows/generate.yml | 28 +- .../workflows/generate_publish_release.yml | 4 +- docs/Adding-a-New-API-Version.md | 46 +- docs/Multi-Version-SDK-Flow.md | 2 +- docs/SDK-Generation-Publishing-Flow.md | 419 ------------------ docs/Troubleshooting-Guide.md | 16 +- docs/Workflow-and-Configuration-Reference.md | 46 +- 8 files changed, 90 insertions(+), 474 deletions(-) delete mode 100644 docs/SDK-Generation-Publishing-Flow.md diff --git a/.github/config_validator.rb b/.github/config_validator.rb index 8c59d51..25b3d1d 100644 --- a/.github/config_validator.rb +++ b/.github/config_validator.rb @@ -75,3 +75,6 @@ def validate_semantic_versioning! end end end + +# CLI Interface - allows direct execution from GitHub Actions +ConfigValidator.validate!(ARGV[0], ARGV[1]) if __FILE__ == $0 diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index 64e58db..3954bd3 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -28,37 +28,17 @@ jobs: spec_url: ${{ steps.validate.outputs.spec_url }} steps: - uses: actions/checkout@v3 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.1 - name: Validate configuration id: validate run: | API_VERSION="${{ github.event.inputs.api_version }}" CONFIG_FILE="openapi/config-${API_VERSION}.yml" - # Versioned spec URLs (manual workflow uses 'master' branch) - # Note: Manual workflow doesn't need commit SHA since developer controls timing - # CDN cache race condition only affects automated repository_dispatch triggers - # v20111101 -> https://raw.githubusercontent.com/mxenabled/openapi/master/openapi/v20111101.yml - # v20250224 -> https://raw.githubusercontent.com/mxenabled/openapi/master/openapi/v20250224.yml SPEC_URL="https://raw.githubusercontent.com/mxenabled/openapi/master/openapi/${API_VERSION}.yml" - # Check config file exists - if [ ! -f "$CONFIG_FILE" ]; then - echo "❌ Config file not found: $CONFIG_FILE" - exit 1 - fi - - # Validate semantic versioning (major must match API version) - CURRENT_VERSION=$(grep 'npmVersion' "$CONFIG_FILE" | cut -d':' -f2 | tr -d ' ') - MAJOR_VERSION=$(echo "$CURRENT_VERSION" | cut -d'.' -f1) - - if [ "$API_VERSION" = "v20111101" ] && [ "$MAJOR_VERSION" != "2" ]; then - echo "❌ Semantic versioning error: v20111101 must have major version 2, found $MAJOR_VERSION" - exit 1 - fi - - if [ "$API_VERSION" = "v20250224" ] && [ "$MAJOR_VERSION" != "3" ]; then - echo "❌ Semantic versioning error: v20250224 must have major version 3, found $MAJOR_VERSION" - exit 1 - fi + ruby .github/config_validator.rb "$CONFIG_FILE" "$API_VERSION" echo "✅ Validation passed" echo "config_file=$CONFIG_FILE" >> $GITHUB_OUTPUT diff --git a/.github/workflows/generate_publish_release.yml b/.github/workflows/generate_publish_release.yml index 20eda21..0cb2c2a 100644 --- a/.github/workflows/generate_publish_release.yml +++ b/.github/workflows/generate_publish_release.yml @@ -59,6 +59,8 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: 3.1 + - name: Validate configuration + run: ruby .github/config_validator.rb "${{ matrix.config_file }}" "${{ matrix.api_version }}" - name: Bump version id: bump_version run: | @@ -75,7 +77,7 @@ jobs: # Versioned spec URLs with commit SHA to avoid GitHub CDN cache race condition # Problem: GitHub's raw.githubusercontent.com CDN caches files for 5 minutes # If openapi repo commits and immediately triggers this workflow, CDN may serve stale spec - # Solution: Use commit SHA in URL to bypass cache and guarantee correct spec version + # Using commit SHA in URL to bypass cache and guarantee correct spec version # Falls back to 'master' if openapi doesn't send commit_sha openapi-generator-cli generate \ -i https://raw.githubusercontent.com/mxenabled/openapi/${{ github.event.client_payload.commit_sha || 'master' }}/openapi/${{ matrix.api_version }}.yml \ diff --git a/docs/Adding-a-New-API-Version.md b/docs/Adding-a-New-API-Version.md index 9c37fba..3db0ae4 100644 --- a/docs/Adding-a-New-API-Version.md +++ b/docs/Adding-a-New-API-Version.md @@ -85,28 +85,28 @@ api_version: - v20300101 # NEW ``` -**Location 2: Semantic versioning validation** +**Location 2: Update ConfigValidator** -In the `Validate` job's validation step, add a new conditional check for your version: +In `.github/config_validator.rb`, add your new version to the `SUPPORTED_VERSIONS` mapping: -```yaml -if [ "$API_VERSION" = "v20111101" ] && [ "$MAJOR_VERSION" != "2" ]; then - echo "❌ Semantic versioning error: v20111101 must have major version 2, found $MAJOR_VERSION" - exit 1 -fi - -if [ "$API_VERSION" = "v20250224" ] && [ "$MAJOR_VERSION" != "3" ]; then - echo "❌ Semantic versioning error: v20250224 must have major version 3, found $MAJOR_VERSION" - exit 1 -fi - -if [ "$API_VERSION" = "v20300101" ] && [ "$MAJOR_VERSION" != "4" ]; then - echo "❌ Semantic versioning error: v20300101 must have major version 4, found $MAJOR_VERSION" - exit 1 -fi +```ruby +class ConfigValidator + SUPPORTED_VERSIONS = { + "v20111101" => 2, # Major version must be 2 + "v20250224" => 3, # Major version must be 3 + "v20300101" => 4 # Major version must be 4 (NEW) + }.freeze + # ... rest of class unchanged +end ``` -This ensures the major version in your config file matches the expected value for that API version. +The `ConfigValidator` automatically validates that: +- The API version is in `SUPPORTED_VERSIONS` +- The major version in your config file matches the expected value (e.g., v20300101 → major version 4) +- The config file syntax is valid YAML +- The file exists at the specified path + +**No workflow changes needed** — the existing validation step in `generate.yml` calls `ConfigValidator` with your new version, and it automatically validates using the updated mapping. ### 2.2 Update generate_publish_release.yml @@ -363,7 +363,7 @@ Use this checklist to verify you've completed all steps: - [ ] Created `openapi/config-v20300101.yml` with correct syntax - [ ] Major version in config is unique and sequential (4.0.0 for v20300101) - [ ] Updated `.github/workflows/generate.yml` with new version in dropdown options -- [ ] Updated `.github/workflows/generate.yml` with semantic versioning validation +- [ ] Updated `.github/config_validator.rb` with new version in `SUPPORTED_VERSIONS` mapping - [ ] Updated `.github/workflows/generate_publish_release.yml` with version-to-config mapping in Setup job - [ ] Updated `.github/changelog_manager.rb` with new version in `API_VERSION_ORDER` array - [ ] Updated `.github/workflows/on-push-master.yml` path triggers with `v20300101/**` @@ -397,16 +397,16 @@ Use this checklist to verify you've completed all steps: **Solution**: Verify the version is listed in the `on.workflow_dispatch.inputs.api_version.options` section and YAML syntax is valid ### Semantic versioning validation fails -**Cause**: Validation check missing for new version or major version mismatch -**Solution**: Ensure the validation check for your version is added to generate.yml and the major version in your config matches the expected value +**Cause**: ConfigValidator not updated with new version or major version mismatch +**Solution**: Ensure the new version is added to `SUPPORTED_VERSIONS` in `.github/config_validator.rb` and the major version in your config file matches the expected value ### Generated version is 2.x.x or 3.x.x instead of 4.0.0 **Cause**: Wrong major version in config file **Solution**: Update `npmVersion: 4.0.0` in config file to use unique major version ### generate_publish_release.yml doesn't recognize new version -**Cause**: Version-to-config mapping missing in Setup job or ChangelogManager not updated -**Solution**: Verify two locations in generate_publish_release.yml are updated: (1) version-to-config mapping in Setup job, and (2) add version to API_VERSION_ORDER in changelog_manager.rb +**Cause**: Version-to-config mapping missing in Setup job, ChangelogManager not updated, or ConfigValidator not updated +**Solution**: Verify three locations are updated: (1) version-to-config mapping in generate_publish_release.yml Setup job, (2) add version to `API_VERSION_ORDER` in `changelog_manager.rb`, and (3) add version to `SUPPORTED_VERSIONS` in `config_validator.rb` ### on-push-master.yml doesn't trigger after merge **Cause**: Path trigger syntax incorrect or matrix not updated diff --git a/docs/Multi-Version-SDK-Flow.md b/docs/Multi-Version-SDK-Flow.md index 3254d98..6945835 100644 --- a/docs/Multi-Version-SDK-Flow.md +++ b/docs/Multi-Version-SDK-Flow.md @@ -2,7 +2,7 @@ **Document Purpose**: Quick-reference guide to the multi-version SDK generation, publishing, and release system. This is your entry point to understanding how the system works. -**Last Updated**: January 27, 2026 +**Last Updated**: January 28, 2026 **Read Time**: 5-10 minutes **Audience**: Anyone joining the team or needing a system overview diff --git a/docs/SDK-Generation-Publishing-Flow.md b/docs/SDK-Generation-Publishing-Flow.md deleted file mode 100644 index a932625..0000000 --- a/docs/SDK-Generation-Publishing-Flow.md +++ /dev/null @@ -1,419 +0,0 @@ -# SDK Generation, Publishing, and Release Flow (LEGACY - Single-Version) - -> ⚠️ **ARCHIVED DOCUMENTATION** - This document describes the **single-version SDK system** that was in use before multi-version support was added. -> -> **For current documentation**, see: -> - [Multi-Version-SDK-Flow.md](Multi-Version-SDK-Flow.md) - Current system overview -> - [Workflow-and-Configuration-Reference.md](Workflow-and-Configuration-Reference.md) - Current technical details -> -> This document is kept for **historical reference** and may be useful for repositories that have not yet migrated to multi-version support. - -**Document Purpose**: This document explains how the Node.js SDK was automatically generated, published to npm, and released in the single-version system. It covers both automatic triggers (from the OpenAPI repository) and manual generation flows (for development and testing). - -**Last Updated**: January 20, 2026 (ARCHIVED) -**Repository**: mx-platform-node -**Author**: DevExperience Team -**Status**: Legacy - Superseded by multi-version flows - ---- - -## Overview - -The mx-platform-node repository has a fully automated pipeline for generating TypeScript SDKs from OpenAPI specifications, publishing them to npm, and creating releases on GitHub. The process is triggered in two ways: - -1. **Automatic Flow** - When OpenAPI specifications change in the upstream `openapi` repository -2. **Manual Flow** - When developers manually trigger generation for development, testing, or version bumps - -Both flows use the same underlying generation logic but differ in how they handle commits, publishing, and release creation. - ---- - -## Flow 1: Automatic Generation (Repository Dispatch) - -### Trigger -OpenAPI specifications in the upstream `openapi` repository change → Repository sends `repository_dispatch` event to `mx-platform-node` → `generate_publish_release.yml` workflow is triggered - -### Current Implementation - -**Workflow**: `.github/workflows/generate_publish_release.yml` - -This is the **production flow** that automatically generates, publishes, and releases SDKs when upstream APIs change. - -#### Step 1: Generate SDK from OpenAPI Spec -- **Input**: OpenAPI specification from `https://raw.githubusercontent.com/mxenabled/openapi/master/openapi/mx_platform_api.yml` -- **Output Directory**: `./latest/` -- **Configuration**: Uses `./openapi/config.yml` to control generation settings - - Package name: `mx-platform-node` - - **Package version source**: `npmVersion` field in `./openapi/config.yml` (the source of truth) - - **Version bump**: `client_payload.version` from repository dispatch (e.g., "patch", "minor") tells `version.rb` which component to increment - - Flow: `client_payload.version` (bump instruction) → `version.rb` reads config → updates `npmVersion` in config → `package.mustache` uses updated `npmVersion` to create `package.json` -- **Templates**: Uses `./openapi/templates/` (package.mustache, README.mustache, etc.) -- **Process**: - 1. Clean repository using `clean.rb` (removes old generated files) - 2. Bump version: `version.rb` reads current `npmVersion` from config, increments based on `client_payload.version`, writes updated version back to config - 3. Run OpenAPI Generator to create TypeScript-Axios SDK (uses updated config with new `npmVersion`) - 4. Copy documentation files (LICENSE, CHANGELOG.md, MIGRATION.md) to generated directory - -#### Step 2: Commit Changes to Master -- **Git Config**: Uses `devexperience` bot account -- **Commit Message**: `"Generated version X.Y.Z - This commit was automatically created by a GitHub Action..."` -- **Target Branch**: Directly commits to `master` (no PR created) - -#### Step 3: Publish to npm -- **Trigger**: After successful commit, dispatches `publish_sdk` repository_dispatch event -- **Workflow**: `.github/workflows/publish.yml` (triggered via repository_dispatch) -- **Process**: - 1. Navigate to `./latest/` directory - 2. Install dependencies: `npm install` - 3. Publish to npm registry with `--tag next` - 4. Uses NPM_AUTH_TOKEN secret for authentication - -#### Step 4: Create GitHub Release -- **Trigger**: After successful publish, dispatches `release_sdk` repository_dispatch event -- **Workflow**: `.github/workflows/release.yml` (triggered via repository_dispatch) -- **Process**: - 1. Read version from `./latest/package.json` - 2. Create GitHub release tagged with version (e.g., `v2.0.5`) - 3. Slack notification on failure - ---- - -## Flow 2: Manual Generation (Workflow Dispatch) - -### Trigger -Developer manually clicks "Run workflow" on `generate.yml` in GitHub Actions UI - -### User Inputs -The workflow accepts two parameters: -- **version_level**: Which version component to bump (major, minor, or patch) -- Default: patch - -### Current Implementation - -**Workflow**: `.github/workflows/generate.yml` - -This flow is used for: -- Development and testing -- Creating PRs with proposed SDK updates -- Manual version bumping before merging - -#### Step 1: Version Bumping -- **Script**: `ruby .github/version.rb ` -- **Input File**: `./openapi/config.yml` -- **Process**: - 1. Read current version from config file - 2. Increment major/minor/patch based on input - 3. Write updated version back to config file - 4. Output new version for next steps -- **Example**: - - Current: 2.0.5 → Run with "minor" → New: 2.1.0 - -#### Step 2: Clean Repository -- **Script**: `ruby .github/clean.rb` -- **Process**: - 1. Delete all generated files from previous generation - 2. Protect certain directories from deletion (`.git`, `.github`, `openapi`, `LICENSE`, etc.) - 3. Keeps source configuration and workflow files intact - -#### Step 3: Generate SDK -- **Input**: OpenAPI specification from `https://raw.githubusercontent.com/mxenabled/openapi/master/openapi/mx_platform_api.yml` -- **Output Directory**: `./latest/` -- **Configuration**: Uses `./openapi/config.yml` -- **Process**: - 1. Install OpenAPI Generator CLI globally - 2. Run generator with TypeScript-Axios language - 3. Copy documentation files to output directory - -#### Step 4: Create Feature Branch -- **Branch Name**: `openapi-generator-X.Y.Z` (version from step 1) -- **Process**: - 1. Create new branch from current checkout - 2. Stage all changes with `git add .` - 3. Commit with version number in message - -#### Step 5: Create Pull Request -- **Command**: `gh pr create -f` (uses commit message for PR title/body) -- **Destination**: Targets default branch (master) -- **Review**: PR is created and awaits manual review + merge - -#### Step 6: Trigger Publishing (After Merge) -- **Trigger**: When PR is merged to `master`, `on-push-master.yml` workflow activates -- **Condition**: Triggered only if files matching `latest/**` were modified -- **Workflows Called**: - 1. `./.github/workflows/publish.yml` - 2. `./.github/workflows/release.yml` -- **Result**: Same publishing and releasing as automatic flow - ---- - - - -## Supporting Scripts - -### version.rb -**Purpose**: Increment version numbers in configuration files - -**Usage**: `ruby .github/version.rb [config_file]` - -**Important Nuance - npmVersion as Source of Truth**: - -The `npmVersion` field in the config file is the **authoritative source of truth** for the package version. Here's how the version flows through the system: - -1. **Config File** (Source of Truth) - - `openapi/config.yml` contains `npmVersion: 2.0.0` - - This is the persistent, stored version number - - Lives in Git and is checked in with each update - -2. **version.rb Script** (Updates Source of Truth) - - Reads the current `npmVersion` from the config file - - Receives bump instruction from caller: "patch", "minor", or "major" - - Calculates new version: 2.0.0 → 2.0.1 (patch), 2.1.0 (minor), or 3.0.0 (major) - - **Writes updated npmVersion back to config file** (persists to Git) - - Outputs new version to stdout (for workflow logging) - -3. **package.mustache Template** (Uses Source of Truth) - - Contains placeholder: `"version": "{{npmVersion}}"` - - OpenAPI Generator replaces `{{npmVersion}}` with value from config file - - Generates `package.json` with the correct version number - -4. **Result** - - The generated `package.json` always has the correct version - - Version comes entirely from the config file - - No hardcoding in workflows or templates - -**Important Distinction**: -- `client_payload.version` from repository_dispatch is a **bump instruction** (e.g., "patch") -- `npmVersion` in config file is the **actual version number** (e.g., "2.0.0") -- These are different things! The bump instruction is used to calculate the new version number. - -**Behavior**: -- Reads YAML config file -- Parses current version (major.minor.patch) -- Increments requested component -- Resets lower components to 0 (e.g., 2.1.5 → 3.0.0 when major bumped) -- Writes updated config file -- Outputs new version to stdout - -**Config File Parameter**: Optional, defaults to `./openapi/config.yml` - -**Examples**: -```bash -ruby .github/version.rb patch # Bumps config.yml patch version (2.0.0 → 2.0.1) -ruby .github/version.rb minor openapi/config-v20111101.yml # Bumps config-v20111101 minor version (2.0.0 → 2.1.0) -ruby .github/version.rb major openapi/config-v20250224.yml # Bumps config-v20250224 major version (3.0.0 → 4.0.0) -``` - -### clean.rb -**Purpose**: Remove generated SDK files before regeneration - -**Behavior**: -- Walks through repository root directory -- Deletes all files/directories except those in ALLOW_LIST -- Protected directories: `.git`, `.github`, `.openapi-generator-ignore`, `openapi`, `LICENSE`, `README.md`, `CHANGELOG.md`, `MIGRATION.md` -- Prevents accidental deletion of configuration and workflow files - -**Part 5 Note**: When multi-version support is activated, protected list will include: `v20111101`, `v20250224`, and remove `latest` - -**Part 5 Note**: When multi-version support is activated, protected list will include: `v20111101`, `v20250224`, and remove `latest` - ---- - -## Configuration Files - -### openapi/config.yml - -```yaml ---- -generatorName: typescript-axios -npmName: mx-platform-node -npmVersion: 2.0.0 -supportsES6: true -.openapi-generator-ignore: true -``` - -**Used by**: -- `generate.yml` (manual generation) -- `generate_publish_release.yml` (automatic generation) -- `version.rb` script - -### openapi/templates/ -**Purpose**: Customized templates for package generation - -**Files**: -- `package.mustache`: Controls package.json generation - - **Key Feature**: Includes `"files"` field to explicitly control what gets published to npm - - Controls package name, version, scripts, dependencies -- `README.mustache`: Controls README.md generation - ---- - -## Path-Based Triggers - -### on-push-master.yml -**Purpose**: Automatically trigger publish and release workflows when SDK code changes are pushed to master - -```yaml -on: - push: - branches: [master] - paths: - - 'latest/**' # Only trigger on changes in the latest/ directory -``` - -**This prevents**: -- Enhancement PRs (docs only) from triggering publish -- README updates from triggering releases -- Workflow file changes from triggering publish - ---- - -## Workflow Sequences - -The following sequence diagrams show the timeline and interactions for each flow, making it clear when workflows trigger and what happens at each stage. - -### Flow 1: Automatic Generation (Repository Dispatch) - -```mermaid -sequenceDiagram - participant OpenAPI as OpenAPI
Repository - participant GH as GitHub
Actions - participant Gen as generate_publish
_release.yml - participant npm as npm
Registry - participant GHRel as GitHub
Releases - - OpenAPI->>GH: mx_platform_api.yml changes
(repository_dispatch event) - GH->>+Gen: Trigger workflow - Gen->>Gen: 1. Clean repo
(clean.rb) - Gen->>Gen: 2. Bump version
(version.rb) - Gen->>Gen: 3. Generate SDK
(OpenAPI Generator) - Gen->>Gen: 4. Copy docs
(LICENSE, CHANGELOG, MIGRATION) - Gen->>Gen: 5. Commit to master
(devexperience bot) - Gen->>npm: Dispatch publish_sdk event - Gen->>GHRel: Dispatch release_sdk event - deactivate Gen - - rect rgba(0, 255, 0, .1) - note right of npm: Parallel Publishing & Release - npm->>npm: publish.yml:
npm publish --tag next
from ./latest/ - GHRel->>GHRel: release.yml:
gh release create
with version tag - end - - npm-->>GH: ✅ Published - GHRel-->>GH: ✅ Released -``` - -### Flow 2: Manual Generation (Workflow Dispatch) - -```mermaid -sequenceDiagram - participant Dev as Developer - participant GH as GitHub
Actions - participant Gen as generate.yml - participant Review as Code
Review - participant Trigger as on-push-
master.yml - participant npm as npm
Registry - participant GHRel as GitHub
Releases - - Dev->>GH: Run generate.yml
(version_level: major/minor/patch) - GH->>+Gen: Trigger workflow - Gen->>Gen: 1. Bump version
(version.rb) - Gen->>Gen: 2. Clean repo
(clean.rb) - Gen->>Gen: 3. Generate SDK
(OpenAPI Generator) - Gen->>Gen: 4. Copy docs - Gen->>Gen: 5. Create feature branch
(openapi-generator-X.Y.Z) - Gen->>Gen: 6. Create Pull Request - deactivate Gen - - Review->>Review: 👤 Code Review - Review->>Review: Approve & Merge
to master - - GH->>+Trigger: on-push-master.yml
(path: latest/**) - Trigger->>Trigger: ✅ Path matched - Trigger->>npm: workflow_call publish.yml - Trigger->>GHRel: workflow_call release.yml - deactivate Trigger - - rect rgba(0, 255, 0, .1) - note right of npm: Parallel Publishing & Release - npm->>npm: publish.yml:
npm publish --tag next
from ./latest/ - GHRel->>GHRel: release.yml:
gh release create
with version tag - end - - npm-->>GH: ✅ Published - GHRel-->>GH: ✅ Released -``` - ---- - ---- - -## Environment Variables & Secrets Used - -### Required Secrets (`.github/secrets`) - -| Secret | Used In | Purpose | -|--------|---------|---------| -| `NPM_AUTH_TOKEN` | publish.yml | Authenticate to npm registry for publishing | -| `GITHUB_TOKEN` | All workflows | GitHub API access (auto-provided, but sometimes explicitly referenced) | -| `SLACK_WEBHOOK_URL` | All workflows | Send failure notifications to Slack | -| `PAPI_SDK_APP_ID` | generate_publish_release.yml | GitHub App ID for custom token generation | -| `PAPI_SDK_INSTALLATION_ID` | generate_publish_release.yml | GitHub App installation ID | -| `PAPI_SDK_PRIVATE_KEY` | generate_publish_release.yml | GitHub App private key | - -### Environment Setup - -- **Node**: v20.x -- **Ruby**: 3.1 -- **OpenAPI Generator**: Latest version (installed via npm during workflow) - ---- - -## Current Implementation Status - -### ✅ Fully Implemented -- Automatic generation from OpenAPI spec changes (Flow 1) -- Manual generation for PRs with version bumping (Flow 2) -- Path-based triggers for publish/release -- npm publishing with semantic versioning -- GitHub releases -- Slack notifications -- v2.0.0 stable release confirmed - -### Key Architectural Decisions - -1. **Direct Commit vs PR**: - - Automatic flow (Flow 1) commits directly to master - - Manual flow (Flow 2) creates PR for review - -2. **Tag Strategy**: - - `--tag next`: Used during auto-publish (for staging validation) - - Can be promoted to `latest` tag after validation - -3. **Path-Based Triggers**: Only changes to SDK code (`latest/**`) trigger publish/release, not docs or workflows - -4. **Atomic Operations**: Publishing and releasing happen in sequence within same workflow, preventing version mismatches - ---- - -## Troubleshooting - -### NPM Publish Fails -- Check `NPM_AUTH_TOKEN` secret is valid -- Verify token has publish permissions for `mx-platform-node` package -- Check version isn't already published - -### Release Creation Fails -- Verify `GITHUB_TOKEN` has release creation permissions -- Check if tag already exists (gh release fails if tag exists) - -### Generation Produces No Changes -- Verify OpenAPI spec URL is accessible -- Check openapi-generator-cli installed correctly -- Verify config file exists and is valid YAML - -### publish.yml or release.yml Don't Trigger After Generate -- Check path filter in `on-push-master.yml` matches changed files -- Verify files were actually committed (`git log` to confirm) -- Check branch is `master` and not another branch - ---- diff --git a/docs/Troubleshooting-Guide.md b/docs/Troubleshooting-Guide.md index 3bbab76..90e05e9 100644 --- a/docs/Troubleshooting-Guide.md +++ b/docs/Troubleshooting-Guide.md @@ -2,7 +2,7 @@ **Document Purpose**: Quick reference for diagnosing and fixing issues in the multi-version SDK generation, publishing, and release workflows. -**Last Updated**: January 27, 2026 +**Last Updated**: January 28, 2026 **Audience**: Developers debugging workflow failures --- @@ -31,6 +31,20 @@ Check in this order: ## Common Issues and Solutions +### Generate Workflow: Configuration Validation Fails + +The `generate.yml` and `generate_publish_release.yml` workflows run configuration validation before SDK generation. If validation fails, the workflow stops immediately to prevent invalid configurations from generating code. + +**Validator**: `.github/config_validator.rb` + +Validation checks (in order): +1. API version is supported (v20111101 or v20250224) +2. Config file exists at specified path +3. Config file contains valid YAML +4. Major version in config matches API version requirement + +--- + ### Generate Workflow: Config File Not Found **Error Message**: diff --git a/docs/Workflow-and-Configuration-Reference.md b/docs/Workflow-and-Configuration-Reference.md index 6a29d47..27ab3ad 100644 --- a/docs/Workflow-and-Configuration-Reference.md +++ b/docs/Workflow-and-Configuration-Reference.md @@ -2,7 +2,7 @@ **Document Purpose**: Detailed technical reference for the multi-version SDK generation, publishing, and release workflows. Covers implementation details, configuration files, and system architecture. -**Last Updated**: January 27, 2026 +**Last Updated**: January 28, 2026 **Audience**: Developers who need to understand or modify the implementation --- @@ -189,11 +189,17 @@ Developer manually clicks "Run workflow" on `generate.yml` in GitHub Actions UI **Workflow**: `.github/workflows/generate.yml` -#### Step 1: Validate Inputs +#### Step 1: Validate Configuration -- **Config File Exists**: Verify selected API version config file exists -- **Semantic Versioning Check**: Verify major version matches API version -- **Fail Fast**: If validation fails, workflow stops before any generation +- **Script**: `ruby .github/config_validator.rb ` +- **Validation Checks**: + 1. **API Version Supported**: Verifies API version is in supported versions list (v20111101, v20250224) + 2. **Config File Exists**: Checks that config file exists at specified path + 3. **Config File Readable**: Validates YAML syntax and structure (must parse to Hash) + 4. **Semantic Versioning**: Enforces major version matches API version (v20111101→2.x.x, v20250224→3.x.x) +- **Fail Fast**: If any validation fails, workflow stops before version bumping or generation +- **Error Messages**: Clear, detailed messages indicate which check failed and how to fix it +- **See**: [Troubleshooting-Guide.md](Troubleshooting-Guide.md) for specific error messages and solutions #### Step 2: Version Bumping (Conditional) @@ -408,6 +414,36 @@ The `npmVersion` field in the config file is the **authoritative source of truth - Required parameter: must provide version directory name - Error if parameter missing: raises clear error message +### changelog_manager.rb - Automatic CHANGELOG Updates + +**File**: `.github/changelog_manager.rb` + +**Purpose**: Maintain a shared CHANGELOG.md across multiple API versions with proper version ordering and date ranges + +**Usage**: `ruby .github/changelog_manager.rb v20111101,v20250224` + +**Key Features**: +- **Version Extraction**: Reads version numbers from each API's `package.json` +- **Priority Sorting**: Automatically sorts entries by version (newest first), ensuring changelog follows standard conventions regardless of input order +- **Date Range Tracking**: Calculates date ranges showing what changed since the last update for each API version +- **Atomic Updates**: Inserts new entries at the top of the changelog with proper formatting +- **Validation**: Confirms versions are supported before processing + +**When It's Called**: +- `generate.yml`: After generating a single API version (manual flow) +- `generate_publish_release.yml`: After generating multiple API versions (automatic flow) + +**Example Output**: +```markdown +## [3.2.0] - 2025-01-28 (v20250224 API) +Updated v20250224 API specification... + +## [2.5.3] - 2025-01-28 (v20111101 API) +Updated v20111101 API specification... +``` + +**For Detailed Implementation**: See [Changelog-Manager.md](Changelog-Manager.md) for class methods, version ordering logic, and how to extend it for new API versions. + --- ## Configuration Files From 2fc5340b37f3951819ce9eef6edb5b3b60c9a80f Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 22:35:44 -0700 Subject: [PATCH 09/16] Rename generate_publish_release and all doc references --- ...h_release.yml => openapi-generate-and-push.yml} | 2 +- docs/ARCHIVED/SDK-Generation-Publishing-Flow.md | 12 ++++++------ docs/Adding-a-New-API-Version.md | 14 +++++++------- docs/Multi-Version-SDK-Flow.md | 8 ++++---- docs/Troubleshooting-Guide.md | 4 ++-- docs/Workflow-and-Configuration-Reference.md | 10 +++++----- 6 files changed, 25 insertions(+), 25 deletions(-) rename .github/workflows/{generate_publish_release.yml => openapi-generate-and-push.yml} (99%) diff --git a/.github/workflows/generate_publish_release.yml b/.github/workflows/openapi-generate-and-push.yml similarity index 99% rename from .github/workflows/generate_publish_release.yml rename to .github/workflows/openapi-generate-and-push.yml index 0cb2c2a..13a020f 100644 --- a/.github/workflows/generate_publish_release.yml +++ b/.github/workflows/openapi-generate-and-push.yml @@ -1,4 +1,4 @@ -name: Generate Publish Release +name: "OpenAPI: Automated Generate and Push" on: repository_dispatch: diff --git a/docs/ARCHIVED/SDK-Generation-Publishing-Flow.md b/docs/ARCHIVED/SDK-Generation-Publishing-Flow.md index a932625..0fbb4cb 100644 --- a/docs/ARCHIVED/SDK-Generation-Publishing-Flow.md +++ b/docs/ARCHIVED/SDK-Generation-Publishing-Flow.md @@ -31,11 +31,11 @@ Both flows use the same underlying generation logic but differ in how they handl ## Flow 1: Automatic Generation (Repository Dispatch) ### Trigger -OpenAPI specifications in the upstream `openapi` repository change → Repository sends `repository_dispatch` event to `mx-platform-node` → `generate_publish_release.yml` workflow is triggered +OpenAPI specifications in the upstream `openapi` repository change → Repository sends `repository_dispatch` event to `mx-platform-node` → `openapi-generate-and-push.yml` workflow is triggered ### Current Implementation -**Workflow**: `.github/workflows/generate_publish_release.yml` +**Workflow**: `.github/workflows/openapi-generate-and-push.yml` This is the **production flow** that automatically generates, publishes, and releases SDKs when upstream APIs change. @@ -233,7 +233,7 @@ supportsES6: true **Used by**: - `generate.yml` (manual generation) -- `generate_publish_release.yml` (automatic generation) +- `openapi-generate-and-push.yml` (automatic generation) - `version.rb` script ### openapi/templates/ @@ -356,9 +356,9 @@ sequenceDiagram | `NPM_AUTH_TOKEN` | publish.yml | Authenticate to npm registry for publishing | | `GITHUB_TOKEN` | All workflows | GitHub API access (auto-provided, but sometimes explicitly referenced) | | `SLACK_WEBHOOK_URL` | All workflows | Send failure notifications to Slack | -| `PAPI_SDK_APP_ID` | generate_publish_release.yml | GitHub App ID for custom token generation | -| `PAPI_SDK_INSTALLATION_ID` | generate_publish_release.yml | GitHub App installation ID | -| `PAPI_SDK_PRIVATE_KEY` | generate_publish_release.yml | GitHub App private key | +| `PAPI_SDK_APP_ID` | openapi-generate-and-push.yml | GitHub App ID for custom token generation | +| `PAPI_SDK_INSTALLATION_ID` | openapi-generate-and-push.yml | GitHub App installation ID | +| `PAPI_SDK_PRIVATE_KEY` | openapi-generate-and-push.yml | GitHub App private key | ### Environment Setup diff --git a/docs/Adding-a-New-API-Version.md b/docs/Adding-a-New-API-Version.md index 3db0ae4..9e64594 100644 --- a/docs/Adding-a-New-API-Version.md +++ b/docs/Adding-a-New-API-Version.md @@ -108,9 +108,9 @@ The `ConfigValidator` automatically validates that: **No workflow changes needed** — the existing validation step in `generate.yml` calls `ConfigValidator` with your new version, and it automatically validates using the updated mapping. -### 2.2 Update generate_publish_release.yml +### 2.2 Update openapi-generate-and-push.yml -This workflow is automatically triggered by the OpenAPI repository to generate and publish SDKs for all versions in parallel. +This workflow is automatically triggered by the OpenAPI repository to generate and push SDKs for all versions in parallel. **Location 1: Version-to-config mapping** @@ -198,7 +198,7 @@ Check that your YAML is valid for all three modified files: ```bash ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/generate.yml'))" -ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/generate_publish_release.yml'))" +ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/openapi-generate-and-push.yml'))" ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/on-push-master.yml'))" ``` @@ -364,7 +364,7 @@ Use this checklist to verify you've completed all steps: - [ ] Major version in config is unique and sequential (4.0.0 for v20300101) - [ ] Updated `.github/workflows/generate.yml` with new version in dropdown options - [ ] Updated `.github/config_validator.rb` with new version in `SUPPORTED_VERSIONS` mapping -- [ ] Updated `.github/workflows/generate_publish_release.yml` with version-to-config mapping in Setup job +- [ ] Updated `.github/workflows/openapi-generate-and-push.yml` with version-to-config mapping in Setup job - [ ] Updated `.github/changelog_manager.rb` with new version in `API_VERSION_ORDER` array - [ ] Updated `.github/workflows/on-push-master.yml` path triggers with `v20300101/**` - [ ] Updated `.github/workflows/on-push-master.yml` publish job matrix with new version @@ -404,9 +404,9 @@ Use this checklist to verify you've completed all steps: **Cause**: Wrong major version in config file **Solution**: Update `npmVersion: 4.0.0` in config file to use unique major version -### generate_publish_release.yml doesn't recognize new version +### openapi-generate-and-push.yml doesn't recognize new version **Cause**: Version-to-config mapping missing in Setup job, ChangelogManager not updated, or ConfigValidator not updated -**Solution**: Verify three locations are updated: (1) version-to-config mapping in generate_publish_release.yml Setup job, (2) add version to `API_VERSION_ORDER` in `changelog_manager.rb`, and (3) add version to `SUPPORTED_VERSIONS` in `config_validator.rb` +**Solution**: Verify three locations are updated: (1) version-to-config mapping in openapi-generate-and-push.yml Setup job, (2) add version to `API_VERSION_ORDER` in `changelog_manager.rb`, and (3) add version to `SUPPORTED_VERSIONS` in `config_validator.rb` ### on-push-master.yml doesn't trigger after merge **Cause**: Path trigger syntax incorrect or matrix not updated @@ -426,7 +426,7 @@ Once verified: 2. **Create PR**: Get code review of workflow changes 3. **Merge PR**: Once approved, merge to master 4. **Wait for OpenAPI updates**: New version won't generate until OpenAPI repo sends it in payload -5. **Monitor first generation**: Watch the automatic `generate_publish_release.yml` run when OpenAPI repo triggers it +5. **Monitor first generation**: Watch the automatic `openapi-generate-and-push.yml` run when OpenAPI repo triggers it --- diff --git a/docs/Multi-Version-SDK-Flow.md b/docs/Multi-Version-SDK-Flow.md index 6945835..49c06f9 100644 --- a/docs/Multi-Version-SDK-Flow.md +++ b/docs/Multi-Version-SDK-Flow.md @@ -28,7 +28,7 @@ Each version is independently generated, tested, published to npm, and released ## Three Ways Things Happen ### 🤖 Flow 1: Automatic (Upstream Triggers) -OpenAPI spec changes → `generate_publish_release.yml` runs → SDK generated, published, released +OpenAPI spec changes → `openapi-generate-and-push.yml` runs → SDK generated, published, released **When**: OpenAPI repository sends `repository_dispatch` with API versions **Who**: Automated, no human intervention @@ -173,15 +173,15 @@ sequenceDiagram | File | Purpose | Used By | |------|---------|---------| -| `.github/workflows/generate_publish_release.yml` | Automatic generation from upstream API changes | OpenAPI repo | +| `.github/workflows/openapi-generate-and-push.yml` | Automatic generation from upstream API changes | OpenAPI repo | | `.github/workflows/generate.yml` | Manual generation with version selection | Developer | | `.github/workflows/on-push-master.yml` | Auto-publish trigger with path-based matrix | Any master push | | `.github/workflows/publish.yml` | Publishes SDK to npm | publish_release & on-push-master | | `.github/workflows/release.yml` | Creates GitHub release | publish_release & on-push-master | | `.github/version.rb` | Bumps version in config files | Workflows | | `.github/clean.rb` | Removes old generated files | Workflows | -| `openapi/config-v20111101.yml` | Config for v20111101 generation | generate_publish_release & generate | -| `openapi/config-v20250224.yml` | Config for v20250224 generation | generate_publish_release & generate | +| `openapi/config-v20111101.yml` | Config for v20111101 generation | openapi-generate-and-push & generate | +| `openapi/config-v20250224.yml` | Config for v20250224 generation | openapi-generate-and-push & generate | | `openapi/templates/package.mustache` | npm package.json template | OpenAPI Generator | | `openapi/templates/README.mustache` | README.md template | OpenAPI Generator | diff --git a/docs/Troubleshooting-Guide.md b/docs/Troubleshooting-Guide.md index 90e05e9..1c8ac1c 100644 --- a/docs/Troubleshooting-Guide.md +++ b/docs/Troubleshooting-Guide.md @@ -33,7 +33,7 @@ Check in this order: ### Generate Workflow: Configuration Validation Fails -The `generate.yml` and `generate_publish_release.yml` workflows run configuration validation before SDK generation. If validation fails, the workflow stops immediately to prevent invalid configurations from generating code. +The `generate.yml` and `openapi-generate-and-push.yml` workflows run configuration validation before SDK generation. If validation fails, the workflow stops immediately to prevent invalid configurations from generating code. **Validator**: `.github/config_validator.rb` @@ -275,7 +275,7 @@ fatal: A release with this tag already exists - Workflow runs at 2:02 PM but CDN still has 2:00 PM version - SDK generated from stale spec -**Solution**: Already implemented in `generate_publish_release.yml` +**Solution**: Already implemented in `openapi-generate-and-push.yml` - Uses commit SHA in spec URL: `raw.githubusercontent.com/mxenabled/openapi//openapi/v20111101.yml` - Commit SHA bypasses CDN and guarantees exact spec version - Nothing to do—this is automatic diff --git a/docs/Workflow-and-Configuration-Reference.md b/docs/Workflow-and-Configuration-Reference.md index 27ab3ad..4ef9588 100644 --- a/docs/Workflow-and-Configuration-Reference.md +++ b/docs/Workflow-and-Configuration-Reference.md @@ -10,7 +10,7 @@ ## Flow 1: Automatic Multi-Version Generation (Repository Dispatch) ### Trigger -OpenAPI specifications change in the upstream `openapi` repository → Repository sends `repository_dispatch` event with optional `api_versions` payload → `generate_publish_release.yml` workflow is triggered +OpenAPI specifications change in the upstream `openapi` repository → Repository sends `repository_dispatch` event with optional `api_versions` payload → `openapi-generate-and-push.yml` workflow is triggered ### Backward Compatibility Model - **No Payload (v20111101 only)**: If openapi repo sends `repository_dispatch` without `api_versions` field, defaults to generating v20111101 only @@ -21,7 +21,7 @@ This allows phased migration: current behavior works as-is, and when the openapi ### Implementation -**Workflow**: `.github/workflows/generate_publish_release.yml` +**Workflow**: `.github/workflows/openapi-generate-and-push.yml` #### Step 1: Setup - Determine Versions to Generate @@ -130,7 +130,7 @@ strategy: **Architecture**: After `Commit-and-Push` completes and pushes to master, the automatic `on-push-master.yml` workflow is triggered by GitHub's push event. **Why This Architecture?** -- Separates concerns: `generate_publish_release.yml` owns generation, `on-push-master.yml` owns publishing +- Separates concerns: `openapi-generate-and-push.yml` owns generation, `on-push-master.yml` owns publishing - Enables consistent publish logic: All publishes (whether from automated generation or manual PR merge) go through the same workflow - Prevents duplicate publishes: Manual generate.yml + PR merge only triggers publish once (via on-push-master.yml) @@ -431,7 +431,7 @@ The `npmVersion` field in the config file is the **authoritative source of truth **When It's Called**: - `generate.yml`: After generating a single API version (manual flow) -- `generate_publish_release.yml`: After generating multiple API versions (automatic flow) +- `openapi-generate-and-push.yml`: After generating multiple API versions (automatic flow) **Example Output**: ```markdown @@ -604,7 +604,7 @@ OpenAPI Repo: Commits change to v20111101.yml and v20250224.yml ↓ repository_dispatch: {"api_versions": "v20111101,v20250224"} ↓ -generate_publish_release.yml: Triggered +openapi-generate-and-push.yml: Triggered ├─ Setup: Create matrix from api_versions ├─ Matrix[v20111101]: Clean, Bump, Generate (parallel) ├─ Matrix[v20250224]: Clean, Bump, Generate (parallel) From 301478f40f8ad88320f1c896b8e1d9e6f519244a Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 22:45:23 -0700 Subject: [PATCH 10/16] Remove ruby 3.0 --- .github/workflows/run-specs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-specs.yml b/.github/workflows/run-specs.yml index 492c9e7..6cb6ac6 100644 --- a/.github/workflows/run-specs.yml +++ b/.github/workflows/run-specs.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: - ruby-version: ['3.0', '3.1', '3.2'] + ruby-version: ['3.1', '3.2'] steps: - uses: actions/checkout@v4 From a932caaa65dbf37277a07d1c2d6a2ef2ec1cd30a Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 23:11:25 -0700 Subject: [PATCH 11/16] Re-audit of docs and so many updates --- docs/Adding-a-New-API-Version.md | 67 ++++++--- docs/Multi-Version-SDK-Flow.md | 122 +++++++--------- docs/Troubleshooting-Guide.md | 2 +- docs/Workflow-and-Configuration-Reference.md | 142 ++++++++++++++----- 4 files changed, 206 insertions(+), 127 deletions(-) diff --git a/docs/Adding-a-New-API-Version.md b/docs/Adding-a-New-API-Version.md index 9e64594..20dd24a 100644 --- a/docs/Adding-a-New-API-Version.md +++ b/docs/Adding-a-New-API-Version.md @@ -2,7 +2,7 @@ **Document Purpose**: Step-by-step guide for adding support for a new API version (e.g., `v20300101`) to the mx-platform-node repository. -**Last Updated**: January 27, 2026 +**Last Updated**: January 28, 2026 **Time to Complete**: 30-45 minutes **Prerequisites**: Familiarity with the multi-version architecture (see [Multi-Version-SDK-Flow.md](Multi-Version-SDK-Flow.md)) @@ -146,7 +146,7 @@ This ensures when multiple versions are generated, changelog entries appear in o ### 2.3 Update on-push-master.yml -This workflow automatically triggers publish and release jobs when version directories are pushed to master. +This workflow automatically triggers publish and release jobs when version directories are pushed to master. Since individual version jobs use conditional `if` statements based on path changes, you need to add new conditional jobs for your new version. **Location 1: Path trigger** @@ -164,34 +164,56 @@ on: This ensures the workflow triggers when changes to your version directory are pushed to master. -**Location 2: Publish job matrix** +**Location 2: Add publish job for new version** -In the `publish` job's strategy matrix, add your version entry: +Add a new publish job for your version (copy and modify the existing v20250224 jobs): ```yaml -strategy: - matrix: - version: - - api_version: v20111101 - - api_version: v20250224 - - api_version: v20300101 # NEW - fail-fast: false +publish-v20300101: + runs-on: ubuntu-latest + needs: [check-skip-publish, gate-v20250224-complete] # Gate waits for previous version + if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20300101') + uses: ./.github/workflows/publish.yml@master + with: + version_directory: v20300101 + secrets: inherit ``` -**Location 3: Release job matrix** +**Location 3: Add release job for new version** -In the `release` job's strategy matrix, add your version entry (mirror the publish matrix): +Add a new release job for your version: ```yaml -strategy: - matrix: - version: - - api_version: v20111101 - - api_version: v20250224 - - api_version: v20300101 # NEW - fail-fast: false +release-v20300101: + runs-on: ubuntu-latest + needs: [check-skip-publish, publish-v20300101] + if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20300101') + uses: ./.github/workflows/release.yml@master + with: + version_directory: v20300101 + secrets: inherit ``` +**Location 4: Add gate job for previous version** + +Add a new gate job after the previous version's release to handle serial ordering: + +```yaml +gate-v20250224-complete: + runs-on: ubuntu-latest + needs: [check-skip-publish, release-v20250224] + if: always() && needs.check-skip-publish.outputs.skip_publish == 'false' + steps: + - name: Gate complete - ready for v20300101 + run: echo "v20250224 release workflow complete (or skipped)" +``` + +**Important Notes**: +- Each publish job depends on the **previous version's gate job** to maintain serial ordering +- Each release job depends on its corresponding publish job +- Gate jobs use the `always()` condition so they run even when intermediate jobs are skipped +- This prevents npm registry race conditions and ensures correct behavior whether one or multiple versions are modified + ### 2.4 Verify Workflow Syntax Check that your YAML is valid for all three modified files: @@ -367,8 +389,9 @@ Use this checklist to verify you've completed all steps: - [ ] Updated `.github/workflows/openapi-generate-and-push.yml` with version-to-config mapping in Setup job - [ ] Updated `.github/changelog_manager.rb` with new version in `API_VERSION_ORDER` array - [ ] Updated `.github/workflows/on-push-master.yml` path triggers with `v20300101/**` -- [ ] Updated `.github/workflows/on-push-master.yml` publish job matrix with new version -- [ ] Updated `.github/workflows/on-push-master.yml` release job matrix with new version +- [ ] Updated `.github/workflows/on-push-master.yml` with new publish job for v20300101 +- [ ] Updated `.github/workflows/on-push-master.yml` with new release job for v20300101 +- [ ] Updated `.github/workflows/on-push-master.yml` with new gate job for previous version (v20250224) - [ ] Verified workflow YAML syntax is valid for all three modified files - [ ] Updated root `README.md` with new API version table entry - [ ] Updated root `README.md` with installation example for new version diff --git a/docs/Multi-Version-SDK-Flow.md b/docs/Multi-Version-SDK-Flow.md index 49c06f9..feb9cc2 100644 --- a/docs/Multi-Version-SDK-Flow.md +++ b/docs/Multi-Version-SDK-Flow.md @@ -20,24 +20,30 @@ Each version is independently generated, tested, published to npm, and released 1. **Separate Directories**: Each API version in its own directory (`v20111101/`, `v20250224/`) 2. **Reusable Workflows**: `workflow_call` passes version info to publish/release jobs 3. **One Config Per Version**: `config-v20111101.yml`, `config-v20250224.yml`, etc. -4. **Matrix Parallelization**: All versions generate/publish simultaneously +4. **Single Entrypoint for Publishing**: All paths lead through `on-push-master.yml` for serial, controlled publishing 5. **Safety First**: Skip-publish flags and path-based triggers prevent accidents --- -## Three Ways Things Happen +## Two Paths to Publishing -### 🤖 Flow 1: Automatic (Upstream Triggers) -OpenAPI spec changes → `openapi-generate-and-push.yml` runs → SDK generated, published, released +Both paths follow the same publishing mechanism: commit changes to master → `on-push-master.yml` handles serial publish/release. + +### 🤖 Path 1: Automatic (Upstream Triggers) +OpenAPI spec changes → `openapi-generate-and-push.yml` generates SDK → commits to master → `on-push-master.yml` publishes and releases **When**: OpenAPI repository sends `repository_dispatch` with API versions **Who**: Automated, no human intervention -**Result**: All specified versions generated in parallel, committed, published, released in single workflow +**What Happens**: +1. `openapi-generate-and-push.yml` generates all specified versions in parallel +2. All generated files committed to master +3. `on-push-master.yml` automatically triggered by the push +4. `on-push-master.yml` handles serial publish/release with version gating **Key Details**: See [Workflow-and-Configuration-Reference.md](Workflow-and-Configuration-Reference.md#flow-1-automatic-multi-version-generation-repository-dispatch) -### 👨‍💻 Flow 2: Manual (Developer Triggers) -Developer runs `generate.yml` → SDK generated → PR created → Developer merges → Auto-publish triggers +### 👨‍💻 Path 2: Manual (Developer Triggers) +Developer runs `generate.yml` → SDK generated in feature branch → PR created → code review & approval → merge to master → `on-push-master.yml` publishes and releases **When**: Developer clicks "Run workflow" on `generate.yml` **Who**: Developer (controls version selection and bump strategy) @@ -45,19 +51,16 @@ Developer runs `generate.yml` → SDK generated → PR created → Developer mer - `api_version`: Choose `v20111101` or `v20250224` - `version_bump`: Choose `skip`, `minor`, or `patch` -**Result**: SDK generated in feature branch, PR created for review, auto-publishes on merge +**What Happens**: +1. `generate.yml` generates SDK in feature branch +2. PR created for code review +3. Developer (or team) reviews and approves +4. PR merged to master +5. `on-push-master.yml` automatically triggered by the merge +6. `on-push-master.yml` handles serial publish/release with version gating **Key Details**: See [Workflow-and-Configuration-Reference.md](Workflow-and-Configuration-Reference.md#flow-2-manual-multi-version-generation-workflow-dispatch) -### 🔄 Flow 3: Auto-Publish (Master Push) -Changes pushed to `v20111101/**` or `v20250224/**` → `on-push-master.yml` runs → Publishes and releases - -**When**: Any commit to master with version directory changes -**Who**: Triggered automatically, can be skipped with `[skip-publish]` flag -**Safety**: Only affected version(s) published (no cross-version interference) - -**Key Details**: See [Workflow-and-Configuration-Reference.md](Workflow-and-Configuration-Reference.md#flow-3-auto-publish-trigger-with-path-based-matrix-execution-on-push-masteryml) - --- ## Visual Flows @@ -68,28 +71,27 @@ Changes pushed to `v20111101/**` or `v20250224/**` → `on-push-master.yml` runs sequenceDiagram participant OpenAPI as OpenAPI
Repository participant GH as GitHub
Actions - participant Gen as generate_publish
_release.yml + participant Gen as openapi-generate-
and-push.yml + participant Push as Push to
Master + participant OnPush as on-push-
master.yml participant npm as npm
Registry participant GHRel as GitHub
Releases OpenAPI->>GH: Changes to v20111101.yml
and v20250224.yml
(repository_dispatch) GH->>+Gen: Trigger workflow Gen->>Gen: Matrix: Generate both versions
in parallel - Gen->>Gen: Commit to master
Update CHANGELOG.md + Gen->>Push: Commit to master
Update CHANGELOG.md deactivate Gen - rect rgba(0, 255, 0, .1) - note right of Gen: Parallel Publishing - par publish v20111101 - npm->>npm: npm publish v2.0.1 - and publish v20250224 - npm->>npm: npm publish v3.0.1 - and release v20111101 - GHRel->>GHRel: Create tag v2.0.1 - and release v20250224 - GHRel->>GHRel: Create tag v3.0.1 - end - end + Push->>+OnPush: Push event triggers + OnPush->>OnPush: Check skip-publish flag + OnPush->>OnPush: Serial: v20111101 publish + OnPush->>npm: npm publish v2.x.x + OnPush->>GHRel: Create tag v2.x.x + OnPush->>OnPush: Serial: v20250224 publish + OnPush->>npm: npm publish v3.x.x + OnPush->>GHRel: Create tag v3.x.x + deactivate OnPush ``` ### Manual Flow @@ -100,7 +102,8 @@ sequenceDiagram participant GH as GitHub
Actions participant Gen as generate.yml participant Review as Code
Review - participant Auto as on-push-
master.yml + participant Merge as Merge to
Master + participant OnPush as on-push-
master.yml participant npm as npm participant GHRel as GitHub @@ -110,44 +113,16 @@ sequenceDiagram deactivate Gen Review->>Review: Review & Approve - Review->>Review: Merge PR to master - - GH->>+Auto: on-push-master.yml - Auto->>npm: Publish selected version - Auto->>GHRel: Create release - deactivate Auto + Review->>Merge: Merge PR to master + + Merge->>+OnPush: Push event triggers + OnPush->>OnPush: Check skip-publish flag + OnPush->>OnPush: Serial: publish
& release + OnPush->>npm: npm publish + OnPush->>GHRel: Create release + deactivate OnPush ``` -### Auto-Publish Flow - -```mermaid -sequenceDiagram - participant Push as Git Push - participant Auto as on-push-
master.yml - participant skip as check-skip-
publish - participant pub as publish.yml - participant rel as release.yml - participant gate as gate-
v20111101 - participant npm as npm - participant GHRel as GitHub - - Push->>+Auto: Push to master
(v20111101/** changed) - Auto->>skip: Check skip-publish flag - skip-->>Auto: skip_publish: false - Auto->>pub: publish-v20111101 - pub->>npm: npm publish v2.x.x - Auto->>rel: release-v20111101 - rel->>GHRel: Create tag v2.x.x - rel-->>gate: Release complete - gate->>gate: Gate always runs
(even if v20111101 skipped) - note right of gate: Unblocks v20250224
when only v20250224 changed - gate-->>Auto: Gate complete - Auto->>pub: publish-v20250224
(skipped - not modified) - deactivate Auto -``` - -**Key Feature**: The `gate-v20111101-complete` job uses `always()` to run even when intermediate jobs are skipped, ensuring v20250224 can publish when only v20250224 is modified. - --- ## Common Tasks @@ -175,9 +150,9 @@ sequenceDiagram |------|---------|---------| | `.github/workflows/openapi-generate-and-push.yml` | Automatic generation from upstream API changes | OpenAPI repo | | `.github/workflows/generate.yml` | Manual generation with version selection | Developer | -| `.github/workflows/on-push-master.yml` | Auto-publish trigger with path-based matrix | Any master push | -| `.github/workflows/publish.yml` | Publishes SDK to npm | publish_release & on-push-master | -| `.github/workflows/release.yml` | Creates GitHub release | publish_release & on-push-master | +| `.github/workflows/on-push-master.yml` | Publishes and releases SDKs on master push | Both automatic & manual flows | +| `.github/workflows/publish.yml` | Publishes SDK to npm | on-push-master | +| `.github/workflows/release.yml` | Creates GitHub release | on-push-master | | `.github/version.rb` | Bumps version in config files | Workflows | | `.github/clean.rb` | Removes old generated files | Workflows | | `openapi/config-v20111101.yml` | Config for v20111101 generation | openapi-generate-and-push & generate | @@ -215,12 +190,15 @@ If OpenAPI repo doesn't send new version in payload, the system doesn't break: | Feature | What It Does | When It Helps | |---------|-------------|--------------| +| **Serial publishing** | Each version publishes sequentially to npm (v20111101 then v20250224) | Prevents npm registry conflicts and race conditions | | **Path-based triggers** | Only publish if `v20111101/**` or `v20250224/**` changed | Prevents false publishes from doc-only changes | | **[skip-publish] flag** | Skip publish/release for this commit | During directory migrations or refactors | -| **Matrix conditionals** | Each version publishes only if its path changed | Prevents unintended version bumps | +| **Conditional jobs** | Each version's jobs only run if their paths changed | Prevents unintended version bumps | | **Version validation** | Major version must match API version | Prevents semantic versioning violations | | **Config file validation** | Workflow fails if config doesn't exist | Catches typos early | +**Note on Serial Publishing**: We chose explicit job sequences over matrix strategies to ensure safety. See [Workflow-and-Configuration-Reference.md - Architecture Decision section](Workflow-and-Configuration-Reference.md#architecture-decision-serial-publishing-with-conditional-jobs) for detailed reasoning. + --- ## Environment Variables & Secrets diff --git a/docs/Troubleshooting-Guide.md b/docs/Troubleshooting-Guide.md index 1c8ac1c..fb2dc96 100644 --- a/docs/Troubleshooting-Guide.md +++ b/docs/Troubleshooting-Guide.md @@ -259,7 +259,7 @@ fatal: A release with this tag already exists ``` 4. If not present, update workflow from latest template -**Technical Details**: See [Workflow-and-Configuration-Reference.md](Workflow-and-Configuration-Reference.md#step-3-gate-job---unblock-v20250224-publishing) for full gate job implementation details. +**Technical Details**: See [Workflow-and-Configuration-Reference.md](Workflow-and-Configuration-Reference.md#step-3-gate-job---unblock-v20250224-publishing) in the "Publishing via on-push-master.yml" section for full gate job implementation details. --- diff --git a/docs/Workflow-and-Configuration-Reference.md b/docs/Workflow-and-Configuration-Reference.md index 4ef9588..f8859c4 100644 --- a/docs/Workflow-and-Configuration-Reference.md +++ b/docs/Workflow-and-Configuration-Reference.md @@ -143,29 +143,16 @@ strategy: **Serialization Chain** (for race condition prevention): - v20111101 publish runs first (depends on check-skip-publish) -- v20111101 release runs second (depends on publish) -- **gate-v20111101-complete** runs (uses `always()`, runs even if v20111101 jobs are skipped) ⭐ **NEW - Handles conditional dependencies** -- v20250224 publish runs third (depends on gate job) ← **Serialized to prevent npm race conditions** -- v20250224 release runs fourth (depends on v20250224 publish) - -**The Gate Job Pattern** (Solves Single-Version Publishing): -Previously, when only v20250224 changed, the workflow would fail because `publish-v20250224` depended on `release-v20111101`, which was skipped (v20111101 not modified). - -Solution: The `gate-v20111101-complete` job uses GitHub Actions' `always()` condition, which runs regardless of whether upstream jobs succeeded, failed, or were skipped. This: -- ✅ Allows downstream jobs to proceed even when intermediate jobs are skipped -- ✅ Maintains serial ordering when both versions are modified -- ✅ Fixes the issue where only modifying v20250224 would cause the publish to hang - -**Scenario Examples**: -- **Both versions modified**: publish v20111101 → release v20111101 → gate (runs) → publish v20250224 → release v20250224 -- **Only v20250224 modified**: (v20111101 jobs skipped) → gate (always runs, unblocks downstream) → publish v20250224 → release v20250224 -- **Only v20111101 modified**: publish v20111101 → release v20111101 → gate (always runs) → publish v20250224 (skipped, not modified) → release v20250224 (skipped) - -**Result**: -- Versions publish sequentially to npm (prevents registry conflicts) -- Each version has independent release history on GitHub -- Only affected versions are published (path-based filtering) -- Can be skipped with `[skip-publish]` flag in commit message +- v20111101 release runs second (depends on publish) - waits for npm registry confirmation +- **gate-v20111101-complete** runs (uses `always()`, runs even if v20111101 jobs are skipped) ⭐ **Critical: Enables single-version publishing** +- v20250224 publish runs third (depends on gate job) ← **Serial ordering enforced** +- v20250224 release runs fourth (depends on v20250224 publish) - waits for npm registry confirmation + +**Why This Order Matters**: +- Each version publishes to npm sequentially, never in parallel +- npm registry expects sequential API calls; parallel publishes can cause conflicts +- Gate job ensures this ordering works correctly whether 1 or 2 versions are modified +- Release jobs complete before the next version starts publishing --- @@ -251,7 +238,7 @@ Developer manually clicks "Run workflow" on `generate.yml` in GitHub Actions UI #### Step 8: Trigger Publishing (After PR Merge) -**Trigger**: When PR is merged to `master`, `on-push-master.yml` automatically activates (see Flow 3) +**Trigger**: When PR is merged to `master`, `on-push-master.yml` automatically activates **Workflows Called**: 1. `publish.yml` (via workflow_call with version_directory input) @@ -261,7 +248,81 @@ Developer manually clicks "Run workflow" on `generate.yml` in GitHub Actions UI --- -## Flow 3: Auto-Publish Trigger with Path-Based Matrix Execution (on-push-master.yml) +## Publishing via on-push-master.yml + +All SDKs (whether from automatic generation or manual PR merge) are published through a single mechanism: the `on-push-master.yml` workflow that is triggered when changes are pushed to master. + +This is the **only** path to publishing. Developers cannot publish directly; all publishes go through this workflow. In the future, master will be locked to prevent direct commits, ensuring only the automated `openapi-generate-and-push.yml` workflow can commit directly to master. + +### Architecture Decision: Serial Publishing with Conditional Jobs + +This section explains **why** we chose serial job chaining with conditionals instead of a more DRY (Don't Repeat Yourself) matrix-based approach. + +#### Why Not Matrix Strategy? + +**Matrix Approach** (More DRY, but unsafe): +```yaml +strategy: + matrix: + version: + - { api: v20111101, dir: v20111101, prev_gate: check-skip-publish } + - { api: v20250224, dir: v20250224, prev_gate: gate-v20111101 } + +# Single publish job that runs for each version in parallel +publish: + if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, matrix.version.dir) + with: + version_directory: ${{ matrix.version.dir }} +``` + +**Why we rejected this**: +- ❌ **Race conditions**: Both versions could start publishing simultaneously to npm registry + - `npm publish` can be slow; timing varies per version + - If both hit npm at nearly the same time, registry locks/conflicts could occur + - npm doesn't guarantee atomic operations across parallel publishes +- ❌ **Loss of visibility**: When one version succeeds and another fails, the matrix obscures which one + - GitHub Actions matrix UI shows one line, making it harder to debug individual version failures + - Logs are nested, making failure diagnosis harder +- ❌ **Harder to understand**: New developers see one job with matrix logic; harder to reason about sequence +- ❌ **Less flexible**: Adding safety checks per version becomes complicated with matrix expansion + +#### Why Serial Conditionals (Our Choice) + +**Serial Approach** (Explicit, safe, maintainable): +```yaml +publish-v20111101: + if: skip_publish == false && contains(modified, 'v20111101') + +publish-v20250224: + needs: [gate-v20111101-complete] # Must wait + if: skip_publish == false && contains(modified, 'v20250224') +``` + +**Advantages**: +- ✅ **Safe**: v20250224 cannot start publishing until v20111101 finishes + - Gate job ensures serial ordering at job level, not just workflow level + - npm registry sees sequential requests, no conflicts + - Clear happens-before relationship in GitHub Actions UI +- ✅ **Visible**: Each version has individual jobs that are easy to identify + - GitHub Actions shows separate rows for each version + - Failures are obvious: "publish-v20250224 failed" vs "publish[v20250224] in matrix" + - Each job can have version-specific comments and documentation +- ✅ **Debuggable**: Clear dependencies make it obvious what blocks what + - When only v20250224 is modified, you see: `publish-v20111101 (skipped)` → `gate (runs)` → `publish-v20250224 (runs)` + - Matrix approach would be harder to understand why certain jobs run/skip +- ✅ **Maintainable**: Adding a new version requires adding 3 explicit jobs (publish, release, gate) + - More code, but each job is self-documenting + - No complex matrix expansion logic to understand + - Future developers can see the pattern easily: "oh, each version gets 3 jobs" +- ✅ **Future-proof**: When you lock master, this structure stays the same + - Matrix would need version list hardcoded; serial jobs just live alongside each other + +**Tradeoff we accepted**: +- We have more code (repetition): `publish-v20111101`, `publish-v20250224`, etc. +- BUT: The repetition is worth it for safety, clarity, and debuggability +- This is a conscious choice: **explicitness over DRY** for critical infrastructure + + ### Trigger Push to `master` branch with changes in version-specific directories (`v20111101/**` or `v20250224/**`) @@ -328,14 +389,31 @@ gate-v20111101-complete: **Key Feature**: Uses `always()` condition - runs even when `release-v20111101` is skipped -**Why This Exists**: -- When only v20250224 is modified, `release-v20111101` is skipped, which would normally prevent downstream jobs from running -- The gate job always runs (via `always()` condition), allowing downstream jobs to proceed -- Enables correct behavior in all scenarios: single-version or multi-version changes +**Why This Pattern Exists**: + +The gate job solves a critical dependency problem in serial publishing: + +1. **The Problem**: + - If v20250224 publish job depends on `release-v20111101`, it fails when v20111101 is skipped (not modified) + - When only v20250224 is modified, we want it to publish, but it's blocked by skipped v20111101 job + - This would cause the workflow to hang/fail when only one version is modified + +2. **The Solution**: + - Gate job uses `always()` so it runs whether v20111101 succeeds, fails, or is skipped + - v20250224 jobs depend on the gate job (which always runs), not on v20111101 (which might be skipped) + - This unblocks v20250224 while maintaining serial ordering when both versions are modified + +3. **The Behavior**: + - **Both versions modified**: publish v20111101 → release v20111101 → gate (runs) → publish v20250224 → release v20250224 + - **Only v20250224 modified**: (v20111101 jobs skipped) → gate (always runs, unblocks) → publish v20250224 → release v20250224 + - **Only v20111101 modified**: publish v20111101 → release v20111101 → gate (always runs) → publish v20250224 (skipped) → release v20250224 (skipped) -**Gate Job Executes When**: -- No `[skip-publish]` flag in commit message -- Always runs regardless of whether v20111101 jobs were skipped +**Why Not Use Direct Dependencies?** +If v20250224 jobs depended directly on v20111101's release job, the workflow would fail whenever v20111101 was skipped (not modified). The gate job pattern enables: +- ✅ Correct behavior in single-version and multi-version scenarios +- ✅ Maintains serial ordering when both versions change +- ✅ Prevents race conditions at npm registry level +- ✅ Clear, explicit dependency chain in GitHub Actions UI #### Step 4: Publish and Release v20250224 (Second in Serial Chain) From 776ae81100c4d9be7065e65016e5a71737b368fd Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 23:21:56 -0700 Subject: [PATCH 12/16] Add Changed subtitle to new entries --- .github/changelog_manager.rb | 4 +++- .github/spec/changelog_manager_spec.rb | 23 ++++++++++++++++++++ CHANGELOG.md | 6 ++--- docs/Changelog-Manager.md | 16 ++++++++++---- docs/Workflow-and-Configuration-Reference.md | 6 +++-- 5 files changed, 45 insertions(+), 10 deletions(-) diff --git a/.github/changelog_manager.rb b/.github/changelog_manager.rb index 85a4667..b42ffa9 100644 --- a/.github/changelog_manager.rb +++ b/.github/changelog_manager.rb @@ -109,7 +109,9 @@ def build_entry(api_version, version_number, date) <<~ENTRY ## [#{version_number}] - #{date_str} (#{api_version} API) - #{message} + + ### Changed + - #{message} ENTRY end diff --git a/.github/spec/changelog_manager_spec.rb b/.github/spec/changelog_manager_spec.rb index 2b21090..86739b4 100644 --- a/.github/spec/changelog_manager_spec.rb +++ b/.github/spec/changelog_manager_spec.rb @@ -59,6 +59,7 @@ def read_changelog updated_content = read_changelog expect(updated_content).to include('## [3.0.0] - 2026-01-28 (v20250224 API)') + expect(updated_content).to include('### Changed') expect(updated_content).to include('Updated v20250224 API specification to most current version') expect(updated_content).to include('[API changelog]') @@ -86,6 +87,7 @@ def read_changelog expect(updated_content).to include('## [2.0.0] - 2026-01-28 (v20111101 API)') expect(updated_content).to include('Updated v20250224 API specification to most current version') expect(updated_content).to include('Updated v20111101 API specification to most current version') + expect(updated_content).to include('### Changed') # v20250224 should come BEFORE v20111101 (sorting) v20250224_pos = updated_content.index('[3.0.0]') @@ -103,6 +105,7 @@ def read_changelog expect(result).to be true updated_content = read_changelog expect(updated_content).to include('## [3.0.0] - 2026-01-28 (v20250224 API)') + expect(updated_content).to include('### Changed') end end @@ -123,6 +126,21 @@ def read_changelog v20111101_pos = updated_content.index('[2.0.0]') expect(v20250224_pos).to be < v20111101_pos end + + it 'includes changed section for each entry' do + setup_changelog + setup_version_directory('v20250224', v20250224_package_fixture) + setup_version_directory('v20111101', v20111101_package_fixture) + + result = ChangelogManager.update('v20250224,v20111101') + + expect(result).to be true + updated_content = read_changelog + + # Count occurrences of ### Changed - should be 2 for the new entries + changed_count = updated_content.scan(/### Changed/).length + expect(changed_count).to be >= 2 # At least 2 from the new entries + end end describe '.update with date range behavior' do @@ -150,6 +168,8 @@ def read_changelog expect(updated_content).to include('Updated v20250224 API specification to most current version. Please check full [API changelog]') # Should NOT have a "between" clause expect(updated_content).not_to match(/between \d{4}-\d{2}-\d{2} and \d{4}-\d{2}-\d{2}.*v20250224/) + # Should include Changed section + expect(updated_content).to include('### Changed') end it 'uses correct dates in range for multiple version updates' do @@ -168,6 +188,8 @@ def read_changelog # v20250224 should NOT have date range (no prior entry) v20250224_section = updated_content[/## \[3\.0\.0\].*?(?=##|\z)/m] expect(v20250224_section).not_to match(/between.*v20250224/) + # Both should have Changed section + expect(updated_content).to include('### Changed') end end @@ -239,6 +261,7 @@ def read_changelog # Verify changelog was updated updated_content = read_changelog expect(updated_content).to include('## [3.0.0] - 2026-01-28 (v20250224 API)') + expect(updated_content).to include('### Changed') end it 'exits with error when versions argument is nil' do diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a0fd47..0ac8e82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2.0.0] - 2026-01-07 +## [2.0.0] - 2026-01-07 (v20111101 API) ### Changed - **Versioning Correction:** Re-released as v2.0.0 to properly indicate breaking changes that were inadvertently introduced in v1.10.1 @@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Restructured API classes from single `MxPlatformApi` to domain-specific classes -## [1.12.1] - 2025-11-25 +## [1.12.1] - 2025-11-25 (v20111101 API) ### Fixed - Updated package template (`package.mustache`) to fix recurring dependency regression @@ -42,7 +42,7 @@ These versions (v1.10.1 through v1.12.0) contain the breaking API restructure bu **If you are on any of these versions:** Please upgrade to v2.0.0. -## [1.10.0] - 2025-11-05 +## [1.10.0] - 2025-11-05 (v20111101 API) ### Note - Last stable version with unified `MxPlatformApi` class diff --git a/docs/Changelog-Manager.md b/docs/Changelog-Manager.md index a2c3c13..9cb4c78 100644 --- a/docs/Changelog-Manager.md +++ b/docs/Changelog-Manager.md @@ -55,10 +55,14 @@ The entries are inserted in version order (v20250224 first, v20111101 second), e Result: ```markdown ## [3.2.0] - 2025-01-28 (v20250224 API) -Updated v20250224 API specification to most current version... + +### Changed +- Updated v20250224 API specification to most current version... ## [2.5.3] - 2025-01-28 (v20111101 API) -Updated v20111101 API specification to most current version... + +### Changed +- Updated v20111101 API specification to most current version... ``` ### Example Output @@ -66,13 +70,17 @@ Updated v20111101 API specification to most current version... With a prior entry: ```markdown ## [3.2.0] - 2025-01-28 (v20250224 API) -Updated v20250224 API specification to most current version. Please check full [API changelog](https://docs.mx.com/resources/changelog/platform) for any changes made between 2025-01-15 and 2025-01-28. + +### Changed +- Updated v20250224 API specification to most current version. Please check full [API changelog](https://docs.mx.com/resources/changelog/platform) for any changes made between 2025-01-15 and 2025-01-28. ``` Without a prior entry: ```markdown ## [3.2.0] - 2025-01-28 (v20250224 API) -Updated v20250224 API specification to most current version. Please check full [API changelog](https://docs.mx.com/resources/changelog/platform) for any changes. + +### Changed +- Updated v20250224 API specification to most current version. Please check full [API changelog](https://docs.mx.com/resources/changelog/platform) for any changes. ``` ## Location diff --git a/docs/Workflow-and-Configuration-Reference.md b/docs/Workflow-and-Configuration-Reference.md index f8859c4..befce32 100644 --- a/docs/Workflow-and-Configuration-Reference.md +++ b/docs/Workflow-and-Configuration-Reference.md @@ -106,10 +106,12 @@ strategy: # Changelog ## [3.0.1] - 2026-01-27 (v20250224 API) - Updated v20250224 API specification... + ### Changed + - Updated v20250224 API specification... ## [2.0.1] - 2026-01-27 (v20111101 API) - Updated v20111101 API specification... + ### Changed + - Updated v20111101 API specification... ``` 4. **Copy Documentation to Version Directories** From e1569943754540d5aac927f994e8a8a598045c1b Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 23:27:36 -0700 Subject: [PATCH 13/16] Fix on push master...hopefully --- .github/workflows/on-push-master.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/on-push-master.yml b/.github/workflows/on-push-master.yml index 572ef30..2ad1a50 100644 --- a/.github/workflows/on-push-master.yml +++ b/.github/workflows/on-push-master.yml @@ -31,7 +31,6 @@ jobs: # Only runs if [skip-publish] flag is NOT present AND files for that version were modified publish-v20111101: - runs-on: ubuntu-latest needs: check-skip-publish if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20111101') uses: ./.github/workflows/publish.yml@master @@ -40,7 +39,6 @@ jobs: secrets: inherit release-v20111101: - runs-on: ubuntu-latest needs: [check-skip-publish, publish-v20111101] if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20111101') uses: ./.github/workflows/release.yml@master @@ -61,7 +59,6 @@ jobs: run: echo "v20111101 release workflow complete (or skipped)" publish-v20250224: - runs-on: ubuntu-latest needs: [check-skip-publish, gate-v20111101-complete] if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20250224') uses: ./.github/workflows/publish.yml@master @@ -70,7 +67,6 @@ jobs: secrets: inherit release-v20250224: - runs-on: ubuntu-latest needs: [check-skip-publish, publish-v20250224] if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20250224') uses: ./.github/workflows/release.yml@master From 980275981f4422d52fc9ae60adf447ce530eff9e Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Wed, 28 Jan 2026 23:28:59 -0700 Subject: [PATCH 14/16] Please oh please let this one work --- .github/workflows/on-push-master.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/on-push-master.yml b/.github/workflows/on-push-master.yml index 2ad1a50..adf85f3 100644 --- a/.github/workflows/on-push-master.yml +++ b/.github/workflows/on-push-master.yml @@ -33,7 +33,7 @@ jobs: publish-v20111101: needs: check-skip-publish if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20111101') - uses: ./.github/workflows/publish.yml@master + uses: ./.github/workflows/publish.yml with: version_directory: v20111101 secrets: inherit @@ -41,7 +41,7 @@ jobs: release-v20111101: needs: [check-skip-publish, publish-v20111101] if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20111101') - uses: ./.github/workflows/release.yml@master + uses: ./.github/workflows/release.yml with: version_directory: v20111101 secrets: inherit @@ -61,7 +61,7 @@ jobs: publish-v20250224: needs: [check-skip-publish, gate-v20111101-complete] if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20250224') - uses: ./.github/workflows/publish.yml@master + uses: ./.github/workflows/publish.yml with: version_directory: v20250224 secrets: inherit @@ -69,7 +69,7 @@ jobs: release-v20250224: needs: [check-skip-publish, publish-v20250224] if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20250224') - uses: ./.github/workflows/release.yml@master + uses: ./.github/workflows/release.yml with: version_directory: v20250224 secrets: inherit From 2ec3f1e38dc463a2a431fdc645a095108f783057 Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Thu, 29 Jan 2026 00:05:32 -0700 Subject: [PATCH 15/16] Fix more no nos I found --- .../workflows/openapi-generate-and-push.yml | 14 ++--- .github/workflows/publish.yml | 9 ++- .github/workflows/release.yml | 9 ++- docs/Adding-a-New-API-Version.md | 59 ++++++++++++++++--- docs/Workflow-and-Configuration-Reference.md | 2 +- 5 files changed, 72 insertions(+), 21 deletions(-) diff --git a/.github/workflows/openapi-generate-and-push.yml b/.github/workflows/openapi-generate-and-push.yml index 13a020f..6e2f09e 100644 --- a/.github/workflows/openapi-generate-and-push.yml +++ b/.github/workflows/openapi-generate-and-push.yml @@ -91,7 +91,7 @@ jobs: name: generated-${{ matrix.api_version }} path: ./${{ matrix.api_version }} - Commit-and-Push: + Process-and-Push: runs-on: ubuntu-latest needs: [Setup, Generate] steps: @@ -127,12 +127,12 @@ jobs: ruby .github/changelog_manager.rb "$VERSIONS_CSV" - name: Copy documentation run: | - for dir in ./v20111101 ./v20250224; do - if [ -d "$dir" ]; then - cp LICENSE "$dir/LICENSE" - cp CHANGELOG.md "$dir/CHANGELOG.md" - cp MIGRATION.md "$dir/MIGRATION.md" - fi + GENERATED_VERSIONS="${{ steps.track_versions.outputs.generated_versions }}" + + for VERSION in $GENERATED_VERSIONS; do + cp LICENSE "./$VERSION/LICENSE" + cp CHANGELOG.md "./$VERSION/CHANGELOG.md" + cp MIGRATION.md "./$VERSION/MIGRATION.md" done - name: Checkout master run: git checkout master diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 10a0ca4..b69c8c4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,13 +4,16 @@ on: workflow_dispatch: inputs: version_directory: - description: 'Version directory to publish from' + description: 'API version directory to publish from' required: true - type: string + type: choice + options: + - v20111101 + - v20250224 workflow_call: inputs: version_directory: - description: 'Version directory to publish from' + description: 'API version directory to publish from' required: true type: string diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a4a1f9b..c1236f1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,13 +4,16 @@ on: workflow_dispatch: inputs: version_directory: - description: 'Version directory to read version from' + description: 'API version directory to release from' required: true - type: string + type: choice + options: + - v20111101 + - v20250224 workflow_call: inputs: version_directory: - description: 'Version directory to read version from' + description: 'API version directory to release from' required: true type: string diff --git a/docs/Adding-a-New-API-Version.md b/docs/Adding-a-New-API-Version.md index 20dd24a..47528b5 100644 --- a/docs/Adding-a-New-API-Version.md +++ b/docs/Adding-a-New-API-Version.md @@ -108,15 +108,56 @@ The `ConfigValidator` automatically validates that: **No workflow changes needed** — the existing validation step in `generate.yml` calls `ConfigValidator` with your new version, and it automatically validates using the updated mapping. -### 2.2 Update openapi-generate-and-push.yml +### 2.2 Update release.yml and publish.yml + +Both the `release.yml` and `publish.yml` workflows use `workflow_dispatch` with choice inputs to allow manual triggering with the correct version directory. + +#### 2.2a Update release.yml + +**Location: Workflow dispatch options** + +In the `on.workflow_dispatch.inputs.version_directory.options` section, add the new version to the dropdown list: + +```yaml +version_directory: + description: 'API version directory' + required: true + type: choice + options: + - v20111101 + - v20250224 + - v20300101 # NEW +``` + +#### 2.2b Update publish.yml + +**Location: Workflow dispatch options** + +In the `on.workflow_dispatch.inputs.version_directory.options` section, add the new version to the dropdown list: + +```yaml +version_directory: + description: 'API version directory' + required: true + type: choice + options: + - v20111101 + - v20250224 + - v20300101 # NEW +``` + +**Why both workflows need updating**: These choices allow users to manually trigger release and publish workflows for any version. Adding the new version ensures it can be selected in the GitHub Actions UI when manually triggering these workflows. + +### 2.3 Update openapi-generate-and-push.yml This workflow is automatically triggered by the OpenAPI repository to generate and push SDKs for all versions in parallel. **Location 1: Version-to-config mapping** -In the `Setup` job's `Set up matrix` step, add an `elif` branch to map your new version to its config file: +In the `Setup` job's `Set up matrix` step, find the section with the version-to-config mapping and add an `elif` branch for your new version: ```yaml +# Map version to config file and major version if [ "$VERSION" = "v20111101" ]; then CONFIG="openapi/config-v20111101.yml" elif [ "$VERSION" = "v20250224" ]; then @@ -126,7 +167,7 @@ elif [ "$VERSION" = "v20300101" ]; then fi ``` -This dynamically builds the matrix that determines which config file each version uses during generation. +This dynamically builds the matrix JSON that determines which config file each version uses during generation. **Location 2: Add version to ChangelogManager priority order** @@ -144,7 +185,7 @@ This ensures when multiple versions are generated, changelog entries appear in o - Extracts date ranges from existing entries - Inserts properly formatted entries at the top of the changelog -### 2.3 Update on-push-master.yml +### 2.4 Update on-push-master.yml This workflow automatically triggers publish and release jobs when version directories are pushed to master. Since individual version jobs use conditional `if` statements based on path changes, you need to add new conditional jobs for your new version. @@ -214,12 +255,14 @@ gate-v20250224-complete: - Gate jobs use the `always()` condition so they run even when intermediate jobs are skipped - This prevents npm registry race conditions and ensures correct behavior whether one or multiple versions are modified -### 2.4 Verify Workflow Syntax +### 2.5 Verify Workflow Syntax -Check that your YAML is valid for all three modified files: +Check that your YAML is valid for all four modified files: ```bash ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/generate.yml'))" +ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/release.yml'))" +ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/publish.yml'))" ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/openapi-generate-and-push.yml'))" ruby -e "require 'yaml'; puts YAML.load(File.read('.github/workflows/on-push-master.yml'))" ``` @@ -386,13 +429,15 @@ Use this checklist to verify you've completed all steps: - [ ] Major version in config is unique and sequential (4.0.0 for v20300101) - [ ] Updated `.github/workflows/generate.yml` with new version in dropdown options - [ ] Updated `.github/config_validator.rb` with new version in `SUPPORTED_VERSIONS` mapping +- [ ] Updated `.github/workflows/release.yml` with new version in dropdown options +- [ ] Updated `.github/workflows/publish.yml` with new version in dropdown options - [ ] Updated `.github/workflows/openapi-generate-and-push.yml` with version-to-config mapping in Setup job - [ ] Updated `.github/changelog_manager.rb` with new version in `API_VERSION_ORDER` array - [ ] Updated `.github/workflows/on-push-master.yml` path triggers with `v20300101/**` - [ ] Updated `.github/workflows/on-push-master.yml` with new publish job for v20300101 - [ ] Updated `.github/workflows/on-push-master.yml` with new release job for v20300101 - [ ] Updated `.github/workflows/on-push-master.yml` with new gate job for previous version (v20250224) -- [ ] Verified workflow YAML syntax is valid for all three modified files +- [ ] Verified workflow YAML syntax is valid for all five modified files - [ ] Updated root `README.md` with new API version table entry - [ ] Updated root `README.md` with installation example for new version - [ ] Updated `MIGRATION.md` with new migration section diff --git a/docs/Workflow-and-Configuration-Reference.md b/docs/Workflow-and-Configuration-Reference.md index befce32..bd72063 100644 --- a/docs/Workflow-and-Configuration-Reference.md +++ b/docs/Workflow-and-Configuration-Reference.md @@ -129,7 +129,7 @@ strategy: #### Step 5: Automatic Publish and Release (via on-push-master.yml) -**Architecture**: After `Commit-and-Push` completes and pushes to master, the automatic `on-push-master.yml` workflow is triggered by GitHub's push event. +**Architecture**: After `Process-and-Push` completes and pushes to master, the automatic `on-push-master.yml` workflow is triggered by GitHub's push event. **Why This Architecture?** - Separates concerns: `openapi-generate-and-push.yml` owns generation, `on-push-master.yml` owns publishing From 50f0c3f75a4fa813cc44f9741dac6d35a0d204be Mon Sep 17 00:00:00 2001 From: Genevieve Nuebel Date: Thu, 29 Jan 2026 08:17:00 -0700 Subject: [PATCH 16/16] Remove last references for config --- openapi/config.yml.bak | 6 ------ openapi/templates/README.mustache | 13 ------------- 2 files changed, 19 deletions(-) delete mode 100644 openapi/config.yml.bak diff --git a/openapi/config.yml.bak b/openapi/config.yml.bak deleted file mode 100644 index cb020e7..0000000 --- a/openapi/config.yml.bak +++ /dev/null @@ -1,6 +0,0 @@ -# Archived to support multiple versions. Reference ---- -npmName: mx-platform-node -npmVersion: 2.0.0-rc.1 # Pre-release for testing new directory structure -supportsES6: true -.openapi-generator-ignore: true diff --git a/openapi/templates/README.mustache b/openapi/templates/README.mustache index 6c1c1fb..a903639 100644 --- a/openapi/templates/README.mustache +++ b/openapi/templates/README.mustache @@ -112,19 +112,6 @@ console.log(response.data); > **⚠️ Breaking Changes in v2.0.0:** If you're upgrading from v1.10.0 or earlier, the API structure has changed significantly. See the [Migration Guide](MIGRATION.md) for detailed instructions on updating your code. -## Development - -This project was generated by the [OpenAPI Generator](https://openapi-generator.tech). To generate this library, verify you have the latest version of the `openapi-generator-cli` found [here.](https://github.com/OpenAPITools/openapi-generator#17---npm) - -Running the following command in this repo's directory will generate this library using the [MX Platform API OpenAPI spec](https://github.com/mxenabled/openapi/blob/master/openapi/mx_platform_api.yml) with our [configuration and templates.](https://github.com/mxenabled/mx-platform-ruby/tree/master/openapi) -```shell -openapi-generator-cli generate \ --i https://raw.githubusercontent.com/mxenabled/openapi/master/openapi/mx_platform_api.yml \ --g typescript-axios \ --c ./openapi/config.yml \ --t ./openapi/templates -``` - ## Contributing Please [open an issue](https://github.com/mxenabled/mx-platform-node/issues) or [submit a pull request.](https://github.com/mxenabled/mx-platform-node/pulls)