diff --git a/.github/scripts/changelog.js b/.github/scripts/changelog.js index 73d21f5..3372552 100644 --- a/.github/scripts/changelog.js +++ b/.github/scripts/changelog.js @@ -19,7 +19,7 @@ import { execFileSync } from 'node:child_process'; import https from 'node:https'; -const OPENAI_MODEL = 'gpt-4-turbo-2024-04-09'; +const OPENAI_MODEL = 'gpt-4o-mini-2024-07-18'; const PROMPT = ` You're the head of developer relations at a SaaS. Write a concise, professional, and fun changelog, prioritizing important changes. @@ -33,7 +33,7 @@ For each commit, use this format: - **Bold 3-5 word Summary** {optional related GitHub emoji}: Continuation with 1-3 sentence description. @author (optional #PR) - Sub-bullets for key details (include only if necessary) -Place PR/issue numbers matching the exact pattern #\d+ (e.g., #123) at the end of the section in parentheses. +Place PR/issue numbers matching the exact pattern #\\d+ (e.g., #123) at the end of the section in parentheses. Do not use commit hashes as PR numbers. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8ad5bd..869d029 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,9 @@ jobs: disable-wiki: false wiki-sidebar-changelog-max: 10 delete-legacy-tags: false # Note: We don't want to delete tags in this repository - module-change-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/** + terraform-docs-version: v0.19.0 + module-path-ignore: tf-modules/kms/examples/complete + module-change-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/**,examples/** module-asset-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/** use-ssh-source-format: true diff --git a/README.md b/README.md index a7d076d..26da14b 100644 --- a/README.md +++ b/README.md @@ -17,31 +17,24 @@ documentation. [4]: https://github.com/techpivot/terraform-module-releaser/actions/workflows/codeql-analysis.yml [5]: https://sonarcloud.io/summary/new_code?id=terraform-module-releaser -Simplify the management of Terraform modules in your monorepo with this **GitHub Action**, designed to automate -module-specific versioning and releases. By streamlining the Terraform module release process, this action allows you to -manage multiple modules in a single repository while still maintaining independence and flexibility. Additionally, it -generates a beautifully crafted wiki for each module, complete with readme information, usage examples, Terraform-docs -details, and a full changelog. - -## Key Features - -- **Efficient Module Tagging**: Module tags are specifically designed to only include the current Terraform module - directory (and nothing else), thereby dramatically decreasing the size and improving Terraform performance. -- **Automated Release Management**: Identifies Terraform modules affected by changes in a pull request and determines - the necessary release type (major, minor, or patch) based on commit messages. -- **Versioning and Tagging**: Calculates the next version tag for each module and commits, tags, and pushes new versions - for each module individually. -- **Release Notes and Comments**: Generates a pull request comment summarizing module changes and release types, and - creates a GitHub release for each module with a dynamically generated description. -- **Wiki Integration**: Updates the wiki with new release information, including: - - README.md information for each module - - Beautifully crafted module usage examples - - `terraform-docs` details for each module - - Full changelog for each module -- **Deletes Synced**: Automatically removes tags from deleted Terraform modules, keeping your repository organized and - up-to-date. -- **Flexible Configuration**: Offers advanced input options for customization, allowing you to tailor the action to your - specific needs. +Simplify the management of Terraform modules in your monorepo with this **GitHub Action**. It automates module-specific +versioning and releases by creating proper Git tags and GitHub releases based on your commit messages. Each module +maintains independence while living in the same repository, with proper isolation for clean dependency management. +Additionally, the action generates a beautifully crafted wiki for each module, complete with readme information, usage +examples, Terraform-docs details, and a full changelog. + +## 🚀 Features + +- **Efficient Module Tagging** – Only includes module directory content, dramatically improving Terraform performance. +- **Smart Versioning** – Automatically determines release types (major, minor, patch) based on commit messages. +- **Comprehensive Wiki** – Generates beautiful documentation with usage examples, terraform-docs output, and full + changelogs. +- **Release Automation** – Creates GitHub releases, pull request comments, and version tags with minimal effort. +- **Self-Maintaining** – Automatically removes tags from deleted modules, keeping your repository clean and organized. +- **100% GitHub Native** – No external dependencies or services required for modules or operation, everything stays + within your GitHub ecosystem. +- **Zero Configuration** – Works out-of-the-box with sensible defaults for immediate productivity. +- **Flexible & Extensible** – Customizable settings to precisely match your team's specific workflow requirements. ## Demo @@ -180,10 +173,38 @@ configuring the following optional input parameters as needed. | `disable-wiki` | Whether to disable wiki generation for Terraform modules | `false` | | `wiki-sidebar-changelog-max` | An integer that specifies how many changelog entries are displayed in the sidebar per module | `5` | | `disable-branding` | Controls whether a small branding link to the action's repository is added to PR comments. Recommended to leave enabled to support OSS. | `false` | +| `module-path-ignore` | Comma separated list of module paths to completely ignore (relative to working directory). This will prevent any versioning, release, or documentation for these modules. | `` (empty) | | `module-change-exclude-patterns` | A comma-separated list of file patterns to exclude from triggering version changes in Terraform modules. Patterns follow glob syntax (e.g., `.gitignore,_.md`) and are relative to each Terraform module directory. Files matching these patterns will not affect version changes. **WARNING**: Avoid excluding '`_.tf`' files, as they are essential for module detection and versioning processes. | `.gitignore, *.md, *.tftest.hcl, tests/**` | | `module-asset-exclude-patterns` | A comma-separated list of file patterns to exclude when bundling a Terraform module for tag/release. Patterns follow glob syntax (e.g., `tests/\*\*`) and are relative to each Terraform module directory. Files matching these patterns will be excluded from the bundled output. | `.gitignore, *.md, *.tftest.hcl, tests/**` | | `use-ssh-source-format` | If enabled, all links to source code in generated Wiki documentation will use SSH standard format (e.g., `git::ssh://git@github.com/owner/repo.git`) instead of HTTPS format (`git::https://github.com/owner/repo.git`) | `false` | +### Understanding the filtering options + +- **`module-path-ignore`**: Completely ignores specified module paths. Any module whose path matches any pattern in this + list will not be processed at all by the action. This is useful for: + + - Excluding example modules (e.g., `**/examples/**`) + - Skipping test modules (e.g., `**/test/**`) + - Ignoring documentation-focused modules (e.g., `**/docs/**`) + - Excluding entire directories or paths that contain Terraform files but shouldn't be versioned as modules + + Example: + + ```yaml + module-path-ignore: "**/examples/**,**/test/**,root-modules" + ``` + +- **`module-change-exclude-patterns`**: These patterns determine which file changes are _ignored_ when checking if a + module needs a new release. For example, changes to documentation, examples, or workflow files typically don't require + a new module release. +- **`module-asset-exclude-patterns`**: When building a release asset for a module, these patterns determine which files + are _excluded_ from the asset. This helps reduce the asset size by omitting test files, examples, documentation, etc. + +All pattern matching is implemented using [minimatch](https://github.com/isaacs/minimatch), which supports glob patterns +similar to those used in `.gitignore` files. For more details on the pattern matching implementation, see our +[source code](https://github.com/techpivot/terraform-module-releaser/blob/main/src/utils/file.ts) or visit the +[minimatch documentation](https://github.com/isaacs/minimatch). + ### Example Usage with Inputs ```yml @@ -219,6 +240,7 @@ jobs: module-change-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/** module-asset-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/** use-ssh-source-format: false + module-path-ignore: path/to/ignore1,path/to/ignore2 ``` ## Outputs diff --git a/__mocks__/config.ts b/__mocks__/config.ts index 4743323..815a2fc 100644 --- a/__mocks__/config.ts +++ b/__mocks__/config.ts @@ -21,6 +21,7 @@ const defaultConfig: Config = { disableWiki: false, wikiSidebarChangelogMax: 10, disableBranding: false, + modulePathIgnore: ['tf-modules/kms/examples/complete'], moduleChangeExcludePatterns: ['.gitignore', '*.md'], moduleAssetExcludePatterns: ['tests/**', 'examples/**'], githubToken: 'ghp_test_token_2c6912E7710c838347Ae178B4', @@ -40,6 +41,7 @@ const validConfigKeys = [ 'disableWiki', 'wikiSidebarChangelogMax', 'disableBranding', + 'modulePathIgnore', 'moduleChangeExcludePatterns', 'moduleAssetExcludePatterns', 'githubToken', diff --git a/__tests__/config.test.ts b/__tests__/config.test.ts index 2389646..be14bc2 100644 --- a/__tests__/config.test.ts +++ b/__tests__/config.test.ts @@ -1,5 +1,15 @@ import { clearConfigForTesting, config, getConfig } from '@/config'; -import { booleanConfigKeys, booleanInputs, requiredInputs, stubInputEnv } from '@/tests/helpers/inputs'; +import { + arrayInputs, + booleanInputs, + inputToConfigKey, + inputToConfigKeyMap, + optionalInputs, + requiredInputs, + stringInputs, + stubInputEnv, +} from '@/tests/helpers/inputs'; +import type { Config } from '@/types'; import { endGroup, getBooleanInput, getInput, info, startGroup } from '@actions/core'; import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; @@ -26,6 +36,30 @@ describe('config', () => { }); } + for (const input of optionalInputs) { + it(`should handle optional input "${input}" when not present`, () => { + stubInputEnv({ [input]: null }); + // Simply verify it doesn't throw without the specific error object + expect(() => getConfig()).not.toThrow(); + + // Get the config and check the actual value + const config = getConfig(); + // Get the config key using the mapping directly if possible + const configKey = inputToConfigKeyMap[input] || inputToConfigKey(input); + + // Type-safe access using the mapping + if (arrayInputs.includes(input)) { + // Cast configKey to keyof Config to ensure type safety + expect(config[configKey as keyof Config]).toEqual([]); + } + if (stringInputs.includes(input)) { + expect(config[configKey as keyof Config]).toEqual(''); + } + + expect(getInput).toHaveBeenCalled(); + }); + } + for (const input of booleanInputs) { it(`should throw error when input "${input}" has an invalid boolean value`, () => { stubInputEnv({ [input]: 'invalid-boolean' }); @@ -70,8 +104,10 @@ describe('config', () => { // Check the boolean conversion for each key in booleanInputs const config = getConfig(); - for (const inputKey of booleanConfigKeys) { - expect(config[inputKey]).toBe(booleanValue.toLowerCase() === 'true'); + for (const booleanInput of booleanInputs) { + // Get config key from the mapping, which is already typed as keyof Config + const configKey = inputToConfigKeyMap[booleanInput]; + expect(config[configKey]).toBe(booleanValue.toLowerCase() === 'true'); } } }); @@ -117,11 +153,11 @@ describe('config', () => { expect(config.githubToken).toBe('ghp_test_token_2c6912E7710c838347Ae178B4'); expect(config.moduleChangeExcludePatterns).toEqual(['.gitignore', '*.md']); expect(config.moduleAssetExcludePatterns).toEqual(['tests/**', 'examples/**']); + expect(config.modulePathIgnore).toEqual(['tf-modules/kms/examples/complete']); expect(config.useSSHSourceFormat).toBe(false); expect(startGroup).toHaveBeenCalledWith('Initializing Config'); expect(startGroup).toHaveBeenCalledTimes(1); expect(endGroup).toHaveBeenCalledTimes(1); - expect(info).toHaveBeenCalledTimes(11); expect(vi.mocked(info).mock.calls).toEqual([ ['Major Keywords: MAJOR CHANGE, BREAKING CHANGE, !'], ['Minor Keywords: feat, feature'], @@ -131,6 +167,7 @@ describe('config', () => { ['Delete Legacy Tags: false'], ['Disable Wiki: false'], ['Wiki Sidebar Changelog Max: 10'], + ['Module Paths to Ignore: tf-modules/kms/examples/complete'], ['Module Change Exclude Patterns: .gitignore, *.md'], ['Module Asset Exclude Patterns: tests/**, examples/**'], ['Use SSH Source Format: false'], @@ -180,5 +217,11 @@ describe('config', () => { expect(config.majorKeywords).toEqual(['BREAKING CHANGE', '!']); expect(config.moduleChangeExcludePatterns).toEqual(['.gitignore', '*.md']); }); + + it('should handle empty modulePathIgnore', () => { + stubInputEnv({ 'module-path-ignore': '' }); + const config = getConfig(); + expect(config.modulePathIgnore).toEqual([]); + }); }); }); diff --git a/__tests__/fixtures/Home.md b/__tests__/fixtures/Home.md index dc73fb8..0a31d2b 100644 --- a/__tests__/fixtures/Home.md +++ b/__tests__/fixtures/Home.md @@ -7,6 +7,8 @@ providing an overview of their functionality and the latest versions. | Module Name | Latest Version | | -- | -- | +| [kms](/techpivot/terraform-module-releaser/wiki/kms) | null | +| [kms/examples/complete](/techpivot/terraform-module-releaser/wiki/kms∕examples∕complete) | null | | [s3-bucket-object](/techpivot/terraform-module-releaser/wiki/s3‒bucket‒object) | null | | [vpc-endpoint](/techpivot/terraform-module-releaser/wiki/vpc‒endpoint) | v1.0.0 | diff --git a/__tests__/fixtures/_Sidebar.md b/__tests__/fixtures/_Sidebar.md index fb2f4bb..c633141 100644 --- a/__tests__/fixtures/_Sidebar.md +++ b/__tests__/fixtures/_Sidebar.md @@ -3,6 +3,26 @@ ## Terraform Modules