Skip to content

Commit cb497ba

Browse files
committed
feat: enhance tag generation configuration with detailed descriptions and examples for directory separator and version prefix options
1 parent aac46ea commit cb497ba

File tree

8 files changed

+97
-38
lines changed

8 files changed

+97
-38
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ jobs:
5454
module-change-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/**,examples/**
5555
module-asset-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/**
5656
use-ssh-source-format: true
57+
tag-directory-separator: "-"
58+
use-version-prefix: false
5759

5860
- name: Test Action Outputs
5961
id: test-outputs

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ configuring the following optional input parameters as needed.
199199
| `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.<br><sub>[Read more here](#understanding-the-filtering-options)</sub> | `.gitignore,*.md,*.tftest.hcl,tests/**` |
200200
| `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/**` |
201201
| `use-ssh-source-format` | If enabled, all links to source code in generated Wiki documentation will use SSH standard format (e.g., `git::ssh://[email protected]/owner/repo.git`) instead of HTTPS format (`git::https://github.com/owner/repo.git`) | `false` |
202+
| `tag-directory-separator` | Character used to separate directory path components in Git tags. Supports `/`, `-`, `_`, or `.` | `/` |
203+
| `use-version-prefix` | Whether to include the 'v' prefix on version tags (e.g., v1.2.3 vs 1.2.3) | `true` |
202204

203205
### Understanding the filtering options
204206

action.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,24 @@ inputs:
110110
required: true
111111
default: ${{ github.token }}
112112
tag-directory-separator:
113-
description: The separator character to use between module directory parts in the git tag.
113+
description: >
114+
Character used to separate directory path components in Git tags. This separator is used to convert
115+
module directory paths into tag names (e.g., 'modules/aws/s3-bucket' becomes 'modules-aws-s3-bucket-v1.0.0'
116+
when using '-'). Must be a single character from: /, -, _, or .
117+
118+
Examples with different separators:
119+
- "/" (default): modules/aws/s3-bucket/v1.0.0
120+
- "-": modules-aws-s3-bucket-v1.0.0
121+
- "_": modules_aws_s3_bucket_v1.0.0
122+
- ".": modules.aws.s3.bucket.v1.0.0
114123
required: true
115124
default: /
116125
use-version-prefix:
117-
description: Whether to include the 'v' prefix on version tags (e.g., v1.2.3).
126+
description: >
127+
Whether to include the 'v' prefix on version tags (e.g., v1.2.3 vs 1.2.3). When enabled, all new version
128+
tags will include the 'v' prefix. For initial releases, this setting takes precedence over any 'v' prefix
129+
specified in the default-first-tag - if use-version-prefix is false and default-first-tag contains 'v',
130+
the 'v' will be automatically removed to ensure consistency.
118131
required: true
119132
default: "true"
120133

src/config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,19 @@ function initializeConfig(): Config {
6666
}'`,
6767
);
6868
}
69-
7069
// Validate default first tag format
7170
if (!VERSION_TAG_REGEX.test(configInstance.defaultFirstTag)) {
7271
throw new TypeError(
7372
`Default first tag must be in format v#.#.# or #.#.# (e.g., v1.0.0 or 1.0.0). Got: '${configInstance.defaultFirstTag}'`,
7473
);
7574
}
7675

76+
// If we aren't using "v" prefix but the default first tag was specified with a "v"
77+
// prefix, then strip this to enforce.
78+
if (!configInstance.useVersionPrefix && configInstance.defaultFirstTag.startsWith('v')) {
79+
configInstance.defaultFirstTag = configInstance.defaultFirstTag.substring(1);
80+
}
81+
7782
info(`Major Keywords: ${configInstance.majorKeywords.join(', ')}`);
7883
info(`Minor Keywords: ${configInstance.minorKeywords.join(', ')}`);
7984
info(`Patch Keywords: ${configInstance.patchKeywords.join(', ')}`);

src/terraform-module.ts

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
VALID_TAG_DIRECTORY_SEPARATORS,
1010
VERSION_TAG_REGEX,
1111
} from '@/utils/constants';
12-
import { removeTrailingCharacters } from '@/utils/string';
1312
import { endGroup, info, startGroup } from '@actions/core';
1413

1514
/**
@@ -224,7 +223,7 @@ export class TerraformModule {
224223
return null;
225224
}
226225

227-
const match = latestTag.match(MODULE_TAG_REGEX);
226+
const match = MODULE_TAG_REGEX.exec(latestTag);
228227

229228
return match ? match[3] : null;
230229
}
@@ -471,7 +470,7 @@ export class TerraformModule {
471470
return null;
472471
}
473472

474-
return `${this.name}/${releaseTagVersion}`;
473+
return `${this.name}${config.tagDirectorySeparator}${releaseTagVersion}`;
475474
}
476475

477476
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -617,29 +616,23 @@ export class TerraformModule {
617616
*
618617
* The function transforms the directory path by:
619618
* - Trimming whitespace
620-
* - Replacing invalid characters with hyphens
621-
* - Normalizing slashes
622-
* - Removing leading/trailing slashes
623-
* - Handling consecutive dots and hyphens
624-
* - Removing any remaining whitespace
625619
* - Converting to lowercase (for consistency)
626-
* - Removing trailing dots, hyphens, and underscores
620+
* - Normalizing path separators (both backslashes and forward slashes) to the configured tag directory separator
621+
* - Replacing invalid characters with hyphens (preserving only alphanumeric, "/", ".", "-", "_")
622+
* - Normalizing consecutive special characters ("/", ".", "-", "_") to single instances
623+
* - Removing leading/trailing special characters ("/", ".", "-", "_")
627624
*
628625
* @param {string} terraformDirectory - The relative directory path from which to generate the module name.
629626
* @returns {string} A valid Terraform module name based on the provided directory path.
630627
*/
631628
public static getTerraformModuleNameFromRelativePath(terraformDirectory: string): string {
632-
const cleanedDirectory = terraformDirectory
629+
return terraformDirectory
633630
.trim()
634-
.replace(/[^a-zA-Z0-9/_-]+/g, '-')
635-
.replace(/\/{2,}/g, '/')
636-
.replace(/\/\.+/g, '/')
637-
.replace(/(^\/|\/$)/g, '')
638-
.replace(/\.\.+/g, '.')
639-
.replace(/--+/g, '-')
640-
.replace(/\s+/g, '')
641-
.toLowerCase();
642-
return removeTrailingCharacters(cleanedDirectory, ['.', '-', '_']);
631+
.toLowerCase()
632+
.replace(/[/\\]/g, config.tagDirectorySeparator) // Normalize backslashes and forward slashes to configured separator
633+
.replace(/[^a-zA-Z0-9/._-]+/g, '-') // Replace invalid characters with hyphens (preserve alphanumeric, /, ., _, -)
634+
.replace(/[/._-]{2,}/g, (match) => match[0]) // Normalize consecutive special characters to single instances
635+
.replace(/(^[/._-]+|[/._-]+$)/g, ''); // Remove leading/trailing special characters
643636
}
644637

645638
/**

src/types/config.types.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,21 +105,38 @@ export interface Config {
105105
useSSHSourceFormat: boolean;
106106

107107
/**
108-
* The character used to separate the module name from the version in tags.
108+
* The character used to separate directory path components when creating Git tags from module paths.
109+
* This separator is applied throughout the entire directory structure conversion process, not just
110+
* between the module name and version.
111+
*
109112
* Must be a single character and one of: -, _, /, .
110113
*
111-
* Examples:
112-
* - "/" (default): module/aws-s3-bucket/v1.0.0
113-
* - "-": module-aws-s3-bucket-v1.0.0
114-
* - "_": module_aws-s3-bucket_v1.0.0
115-
* - ".": module.aws-s3-bucket.v1.0.0
114+
* When converting a module path like 'modules/aws/s3-bucket' to a Git tag, this separator determines
115+
* how directory separators (/) are replaced in the tag name portion:
116+
*
117+
* Examples with module path 'modules/aws/s3-bucket' and version 'v1.0.0':
118+
* - "/" (default): modules/aws/s3-bucket/v1.0.0
119+
* - "-": modules-aws-s3-bucket-v1.0.0
120+
* - "_": modules_aws_s3_bucket_v1.0.0
121+
* - ".": modules.aws.s3-bucket.v1.0.0
122+
*
123+
* This setting affects tag creation, tag parsing, and tag association logic throughout the system.
116124
*/
117125
tagDirectorySeparator: string;
118126

119127
/**
120128
* Whether to include the "v" prefix in version tags.
121-
* When true (default), tags will be formatted as: module/v1.2.3
122-
* When false, tags will be formatted as: module/1.2.3
129+
*
130+
* When true (default), version tags will include the "v" prefix:
131+
* - Example: module/v1.2.3
132+
*
133+
* When false, version tags will not include the "v" prefix:
134+
* - Example: module/1.2.3
135+
*
136+
* For initial releases, this setting takes precedence over any "v" prefix specified in the
137+
* defaultFirstTag configuration. If useVersionPrefix is false and defaultFirstTag contains
138+
* a "v" prefix (e.g., "v1.0.0"), the "v" will be automatically removed to ensure consistency
139+
* with the useVersionPrefix setting (resulting in "1.0.0").
123140
*/
124141
useVersionPrefix: boolean;
125142
}

src/types/metadata.types.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,41 @@
11
import type { Config } from '@/types/config.types';
22

33
/**
4-
* Metadata about GitHub Action inputs, including their types, defaults, and mapping to config properties.
5-
* This serves as the single source of truth for action configuration.
4+
* Metadata definition for GitHub Action inputs that enables dynamic configuration mapping.
65
*
7-
* @todo update doc - defaults at runtime come from action.yml, testing see helpers/inputs.ts
6+
* This interface serves as the translation layer between GitHub Action inputs defined in
7+
* action.yml and our internal Config type. It provides the necessary metadata to:
8+
* - Parse input values according to their expected types
9+
* - Map action inputs to the corresponding config property names
10+
* - Enforce required/optional input validation
11+
* - Support dynamic config creation in createConfigFromInputs()
12+
*
13+
* The metadata is used by the ACTION_INPUTS constant in metadata.ts to create a
14+
* comprehensive mapping of all action inputs, which then drives the automatic
15+
* config generation process.
16+
*
17+
* @see {@link /workspaces/terraform-module-releaser/src/utils/metadata.ts} for usage
18+
* @see {@link https://docs.github.com/en/actions/reference/metadata-syntax-for-github-actions#inputs} GitHub Actions input reference
819
*/
920
export interface ActionInputMetadata {
10-
/** The config property name this input maps to */
21+
/**
22+
* The config property name this input maps to.
23+
* Must be a valid key from the Config interface.
24+
*/
1125
configKey: keyof Config;
1226

13-
/** Whether this input is required */
27+
/**
28+
* Whether this input is required by the GitHub Action.
29+
* When true, the action will fail if the input is not provided.
30+
*/
1431
required: boolean;
1532

16-
/** The input type for proper parsing */
33+
/**
34+
* The expected data type of the input for proper parsing and validation.
35+
* - 'string': Direct string value
36+
* - 'boolean': Parsed using getBooleanInput for proper true/false handling
37+
* - 'number': Parsed using parseInt for integer conversion
38+
* - 'array': Comma-separated string parsed into array with deduplication
39+
*/
1740
type: 'string' | 'boolean' | 'number' | 'array';
18-
}
41+
}

src/utils/constants.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,12 @@ export const VERSION_TAG_REGEX = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
4444
* - "module-v1.0.0" → ["module-v1.0.0", "module", "-", "v1.0.0", "1", "0", "0"]
4545
* - "feature_2.3.4" → ["feature_2.3.4", "feature", "_", "2.3.4", "2", "3", "4"]
4646
* - "service/v0.1.0" → ["service/v0.1.0", "service", "/", "v0.1.0", "0", "1", "0"]
47+
*
48+
* Note: In the character class [-_/.], only the dot (.) requires escaping to match literal periods.
49+
* The hyphen (-) doesn't need escaping when at the start/end of the character class.
50+
* The forward slash (/) doesn't need escaping in JavaScript regex character classes.
4751
*/
48-
export const MODULE_TAG_REGEX = /^(.+)([-_\/\.])(v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*))$/;
52+
export const MODULE_TAG_REGEX = /^(.+)([-_/.])(v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*))$/;
4953

5054
/**
5155
* Release type constants for semantic versioning

0 commit comments

Comments
 (0)