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
55 changes: 55 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: CI

on:
pull_request:
push:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
test:
name: Lint, Test & Build
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Check
run: npm run format:check

- name: Typecheck
run: npm run typecheck

- name: Run tests
run: npm test

- name: Build
run: npm run build

- name: Verify dist/ is up to date
run: |
if [ -n "$(git status --porcelain dist/)" ]; then
echo "dist/ is out of date. Please run 'npm run build' and commit the result."
git diff dist/
exit 1
fi
44 changes: 44 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Release

on:
push:
tags:
- 'v*'

permissions:
contents: write

jobs:
release:
name: Create Release & Update Major Tag
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true

- name: Update major version tag
run: |
FULL_TAG="${GITHUB_REF_NAME}"
MAJOR_TAG="$(echo "$FULL_TAG" | grep -oP '^v\d+')"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -fa "$MAJOR_TAG" -m "Update $MAJOR_TAG to $FULL_TAG"
git push origin "$MAJOR_TAG" --force
33 changes: 33 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Logs
logs
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Dependencies
node_modules/

# Build artifacts (but NOT dist/)
*.tgz
*.tar.gz

# Editor directories and files
.idea/
.vscode/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# OS
.DS_Store
Thumbs.db

# Test coverage
coverage/
6 changes: 6 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 120,
"singleQuote": true,
"ignorePatterns": ["dist", "node_modules"]
}
18 changes: 18 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["import", "vitest", "promise", "oxc", "typescript", "unicorn"],
"categories": {
"correctness": "error",
"suspicious": "warn",
"pedantic": "off",
"perf": "warn",
"style": "off",
"restriction": "off",
"nursery": "off"
},
"rules": {
"no-unused-vars": "warn",
"no-console": "off"
},
"ignorePatterns": ["node_modules", "dist"]
}
1 change: 1 addition & 0 deletions AGENTS.md
74 changes: 74 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# heal-trigger — Agent Practices

## Build

- Bundler: `@vercel/ncc` compiles `src/index.ts` → `dist/index.js` (single file, no node_modules at runtime)
- **Always rebuild and stage dist/ before committing:**
```bash
npm run build && git add dist/
```
- The pre-commit hook (lefthook) runs lint, format check, typecheck, and tests — it does **not** rebuild dist/
- CI has a git diff guard on `dist/` that fails if the bundle is stale

## Commands

```bash
npm run build # bundle src/ → dist/index.js (ncc)
npm test # vitest
npm run lint # oxlint
npm run lint:fix # oxlint (auto-fix)
npm run format # oxfmt (writes changes)
npm run format:check # oxfmt (read-only check)
npm run typecheck # tsgo --noEmit
```

## Dependencies

- `@actions/core` must stay at `^1.x` — v3+ is ESM-only and ncc compiles to CJS, causing "Package path . is not exported" errors
- No `@actions/github` — not needed; the action calls the Heal backend directly via `fetch`
- Do not add glob/minimatch libraries — see Glob patterns section below

## API

- Endpoint: `POST https://backend.heal.dev/api/v1/executions/trigger` (overridable via `backend-url` input)
- Auth header: `Authorization: ApiKey <token>` (not Bearer)
- Request body: `TriggerExecutionRequest` (see `src/types.ts`)
- Response: `ExternalExecutionTriggeredResponse` with `executionId` and `healExecutionUrl`

## Glob patterns

The backend filters slugs with PostgreSQL's `~*` operator (case-insensitive POSIX regex). JS regex libraries like minimatch generate syntax PostgreSQL rejects (`(?:...)`, `(?!\.)`, `(?=.)`). Use the custom `globToRegex()` in `src/utils.ts`:

- Bare slug → `^slug$` (exact match)
- `*nance` → `.*nance`
- `*login*` → `.*login.*`
- Regex special chars are escaped before `*` conversion

## Testing

- Framework: Vitest
- Tests live in `__tests__/index.test.ts`
- Mock `fetch` with `vi.stubGlobal` and `@actions/core` with `vi.mock`
- Use `vi.resetModules()` + dynamic `import('../src/index')` per test to re-run `run()`
- Assert glob behavior by testing the regex against slugs (`new RegExp(regex, 'i').test(slug)`) — do not assert exact regex strings since the output is an implementation detail

## Tooling

- Linter: Oxlint (`npm run lint`)
- Formatter: Oxfmt (`npm run format:check` / `npm run format`)
- Type checker: `tsgo --noEmit` (TypeScript native preview)
- Pre-commit: Lefthook — runs lint, format:check, typecheck, test

## Action inputs/outputs

| Input | Required | Default |
| ------------- | -------- | ------------------------ |
| `api-token` | Yes | |
| `team` | No | |
| `feature` | No | |
| `test-case` | No | |
| `backend-url` | No | https://backend.heal.dev |

| Output | Description |
| ------ | ---------------------------------------- |
| `url` | `healExecutionUrl` from the API response |
155 changes: 154 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,154 @@
# heal-trigger
# heal-trigger

[![CI](https://github.com/heal-dev/heal-trigger/actions/workflows/ci.yml/badge.svg)](https://github.com/heal-dev/heal-trigger/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

A GitHub Action that triggers a [Heal.dev](https://heal.dev) test execution and surfaces the execution URL directly in your workflow run.

## Usage

```yaml
name: Heal.dev CI
on:
push:

jobs:
heal-dev:
name: Heal.dev
runs-on: ubuntu-latest
steps:
- name: Trigger Heal Suite Execution
uses: heal-dev/heal-trigger@v1
with:
api-token: ${{ secrets.HEAL_API_TOKEN }}
```

### Filter examples

Filters accept exact slugs or glob patterns (`*` matches any sequence of characters). The API applies them case-insensitively.

```yaml
# Run every test case in the "finance" team
- uses: heal-dev/heal-trigger@v1
with:
api-token: ${{ secrets.HEAL_API_TOKEN }}
team: finance

# Run teams whose slug ends in "nance" (e.g. "finance")
- uses: heal-dev/heal-trigger@v1
with:
api-token: ${{ secrets.HEAL_API_TOKEN }}
team: '*nance'

# Run a specific feature across all teams
- uses: heal-dev/heal-trigger@v1
with:
api-token: ${{ secrets.HEAL_API_TOKEN }}
feature: decision-analysis

# Run a feature scoped to a team pattern
- uses: heal-dev/heal-trigger@v1
with:
api-token: ${{ secrets.HEAL_API_TOKEN }}
team: '*nan*'
feature: decision-analysis

# Run test cases whose slug contains "login" in the "finance" team
- uses: heal-dev/heal-trigger@v1
with:
api-token: ${{ secrets.HEAL_API_TOKEN }}
team: finance
test-case: '*login*'
```

## Inputs

| Input | Description | Required | Default |
| ------------- | ----------------------------------------------------------------- | -------- | -------------------------- |
| `api-token` | Your Heal API token | Yes | |
| `team` | Filter by team slug. Supports glob patterns (e.g. `*nance`) | No | |
| `feature` | Filter by feature slug. Supports glob patterns | No | |
| `test-case` | Filter by test case slug. Supports glob patterns (e.g. `*login*`) | No | |
| `backend-url` | Override the Heal backend base URL | No | `https://backend.heal.dev` |

## Outputs

| Output | Description |
| ------ | ------------------------------ |
| `url` | URL of the triggered execution |

## Development

### Prerequisites

- Node.js 24+
- npm

### Setup

```bash
git clone https://github.com/heal-dev/heal-trigger.git
cd heal-trigger
npm install
```

[Lefthook](https://github.com/evilmartians/lefthook) pre-commit hooks are installed automatically and run lint, format check, typecheck, and tests before every commit.

### Scripts

| Script | Description |
| ---------------------- | ---------------------------------------------------- |
| `npm test` | Run unit tests with Vitest |
| `npm run lint` | Lint source files with Oxlint |
| `npm run lint:fix` | Lint and auto-fix source files |
| `npm run format` | Format source files with Oxfmt |
| `npm run format:check` | Check formatting without writing |
| `npm run typecheck` | Type-check with TypeScript (no emit) |
| `npm run build` | Bundle `src/index.ts` into `dist/index.js` using ncc |

### Build

The action is bundled with [@vercel/ncc](https://github.com/vercel/ncc) so no `node_modules` are needed at runtime. After making changes to `src/`, always rebuild:

```bash
npm run build
```

Always rebuild before committing so `dist/` stays in sync with your changes.

### Testing

```bash
npm test
```

Tests use [Vitest](https://vitest.dev/) and live in `__tests__/`.

### Linting & Formatting

```bash
npm run lint # Oxlint
npm run lint:fix # Oxlint (auto-fix)
npm run format # Oxfmt (writes changes)
npm run format:check # Oxfmt (read-only check)
npm run typecheck # TypeScript type check
```

## Releasing

Push a version tag to trigger the release workflow:

```bash
git tag v1.0.0
git push origin v1.0.0
```

The release workflow will:

1. Build `dist/index.js`
2. Create a GitHub Release with auto-generated notes
3. Update the floating major version tag (e.g. `v1`)

## License

MIT © heal-dev
Loading