diff --git a/.github/changelog_manager.rb b/.github/changelog_manager.rb new file mode 100644 index 0000000..b42ffa9 --- /dev/null +++ b/.github/changelog_manager.rb @@ -0,0 +1,157 @@ +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 + def run(versions_arg) + validate_versions(versions_arg) + update(versions_arg) + puts "✅ CHANGELOG updated successfully" + end + + def update(versions) + versions_array = normalize_versions(versions) + + 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 + + sorted_data = sort_versions(version_data) + current_changelog = File.read(CHANGELOG_PATH) + + # Build changelog entries for each version and updated changelog + entries = sorted_data.map { |api_version, version_num| build_entry(api_version, version_num, TODAY) } + 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 + + 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 + + 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 + + + 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 + + def build_entry(api_version, version_number, date) + date_str = date.strftime('%Y-%m-%d') + last_change_date = extract_last_change_date(api_version) + + 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) + + ### Changed + - #{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 + # 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) + + 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 + def insert_entries(changelog, entries) + lines = changelog.split("\n") + + 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 + + header = lines[0...first_entry_index] + 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/config_validator.rb b/.github/config_validator.rb new file mode 100644 index 0000000..25b3d1d --- /dev/null +++ b/.github/config_validator.rb @@ -0,0 +1,80 @@ +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 + +# CLI Interface - allows direct execution from GitHub Actions +ConfigValidator.validate!(ARGV[0], ARGV[1]) 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..86739b4 --- /dev/null +++ b/.github/spec/changelog_manager_spec.rb @@ -0,0 +1,303 @@ +require 'rspec' +require 'json' +require 'date' +require 'fileutils' +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', changelog_path) + stub_const('ChangelogManager::BASE_PATH', temp_dir) + stub_const('ChangelogManager::TODAY', Date.new(2026, 01, 28)) + end + + after(:each) do + # Clean up temp directory + 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_version_with_changelog('v20250224', v20250224_package_fixture) + + result = ChangelogManager.update('v20250224') + + 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') + 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 + 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 + + # Both versions should be present + 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') + expect(updated_content).to include('### Changed') + + # 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_version_with_changelog('v20250224', v20250224_package_fixture) + + result = ChangelogManager.update(['v20250224']) + + 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 + + describe '.update sorting behavior' do + it 'always places v20250224 before v20111101' do + 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') + + expect(result).to be true + updated_content = read_changelog + + # 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 + + 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 + it 'includes date range when prior entry exists for API version' do + setup_version_with_changelog('v20111101', v20111101_package_fixture) + + result = ChangelogManager.update('v20111101') + + expect(result).to be true + updated_content = read_changelog + + # Should include the date range message + 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_version_with_changelog('v20250224', v20250224_package_fixture) + + result = ChangelogManager.update('v20250224') + + expect(result).to be true + 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]') + # 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 + 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 + + # v20111101 should have date range (prior entry on 2025-01-15) + 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] + expect(v20250224_section).not_to match(/between.*v20250224/) + # Both should have Changed section + expect(updated_content).to include('### Changed') + 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 + version_dir = setup_version_directory('v20250224', v20250224_package_fixture) + File.write(File.join(version_dir, 'package.json'), 'invalid json {]') + + expect { + ChangelogManager.update('v20250224') + }.to raise_error(JSON::ParserError) + end + + it 'raises error when version is not in package.json' do + setup_changelog + version_dir = setup_version_directory('v20250224', v20250224_package_fixture) + File.write( + File.join(version_dir, 'package.json'), + JSON.generate({ name: '@mx-platform/node' }) + ) + + expect { + ChangelogManager.update('v20250224') + }.to raise_error(/Could not read version/) + end + + it 'raises error when changelog has no existing entries' do + File.write(changelog_path, "# Changelog\n\nNo entries here") + setup_version_directory('v20250224', v20250224_package_fixture) + + 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_version_with_changelog('v20250224', v20250224_package_fixture) + + expect { + ChangelogManager.run('v20250224') + }.to output(/✅ CHANGELOG updated successfully/).to_stdout + + # 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 + 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/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/.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..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 @@ -98,29 +78,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/on-push-master.yml b/.github/workflows/on-push-master.yml index 4c8a300..adf85f3 100644 --- a/.github/workflows/on-push-master.yml +++ b/.github/workflows/on-push-master.yml @@ -27,35 +27,49 @@ 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: 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) + if: needs.check-skip-publish.outputs.skip_publish == 'false' && contains(github.event.head_commit.modified, 'v20111101') uses: ./.github/workflows/publish.yml 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) + + 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 + with: + version_directory: v20111101 + secrets: inherit + + # 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: + 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 + with: + version_directory: v20250224 + secrets: inherit + + 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 with: - version_directory: ${{ matrix.version.api_version }} + version_directory: v20250224 secrets: inherit diff --git a/.github/workflows/generate_publish_release.yml b/.github/workflows/openapi-generate-and-push.yml similarity index 59% rename from .github/workflows/generate_publish_release.yml rename to .github/workflows/openapi-generate-and-push.yml index 4211d57..6e2f09e 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: @@ -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 \ @@ -83,18 +85,13 @@ 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: name: generated-${{ matrix.api_version }} path: ./${{ matrix.api_version }} - Commit-and-Push: + Process-and-Push: runs-on: ubuntu-latest needs: [Setup, Generate] steps: @@ -118,57 +115,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="" + # 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: | + GENERATED_VERSIONS="${{ steps.track_versions.outputs.generated_versions }}" - # 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) - fi + cp LICENSE "./$VERSION/LICENSE" + cp CHANGELOG.md "./$VERSION/CHANGELOG.md" + cp MIGRATION.md "./$VERSION/MIGRATION.md" 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 @@ -183,32 +148,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/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/.github/workflows/run-specs.yml b/.github/workflows/run-specs.yml new file mode 100644 index 0000000..6cb6ac6 --- /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.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/.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: diff --git a/CHANGELOG.md b/CHANGELOG.md index 330816d..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] - 1/7/2026 +## [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] - 11/25/2025 +## [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] - 11/5/2025 +## [1.10.0] - 2025-11-05 (v20111101 API) ### Note - Last stable version with unified `MxPlatformApi` class 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' 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 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/SDK-Generation-Publishing-Flow.md b/docs/ARCHIVED/SDK-Generation-Publishing-Flow.md similarity index 97% rename from docs/SDK-Generation-Publishing-Flow.md rename to docs/ARCHIVED/SDK-Generation-Publishing-Flow.md index a932625..0fbb4cb 100644 --- a/docs/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 e61e7dc..47528b5 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)) @@ -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,51 +64,135 @@ 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. -Add the new version to the matrix strategy: +**Location 1: Workflow dispatch options** -**File**: `.github/workflows/on-push-master.yml` +In the `on.workflow_dispatch.inputs.api_version.options` section, add the new version to the dropdown list: -**Find this section**: ```yaml -strategy: - matrix: - version: - - api_version: v20111101 - npm_version: 2 - - api_version: v20250224 - npm_version: 3 +api_version: + description: "API version to generate" + required: true + type: choice + options: + - v20111101 + - v20250224 + - v20300101 # NEW ``` -**Add new entry**: +**Location 2: Update ConfigValidator** + +In `.github/config_validator.rb`, add your new version to the `SUPPORTED_VERSIONS` mapping: + +```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 +``` + +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 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 -strategy: - matrix: - version: - - api_version: v20111101 - npm_version: 2 - - api_version: v20250224 - npm_version: 3 - - api_version: v20300101 # NEW - npm_version: 4 +version_directory: + description: 'API version directory' + required: true + type: choice + options: + - v20111101 + - v20250224 + - v20300101 # NEW ``` -### 2.2 Update Path Triggers +#### 2.2b Update publish.yml -In the same file, add the new path trigger: +**Location: Workflow dispatch options** + +In the `on.workflow_dispatch.inputs.version_directory.options` section, add the new version to the dropdown list: -**Find this section**: ```yaml -on: - push: - branches: [master] - paths: - - 'v20111101/**' - - 'v20250224/**' +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, 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 + CONFIG="openapi/config-v20250224.yml" +elif [ "$VERSION" = "v20300101" ]; then + CONFIG="openapi/config-v20300101.yml" +fi +``` + +This dynamically builds the matrix JSON that determines which config file each version uses during generation. + +**Location 2: Add version to ChangelogManager priority order** + +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 ``` -**Add new path**: +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.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. + +**Location 1: Path trigger** + +In the `on.push.paths` section, add a new path for your version: + ```yaml on: push: @@ -114,68 +200,161 @@ on: paths: - 'v20111101/**' - 'v20250224/**' - - 'v20300101/**' # NEW + - 'v20300101/**' # NEW +``` + +This ensures the workflow triggers when changes to your version directory are pushed to master. + +**Location 2: Add publish job for new version** + +Add a new publish job for your version (copy and modify the existing v20250224 jobs): + +```yaml +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: Add release job for new version** + +Add a new release job for your version: + +```yaml +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)" ``` -This ensures that when changes to `v20300101/` are pushed to master, the publish and release workflows automatically trigger. +**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.5 Verify Workflow Syntax -### 2.3 Verify Workflow Syntax +Check that your YAML is valid for all four modified files: -Check that your YAML is valid: ```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'))" ``` +All commands should output valid parsed YAML without errors. + --- -## Step 3: Coordinate with OpenAPI Repository +## Step 3: Update Documentation -The OpenAPI repository must be updated to send the new API version in the `repository_dispatch` event payload. +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. -### What Needs to Change in openapi repo +### 3.1 Update Root README.md -When openapi repository wants to trigger generation for the new version, it should send: +Update the API versions table to include your new version. -```json -{ - "api_versions": "v20111101,v20250224,v20300101" -} +**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) | +``` + +**Location: Installation section** + +Also add an installation example for your version in the Installation section: + +```bash +# For v20300101 API +npm install mx-platform-node@^4 +``` + +### 3.2 Update MIGRATION.md + +Add a new migration section for users upgrading from the previous API version to your new version. + +**New section to add** (before the existing v20111101→v20250224 migration section): + +```markdown +## Upgrading from v20250224 (v3.x) to v20300101 (v4.x) + +The v20300101 API is now available, and v4.0.0 of this SDK provides support as an independent major version. + +### 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 +424,24 @@ 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/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 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 +- [ ] 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 +452,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**: 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 +### 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 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 -**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 --- @@ -291,7 +494,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/Changelog-Manager.md b/docs/Changelog-Manager.md new file mode 100644 index 0000000..9cb4c78 --- /dev/null +++ b/docs/Changelog-Manager.md @@ -0,0 +1,90 @@ +# 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) + +### Changed +- Updated v20250224 API specification to most current version... + +## [2.5.3] - 2025-01-28 (v20111101 API) + +### Changed +- Updated v20111101 API specification to most current version... +``` + +### Example Output + +With a prior entry: +```markdown +## [3.2.0] - 2025-01-28 (v20250224 API) + +### 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) + +### 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 + +- **Class**: `.github/changelog_manager.rb` +- **Tests**: `.github/spec/changelog_manager_spec.rb` +- **Fixtures**: `.github/spec/fixtures/` diff --git a/docs/Multi-Version-SDK-Flow.md b/docs/Multi-Version-SDK-Flow.md index 5b9e319..feb9cc2 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 @@ -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 → `generate_publish_release.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,31 +113,14 @@ 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 -``` - -### Auto-Publish Flow - -```mermaid -sequenceDiagram - participant Push as Git Push - participant Auto as on-push-
master.yml - participant pub as publish.yml - participant rel as release.yml - 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 - 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 ``` --- @@ -162,15 +148,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/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 | 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 | @@ -204,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 030fe37..fb2dc96 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 `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` + +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**: @@ -214,6 +228,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) in the "Publishing via on-push-master.yml" section for full gate job implementation details. + +--- + ### Generation Produces Stale Spec Files **Symptom**: Generated SDK doesn't include changes that were in the OpenAPI spec @@ -226,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 a2827e4..bd72063 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 --- @@ -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 @@ -93,64 +93,68 @@ 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. + ### Changed + - Updated v20250224 API specification... + + ## [2.0.1] - 2026-01-27 (v20111101 API) + ### Changed + - 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 - **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 `Process-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: `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) -**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 - -**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 +**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 -#### Step 6: Create GitHub Releases (Parallel Matrix Execution) +**Serialization Chain** (for race condition prevention): +- v20111101 publish runs first (depends on check-skip-publish) +- 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 -**Same architecture as publish**: Uses `workflow_call` to invoke `release.yml` - -**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 - -**Result**: -- GitHub release `v2.0.1` created (v20111101 API) -- GitHub release `v3.0.1` created (v20250224 API) -- Both versions have separate release history +**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 --- @@ -174,11 +178,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) @@ -210,9 +220,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 @@ -229,7 +240,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) @@ -239,7 +250,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/**`) @@ -253,7 +338,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 @@ -273,52 +358,84 @@ 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) + +**Jobs**: `publish-v20111101` and `release-v20111101` + +**publish-v20111101 executes when**: +- No `[skip-publish]` flag +- Files in `v20111101/**` were changed + +**release-v20111101 executes when**: +- No `[skip-publish]` flag +- Files in `v20111101/**` were changed +- **AND** `publish-v20111101` completes + +**Process**: +1. Publish job calls `publish.yml` with `version_directory: v20111101` +2. Release job calls `release.yml` after publish completes + +#### Step 3: Gate Job - Unblock v20250224 Publishing + +**Job**: `gate-v20111101-complete` -**Matrix Definition**: ```yaml -strategy: - matrix: - version: - - api_version: v20111101 - npm_version: 2 - - api_version: v20250224 - npm_version: 3 +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)" ``` -**For Each Version**: +**Key Feature**: Uses `always()` condition - runs even when `release-v20111101` is skipped -**Job Executes When**: -- No upstream failures (`!cancelled()`) -- No `[skip-publish]` flag in commit message -- Files in this version's directory were changed +**Why This Pattern Exists**: -**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 +The gate job solves a critical dependency problem in serial publishing: -**Result**: Each version publishes independently, no race conditions, parallel execution when both versions changed +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 -#### Step 3: Matrix-Based Release Jobs +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 -**Same Matrix Strategy as Publish** +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) -**For Each Version**: +**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 -**Job Executes When**: -- Same conditions as publish (skip-publish flag, path match) -- **Plus**: Only after its corresponding publish job succeeds +#### Step 4: Publish and Release v20250224 (Second in Serial Chain) -This ensures each version's release depends only on its own publish job, preventing race conditions. +**Jobs**: `publish-v20250224` and `release-v20250224` + +**publish-v20250224 executes when**: +- No `[skip-publish]` flag +- Files in `v20250224/**` were changed +- **AND** `gate-v20111101-complete` completes (ensures serial ordering) + +**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. --- @@ -377,6 +494,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) +- `openapi-generate-and-push.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 @@ -537,18 +684,23 @@ 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) ├─ 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) + ↓ +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, CHANGELOG updated +Result: Both versions published and released sequentially, CHANGELOG updated ``` ### Manual Flow Timeline @@ -557,16 +709,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 ``` 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)