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
44 changes: 31 additions & 13 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,47 @@ jobs:
contents: write
packages: write
issues: write

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

- uses: actions/setup-node@v6
with:
node-version: '20.x'
node-version: 24.x
registry-url: 'https://npm.pkg.github.com'
scope: '@nemerosa'
- id: npm-ci
run: npm ci

- id: npm-lint
run: npm run lint
- run: npm ci

- name: npm-test
- run: npm run lint

- name: Test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm run test
run: npm test

- id: npm-publish
- name: Semantic release
id: release
if: ${{ github.ref == 'refs/heads/main' }}
run: npx semantic-release
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 }}
NODE_AUTH_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"
49 changes: 29 additions & 20 deletions .releaserc
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
{
"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"}
],
}],
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
"@semantic-release/git",
"@semantic-release/github"
]
"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/npm",
["@semantic-release/git", {
"assets": ["CHANGELOG.md", "package.json", "package-lock.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}],
"@semantic-release/github"
]
}
51 changes: 51 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 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 test # Jest — integration tests (skip when env secrets are absent)
```

There is no `build` step in this repo — it is a plain Node.js library, not a bundled GitHub Action. The published package contains `index.js` directly.

## Architecture

This is a reusable Node.js module published as `@nemerosa/ontrack-github-actions-module-install` to GitHub Packages. It is consumed by other `ontrack-github-actions-*` repos to download and configure the [Yontrack CLI](https://github.com/nemerosa/ontrack-cli).

**Public API** (`index.js`):

```js
const cli = require('@nemerosa/ontrack-github-actions-module-install');
const { dir, cliExecutable, version } = await cli.install({
version, // optional — defaults to latest GitHub release
githubToken, // required when version is omitted
acceptDraft, // include draft releases when picking latest
logging, // verbose console output
yontrackUrl, // when set, the CLI is configured for this Yontrack instance
yontrackToken, // required when yontrackUrl is set
yontrackLocalConfig, // CLI config name (default: "default")
connRetryCount,
connRetryWait,
});
```

**Flow:**
1. `downloadCLI` — resolves the version (latest GitHub release if none provided), maps `os.platform()`/`os.arch()` to the binary suffix, downloads from the `nemerosa/ontrack-cli` GitHub releases, and writes the binary to a tmp dir.
2. `configureCLI` — when `yontrackUrl` is set, runs `yontrack config create ...` to set up the local CLI config.

**Tests** (`index.test.js`) are integration tests that hit the real network and (optionally) a real Yontrack instance. Tests requiring `GITHUB_TOKEN`, `YONTRACK_URL`, or `YONTRACK_TOKEN` are gated to skip when those env vars are unset, so PR CI without secrets passes cleanly.

## Release process

Releases are automated via `semantic-release` (`.releaserc`). Commit messages follow Angular convention. With the configured `releaseRules`, every conventional-commit type triggers at least a patch; `feat` is minor; `refactor` and any commit with a `BREAKING CHANGE:` footer (or `type!:` shorthand) is major.

The CI workflow on `main`:
1. Runs `npx semantic-release` — publishes to GH Packages, commits an updated `CHANGELOG.md` / `package.json` / `package-lock.json` via `@semantic-release/git`, and creates a `vX.Y.Z` GitHub release.
2. Force-updates floating `vX` and `vX.Y` tags to point at the new release, so consumers can pin to a major/minor and ride patch updates.

## NPM registry

This package is published to **GitHub Packages**, not npmjs.com. Consumers need an `.npmrc` configured with `@nemerosa:registry=https://npm.pkg.github.com` and a `NODE_AUTH_TOKEN` with `read:packages` scope.
23 changes: 5 additions & 18 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
import globals from "globals";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});

export default [
{
ignores: ["eslint.config.mjs", "node_modules/**"]
ignores: ["eslint.config.mjs", "node_modules/**", "coverage/**"],
},
...compat.extends("eslint:recommended"),
js.configs.recommended,
{
languageOptions: {
globals: {
Expand All @@ -26,11 +15,9 @@ export default [
Atomics: "readonly",
SharedArrayBuffer: "readonly",
},

ecmaVersion: 2018,
ecmaVersion: 2024,
sourceType: "commonjs",
},

rules: {},
}
];
},
];
5 changes: 2 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
const github = require('@actions/github');
const os = require("os");
const fs = require("fs");
const path = require("path");
const io = require('@actions/io');
const {Readable} = require('stream');
const {exec} = require('child_process');

Expand Down Expand Up @@ -77,7 +75,7 @@ async function downloadAndSetup({downloadUrl, cliName, logging}) {
const exeSuffix = os.platform().startsWith('win') ? '.exe' : '';

const cliExecutable = `${cliName}${exeSuffix}`;
await io.mv(cliPath, [dir, cliExecutable].join(path.sep))
await fs.promises.rename(cliPath, [dir, cliExecutable].join(path.sep))

return {
dir,
Expand All @@ -91,6 +89,7 @@ async function downloadCLI({version, logging, githubToken, acceptDraft}) {
if (!githubToken) {
throw "GitHub token must be provided in order to get the latest version of the CLI."
}
const github = await import('@actions/github');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rathpc , when launching the GH workflow on the PR, I get:

  ● downloading the CLI using the latest version

    TypeError: A dynamic import callback was invoked without --experimental-vm-modules

      90 |             throw "GitHub token must be provided in order to get the latest version of the CLI."
      91 |         }
    > 92 |         const github = await import('@actions/github');
         |                        ^
      93 |         const octokit = github.getOctokit(githubToken)
      94 |         const releases = await octokit.rest.repos.listReleases({
      95 |             owner: "nemerosa",

      at downloadCLI (index.js:92:24)
      at Object.downloadCLI [as install] (index.js:211:64)
      at Object.install (index.test.js:21:44)

https://github.com/nemerosa/ontrack-github-actions-module-install/actions/runs/25269405182/job/74095812347

Can you have a look?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rathpc , I just saw your comment above :) So yes, it seems we need --experimental-vm-modules.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok not a problem I will add that later today, thanks!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dcoraboeuf updated, please try running the workflow again. Thank you!

const octokit = github.getOctokit(githubToken)
const releases = await octokit.rest.repos.listReleases({
owner: "nemerosa",
Expand Down
32 changes: 15 additions & 17 deletions index.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
const cli = require('./index')
const {execSync} = require('child_process')
const io = require('@actions/io')
const path = require('path')

const testVersion = '5.0.0-alpha+006'

const hasGithubToken = !!process.env.GITHUB_TOKEN
const hasYontrack = !!(process.env.YONTRACK_URL && process.env.YONTRACK_TOKEN)
const testWithToken = hasGithubToken ? test : test.skip
const testWithYontrack = hasYontrack ? test : test.skip

test('downloading the CLI using a fixed version', async () => {
const {dir, cliExecutable} = await cli.install({logging: true, version: testVersion})
const exe = `${dir}/${cliExecutable}`
Expand All @@ -13,33 +17,27 @@ test('downloading the CLI using a fixed version', async () => {
expect(output).toBe(testVersion)
})

test('downloading the CLI using the latest version', async () => {
const githubToken = process.env.GITHUB_TOKEN
if (!githubToken) {
throw "GITHUB_TOKEN environment variable must be set to run this test."
}
const {dir, cliExecutable} = await cli.install({logging: true, githubToken, acceptDraft: true})
testWithToken('downloading the CLI using the latest version', async () => {
const {dir, cliExecutable} = await cli.install({
logging: true,
githubToken: process.env.GITHUB_TOKEN,
acceptDraft: true,
})
const exe = `${dir}/${cliExecutable}`

const output = execSync(`${exe} version --cli`, {encoding: 'utf-8'}).trim()
expect(output).not.toBe('')
})

test('creation of the configuration', async () => {
testWithYontrack('creation of the configuration', async () => {
const configPath = path.join(process.cwd(), '.yontrack-config.yaml')
await io.rmRF(configPath)

const yontrackUrl = process.env.YONTRACK_URL
const yontrackToken = process.env.YONTRACK_TOKEN
if (!yontrackUrl || !yontrackToken) {
throw new Error('YONTRACK_URL and YONTRACK_TOKEN environment variables must be set')
}
await require('fs').promises.rm(configPath, { force: true })

const {dir, cliExecutable} = await cli.install({
logging: true,
version: testVersion,
yontrackUrl,
yontrackToken,
yontrackUrl: process.env.YONTRACK_URL,
yontrackToken: process.env.YONTRACK_TOKEN,
})
const exe = `${dir}/${cliExecutable}`

Expand Down
Loading