Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
1 change: 0 additions & 1 deletion .eslintignore

This file was deleted.

18 changes: 0 additions & 18 deletions .eslintrc.json

This file was deleted.

66 changes: 0 additions & 66 deletions .github/tag.sh

This file was deleted.

25 changes: 0 additions & 25 deletions .github/workflows/build.yml

This file was deleted.

67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: ci

on:
pull_request:
push:
branches: [main]

concurrency: "ci-${{ github.ref }}"

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
persist-credentials: true

- uses: actions/setup-node@v6
with:
node-version: 24.x

- run: npm ci

- run: npm run lint

- run: npm run build

- run: npm test

- name: Self-test (CLI setup)
if: github.repository_owner == 'nemerosa'
uses: ./
with:
url: ${{ vars.ONTRACK_URL }}
token: ${{ secrets.ONTRACK_TOKEN }}
config: github.com
indexation: 120
github-token: ${{ github.token }}
auto-validation-stamps: true
auto-promotion-levels: true
promotions: ./.github/ontrack/promotions.yml

- name: Semantic release
id: release
if: ${{ github.ref == 'refs/heads/main' }}
run: |
npx semantic-release
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
echo "version=$VERSION" >> $GITHUB_OUTPUT
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Update floating major/minor tags
if: ${{ github.ref == 'refs/heads/main' && steps.release.outputs.version != '' }}
run: |
VERSION="${{ steps.release.outputs.version }}"
[[ "$VERSION" =~ ^v([0-9]+)\.([0-9]+)\.[0-9]+$ ]] || exit 0
MAJOR="v${BASH_REMATCH[1]}"
MINOR="v${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"
git tag -f "$MAJOR" "$VERSION"
git tag -f "$MINOR" "$VERSION"
git push origin -f "$MAJOR" "$MINOR"
30 changes: 30 additions & 0 deletions .releaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"branches": ["main"],
"plugins": [
["@semantic-release/commit-analyzer", {
"preset": "angular",
"releaseRules": [
{ "breaking": true, "release": "major" },
{ "type": "feat", "release": "minor" },
{ "type": "refactor", "release": "major" },
{ "type": "fix", "release": "patch" },
{ "type": "perf", "release": "patch" },
{ "type": "chore", "release": "patch" },
{ "type": "docs", "release": "patch" },
{ "type": "style", "release": "patch" },
{ "type": "test", "release": "patch" },
{ "type": "build", "release": "patch" },
{ "type": "ci", "release": "patch" },
{ "type": "revert", "release": "patch" },
{ "type": "release", "release": "patch" }
]
}],
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
["@semantic-release/git", {
"assets": ["dist/**/*", "CHANGELOG.md", "package.json", "package-lock.json", "action.yml"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}],
"@semantic-release/github"
]
}
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Changelog

(This file is populated by semantic-release.)
49 changes: 49 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

```bash
npm run lint # ESLint (flat config, v10)
npm run build # esbuild bundle to dist/index.js + esbuild-plugin-license writes dist/licenses.txt
npm test # Jest unit tests (mocked @actions/* deps)
npm run all # lint + build + test
```

`npm run build` runs `node build.js`, which invokes esbuild with `esbuild-plugin-license` to produce `dist/index.js` and license attribution. ncc is no longer used.

## Architecture

This is a JavaScript GitHub Action that installs the [Yontrack CLI](https://github.com/nemerosa/ontrack-cli) on the runner, optionally configures it against a Yontrack instance, and optionally sets up the project + branch + auto-promotions in Yontrack. It exposes `installed` / `project` / `branch` outputs.

**Key files:**
- `index.js` — source. A top-level IIFE loads pure-ESM `@actions/*` packages via dynamic import and calls `runAction({core, exec, github, tc})`. The action's logic lives in `runAction` and four exported helpers: `configureCLI`, `configureProject`, `configureAutoPromotion`, `downloadAndSetup`. Pure helpers `mapArch`, `mapOS`, `argsWithConfig` are also exported.
- `dist/index.js` — bundled output (esbuild, CJS) committed alongside source changes. The runner executes this, not `index.js` directly.
- `dist/licenses.txt` — license attributions, generated by `esbuild-plugin-license`.
- `action.yml` — action metadata: many inputs (version, url, token, config, promotions, etc.), three outputs (installed, project, branch), `runs.using: node24`, `runs.main: dist/index.js`.
- `index.test.js` — Jest unit tests with hand-rolled mock objects.
- `build.js` — esbuild driver. Note the `.default` import for `esbuild-plugin-license` (CJS interop quirk).
- `.github/ontrack/promotions.yml` — config used by the action's self-test in CI.

**Why dependency injection:** All `@actions/*` packages at the latest majors are pure ESM. Top-level CJS imports no longer work. The IIFE loads ESM deps once at startup and passes them into `runAction`.

**Why `@actions/io` is gone:** It was only used for `io.mv` which `fs.promises.rename` covers natively for the in-tmpdir use case. Dropping it reduces the dep surface.

**Why `@actions/tool-cache` is kept:** `tc.downloadTool` handles auth, retries, and platform-specific download semantics that we'd have to reimplement otherwise.

## Release process

Releases are automated via `semantic-release` (`.releaserc`). Commit messages follow Angular convention. Every conventional-commit type triggers at least a patch release; `feat` is minor; `refactor` and any commit with a `BREAKING CHANGE:` footer is major.

The CI workflow on `main`:
1. Runs `npm run lint && npm run build && npm test`.
2. Runs the action against itself (`uses: ./`) as a self-test (gated to `nemerosa` repo owner so fork PRs don't try to authenticate).
3. Runs `npx semantic-release` — produces `vX.Y.Z` GitHub release; `@semantic-release/git` commits the regenerated `dist/`, `CHANGELOG.md`, `package.json`, `package-lock.json`, and `action.yml`.
4. Force-updates floating `vX` and `vX.Y` tags to point at the new release SHA.

The previous manual `tag.sh` flow has been removed. Existing floating `@v2` tags created by `tag.sh` are left alone (the breaking release lands as `@v3.0.0`, with new `@v3` and `@v3.0` floating tags).

## NPM registry

This repo's runtime deps come from npmjs.com (the public registry). No GH Packages auth needed.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,11 @@ npm install
To build the distribution:

```bash
ncc build
npm run build
```

This runs `node build.js`, which invokes esbuild with `esbuild-plugin-license` to produce `dist/index.js` (the bundle the action runner executes) and `dist/licenses.txt` (license attributions). To run lint, build, and tests in one go:

```bash
npm run all
```
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,5 @@ outputs:
branch:
description: Ontrack branch name
runs:
using: 'node16'
using: 'node24'
main: 'dist/index.js'
31 changes: 31 additions & 0 deletions build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const esbuild = require('esbuild');
const license = require('esbuild-plugin-license').default;

esbuild.build({
entryPoints: ['index.js'],
bundle: true,
platform: 'node',
format: 'cjs',
target: 'node24',
outfile: 'dist/index.js',
sourcemap: true,
plugins: [license({
thirdParty: {
output: {
file: 'dist/licenses.txt',
template(dependencies) {
return dependencies
.map(dep => [
dep.packageJson.name,
dep.packageJson.license,
dep.licenseText,
].filter(Boolean).join('\n'))
.join('\n\n');
},
},
},
})],
}).catch((err) => {
console.error(err);
process.exit(1);
});
Loading