Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -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 }}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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` |
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
32 changes: 23 additions & 9 deletions src/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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}`,
Expand Down Expand Up @@ -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,
Expand Down
55 changes: 54 additions & 1 deletion test/action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
);
}
});
Expand Down
3 changes: 3 additions & 0 deletions workflow-versions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"wire": "0.1.0"
}