From ab4425ca7ce14909491d116416f4c8deb1723611 Mon Sep 17 00:00:00 2001 From: Illia Date: Mon, 28 Jul 2025 18:55:33 +0300 Subject: [PATCH 01/19] Add wiki-custom-usage-string setting --- action.yml | 3 +++ src/config.ts | 1 + src/types/config.types.ts | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/action.yml b/action.yml index 124dd781..7cd5da58 100644 --- a/action.yml +++ b/action.yml @@ -102,6 +102,9 @@ inputs: If enabled, all links to source code in generated Wiki documentation will use SSH format instead of HTTPS format. required: true default: "false" + wiki-custom-usage-string: + description: A raw, multi-line string to override the default 'Usage' section in the generated wiki. If not provided, a default usage block will be generated. + required: false github_token: description: > Required for retrieving pull request metadata, tags, releases, updating PR comments, wiki, and creating diff --git a/src/config.ts b/src/config.ts index 45390210..00c4e604 100644 --- a/src/config.ts +++ b/src/config.ts @@ -74,6 +74,7 @@ function initializeConfig(): Config { moduleChangeExcludePatterns: getArrayInput('module-change-exclude-patterns', false), moduleAssetExcludePatterns: getArrayInput('module-asset-exclude-patterns', false), useSSHSourceFormat: getBooleanInput('use-ssh-source-format', { required: true }), + wikiCustomUsageString: getInput('wiki-custom-usage-string', { required: false }), }; // Validate that *.tf is not in excludePatterns diff --git a/src/types/config.types.ts b/src/types/config.types.ts index dea8abf6..c2f98576 100644 --- a/src/types/config.types.ts +++ b/src/types/config.types.ts @@ -102,4 +102,10 @@ export interface Config { * Paths are relative to the workspace directory. */ modulePathIgnore: string[]; + + /** + * A raw, multi-line string to override the default 'Usage' section in the generated wiki. + * If not provided, a default usage block will be generated. + */ + wikiCustomUsageString?: string; } From 0214a7f588dd892244f7f05425ba848540cdeefb Mon Sep 17 00:00:00 2001 From: Illia Date: Mon, 28 Jul 2025 18:56:18 +0300 Subject: [PATCH 02/19] Use config.wikiCustomUsageString in the wiki generation logic --- src/wiki.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/wiki.ts b/src/wiki.ts index 89b95c9a..f3b6ff38 100644 --- a/src/wiki.ts +++ b/src/wiki.ts @@ -304,15 +304,21 @@ async function generateWikiTerraformModule(terraformModule: TerraformModule): Pr const changelog = getTerraformModuleFullReleaseChangelog(terraformModule); const tfDocs = await generateTerraformDocs(terraformModule); const moduleSource = getModuleSource(context.repoUrl, config.useSSHSourceFormat); + const usage = + config.wikiCustomUsageString || + [ + 'To use this module in your Terraform, refer to the below module example:\n', + '```hcl', + `module "${terraformModule.name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}" {`, + ` source = "git::${moduleSource}?ref=${terraformModule.getLatestTag()}"`, + '\n # See inputs below for additional required parameters', + '}', + '```', + ].join('\n'); + const content = [ '# Usage\n', - 'To use this module in your Terraform, refer to the below module example:\n', - '```hcl', - `module "${terraformModule.name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}" {`, - ` source = "git::${moduleSource}?ref=${terraformModule.getLatestTag()}"`, - '\n # See inputs below for additional required parameters', - '}', - '```', + usage, '\n# Attributes\n', '', tfDocs, From 7ff9ac3d1548cf6d7107b4d0a2df843ff7e8247b Mon Sep 17 00:00:00 2001 From: Illia Date: Tue, 29 Jul 2025 12:00:51 +0300 Subject: [PATCH 03/19] Extract default usage block into a separate function --- src/wiki.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/wiki.ts b/src/wiki.ts index f3b6ff38..10f8d56f 100644 --- a/src/wiki.ts +++ b/src/wiki.ts @@ -29,6 +29,17 @@ import which from 'which'; // Special subdirectory inside the primary repository where the wiki is checked out. const WIKI_SUBDIRECTORY_NAME = '.wiki'; +const DEFAULT_USAGE_BLOCK = (terraformModule: TerraformModule, moduleSource: string): string => + [ + 'To use this module in your Terraform, refer to the below module example:\n', + '```hcl', + `module "${terraformModule.name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}" {`, + ` source = "git::${moduleSource}?ref=${terraformModule.getLatestTag()}"`, + '\n # See inputs below for additional required parameters', + '}', + '```', + ].join('\n'); + /** * Clones the wiki repository for the current GitHub repository into a specified subdirectory. * @@ -304,17 +315,7 @@ async function generateWikiTerraformModule(terraformModule: TerraformModule): Pr const changelog = getTerraformModuleFullReleaseChangelog(terraformModule); const tfDocs = await generateTerraformDocs(terraformModule); const moduleSource = getModuleSource(context.repoUrl, config.useSSHSourceFormat); - const usage = - config.wikiCustomUsageString || - [ - 'To use this module in your Terraform, refer to the below module example:\n', - '```hcl', - `module "${terraformModule.name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}" {`, - ` source = "git::${moduleSource}?ref=${terraformModule.getLatestTag()}"`, - '\n # See inputs below for additional required parameters', - '}', - '```', - ].join('\n'); + const usage = config.wikiCustomUsageString || DEFAULT_USAGE_BLOCK(terraformModule, moduleSource); const content = [ '# Usage\n', From 0dd7a8da918bf23a374cec913f404d82e2fc0360 Mon Sep 17 00:00:00 2001 From: Illia Date: Tue, 29 Jul 2025 12:01:51 +0300 Subject: [PATCH 04/19] A couple of test cases to check the new logic of "usage" block generation --- __tests__/wiki.test.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/__tests__/wiki.test.ts b/__tests__/wiki.test.ts index 5c2563da..fbf84ffe 100644 --- a/__tests__/wiki.test.ts +++ b/__tests__/wiki.test.ts @@ -257,6 +257,39 @@ describe('wiki', async () => { 'https://github.com/techpivot/terraform-module-releaser/wiki/aws∕vpc', ); }); + + it('should use the custom usage string when provided', async () => { + const customUsage = 'This is a custom usage string.'; + config.set({ wikiCustomUsageString: customUsage }); + const files = await generateWikiFiles(terraformModules); + for (const file of files) { + if ( + file.endsWith('.md') && + basename(file) !== 'Home.md' && + basename(file) !== '_Sidebar.md' && + basename(file) !== '_Footer.md' + ) { + const content = readFileSync(file, 'utf8'); + expect(content).toContain(`# Usage\n\n${customUsage}`); + } + } + }); + + it('should use the default usage block when custom string is not provided', async () => { + config.set({ wikiCustomUsageString: undefined }); + const files = await generateWikiFiles(terraformModules); + for (const file of files) { + if ( + file.endsWith('.md') && + basename(file) !== 'Home.md' && + basename(file) !== '_Sidebar.md' && + basename(file) !== '_Footer.md' + ) { + const content = readFileSync(file, 'utf8'); + expect(content).toContain('To use this module in your Terraform, refer to the below module example:'); + } + } + }); }); describe('commitAndPushWikiChanges()', () => { From 6a9e7150d4da564468d0132a2cedc48ab1b28790 Mon Sep 17 00:00:00 2001 From: Illia Date: Tue, 29 Jul 2025 12:02:17 +0300 Subject: [PATCH 05/19] Mention wiki-custom-usage-string in the README --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 191101eb..e4f70435 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,7 @@ configuring the following optional input parameters as needed. | `module-change-exclude-patterns` | Comma-separated list of file patterns (relative to each module) to exclude from triggering version changes. Lets you release a module but control which files inside it do not force a version bump.
[Read more here](#understanding-the-filtering-options) | `.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` | +| `wiki-custom-usage-string` | A raw, multi-line string to override the default 'Usage' section in the generated wiki. If not provided, a default usage block will be generated. | `` (empty string) | ### Understanding the filtering options @@ -317,6 +318,17 @@ jobs: module-change-exclude-patterns: .gitignore,*.md,docs/**,examples/**,*.tftest.hcl,tests/** module-asset-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/** use-ssh-source-format: false + wiki-custom-usage-string: | + # My Custom Usage Instructions + + This is a custom usage block. You can add any markdown you want here. + + ```hcl + module "my_module" { + source = "..." + # ... + } + ``` ``` ## Outputs From 4c39fa2585e2d37268102e8558e8acfebc309bcb Mon Sep 17 00:00:00 2001 From: Illia Date: Tue, 29 Jul 2025 14:35:10 +0300 Subject: [PATCH 06/19] Fix linter errors --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e4f70435..251ff1e9 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ configuring the following optional input parameters as needed. | `module-change-exclude-patterns` | Comma-separated list of file patterns (relative to each module) to exclude from triggering version changes. Lets you release a module but control which files inside it do not force a version bump.
[Read more here](#understanding-the-filtering-options) | `.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` | -| `wiki-custom-usage-string` | A raw, multi-line string to override the default 'Usage' section in the generated wiki. If not provided, a default usage block will be generated. | `` (empty string) | +| `wiki-custom-usage-string` | A raw, multi-line string to override the default 'Usage' section in the generated wiki. If not provided, a default usage block will be generated. | `` (empty string) | ### Understanding the filtering options @@ -284,7 +284,7 @@ similar to those used in `.gitignore` files. For more details on the pattern mat ### Example Usage with Inputs -```yml +````yml name: Terraform Module Releaser on: pull_request: @@ -329,7 +329,7 @@ jobs: # ... } ``` -``` +```` ## Outputs From 67f2b2ee9d93f488e934ac27bc1ca6de8670b42e Mon Sep 17 00:00:00 2001 From: Illia Date: Wed, 30 Jul 2025 13:18:18 +0300 Subject: [PATCH 07/19] Replace wiki-custom-usage-string with wiki-custom-usage-template in config and readme --- README.md | 10 +++++++--- action.yml | 2 +- src/config.ts | 2 +- src/types/config.types.ts | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 251ff1e9..0ec907fe 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ configuring the following optional input parameters as needed. | `module-change-exclude-patterns` | Comma-separated list of file patterns (relative to each module) to exclude from triggering version changes. Lets you release a module but control which files inside it do not force a version bump.
[Read more here](#understanding-the-filtering-options) | `.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` | -| `wiki-custom-usage-string` | A raw, multi-line string to override the default 'Usage' section in the generated wiki. If not provided, a default usage block will be generated. | `` (empty string) | +| `wiki-custom-usage-template` | A raw, multi-line string to override the default 'Usage' section in the generated wiki. Allow using variables like {{module_name}}, {{latest_tag}} and {{latest_tag_version}}. If not provided, a default usage block will be generated. | `` (empty string) | ### Understanding the filtering options @@ -318,10 +318,14 @@ jobs: module-change-exclude-patterns: .gitignore,*.md,docs/**,examples/**,*.tftest.hcl,tests/** module-asset-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/** use-ssh-source-format: false - wiki-custom-usage-string: | + wiki-custom-usage-template: | # My Custom Usage Instructions - This is a custom usage block. You can add any markdown you want here. + This is a custom usage block. + + You can add any markdown you want here. + + And use variables like {{module_name}}, {{latest_tag}}, and {{latest_tag_version}}. ```hcl module "my_module" { diff --git a/action.yml b/action.yml index 7cd5da58..f7f5554b 100644 --- a/action.yml +++ b/action.yml @@ -102,7 +102,7 @@ inputs: If enabled, all links to source code in generated Wiki documentation will use SSH format instead of HTTPS format. required: true default: "false" - wiki-custom-usage-string: + wiki-custom-usage-template: description: A raw, multi-line string to override the default 'Usage' section in the generated wiki. If not provided, a default usage block will be generated. required: false github_token: diff --git a/src/config.ts b/src/config.ts index 00c4e604..b474d4c7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -74,7 +74,7 @@ function initializeConfig(): Config { moduleChangeExcludePatterns: getArrayInput('module-change-exclude-patterns', false), moduleAssetExcludePatterns: getArrayInput('module-asset-exclude-patterns', false), useSSHSourceFormat: getBooleanInput('use-ssh-source-format', { required: true }), - wikiCustomUsageString: getInput('wiki-custom-usage-string', { required: false }), + wikiCustomUsageTemplate: getInput('wiki-custom-usage-template', { required: false }), }; // Validate that *.tf is not in excludePatterns diff --git a/src/types/config.types.ts b/src/types/config.types.ts index c2f98576..c7cf5e5c 100644 --- a/src/types/config.types.ts +++ b/src/types/config.types.ts @@ -107,5 +107,5 @@ export interface Config { * A raw, multi-line string to override the default 'Usage' section in the generated wiki. * If not provided, a default usage block will be generated. */ - wikiCustomUsageString?: string; + wikiCustomUsageTemplate?: string; } From 2088bd48d2446cfae6193ed6b886e9d2e946c586 Mon Sep 17 00:00:00 2001 From: Illia Date: Wed, 30 Jul 2025 13:18:52 +0300 Subject: [PATCH 08/19] New method to get the version number without "v" --- src/terraform-module.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/terraform-module.ts b/src/terraform-module.ts index 4d2894e8..7da8f36f 100644 --- a/src/terraform-module.ts +++ b/src/terraform-module.ts @@ -220,6 +220,19 @@ export class TerraformModule { return this.tags[0].replace(`${this.name}/`, ''); } + /** + * Returns the version part of the latest tag for this module, without any "v" prefix. + * + * @returns {string | null} The version string without any prefixes (e.g., '1.2.3'), or null if no tags exist. + */ + public getLatestTagVersionNumber(): string | null { + const version = this.getLatestTagVersion(); + if (!version) { + return null; + } + return version.replace(/^v/, ''); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Releases ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From 968ed7f895529cd35448c2b025e9a70554cc2198 Mon Sep 17 00:00:00 2001 From: Illia Date: Wed, 30 Jul 2025 13:20:13 +0300 Subject: [PATCH 09/19] Add new module to handle template rendering --- __tests__/templating.test.ts | 39 ++++++++++++++++++++++++++++++++++++ src/templating.ts | 12 +++++++++++ 2 files changed, 51 insertions(+) create mode 100644 __tests__/templating.test.ts create mode 100644 src/templating.ts diff --git a/__tests__/templating.test.ts b/__tests__/templating.test.ts new file mode 100644 index 00000000..82c62d4e --- /dev/null +++ b/__tests__/templating.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from 'vitest'; +import { render } from '../src/templating'; + +describe('templating', () => { + it('should replace a single placeholder', () => { + const template = 'Hello, {{name}}!'; + const variables = { name: 'World' }; + const result = render(template, variables); + expect(result).toBe('Hello, World!'); + }); + + it('should replace multiple placeholders', () => { + const template = '{{greeting}}, {{name}}!'; + const variables = { greeting: 'Hi', name: 'There' }; + const result = render(template, variables); + expect(result).toBe('Hi, There!'); + }); + + it('should handle templates with no placeholders', () => { + const template = 'Just a plain string.'; + const variables = { name: 'World' }; + const result = render(template, variables); + expect(result).toBe('Just a plain string.'); + }); + + it('should handle empty string values', () => { + const template = 'A{{key}}B'; + const variables = { key: '' }; + const result = render(template, variables); + expect(result).toBe('AB'); + }); + + it('should leave unmapped placeholders untouched', () => { + const template = 'Hello, {{name}} and {{unmapped}}!'; + const variables = { name: 'World' }; + const result = render(template, variables); + expect(result).toBe('Hello, World and {{unmapped}}!'); + }); +}); diff --git a/src/templating.ts b/src/templating.ts new file mode 100644 index 00000000..19e1f84b --- /dev/null +++ b/src/templating.ts @@ -0,0 +1,12 @@ +/** + * Renders a template string by replacing placeholders with provided values. + * + * @param template The template string containing placeholders in the format `{{key}}`. + * @param variables An object where keys correspond to placeholder names and values are their replacements. + * @returns The rendered string with placeholders replaced. + */ +export const render = (template: string, variables: Record): string => { + return template.replace(/\{\{(\w+)\}\}/g, (placeholder, key) => { + return variables.hasOwnProperty(key) ? variables[key] : placeholder; + }); +}; From f02a756bb4ed86dcbeed02200f009d04007ab107 Mon Sep 17 00:00:00 2001 From: Illia Date: Wed, 30 Jul 2025 13:21:13 +0300 Subject: [PATCH 10/19] Use newly defined render function for the wiki generation --- src/wiki.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/wiki.ts b/src/wiki.ts index 10f8d56f..d63d05c4 100644 --- a/src/wiki.ts +++ b/src/wiki.ts @@ -8,6 +8,7 @@ import { getTerraformModuleFullReleaseChangelog } from '@/changelog'; import { config } from '@/config'; import { context } from '@/context'; import { generateTerraformDocs } from '@/terraform-docs'; +import { render } from '@/templating'; import type { TerraformModule } from '@/terraform-module'; import type { ExecSyncError, WikiStatusResult } from '@/types'; import { @@ -315,7 +316,18 @@ async function generateWikiTerraformModule(terraformModule: TerraformModule): Pr const changelog = getTerraformModuleFullReleaseChangelog(terraformModule); const tfDocs = await generateTerraformDocs(terraformModule); const moduleSource = getModuleSource(context.repoUrl, config.useSSHSourceFormat); - const usage = config.wikiCustomUsageString || DEFAULT_USAGE_BLOCK(terraformModule, moduleSource); + + let usage: string; + if (config.wikiCustomUsageTemplate) { + const variables = { + module_name: terraformModule.name, + latest_tag: terraformModule.getLatestTag(), + latest_tag_version: terraformModule.getLatestTagVersionNumber(), + }; + usage = render(config.wikiCustomUsageTemplate, variables); + } else { + usage = DEFAULT_USAGE_BLOCK(terraformModule, moduleSource); + } const content = [ '# Usage\n', From 19ae9bc74cda02dd6e834fc2d293fc4606b2b61d Mon Sep 17 00:00:00 2001 From: Illia Date: Wed, 30 Jul 2025 13:21:59 +0300 Subject: [PATCH 11/19] Tests for new templated "usage" block genration --- __tests__/wiki.test.ts | 53 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/__tests__/wiki.test.ts b/__tests__/wiki.test.ts index fbf84ffe..f7821118 100644 --- a/__tests__/wiki.test.ts +++ b/__tests__/wiki.test.ts @@ -258,9 +258,9 @@ describe('wiki', async () => { ); }); - it('should use the custom usage string when provided', async () => { - const customUsage = 'This is a custom usage string.'; - config.set({ wikiCustomUsageString: customUsage }); + it('should use the custom usage template when provided', async () => { + const customUsage = 'This is a custom usage template: {{module_name}}'; + config.set({ wikiCustomUsageTemplate: customUsage }); const files = await generateWikiFiles(terraformModules); for (const file of files) { if ( @@ -270,13 +270,14 @@ describe('wiki', async () => { basename(file) !== '_Footer.md' ) { const content = readFileSync(file, 'utf8'); - expect(content).toContain(`# Usage\n\n${customUsage}`); + const moduleName = basename(file, '.md'); + expect(content).toContain(`# Usage\n\nThis is a custom usage template: ${moduleName}`); } } }); - it('should use the default usage block when custom string is not provided', async () => { - config.set({ wikiCustomUsageString: undefined }); + it('should use the default usage block when custom template is not provided', async () => { + config.set({ wikiCustomUsageTemplate: undefined }); const files = await generateWikiFiles(terraformModules); for (const file of files) { if ( @@ -290,6 +291,46 @@ describe('wiki', async () => { } } }); + + it('should handle missing variables in the custom usage template', async () => { + const customUsage = 'Module: {{module_name}}, Missing: {{missing_variable}}'; + config.set({ wikiCustomUsageTemplate: customUsage }); + const files = await generateWikiFiles(terraformModules); + for (const file of files) { + if ( + file.endsWith('.md') && + basename(file) !== 'Home.md' && + basename(file) !== '_Sidebar.md' && + basename(file) !== '_Footer.md' + ) { + const content = readFileSync(file, 'utf8'); + const moduleName = basename(file, '.md'); + expect(content).toContain(`# Usage\n\nModule: ${moduleName}, Missing: {{missing_variable}}`); + } + } + }); + + it('should handle all variables in the custom usage template', async () => { + const customUsage = + 'Name: {{module_name}}, Tag: {{latest_tag}}, Version: {{latest_tag_version}}'; + config.set({ wikiCustomUsageTemplate: customUsage }); + const files = await generateWikiFiles(terraformModules); + for (const file of files) { + if ( + file.endsWith('.md') && + basename(file) !== 'Home.md' && + basename(file) !== '_Sidebar.md' && + basename(file) !== '_Footer.md' + ) { + const content = readFileSync(file, 'utf8'); + const moduleName = basename(file, '.md'); + // vpc-endpoint is the only one with a tag in the test setup + if (moduleName === 'vpc‒endpoint') { + expect(content).toContain('# Usage\n\nName: vpc-endpoint, Tag: vpc-endpoint/v1.0.0, Version: 1.0.0'); + } + } + } + }); }); describe('commitAndPushWikiChanges()', () => { From f4384fce885e90e531a21cf6f1f7c505a19ecc69 Mon Sep 17 00:00:00 2001 From: Illia Date: Thu, 31 Jul 2025 13:52:29 +0300 Subject: [PATCH 12/19] Update README --- README.md | 55 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 0ec907fe..16227690 100644 --- a/README.md +++ b/README.md @@ -184,22 +184,22 @@ resources. While the out-of-the-box defaults are suitable for most use cases, you can further customize the action's behavior by configuring the following optional input parameters as needed. -| Input | Description | Default | -| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -| `major-keywords` | Keywords in commit messages that indicate a major release | `major change,breaking change` | -| `minor-keywords` | Keywords in commit messages that indicate a minor release | `feat,feature` | -| `patch-keywords` | Keywords in commit messages that indicate a patch release | `fix,chore,docs` | -| `default-first-tag` | Specifies the default tag version | `v1.0.0` | -| `terraform-docs-version` | Specifies the terraform-docs version used to generate documentation for the wiki | `v0.19.0` | -| `delete-legacy-tags` | Specifies a boolean that determines whether tags and releases from Terraform modules that have been deleted should be automatically removed | `true` | -| `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. Modules matching any pattern here are excluded from all versioning, releases, and documentation.
[Read more here](#understanding-the-filtering-options) | `` (empty string) | -| `module-change-exclude-patterns` | Comma-separated list of file patterns (relative to each module) to exclude from triggering version changes. Lets you release a module but control which files inside it do not force a version bump.
[Read more here](#understanding-the-filtering-options) | `.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` | -| `wiki-custom-usage-template` | A raw, multi-line string to override the default 'Usage' section in the generated wiki. Allow using variables like {{module_name}}, {{latest_tag}} and {{latest_tag_version}}. If not provided, a default usage block will be generated. | `` (empty string) | +| Input | Description | Default | +| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `major-keywords` | Keywords in commit messages that indicate a major release | `major change,breaking change` | +| `minor-keywords` | Keywords in commit messages that indicate a minor release | `feat,feature` | +| `patch-keywords` | Keywords in commit messages that indicate a patch release | `fix,chore,docs` | +| `default-first-tag` | Specifies the default tag version | `v1.0.0` | +| `terraform-docs-version` | Specifies the terraform-docs version used to generate documentation for the wiki | `v0.19.0` | +| `delete-legacy-tags` | Specifies a boolean that determines whether tags and releases from Terraform modules that have been deleted should be automatically removed | `true` | +| `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. Modules matching any pattern here are excluded from all versioning, releases, and documentation.
[Read more here](#understanding-the-filtering-options) | `` (empty string) | +| `module-change-exclude-patterns` | Comma-separated list of file patterns (relative to each module) to exclude from triggering version changes. Lets you release a module but control which files inside it do not force a version bump.
[Read more here](#understanding-the-filtering-options) | `.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` | +| `wiki-usage-template` | A raw, multi-line string to override the default 'Usage' section in the generated wiki. Allows using variables like {{module_name}}, {{latest_tag}}, {{latest_tag_version_number}} and more.
[Read more here](#configuring-the-usage-template) | [See action.yml](https://github.com/polleuretan/terraform-module-releaser/blob/main/action.yml#L108) | ### Understanding the filtering options @@ -282,6 +282,19 @@ similar to those used in `.gitignore` files. For more details on the pattern mat [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). +### Configuring the Usage Template + +The `wiki-usage-template` input allows you to customize the "Usage" section of the generated wiki page for each module. +You can use the following dynamic variables in your template: + +| Variable | Description | Example | +| ------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------- | +| `{{module_name}}` | The name of the module. | `aws/s3-bucket` | +| `{{latest_tag}}` | The latest Git tag for the module. | `aws/s3-bucket/v1.2.3` | +| `{{latest_tag_version_number}}` | The version number of the latest tag. | `1.2.3` | +| `{{module_source}}` | The Git source URL for the module, respecting the `use-ssh-source-format` input. | `git::https://github.com/owner/repo.git` | +| `{{module_name_terraform}}` | A Terraform-safe version of the module name (e.g., special characters replaced with underscores). | `aws_s3_bucket` | + ### Example Usage with Inputs ````yml @@ -318,18 +331,20 @@ jobs: module-change-exclude-patterns: .gitignore,*.md,docs/**,examples/**,*.tftest.hcl,tests/** module-asset-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/** use-ssh-source-format: false - wiki-custom-usage-template: | + wiki-usage-template: | # My Custom Usage Instructions This is a custom usage block. You can add any markdown you want here. - And use variables like {{module_name}}, {{latest_tag}}, and {{latest_tag_version}}. + And use variables like {{module_name}}, {{latest_tag}}, {{latest_tag_version_number}}, + {{module_source}} and {{module_name_terraform}}. ```hcl - module "my_module" { - source = "..." + module "{{module_name_terraform}}" { + source = "{{module_source}}?ref={{latest_tag}}" + version = "{{latest_tag_version_number}}" # ... } ``` From 73fd8519b53e4fb64debf130c73342ebbe9de661 Mon Sep 17 00:00:00 2001 From: Illia Date: Thu, 31 Jul 2025 13:53:35 +0300 Subject: [PATCH 13/19] Update config name and default value in action.yaml --- action.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/action.yml b/action.yml index f7f5554b..958186ba 100644 --- a/action.yml +++ b/action.yml @@ -102,9 +102,19 @@ inputs: If enabled, all links to source code in generated Wiki documentation will use SSH format instead of HTTPS format. required: true default: "false" - wiki-custom-usage-template: + wiki-usage-template: description: A raw, multi-line string to override the default 'Usage' section in the generated wiki. If not provided, a default usage block will be generated. required: false + default: | + To use this module in your Terraform, refer to the below module example: + + ```hcl + module "{{module_name_terraform}}" { + source = "git::{{module_source}}?ref={{latest_tag}}" + + # See inputs below for additional required parameters + } + ``` github_token: description: > Required for retrieving pull request metadata, tags, releases, updating PR comments, wiki, and creating From 5986d7b13df1e1bc505632a7953a45f0cff8cf35 Mon Sep 17 00:00:00 2001 From: Illia Date: Thu, 31 Jul 2025 13:55:15 +0300 Subject: [PATCH 14/19] Drop DEFAULT_USAGE_BLOCK function and use just render helper --- src/config.ts | 2 +- src/types/config.types.ts | 2 +- src/wiki.ts | 29 +++++++---------------------- 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/src/config.ts b/src/config.ts index b474d4c7..4a620f18 100644 --- a/src/config.ts +++ b/src/config.ts @@ -74,7 +74,7 @@ function initializeConfig(): Config { moduleChangeExcludePatterns: getArrayInput('module-change-exclude-patterns', false), moduleAssetExcludePatterns: getArrayInput('module-asset-exclude-patterns', false), useSSHSourceFormat: getBooleanInput('use-ssh-source-format', { required: true }), - wikiCustomUsageTemplate: getInput('wiki-custom-usage-template', { required: false }), + wikiUsageTemplate: getInput('wiki-usage-template', { required: false }), }; // Validate that *.tf is not in excludePatterns diff --git a/src/types/config.types.ts b/src/types/config.types.ts index c7cf5e5c..32543a88 100644 --- a/src/types/config.types.ts +++ b/src/types/config.types.ts @@ -107,5 +107,5 @@ export interface Config { * A raw, multi-line string to override the default 'Usage' section in the generated wiki. * If not provided, a default usage block will be generated. */ - wikiCustomUsageTemplate?: string; + wikiUsageTemplate?: string; } diff --git a/src/wiki.ts b/src/wiki.ts index d63d05c4..5e99b908 100644 --- a/src/wiki.ts +++ b/src/wiki.ts @@ -30,17 +30,6 @@ import which from 'which'; // Special subdirectory inside the primary repository where the wiki is checked out. const WIKI_SUBDIRECTORY_NAME = '.wiki'; -const DEFAULT_USAGE_BLOCK = (terraformModule: TerraformModule, moduleSource: string): string => - [ - 'To use this module in your Terraform, refer to the below module example:\n', - '```hcl', - `module "${terraformModule.name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}" {`, - ` source = "git::${moduleSource}?ref=${terraformModule.getLatestTag()}"`, - '\n # See inputs below for additional required parameters', - '}', - '```', - ].join('\n'); - /** * Clones the wiki repository for the current GitHub repository into a specified subdirectory. * @@ -317,17 +306,13 @@ async function generateWikiTerraformModule(terraformModule: TerraformModule): Pr const tfDocs = await generateTerraformDocs(terraformModule); const moduleSource = getModuleSource(context.repoUrl, config.useSSHSourceFormat); - let usage: string; - if (config.wikiCustomUsageTemplate) { - const variables = { - module_name: terraformModule.name, - latest_tag: terraformModule.getLatestTag(), - latest_tag_version: terraformModule.getLatestTagVersionNumber(), - }; - usage = render(config.wikiCustomUsageTemplate, variables); - } else { - usage = DEFAULT_USAGE_BLOCK(terraformModule, moduleSource); - } + const usage = render(config.wikiUsageTemplate, { + module_name: terraformModule.name, + latest_tag: terraformModule.getLatestTag(), + latest_tag_version_number: terraformModule.getLatestTagVersionNumber(), + module_source: moduleSource, + module_name_terraform: terraformModule.name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase(), + }); const content = [ '# Usage\n', From 998d6d5c6730d52e07abe26766516d76f0a22155 Mon Sep 17 00:00:00 2001 From: Illia Date: Thu, 31 Jul 2025 13:56:17 +0300 Subject: [PATCH 15/19] Rename wikiCustomUsageTemplate -> wikiUsageTemplate in existing tests --- __tests__/wiki.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/__tests__/wiki.test.ts b/__tests__/wiki.test.ts index f7821118..5cfe7a6d 100644 --- a/__tests__/wiki.test.ts +++ b/__tests__/wiki.test.ts @@ -260,7 +260,7 @@ describe('wiki', async () => { it('should use the custom usage template when provided', async () => { const customUsage = 'This is a custom usage template: {{module_name}}'; - config.set({ wikiCustomUsageTemplate: customUsage }); + config.set({ wikiUsageTemplate: customUsage }); const files = await generateWikiFiles(terraformModules); for (const file of files) { if ( @@ -277,7 +277,7 @@ describe('wiki', async () => { }); it('should use the default usage block when custom template is not provided', async () => { - config.set({ wikiCustomUsageTemplate: undefined }); + config.set({ wikiUsageTemplate: undefined }); const files = await generateWikiFiles(terraformModules); for (const file of files) { if ( @@ -294,7 +294,7 @@ describe('wiki', async () => { it('should handle missing variables in the custom usage template', async () => { const customUsage = 'Module: {{module_name}}, Missing: {{missing_variable}}'; - config.set({ wikiCustomUsageTemplate: customUsage }); + config.set({ wikiUsageTemplate: customUsage }); const files = await generateWikiFiles(terraformModules); for (const file of files) { if ( From efd1dcd99845901edd0494d8e9fedb5181f6c8eb Mon Sep 17 00:00:00 2001 From: Illia Date: Thu, 31 Jul 2025 13:57:02 +0300 Subject: [PATCH 16/19] Add new variables to the test case --- __tests__/wiki.test.ts | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/__tests__/wiki.test.ts b/__tests__/wiki.test.ts index 5cfe7a6d..2b003ba4 100644 --- a/__tests__/wiki.test.ts +++ b/__tests__/wiki.test.ts @@ -258,9 +258,7 @@ describe('wiki', async () => { ); }); - it('should use the custom usage template when provided', async () => { - const customUsage = 'This is a custom usage template: {{module_name}}'; - config.set({ wikiUsageTemplate: customUsage }); + it('should use the default usage block when custom template is not provided', async () => { const files = await generateWikiFiles(terraformModules); for (const file of files) { if ( @@ -270,15 +268,16 @@ describe('wiki', async () => { basename(file) !== '_Footer.md' ) { const content = readFileSync(file, 'utf8'); - const moduleName = basename(file, '.md'); - expect(content).toContain(`# Usage\n\nThis is a custom usage template: ${moduleName}`); + expect(content).toContain('To use this module in your Terraform, refer to the below module example:'); } } }); - it('should use the default usage block when custom template is not provided', async () => { - config.set({ wikiUsageTemplate: undefined }); - const files = await generateWikiFiles(terraformModules); + it('should use the custom usage template when provided', async () => { + const customUsage = 'This is a custom usage template: {{module_name}}'; + config.set({ wikiUsageTemplate: customUsage }); + const terraformModule = terraformModules[0]; + const files = await generateWikiFiles([terraformModule]); for (const file of files) { if ( file.endsWith('.md') && @@ -287,7 +286,8 @@ describe('wiki', async () => { basename(file) !== '_Footer.md' ) { const content = readFileSync(file, 'utf8'); - expect(content).toContain('To use this module in your Terraform, refer to the below module example:'); + const moduleName = basename(file, '.md'); + expect(content).toContain(`# Usage\n\nThis is a custom usage template: ${moduleName}`); } } }); @@ -295,7 +295,8 @@ describe('wiki', async () => { it('should handle missing variables in the custom usage template', async () => { const customUsage = 'Module: {{module_name}}, Missing: {{missing_variable}}'; config.set({ wikiUsageTemplate: customUsage }); - const files = await generateWikiFiles(terraformModules); + const terraformModule = terraformModules[0]; + const files = await generateWikiFiles([terraformModule]); for (const file of files) { if ( file.endsWith('.md') && @@ -305,15 +306,15 @@ describe('wiki', async () => { ) { const content = readFileSync(file, 'utf8'); const moduleName = basename(file, '.md'); - expect(content).toContain(`# Usage\n\nModule: ${moduleName}, Missing: {{missing_variable}}`); + expect(content).toContain(`# Usage\n\nModule: ${terraformModule.name}, Missing: {{missing_variable}}`); } } }); it('should handle all variables in the custom usage template', async () => { const customUsage = - 'Name: {{module_name}}, Tag: {{latest_tag}}, Version: {{latest_tag_version}}'; - config.set({ wikiCustomUsageTemplate: customUsage }); + 'Name: {{module_name}}, Tag: {{latest_tag}}, Version: {{latest_tag_version_number}}, Source: {{module_source}}, TFName: {{module_name_terraform}}'; + config.set({ wikiUsageTemplate: customUsage }); const files = await generateWikiFiles(terraformModules); for (const file of files) { if ( @@ -326,7 +327,9 @@ describe('wiki', async () => { const moduleName = basename(file, '.md'); // vpc-endpoint is the only one with a tag in the test setup if (moduleName === 'vpc‒endpoint') { - expect(content).toContain('# Usage\n\nName: vpc-endpoint, Tag: vpc-endpoint/v1.0.0, Version: 1.0.0'); + expect(content).toContain( + 'Name: vpc-endpoint, Tag: vpc-endpoint/v1.0.0, Version: 1.0.0, Source: https://github.com/techpivot/terraform-module-releaser.git, TFName: vpc_endpoint', + ); } } } From 8226aef291ef69da5fe8e01b9dbbc2b1008dbc29 Mon Sep 17 00:00:00 2001 From: Illia Date: Thu, 31 Jul 2025 14:23:52 +0300 Subject: [PATCH 17/19] Linter feedback --- src/templating.ts | 4 ++-- src/wiki.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/templating.ts b/src/templating.ts index 19e1f84b..2bea5401 100644 --- a/src/templating.ts +++ b/src/templating.ts @@ -5,8 +5,8 @@ * @param variables An object where keys correspond to placeholder names and values are their replacements. * @returns The rendered string with placeholders replaced. */ -export const render = (template: string, variables: Record): string => { +export const render = (template: string, variables: Record): string => { return template.replace(/\{\{(\w+)\}\}/g, (placeholder, key) => { - return variables.hasOwnProperty(key) ? variables[key] : placeholder; + return variables.hasOwn(key) ? variables[key] : placeholder; }); }; diff --git a/src/wiki.ts b/src/wiki.ts index 5e99b908..49ba5f4c 100644 --- a/src/wiki.ts +++ b/src/wiki.ts @@ -7,8 +7,8 @@ import { join, resolve } from 'node:path'; import { getTerraformModuleFullReleaseChangelog } from '@/changelog'; import { config } from '@/config'; import { context } from '@/context'; -import { generateTerraformDocs } from '@/terraform-docs'; import { render } from '@/templating'; +import { generateTerraformDocs } from '@/terraform-docs'; import type { TerraformModule } from '@/terraform-module'; import type { ExecSyncError, WikiStatusResult } from '@/types'; import { From 76cd96b5532c33446a1466711b39c6d620d0f82d Mon Sep 17 00:00:00 2001 From: Illia Date: Thu, 31 Jul 2025 17:26:17 +0300 Subject: [PATCH 18/19] Use `key in variables` to make sure the key is present --- src/templating.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templating.ts b/src/templating.ts index 2bea5401..da3eec67 100644 --- a/src/templating.ts +++ b/src/templating.ts @@ -7,6 +7,6 @@ */ export const render = (template: string, variables: Record): string => { return template.replace(/\{\{(\w+)\}\}/g, (placeholder, key) => { - return variables.hasOwn(key) ? variables[key] : placeholder; + return key in variables ? variables[key] : placeholder; }); }; From f69595a3c2d309c2578e6c2adcb4592455cb5748 Mon Sep 17 00:00:00 2001 From: Illia Date: Thu, 31 Jul 2025 17:30:01 +0300 Subject: [PATCH 19/19] Add default wiki usage template to test mocks --- __mocks__/config.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/__mocks__/config.ts b/__mocks__/config.ts index 26a0a7e9..5c43f6b4 100644 --- a/__mocks__/config.ts +++ b/__mocks__/config.ts @@ -26,6 +26,17 @@ const defaultConfig: Config = { moduleAssetExcludePatterns: ['tests/**', 'examples/**'], githubToken: 'ghp_test_token_2c6912E7710c838347Ae178B4', useSSHSourceFormat: false, + wikiUsageTemplate: ` + To use this module in your Terraform, refer to the below module example: + + \`\`\`hcl + module "{{module_name_terraform}}" { + source = "git::{{module_source}}?ref={{latest_tag}}" + + # See inputs below for additional required parameters + } + \`\`\` +` }; /**