diff --git a/README.md b/README.md
index 191101eb..16227690 100644
--- a/README.md
+++ b/README.md
@@ -184,21 +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` |
+| 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
@@ -281,9 +282,22 @@ 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
+````yml
name: Terraform Module Releaser
on:
pull_request:
@@ -317,7 +331,24 @@ 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-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}}, {{latest_tag_version_number}},
+ {{module_source}} and {{module_name_terraform}}.
+
+ ```hcl
+ module "{{module_name_terraform}}" {
+ source = "{{module_source}}?ref={{latest_tag}}"
+ version = "{{latest_tag_version_number}}"
+ # ...
+ }
+ ```
+````
## Outputs
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
+ }
+ \`\`\`
+`
};
/**
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/__tests__/wiki.test.ts b/__tests__/wiki.test.ts
index 5c2563da..2b003ba4 100644
--- a/__tests__/wiki.test.ts
+++ b/__tests__/wiki.test.ts
@@ -257,6 +257,83 @@ describe('wiki', async () => {
'https://github.com/techpivot/terraform-module-releaser/wiki/aws∕vpc',
);
});
+
+ 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 (
+ 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:');
+ }
+ }
+ });
+
+ 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') &&
+ 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\nThis is a custom usage template: ${moduleName}`);
+ }
+ }
+ });
+
+ it('should handle missing variables in the custom usage template', async () => {
+ const customUsage = 'Module: {{module_name}}, Missing: {{missing_variable}}';
+ config.set({ wikiUsageTemplate: customUsage });
+ const terraformModule = terraformModules[0];
+ const files = await generateWikiFiles([terraformModule]);
+ 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: ${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_number}}, Source: {{module_source}}, TFName: {{module_name_terraform}}';
+ config.set({ wikiUsageTemplate: 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(
+ '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',
+ );
+ }
+ }
+ }
+ });
});
describe('commitAndPushWikiChanges()', () => {
diff --git a/action.yml b/action.yml
index 124dd781..958186ba 100644
--- a/action.yml
+++ b/action.yml
@@ -102,6 +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-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
diff --git a/src/config.ts b/src/config.ts
index 45390210..4a620f18 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 }),
+ wikiUsageTemplate: getInput('wiki-usage-template', { required: false }),
};
// Validate that *.tf is not in excludePatterns
diff --git a/src/templating.ts b/src/templating.ts
new file mode 100644
index 00000000..da3eec67
--- /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 key in variables ? variables[key] : placeholder;
+ });
+};
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
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/types/config.types.ts b/src/types/config.types.ts
index dea8abf6..32543a88 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.
+ */
+ wikiUsageTemplate?: string;
}
diff --git a/src/wiki.ts b/src/wiki.ts
index 89b95c9a..49ba5f4c 100644
--- a/src/wiki.ts
+++ b/src/wiki.ts
@@ -7,6 +7,7 @@ import { join, resolve } from 'node:path';
import { getTerraformModuleFullReleaseChangelog } from '@/changelog';
import { config } from '@/config';
import { context } from '@/context';
+import { render } from '@/templating';
import { generateTerraformDocs } from '@/terraform-docs';
import type { TerraformModule } from '@/terraform-module';
import type { ExecSyncError, WikiStatusResult } from '@/types';
@@ -304,15 +305,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 = 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',
- '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,