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
1 change: 0 additions & 1 deletion .eslintignore

This file was deleted.

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

This file was deleted.

67 changes: 26 additions & 41 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,32 @@ jobs:

steps:

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

- name: Use Node.js 20.x
uses: actions/setup-node@v6
- uses: actions/setup-node@v6
with:
node-version: 20.x
node-version: 24.x
registry-url: 'https://npm.pkg.github.com'
scope: '@nemerosa'

- name: Install dependencies
id: npm-ci
run: npm ci
env:
# See https://docs.github.com/en/packages/learn-github-packages/configuring-a-packages-access-control-and-visibility#ensuring-workflow-access-to-your-package
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Building and running tests
id: npm-build
run: npm run build && npm test
- run: npm run lint

# Testing the action
- run: npm run build

- run: npm test

- name: "Test: configuration"
if: github.repository_owner == 'nemerosa' && vars.ONTRACK_URL != ''
id: test-config
uses: ./
env:
Expand All @@ -54,75 +53,61 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: "Test: check injected variables"
if: github.repository_owner == 'nemerosa' && vars.ONTRACK_URL != ''
run: |
# Check all required variables are set
missing=""
[ -z "$YONTRACK_BUILD_ID" ] && missing="${missing} YONTRACK_BUILD_ID"
[ -z "$YONTRACK_BUILD_NAME" ] && missing="${missing} YONTRACK_BUILD_NAME"
[ -z "$YONTRACK_BRANCH_ID" ] && missing="${missing} YONTRACK_BRANCH_ID"
[ -z "$YONTRACK_BRANCH_NAME" ] && missing="${missing} YONTRACK_BRANCH_NAME"
[ -z "$YONTRACK_PROJECT_ID" ] && missing="${missing} YONTRACK_PROJECT_ID"
[ -z "$YONTRACK_PROJECT_NAME" ] && missing="${missing} YONTRACK_PROJECT_NAME"

if [ -n "$missing" ]; then
echo "Error: The following variables are not set or empty:$missing"
exit 1
fi

echo "All required variables are set"

# Validate YONTRACK_BRANCH_NAME equals Git branch name with / replaced by -
CURRENT_BRANCH="${GITHUB_REF#refs/heads/}"
EXPECTED_BRANCH_NAME="${CURRENT_BRANCH//\//-}"

if [ "$YONTRACK_BRANCH_NAME" != "$EXPECTED_BRANCH_NAME" ]; then
echo "Error: YONTRACK_BRANCH_NAME='$YONTRACK_BRANCH_NAME' does not match expected '$EXPECTED_BRANCH_NAME'"
exit 1
fi

echo "YONTRACK_BRANCH_NAME matches Git branch name: $YONTRACK_BRANCH_NAME"

# Validate YONTRACK_PROJECT_NAME equals repository name
REPO_NAME="${GITHUB_REPOSITORY#*/}"

if [ "$YONTRACK_PROJECT_NAME" != "$REPO_NAME" ]; then
echo "Error: YONTRACK_PROJECT_NAME='$YONTRACK_PROJECT_NAME' does not match repository name '$REPO_NAME'"
exit 1
fi

echo "YONTRACK_PROJECT_NAME matches repository name: $YONTRACK_PROJECT_NAME"

echo "All validations passed successfully!"

- name: "Test: validations and promotions"
if: github.repository_owner == 'nemerosa' && vars.ONTRACK_URL != ''
run: |
yontrack validate --validation BUILD --status PASSED
yontrack validate --validation TEST --status PASSED

- name: Commit and push built files
if: ${{ github.ref == 'refs/heads/main' }}
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add dist/
if ! git diff --staged --quiet; then
git commit -m "chore: update built files [skip ci]"
git push
fi
yontrack validate --validation TEST --status PASSED

- name: Publication
id: npm-publish
- name: Semantic release
id: release
if: ${{ github.ref == 'refs/heads/main' }}
run: |
npx semantic-release
# Capture the version from the latest git tag created by semantic-release
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
echo "version=$VERSION" >> $GITHUB_OUTPUT
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Tagging the Yontrack build
if: ${{ github.ref == 'refs/heads/main' && steps.npm-publish.outputs.version != '' }}
- name: Update floating major/minor tags
if: ${{ github.ref == 'refs/heads/main' && steps.release.outputs.version != '' }}
run: |
yontrack build set-property release ${{ steps.npm-publish.outputs.version }}
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"

- name: Tagging the Yontrack build
if: ${{ github.ref == 'refs/heads/main' && steps.release.outputs.version != '' && vars.ONTRACK_URL != '' }}
run: yontrack build set-property release "${{ steps.release.outputs.version }}"
35 changes: 19 additions & 16 deletions .releaserc
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
{
"branches": [
"main"
],
"branches": ["main"],
"plugins": [
["@semantic-release/commit-analyzer", {
"preset": "angular",
"releaseRules": [
{"type": "docs", "release": false},
{"type": "fix", "release": "patch"},
{"type": "feat", "release": "minor"},
{"type": "refactor", "release": "major"},
{"type": "release", "release": "patch"}
],
{ "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/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"
]
}
49 changes: 31 additions & 18 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,49 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Commands

```bash
npm run lint # ESLint
npm run build # Bundle with Vercel NCC (outputs to dist/)
npm test # Jest (--passWithNoTests; no unit tests currently exist)
npm run all # lint + build + test
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/* and @nemerosa/* deps)
npm run all # lint + build + test
```

The build step runs `ncc build index.js -o dist --source-map --license licenses.txt`, producing `dist/index.js` — the file GitHub Actions actually executes.
`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 GitHub Action that installs and configures the [Yontrack CLI](https://github.com/nemerosa/ontrack-cli) and then runs `yontrack ci config` to register the current CI build in a Yontrack instance.
This is a GitHub Action that installs and configures the [Yontrack CLI](https://github.com/nemerosa/ontrack-cli) and then runs `yontrack ci config` to register the current CI build in a Yontrack instance. Outputs/exports build/branch/project identifiers as both action outputs and env vars (`YONTRACK_BUILD_ID`, `YONTRACK_BUILD_NAME`, etc.) for downstream actions.

**Execution flow (`index.js` → 128 lines):**
**Execution flow** (`index.js`):
1. Reads `url`/`token` inputs (falling back to `YONTRACK_URL`/`YONTRACK_TOKEN` env vars)
2. Delegates CLI installation to `@nemerosa/ontrack-github-actions-module-install` — this handles downloading the binary, adding it to PATH, and configuring authentication
3. Reads the config file path (default: `.yontrack/ci.yaml`) and constructs arguments for `yontrack ci config`
4. Collects environment variables to pass to the CLI: predefined GitHub env vars + custom `env-vars` input + any env var starting with `YONTRACK_CI_`
5. Parses the JSON output from the CLI to extract build/branch/project IDs and names
6. Exports those as both GitHub Actions outputs and environment variables (`YONTRACK_BUILD_ID`, etc.)
2. Delegates CLI installation to `@nemerosa/ontrack-github-actions-module-install` — handles binary download, PATH setup, and Yontrack auth config
3. Reads the config file path (default: `.yontrack/ci.yaml`) and constructs `yontrack ci config` args
4. Collects env vars for the CLI: predefined GitHub env vars + `env-vars` input + any var starting with `YONTRACK_CI_`
5. Parses JSON output to extract build/branch/project IDs and names
6. Exports as both action outputs and env vars

**Source shape:** `index.js` exports `runAction({core, exec, client})`. A top-level IIFE loads pure-ESM `@actions/core`/`@actions/exec` via dynamic import (namespace, not `.default`) and `require()`s the CJS `@nemerosa/ontrack-github-actions-module-install` at top level. Tests inject mock objects directly.

**Why dependency injection:** All `@actions/*` packages at the latest majors are pure ESM. Top-level `require('@actions/...')` no longer works in CJS source. `@nemerosa/ontrack-github-actions-module-install` stays CJS so it's required at top level normally.

**Key files:**
- `index.js` — entire action logic (source of truth)
- `dist/index.js` — bundled output committed to the repo; must be rebuilt and committed after changes to `index.js`
- `action.yml` — action metadata: inputs, outputs, `runs: using: node20 main: dist/index.js`
- `.yontrack/ci.yaml` — example Yontrack config file used by the action's own CI
- `index.js` — source. Top-level `require` for module-install + IIFE-loaded `@actions/*`.
- `dist/index.js` — bundled output (esbuild, CJS) committed alongside source changes. The runner executes this.
- `dist/licenses.txt` — license attributions, generated by `esbuild-plugin-license`.
- `action.yml` — action metadata: inputs, outputs, `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).
- `.yontrack/ci.yaml` — example Yontrack config used by the action's own CI self-test.

## Release process

Releases are automated via `semantic-release` (`.releaserc`). Commit messages follow Angular convention: `fix` → patch, `feat` → minor, `refactor` → major. The CI pipeline (`main` branch only) commits the updated `dist/` files and then creates a GitHub release with the new version tag.
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. Self-tests the action (`uses: ./`) against a real Yontrack instance.
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.

## NPM registry

The `@nemerosa` scoped packages come from GitHub Packages. The `.npmrc` file sets this up; you need a valid `NODE_AUTH_TOKEN` to install dependencies.
The `@nemerosa` scoped packages come from GitHub Packages. The `.npmrc` file sets this up; you need a valid `NODE_AUTH_TOKEN` to install dependencies (read:packages scope is sufficient).
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,5 @@ outputs:
projectName:
description: Name of the project created in Yontrack
runs:
using: node20
using: node24
main: dist/index.js
28 changes: 28 additions & 0 deletions build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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