diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..7d17a01 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,61 @@ +name: Release + +on: + workflow_dispatch: + inputs: + bump-type: + description: "Version bump type" + required: true + type: choice + options: + - patch + - minor + - major + +jobs: + release: + name: Build and Release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Typecheck + run: npm run typecheck + + - name: Test + run: npm test + + - name: Build + run: npm run build + + - name: Commit dist if changed + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add dist/ + if ! git diff --staged --quiet; then + git commit -m "build: compile action" + git push + fi + + - name: Release + uses: albertodeago/wire@v0.2.0 + with: + workflows: "wire" + bump-type: ${{ inputs.bump-type }} + tag-pattern: "v{version}" + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 674db47..0bae44b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ # Changelog +## v0.2.0 + +feat: Enable WIRE to publish single workflows without name prefix in tags +`tag-pattern` now doesn't require `{name}` when releasing a single component. + +This means that you can use WIRE to release workflows or actions without the need for a name prefix in the tags, simplifying versioning for single-workflow repositories. +Tags are going to look like `v1.0.0` and `v1` instead of `workflow-a/v1.0.0` and `workflow-a/v1` as users are used to. + ## v0.1.0 - Initial Release - Initial release of WIRE diff --git a/README.md b/README.md index ed21bd8..baece3a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ A GitHub Action for releasing multiple independently-versioned workflows from a single repository. Perfect to share reusable workflows monorepos that are interconnected but that require separate versioning. +> [!IMPORTANT] +> While WIRE is born to manage multiple reusable workflows in a single repository, it can also be used to manage versioning and releases for a single workflow or action. Keep reading for details! + ## Problem It Solves When you distribute multiple reusable GitHub Workflows, managing their versions can get tricky. @@ -103,7 +106,7 @@ jobs: | `workflows` | Workflows to release (comma-separated names, or `'all'`) | true | - | | `bump-type` | Version bump type: `patch`, `minor`, or `major` | true | - | | `versions-file` | Path to JSON file tracking workflow versions | false | `workflow-versions.json` | -| `tag-pattern` | Tag pattern. Use `{name}` and `{version}` placeholders | false | `{name}/v{version}` | +| `tag-pattern` | Tag pattern. Must include `{version}`. `{name}` is required for multiple workflows, optional for single workflow | false | `{name}/v{version}` | | `github-token` | GitHub token for pushing commits and tags | true | - | | `git-user-name` | Git user name for commits | false | `github-actions[bot]`| | `git-user-email` | Git user email for commits | false | `github-actions[bot]@users.noreply.github.com` | @@ -163,6 +166,22 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} ``` +#### Single-workflow repository (no name prefix) + +For repositories with a single action/workflow, you can omit `{name}` from the tag pattern to get clean tags like `v1.0.0` and `v1`: + +```yaml +- uses: albertodeago/wire@v1 + with: + workflows: "my-action" + bump-type: "patch" + tag-pattern: "v{version}" + github-token: ${{ secrets.GITHUB_TOKEN }} +``` + +> [!TIP] +> This is actually how WIRE itself is published + #### Custom versions file location ```yaml @@ -308,6 +327,14 @@ This uses `@vercel/ncc` to bundle the TypeScript code into a single `dist/index. ### Releasing a New Version +Wire is itself released using WIRE! + +To release a new version, trigger the `Release Workflows` workflow from the Actions tab. + +#### If shit happens + +Release it manually by: + **Create and push tags**: First make changes, build, and commit them (dist included, and remember to update the changelog). Then create a version tag and push it: diff --git a/action.yaml b/action.yaml index a76509f..8ae1ecb 100644 --- a/action.yaml +++ b/action.yaml @@ -18,7 +18,7 @@ inputs: required: false default: 'workflow-versions.json' tag-pattern: - description: 'Tag pattern. Use {name} and {version} placeholders.' + description: 'Tag pattern. Must include {version}. {name} is required for multiple workflows, optional for single workflow.' required: false default: '{name}/v{version}' github-token: diff --git a/src/action.ts b/src/action.ts index 05f43c5..89c1348 100644 --- a/src/action.ts +++ b/src/action.ts @@ -10,8 +10,19 @@ function validateBumpType(bumpType: string): bumpType is BumpType { return ["patch", "minor", "major"].includes(bumpType); } -function validateTagPattern(tagPattern: string): boolean { - return tagPattern.includes("{name}") && tagPattern.includes("{version}"); +function validateTagPattern( + tagPattern: string, + workflowCount: number, +): boolean { + // {version} is always required + if (!tagPattern.includes("{version}")) { + return false; + } + // {name} is required when releasing multiple workflows to avoid tag conflicts + if (workflowCount > 1 && !tagPattern.includes("{name}")) { + return false; + } + return true; } function validateCommitMessagePattern(pattern: string): boolean { @@ -86,13 +97,6 @@ export async function run( ); } - if (!validateTagPattern(inputs.tagPattern)) { - logger.error(`Invalid tag pattern provided: ${inputs.tagPattern}`); - return new InvalidTagPattern( - `Invalid tag pattern: ${inputs.tagPattern}. Must include {name} and {version} placeholders.`, - ); - } - if (!validateCommitMessagePattern(inputs.commitMessagePattern)) { logger.error( `Invalid commit message pattern provided: ${inputs.commitMessagePattern}`, @@ -135,6 +139,16 @@ export async function run( logger.debug(`Workflows to release: ${workflowsToRelease.join(", ")}`); + // Validate tag pattern - {name} is only optional for single workflow releases + if (!validateTagPattern(inputs.tagPattern, workflowsToRelease.length)) { + const errorMsg = + workflowsToRelease.length > 1 + ? `Invalid tag pattern: ${inputs.tagPattern}. Must include {name} and {version} placeholders when releasing multiple workflows.` + : `Invalid tag pattern: ${inputs.tagPattern}. Must include {version} placeholder.`; + logger.error(errorMsg); + return new InvalidTagPattern(errorMsg); + } + // Prepare the list of workflows to release and related Tags const releases = versionBumper.bump({ bumpType: inputs.bumpType, diff --git a/test/action.test.ts b/test/action.test.ts index 26e7581..f5ba704 100644 --- a/test/action.test.ts +++ b/test/action.test.ts @@ -247,7 +247,60 @@ describe("action - run", () => { expect(outputs).toBeInstanceOf(Error); if (outputs instanceof Error) { expect(outputs.message).toContain( - "Invalid tag pattern: invalid-pattern. Must include {name} and {version} placeholders.", + "Invalid tag pattern: invalid-pattern. Must include {version} placeholder.", + ); + } + }); + + it("should accept tag-pattern without {name} for single-workflow repos", async () => { + const versionsRepository = createMockVersionsRepository({ + "my-action": "1.0.0", + }); + const versionBumper = createMockVersionBumper(); + const gitClient = createMockGitClient(); + const logger = createMockLogger(); + + const outputs = await run( + { + ...defaultInputs, + workflows: "my-action", + tagPattern: "v{version}", + }, + { versionsRepository, versionBumper, gitClient, logger }, + ); + + if (outputs instanceof Error) { + throw outputs; + } + + expect(outputs.released).toEqual({ + "my-action": "1.0.1", + }); + expect(outputs.tags).toEqual(["v1.0.1", "v1"]); + }); + + it("should require {name} in tag-pattern when releasing multiple workflows", async () => { + const versionsRepository = createMockVersionsRepository({ + "workflow-a": "1.0.0", + "workflow-b": "2.0.0", + }); + const versionBumper = createMockVersionBumper(); + const gitClient = createMockGitClient(); + const logger = createMockLogger(); + + const outputs = await run( + { + ...defaultInputs, + workflows: "workflow-a, workflow-b", + tagPattern: "v{version}", + }, + { versionsRepository, versionBumper, gitClient, logger }, + ); + + expect(outputs).toBeInstanceOf(Error); + if (outputs instanceof Error) { + expect(outputs.message).toContain( + "Must include {name} and {version} placeholders when releasing multiple workflows", ); } }); diff --git a/workflow-versions.json b/workflow-versions.json new file mode 100644 index 0000000..4a4dff9 --- /dev/null +++ b/workflow-versions.json @@ -0,0 +1,3 @@ +{ + "wire": "0.1.0" +}