From 8eb3061b3caad12331c11d4138f421d9f7009205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Garn=C3=A6s?= Date: Mon, 14 Sep 2026 15:35:55 +0200 Subject: [PATCH 01/11] chore: add MIT license Assisted-by: Claude --- LICENSE | 21 +++++++++++++++++++++ package.json | 1 + 2 files changed, 22 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2b2f739 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Reload A/S + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/package.json b/package.json index 86072e0..5404995 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "upsun-github-env-sync", "version": "1.0.0", "private": true, + "license": "MIT", "description": "Reusable Upsun activity script and GitHub Action for GitHub deployment sync", "scripts": { "lint:prettier": "prettier --check *.js *.d.ts", From 1f521d3e21a9bb302d8432f2bb944c256844758a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Garn=C3=A6s?= Date: Mon, 14 Sep 2026 15:37:55 +0200 Subject: [PATCH 02/11] feat: add composite action that installs the activity script integration Consumer repositories run the action to create or update the script integration in their Upsun project and set GH_TOKEN and GH_REPO on it. Ownership is decided by a single rule: a script integration is ours only if it carries the UPSUN_GITHUB_ENV_SYNC_VERSION variable. The action never reads or changes integrations without it, so projects with other script integrations keep working and hand installs are never adopted. The variable doubles as a record of the installed release, read from package.json in the action checkout. On create it is written first so a failure later in the run leaves an integration the next run finds. All API calls go through `upsun api:curl`, which handles authentication. Creating through the API returns the new ID directly, so no list diffing is needed. The Upsun CLI is installed from the upsun/cli installer into a runner temp directory so the step does not depend on sudo. Assisted-by: Claude --- action.yml | 43 +++++++++ package.json | 2 +- setup.js | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++ setup.test.js | 170 ++++++++++++++++++++++++++++++++++ 4 files changed, 464 insertions(+), 1 deletion(-) create mode 100644 action.yml create mode 100755 setup.js create mode 100644 setup.test.js diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..dcedd62 --- /dev/null +++ b/action.yml @@ -0,0 +1,43 @@ +name: "Upsun GitHub Env Sync" +description: "Install or update the Upsun activity script that syncs environments to GitHub deployments" + +inputs: + upsun_project_id: + description: "Upsun project ID" + required: true + upsun_api_token: + description: "Upsun API token with access to the project" + required: true + github_deploy_token: + description: "GitHub token the activity script uses. Needs Contents: read, Pull requests: read, Deployments: read and write, Environments: read and write" + required: true + +outputs: + integration_id: + description: "ID of the script integration that was created or updated" + value: ${{ steps.setup.outputs.integration_id }} + +runs: + using: "composite" + steps: + - name: Install Upsun CLI + shell: bash + env: + INSTALL_DIR: ${{ runner.temp }}/upsun-cli + GITHUB_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + mkdir -p "$INSTALL_DIR" + curl -fsSL https://raw.githubusercontent.com/upsun/cli/main/installer.sh | sh + echo "$INSTALL_DIR" >> "$GITHUB_PATH" + "$INSTALL_DIR/upsun" --version + + - name: Sync script integration + id: setup + shell: bash + env: + UPSUN_PROJECT_ID: ${{ inputs.upsun_project_id }} + UPSUN_CLI_TOKEN: ${{ inputs.upsun_api_token }} + GITHUB_DEPLOY_TOKEN: ${{ inputs.github_deploy_token }} + ACTION_PATH: ${{ github.action_path }} + run: node "$ACTION_PATH/setup.js" diff --git a/package.json b/package.json index 5404995..d93c7c3 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint:types": "tsc --noEmit --allowJs --checkJs --types node --skipLibCheck --strictNullChecks --noUncheckedIndexedAccess --module nodenext --target es2021 *.js *.d.ts", "lint": "concurrently \"npm:lint:prettier\" \"npm:lint:types\"", "format": "prettier --write *.js *.d.ts", - "test": "node --test activity-script.test.js", + "test": "node --test", "check": "concurrently \"npm:lint\" \"npm:test\"" }, "devDependencies": { diff --git a/setup.js b/setup.js new file mode 100755 index 0000000..d4aab10 --- /dev/null +++ b/setup.js @@ -0,0 +1,250 @@ +#!/usr/bin/env node +// @ts-check +"use strict"; + +const { execFileSync } = require("node:child_process"); +const { appendFileSync, readFileSync } = require("node:fs"); +const path = require("node:path"); + +/** Presence marks an integration as ours; value records the installed release. */ +const VERSION_VARIABLE = "UPSUN_GITHUB_ENV_SYNC_VERSION"; + +const EVENTS = [ + "environment.push", + "environment.activate", + "environment.redeploy", + "environment.domain.create", + "environment.domain.delete", + "environment.deactivate", + "environment.delete", +]; + +/** + * @typedef {{ id: string, type: string }} Integration + * @typedef {{ id: string, name: string, value?: string, is_sensitive?: boolean }} Variable + * + * @typedef {object} Client + * @property {() => Integration[]} listIntegrations + * @property {(integrationId: string) => Variable[]} listVariables + * @property {() => string} createIntegration Returns the new integration ID. + * @property {(integrationId: string) => void} updateIntegration + * @property {(integrationId: string, variable: Omit) => void} createVariable + * @property {(integrationId: string, variableId: string, variable: Omit) => void} patchVariable + */ + +/** + * Only integrations carrying the version variable are ours. Others are never touched. + * + * @param {Client} client + * @returns {string | null} + */ +function findManagedIntegration(client) { + const managed = client + .listIntegrations() + .filter((integration) => integration.type === "script") + .map((integration) => String(integration.id)) + .filter((integrationId) => + client + .listVariables(integrationId) + .some((variable) => variable.name === VERSION_VARIABLE), + ); + + if (managed.length > 1) { + throw new Error( + `Found ${managed.length} script integrations with a ${VERSION_VARIABLE} variable. Delete all but one.`, + ); + } + + return managed[0] ?? null; +} + +/** + * @param {Client} client + * @param {string} integrationId + * @param {Omit} variable + */ +function upsertVariable(client, integrationId, variable) { + const existing = client + .listVariables(integrationId) + .find((candidate) => candidate.name === variable.name); + + if (existing) { + client.patchVariable(integrationId, existing.id, { + value: variable.value, + is_sensitive: variable.is_sensitive, + }); + } else { + client.createVariable(integrationId, variable); + } +} + +/** + * @param {object} options + * @param {Client} options.client + * @param {string} options.version + * @param {string} options.githubRepository + * @param {string} options.githubDeployToken + * @param {(message: string) => void} [options.log] + * @returns {string} + */ +function sync({ + client, + version, + githubRepository, + githubDeployToken, + log = () => {}, +}) { + const versionVariable = { + name: VERSION_VARIABLE, + value: version, + is_sensitive: false, + }; + + const existingId = findManagedIntegration(client); + let integrationId = existingId; + + if (integrationId) { + log(`Updating script integration ${integrationId}`); + client.updateIntegration(integrationId); + } else { + log("Creating script integration"); + integrationId = client.createIntegration(); + // Mark first so a failure below leaves an integration the next run recognizes. + upsertVariable(client, integrationId, versionVariable); + } + + upsertVariable(client, integrationId, { + name: "GH_TOKEN", + value: githubDeployToken, + is_sensitive: true, + }); + upsertVariable(client, integrationId, { + name: "GH_REPO", + value: githubRepository, + is_sensitive: false, + }); + if (existingId) { + // Record the version last so it only changes once the update completed. + upsertVariable(client, integrationId, versionVariable); + } + + return integrationId; +} + +/** + * Talks to the Upsun API through `upsun api:curl`, which handles authentication. + * + * @param {object} options + * @param {string} options.projectId + * @param {string} options.scriptFile + * @returns {Client} + */ +function createCliClient({ projectId, scriptFile }) { + /** + * @param {string} apiPath + * @param {"GET" | "POST" | "PATCH"} method + * @param {object} [payload] + * @returns {any} + */ + const api = (apiPath, method, payload) => { + const args = ["api:curl", apiPath, "--request", method, "--yes"]; + if (payload) { + args.push("--json", JSON.stringify(payload)); + } + let output; + try { + output = execFileSync("upsun", args, { + encoding: "utf8", + stdio: ["ignore", "pipe", "inherit"], + }); + } catch (error) { + // execFileSync's own message repeats the command line, payload and secrets included. + const body = /** @type {{ stdout?: string }} */ (error).stdout?.trim(); + throw new Error(`${method} ${apiPath} failed${body ? `: ${body}` : ""}`); + } + return JSON.parse(output || "null"); + }; + + const integrationsPath = `/api/projects/${projectId}/integrations`; + const integrationFields = () => ({ + script: readFileSync(scriptFile, "utf8"), + events: EVENTS, + states: ["*"], + environments: ["*"], + }); + + return { + listIntegrations: () => api(integrationsPath, "GET") ?? [], + listVariables: (integrationId) => + api(`${integrationsPath}/${integrationId}/variables`, "GET") ?? [], + createIntegration: () => { + const response = api(integrationsPath, "POST", { + type: "script", + ...integrationFields(), + }); + const id = response?._embedded?.entity?.id; + if (!id) { + throw new Error( + `Create response has no integration ID: ${JSON.stringify(response)}`, + ); + } + return String(id); + }, + updateIntegration: (integrationId) => { + api(`${integrationsPath}/${integrationId}`, "PATCH", integrationFields()); + }, + createVariable: (integrationId, variable) => { + api(`${integrationsPath}/${integrationId}/variables`, "POST", variable); + }, + patchVariable: (integrationId, variableId, variable) => { + api( + `${integrationsPath}/${integrationId}/variables/${variableId}`, + "PATCH", + variable, + ); + }, + }; +} + +/** + * @param {string} name + * @returns {string} + */ +function requireEnv(name) { + const value = process.env[name]; + if (!value) { + throw new Error(`Missing required environment variable: ${name}`); + } + return value; +} + +function main() { + const actionPath = requireEnv("ACTION_PATH"); + const githubOutput = requireEnv("GITHUB_OUTPUT"); + + const integrationId = sync({ + client: createCliClient({ + projectId: requireEnv("UPSUN_PROJECT_ID"), + scriptFile: path.join(actionPath, "activity-script.js"), + }), + version: require(path.resolve(actionPath, "package.json")).version, + githubRepository: requireEnv("GITHUB_REPOSITORY"), + githubDeployToken: requireEnv("GITHUB_DEPLOY_TOKEN"), + log: console.log, + }); + + appendFileSync(githubOutput, `integration_id=${integrationId}\n`); + console.log(`Synchronized integration ${integrationId}`); +} + +if (require.main === module) { + main(); +} + +module.exports = { + VERSION_VARIABLE, + EVENTS, + findManagedIntegration, + upsertVariable, + sync, +}; diff --git a/setup.test.js b/setup.test.js new file mode 100644 index 0000000..7deb383 --- /dev/null +++ b/setup.test.js @@ -0,0 +1,170 @@ +// @ts-check +"use strict"; + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); + +const { + VERSION_VARIABLE, + findManagedIntegration, + upsertVariable, + sync, +} = require("./setup.js"); + +/** + * Fixtures in, calls out. Writes are recorded, never applied: no test reads + * back what it wrote earlier in the same run. + * + * @param {Array<{ id: string, type: string, variables?: Array<{ id: string, name: string, value?: string }> }>} integrations + */ +function createFakeClient(integrations) { + /** @type {unknown[][]} */ + const calls = []; + /** @param {string} name */ + const record = + (name) => + /** @param {unknown[]} args */ + (...args) => { + calls.push([name, ...args]); + }; + + return { + calls, + listIntegrations: () => integrations, + /** @param {string} integrationId */ + listVariables: (integrationId) => + integrations.find((integration) => integration.id === integrationId) + ?.variables ?? [], + createIntegration: () => { + calls.push(["createIntegration"]); + return "new"; + }, + updateIntegration: record("updateIntegration"), + createVariable: record("createVariable"), + patchVariable: record("patchVariable"), + }; +} + +const marker = { id: "var-1", name: VERSION_VARIABLE, value: "0.1.0" }; + +describe("findManagedIntegration", () => { + it("returns null when no script integration carries the version variable", () => { + const client = createFakeClient([ + { id: "slack", type: "script", variables: [{ id: "v", name: "HOOK" }] }, + { id: "gh", type: "github", variables: [marker] }, + ]); + + assert.equal(findManagedIntegration(client), null); + }); + + it("returns the single marked script integration", () => { + const client = createFakeClient([ + { id: "slack", type: "script" }, + { id: "ours", type: "script", variables: [marker] }, + ]); + + assert.equal(findManagedIntegration(client), "ours"); + }); + + it("fails when more than one script integration is marked", () => { + const client = createFakeClient([ + { id: "a", type: "script", variables: [marker] }, + { id: "b", type: "script", variables: [marker] }, + ]); + + assert.throws(() => findManagedIntegration(client), /Found 2 script/); + }); +}); + +describe("upsertVariable", () => { + it("patches an existing variable by ID", () => { + const client = createFakeClient([ + { id: "ours", type: "script", variables: [marker] }, + ]); + + upsertVariable(client, "ours", { + name: VERSION_VARIABLE, + value: "0.2.0", + is_sensitive: false, + }); + + assert.deepEqual(client.calls, [ + [ + "patchVariable", + "ours", + "var-1", + { value: "0.2.0", is_sensitive: false }, + ], + ]); + }); + + it("creates a missing variable", () => { + const client = createFakeClient([{ id: "ours", type: "script" }]); + + upsertVariable(client, "ours", { name: "GH_REPO", value: "org/repo" }); + + assert.deepEqual(client.calls, [ + ["createVariable", "ours", { name: "GH_REPO", value: "org/repo" }], + ]); + }); +}); + +describe("sync", () => { + const options = { + version: "0.3.0", + githubRepository: "reload/site", + githubDeployToken: "ghp_secret", + }; + const version = { + name: VERSION_VARIABLE, + value: "0.3.0", + is_sensitive: false, + }; + const token = { name: "GH_TOKEN", value: "ghp_secret", is_sensitive: true }; + const repo = { name: "GH_REPO", value: "reload/site", is_sensitive: false }; + + it("creates and marks an integration when none is ours", () => { + const client = createFakeClient([{ id: "slack", type: "script" }]); + + const integrationId = sync({ client, ...options }); + + assert.equal(integrationId, "new"); + assert.deepEqual(client.calls, [ + ["createIntegration"], + ["createVariable", "new", version], + ["createVariable", "new", token], + ["createVariable", "new", repo], + ]); + }); + + it("updates ours in place and leaves other integrations alone", () => { + const client = createFakeClient([ + { id: "slack", type: "script" }, + { + id: "ours", + type: "script", + variables: [marker, { id: "var-2", name: "GH_REPO", value: "old" }], + }, + ]); + + const integrationId = sync({ client, ...options }); + + assert.equal(integrationId, "ours"); + assert.deepEqual(client.calls, [ + ["updateIntegration", "ours"], + ["createVariable", "ours", token], + [ + "patchVariable", + "ours", + "var-2", + { value: "reload/site", is_sensitive: false }, + ], + [ + "patchVariable", + "ours", + "var-1", + { value: "0.3.0", is_sensitive: false }, + ], + ]); + }); +}); From a6ab4e45302bede40ee02d0632a8e80b09cf98da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Garn=C3=A6s?= Date: Mon, 14 Sep 2026 15:38:05 +0200 Subject: [PATCH 03/11] feat: allow pinning the Upsun CLI version The installer reads VERSION and falls back to the latest release when it is empty, so consumers can pin only when a CLI release breaks them. Assisted-by: Claude --- action.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/action.yml b/action.yml index dcedd62..8466b1e 100644 --- a/action.yml +++ b/action.yml @@ -11,6 +11,10 @@ inputs: github_deploy_token: description: "GitHub token the activity script uses. Needs Contents: read, Pull requests: read, Deployments: read and write, Environments: read and write" required: true + upsun_cli_version: + description: "Upsun CLI version to install, for example 5.11.0. Empty installs the latest release" + required: false + default: "" outputs: integration_id: @@ -24,6 +28,7 @@ runs: shell: bash env: INSTALL_DIR: ${{ runner.temp }}/upsun-cli + VERSION: ${{ inputs.upsun_cli_version }} GITHUB_TOKEN: ${{ github.token }} run: | set -euo pipefail From 98d5d2bad03d1d723115b56efa47258a19d0c1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Garn=C3=A6s?= Date: Mon, 14 Sep 2026 15:39:23 +0200 Subject: [PATCH 04/11] ci: release with semantic-release Every push to main with a feat or fix commit creates a GitHub release and tag, updates CHANGELOG.md and package.json, and moves the floating major tag so consumers can pin either an exact tag or vN. Nothing is published to a registry: npmPublish is off and the package stays private. The npm plugin is kept only for the version bump, which the action reads back as the installed version. The release commit is pushed with a token that has bypass rights on the main ruleset, since the workflow token cannot be added to a bypass list. The version starts at 0.0.0 so the first release is 0.1.0. semantic- release derives versions from tags, so a v0.0.0 tag on main is needed before the first run, otherwise it starts at 1.0.0. Assisted-by: Claude --- .github/workflows/release.yml | 53 +++++++++++++++++++++++++++++++++++ .releaserc.json | 17 +++++++++++ package-lock.json | 5 ++-- package.json | 2 +- 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .releaserc.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b0b1333 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,53 @@ +name: Release + +on: + push: + branches: + - main + +permissions: + contents: write + issues: write + pull-requests: write + +concurrency: + group: release + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v7 + with: + node-version: 24 + + - name: Release + id: release + uses: cycjimmy/semantic-release-action@v6 + with: + semantic_version: 25 + extra_plugins: | + @semantic-release/changelog@7 + @semantic-release/git@11 + conventional-changelog-conventionalcommits@9 + env: + # A token with bypass rights on the main ruleset; the release + # commit is pushed straight to main. + GITHUB_TOKEN: ${{ secrets.GH_RELEASE_TOKEN }} + + - name: Move floating major tag + if: steps.release.outputs.new_release_published == 'true' + env: + MAJOR_TAG: v${{ steps.release.outputs.new_release_major_version }} + RELEASE_TAG: ${{ steps.release.outputs.new_release_git_tag }} + run: | + set -euo pipefail + git tag -f "$MAJOR_TAG" "$RELEASE_TAG" + git push -f origin "$MAJOR_TAG" diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..a204eaa --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,17 @@ +{ + "branches": ["main"], + "plugins": [ + ["@semantic-release/commit-analyzer", { "preset": "conventionalcommits" }], + [ + "@semantic-release/release-notes-generator", + { "preset": "conventionalcommits" } + ], + "@semantic-release/changelog", + ["@semantic-release/npm", { "npmPublish": false }], + [ + "@semantic-release/git", + { "assets": ["CHANGELOG.md", "package.json", "package-lock.json"] } + ], + "@semantic-release/github" + ] +} diff --git a/package-lock.json b/package-lock.json index 2d07675..6a6584c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,13 @@ { "name": "upsun-github-env-sync", - "version": "1.0.0", + "version": "0.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "upsun-github-env-sync", - "version": "1.0.0", + "version": "0.0.0", + "license": "MIT", "devDependencies": { "@types/node": "^20.19.10", "concurrently": "^9.2.4", diff --git a/package.json b/package.json index d93c7c3..b5aa75d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "upsun-github-env-sync", - "version": "1.0.0", + "version": "0.0.0", "private": true, "license": "MIT", "description": "Reusable Upsun activity script and GitHub Action for GitHub deployment sync", From 21349a50d4facc8b8c934b868129a88ce2ad42a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Garn=C3=A6s?= Date: Tue, 15 Sep 2026 13:22:11 +0200 Subject: [PATCH 05/11] ci: move checkout and setup-node to their current majors Assisted-by: Claude --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 655294f..d0e9244 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,10 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: npm From 3af5c25dc6cf9ce27ed069bba1bb44f2a64dd3c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Garn=C3=A6s?= Date: Mon, 14 Sep 2026 16:26:24 +0200 Subject: [PATCH 06/11] chore: use Node 24 for development Node 20 was chosen as the oldest LTS supporting ES2021, the level Upsun documents for activity scripts. Upsun does not document a Node version, and the script runs in a sandbox that is not Node: fetch is synchronous and the storage module exists only there. ES2021 compatibility is guarded by the es2021 target in the type check, not by the local Node version. Node 20 is end of life, and the release and commit lint tooling needs Node 22 or newer. Assisted-by: Claude --- .nvmrc | 2 +- package-lock.json | 34 ++++++++++++++++++---------------- package.json | 2 +- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/.nvmrc b/.nvmrc index 209e3ef..a45fd52 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20 +24 diff --git a/package-lock.json b/package-lock.json index 6a6584c..8a92fcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,19 +9,20 @@ "version": "0.0.0", "license": "MIT", "devDependencies": { - "@types/node": "^20.19.10", + "@types/node": "^24.13.4", "concurrently": "^9.2.4", "prettier": "^3.6.2", "typescript": "^5.9.2" } }, "node_modules/@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "version": "24.13.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.4.tgz", + "integrity": "sha512-YJ7EqCstVTzIr0fMr7qul/977en+pQHrfmuKIo6Zr9i75Be21dr3MovcfvGtyvi2HAUrRerWps5sMO9I7WaxDw==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.18.0" } }, "node_modules/ansi-regex": { @@ -291,10 +292,11 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "7.0.0", @@ -352,12 +354,12 @@ }, "dependencies": { "@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "version": "24.13.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.4.tgz", + "integrity": "sha512-YJ7EqCstVTzIr0fMr7qul/977en+pQHrfmuKIo6Zr9i75Be21dr3MovcfvGtyvi2HAUrRerWps5sMO9I7WaxDw==", "dev": true, "requires": { - "undici-types": "~6.21.0" + "undici-types": "~7.18.0" } }, "ansi-regex": { @@ -541,9 +543,9 @@ "dev": true }, "undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true }, "wrap-ansi": { diff --git a/package.json b/package.json index b5aa75d..4b0d2ba 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "check": "concurrently \"npm:lint\" \"npm:test\"" }, "devDependencies": { - "@types/node": "^20.19.10", + "@types/node": "^24.13.4", "concurrently": "^9.2.4", "prettier": "^3.6.2", "typescript": "^5.9.2" From 9bd7c6854171f9090c01cd244279adac5f2ad6e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Garn=C3=A6s?= Date: Mon, 14 Sep 2026 16:26:31 +0200 Subject: [PATCH 07/11] ci: lint commit messages locally npm run lint:commits checks every commit between origin/main and HEAD with the same config as the pull request check, so a rejected message is found before the push. Assisted-by: Claude --- commitlint.config.js | 1 + package-lock.json | 1563 +++++++++++++++++++++++++++++++++++++++++- package.json | 5 +- 3 files changed, 1545 insertions(+), 24 deletions(-) create mode 100644 commitlint.config.js diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..5073c20 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1 @@ +module.exports = { extends: ["@commitlint/config-conventional"] }; diff --git a/package-lock.json b/package-lock.json index 8a92fcb..ad91bfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,519 @@ "version": "0.0.0", "license": "MIT", "devDependencies": { + "@commitlint/cli": "^21.2.2", + "@commitlint/config-conventional": "^21.2.2", "@types/node": "^24.13.4", "concurrently": "^9.2.4", "prettier": "^3.6.2", "typescript": "^5.9.2" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@commitlint/cli": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.2.tgz", + "integrity": "sha512-a+6hQxIxnpdvSvS2apvttPNbEliYsVC3PqFYDiiB2kjbwIsQsj1urvQ4Tkf70pKYozPalKAuRQmm/GHwndduqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-conventional": "^21.2.2", + "@commitlint/format": "^21.2.2", + "@commitlint/lint": "^21.2.2", + "@commitlint/load": "^21.2.2", + "@commitlint/read": "^21.2.1", + "@commitlint/types": "^21.2.0", + "tinyexec": "^1.0.0", + "yargs": "^18.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/cli/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@commitlint/cli/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@commitlint/cli/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@commitlint/cli/node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/cli/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@commitlint/cli/node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/cli/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@commitlint/cli/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@commitlint/cli/node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/cli/node_modules/yargs": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/@commitlint/cli/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.2.2.tgz", + "integrity": "sha512-NxA37SZviusFUEYOQZ5hNnZ1h7O/KiemPkxjOlpzKJNnWxThiwc6/SaZhaPa8fyLvfRBAywhQhJJk8XESHWlpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^21.2.0", + "conventional-changelog-conventionalcommits": "^10.0.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.2.0.tgz", + "integrity": "sha512-t7AzNHAKeIdo/3NRGwzpufKHsKkPHmFs/56N2Fnsh0/r0rGtnQzTxk6vnFgjaGr4hdSQKNB50/KAhR9Yk4LJKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^21.2.0", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/ensure": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.2.0.tgz", + "integrity": "sha512-76IF9vDNS13lAzEEik9eKwzt8f9hYhWiwVXZ2AnyLCz5/f511FsEQ3pw1X3/zSQpdRLQU7i5qDMVKyXi1GWjSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^21.2.0", + "es-toolkit": "^1.46.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-21.0.1.tgz", + "integrity": "sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/format": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.2.2.tgz", + "integrity": "sha512-v6fvxZSc/AvVMROlr3H34+1766bZSYApRUSCAMjWamStPjKMvZ8GdvVA5YW/VQNgbFTmcMz6OYmSTJEvIjPrfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^21.2.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.2.2.tgz", + "integrity": "sha512-9UoKNgfFE3LU7FrzierCvk3CdDfMDeVGC86qZiT/n0TIjfq/dmZ9MHuXd45OTNRa26ZanmJRxEtmiXk/lEJihg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^21.2.0", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/lint": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.2.2.tgz", + "integrity": "sha512-Fy8JxEBzdmsYWFude/61GxXu5O+wEymwiRK2z9GL9R8mCsXphCoGxAFc5iHn5mjlfcSrhiiONE+ksf4KOjnaPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/is-ignored": "^21.2.2", + "@commitlint/parse": "^21.2.2", + "@commitlint/rules": "^21.2.2", + "@commitlint/types": "^21.2.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/load": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.2.2.tgz", + "integrity": "sha512-0Tt6wDPX167cjKC5D4zhm0+20wJJG+TN/TKovMOspfSe78rOnKX+MNzlVNiu6HyQPZChPJ8QBH31MVt6Bb8fCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^21.2.0", + "@commitlint/execute-rule": "^21.0.1", + "@commitlint/resolve-extends": "^21.2.2", + "@commitlint/types": "^21.2.0", + "cosmiconfig": "^9.0.1", + "cosmiconfig-typescript-loader": "^6.1.0", + "es-toolkit": "^1.46.0", + "is-plain-obj": "^4.1.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/message": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.2.0.tgz", + "integrity": "sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/parse": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.2.2.tgz", + "integrity": "sha512-MEkobPfvRp+z06Wro8HMG1BDGHzZmj82A1LH1nWeG3ipHpg/x4m6v3wEDvMBIKjRFUnfR3nBeFs3MVCr7UdAmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^21.2.0", + "conventional-changelog-angular": "^9.0.0", + "conventional-commits-parser": "^7.0.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/read": { + "version": "21.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.2.1.tgz", + "integrity": "sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/top-level": "^21.2.0", + "@commitlint/types": "^21.2.0", + "@conventional-changelog/git-client": "^3.0.0", + "tinyexec": "^1.0.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.2.2.tgz", + "integrity": "sha512-RPkJ/IFi7sMUUVbZLqwWFtWw/zRDcfFsmrPSiTMrt5wb7AdxOr86EGQFvmGzef5QKV5IPBWWCujqVTw1RWX44A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^21.2.0", + "@commitlint/types": "^21.2.0", + "es-toolkit": "^1.46.0", + "global-directory": "^5.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/rules": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.2.2.tgz", + "integrity": "sha512-eplQzyYkBjYB1HyyRj8hkcK11Y9DU9nuBz7uOKEd6NpE9NGDytLFCAnlRE+OoiK/5sHEJsaz2RGhuWBvYzIbNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/ensure": "^21.2.0", + "@commitlint/message": "^21.2.0", + "@commitlint/to-lines": "^21.0.1", + "@commitlint/types": "^21.2.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-21.0.1.tgz", + "integrity": "sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/top-level": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.2.0.tgz", + "integrity": "sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/types": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.2.0.tgz", + "integrity": "sha512-7zVFCDB2reMvJH5dmbKnOQPjZEvjdJTH8jc0U/PIPU1r3/+vf5pD1HlfitV2MWsWXrvu7u39iY1lyLUPOaN0Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-commits-parser": "^7.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@conventional-changelog/git-client": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-3.1.2.tgz", + "integrity": "sha512-jZqwnJwf7nboIlAcw/mkOjVa6DexCcUOgT2oOQgkoi3z9vR8tGFkcMy2BFcYwjhL9sYcDDXkRQDayiDieCoW7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/child-process-utils": "^2.0.0", + "@simple-libs/stream-utils": "^2.0.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "conventional-commits-filter": "^6.0.1", + "conventional-commits-parser": "^7.1.2" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } + } + }, + "node_modules/@conventional-changelog/template": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/template/-/template-1.4.0.tgz", + "integrity": "sha512-aalGyl7dbB5PArRebDIX43ZvBlXrYm9uWzGJ26t+4SzJVPsOuvfILGGbw5X4yX7i50YEmJ8zvbiWnqH/AAnZqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@simple-libs/child-process-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-2.0.0.tgz", + "integrity": "sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^2.0.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@simple-libs/stream-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-2.0.0.tgz", + "integrity": "sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, "node_modules/@types/node": { "version": "24.13.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.4.tgz", @@ -25,6 +532,23 @@ "undici-types": "~7.18.0" } }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -49,6 +573,36 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/argue-cli": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/argue-cli/-/argue-cli-3.2.0.tgz", + "integrity": "sha512-VipTB0gXgGIFO2Rg9yEVN5wLt2AurJcZqDbgmYSwPwsykhLrQhs240/bfceev4w68lI8JshoOJIk9uW7tFzROw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -134,48 +688,379 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/conventional-changelog-angular": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-9.4.0.tgz", + "integrity": "sha512-HdxRxuS8bBXVIuo4V82gvSwAXT0vYQUizrjs/izmPg5JdDstr8v8I5hduGL3iQbG+o310dUDxC4+LetuS5hu9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@conventional-changelog/template": "^1.4.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-10.4.0.tgz", + "integrity": "sha512-Rriac6ZrAlVm6cy9Bz4NSp+WMHpwNXoPIYex+HjCgduAVUSbnew29DQjQw0C4g9u3HtSYzGiGY+pdBXAZo+4aA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@conventional-changelog/template": "^1.4.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-commits-parser": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-7.1.2.tgz", + "integrity": "sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^2.0.0", + "argue-cli": "^3.1.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.3.0.tgz", + "integrity": "sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jiti": "2.6.1" + }, + "engines": { + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-toolkit": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-directory": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-5.0.0.tgz", + "integrity": "sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "6.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "engines": { - "node": ">=6" - } + "license": "MIT" }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } + "license": "MIT" }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/prettier": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", @@ -200,6 +1085,26 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -209,6 +1114,19 @@ "tslib": "^2.1.0" } }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shell-quote": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", @@ -263,6 +1181,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -353,6 +1281,342 @@ } }, "dependencies": { + "@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true + }, + "@commitlint/cli": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.2.tgz", + "integrity": "sha512-a+6hQxIxnpdvSvS2apvttPNbEliYsVC3PqFYDiiB2kjbwIsQsj1urvQ4Tkf70pKYozPalKAuRQmm/GHwndduqA==", + "dev": true, + "requires": { + "@commitlint/config-conventional": "^21.2.2", + "@commitlint/format": "^21.2.2", + "@commitlint/lint": "^21.2.2", + "@commitlint/load": "^21.2.2", + "@commitlint/read": "^21.2.1", + "@commitlint/types": "^21.2.0", + "tinyexec": "^1.0.0", + "yargs": "^18.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + }, + "cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "requires": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "dependencies": { + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + } + } + }, + "emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true + }, + "string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "requires": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + } + }, + "strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "requires": { + "ansi-regex": "^6.2.2" + } + }, + "wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "requires": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "dependencies": { + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + } + } + }, + "yargs": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", + "dev": true, + "requires": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + } + }, + "yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true + } + } + }, + "@commitlint/config-conventional": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.2.2.tgz", + "integrity": "sha512-NxA37SZviusFUEYOQZ5hNnZ1h7O/KiemPkxjOlpzKJNnWxThiwc6/SaZhaPa8fyLvfRBAywhQhJJk8XESHWlpQ==", + "dev": true, + "requires": { + "@commitlint/types": "^21.2.0", + "conventional-changelog-conventionalcommits": "^10.0.0" + } + }, + "@commitlint/config-validator": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.2.0.tgz", + "integrity": "sha512-t7AzNHAKeIdo/3NRGwzpufKHsKkPHmFs/56N2Fnsh0/r0rGtnQzTxk6vnFgjaGr4hdSQKNB50/KAhR9Yk4LJKA==", + "dev": true, + "requires": { + "@commitlint/types": "^21.2.0", + "ajv": "^8.11.0" + } + }, + "@commitlint/ensure": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.2.0.tgz", + "integrity": "sha512-76IF9vDNS13lAzEEik9eKwzt8f9hYhWiwVXZ2AnyLCz5/f511FsEQ3pw1X3/zSQpdRLQU7i5qDMVKyXi1GWjSg==", + "dev": true, + "requires": { + "@commitlint/types": "^21.2.0", + "es-toolkit": "^1.46.0" + } + }, + "@commitlint/execute-rule": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-21.0.1.tgz", + "integrity": "sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==", + "dev": true + }, + "@commitlint/format": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.2.2.tgz", + "integrity": "sha512-v6fvxZSc/AvVMROlr3H34+1766bZSYApRUSCAMjWamStPjKMvZ8GdvVA5YW/VQNgbFTmcMz6OYmSTJEvIjPrfA==", + "dev": true, + "requires": { + "@commitlint/types": "^21.2.0", + "picocolors": "^1.1.1" + } + }, + "@commitlint/is-ignored": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.2.2.tgz", + "integrity": "sha512-9UoKNgfFE3LU7FrzierCvk3CdDfMDeVGC86qZiT/n0TIjfq/dmZ9MHuXd45OTNRa26ZanmJRxEtmiXk/lEJihg==", + "dev": true, + "requires": { + "@commitlint/types": "^21.2.0", + "semver": "^7.6.0" + } + }, + "@commitlint/lint": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.2.2.tgz", + "integrity": "sha512-Fy8JxEBzdmsYWFude/61GxXu5O+wEymwiRK2z9GL9R8mCsXphCoGxAFc5iHn5mjlfcSrhiiONE+ksf4KOjnaPg==", + "dev": true, + "requires": { + "@commitlint/is-ignored": "^21.2.2", + "@commitlint/parse": "^21.2.2", + "@commitlint/rules": "^21.2.2", + "@commitlint/types": "^21.2.0" + } + }, + "@commitlint/load": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.2.2.tgz", + "integrity": "sha512-0Tt6wDPX167cjKC5D4zhm0+20wJJG+TN/TKovMOspfSe78rOnKX+MNzlVNiu6HyQPZChPJ8QBH31MVt6Bb8fCg==", + "dev": true, + "requires": { + "@commitlint/config-validator": "^21.2.0", + "@commitlint/execute-rule": "^21.0.1", + "@commitlint/resolve-extends": "^21.2.2", + "@commitlint/types": "^21.2.0", + "cosmiconfig": "^9.0.1", + "cosmiconfig-typescript-loader": "^6.1.0", + "es-toolkit": "^1.46.0", + "is-plain-obj": "^4.1.0", + "picocolors": "^1.1.1" + } + }, + "@commitlint/message": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.2.0.tgz", + "integrity": "sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==", + "dev": true + }, + "@commitlint/parse": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.2.2.tgz", + "integrity": "sha512-MEkobPfvRp+z06Wro8HMG1BDGHzZmj82A1LH1nWeG3ipHpg/x4m6v3wEDvMBIKjRFUnfR3nBeFs3MVCr7UdAmg==", + "dev": true, + "requires": { + "@commitlint/types": "^21.2.0", + "conventional-changelog-angular": "^9.0.0", + "conventional-commits-parser": "^7.0.0" + } + }, + "@commitlint/read": { + "version": "21.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.2.1.tgz", + "integrity": "sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==", + "dev": true, + "requires": { + "@commitlint/top-level": "^21.2.0", + "@commitlint/types": "^21.2.0", + "@conventional-changelog/git-client": "^3.0.0", + "tinyexec": "^1.0.0" + } + }, + "@commitlint/resolve-extends": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.2.2.tgz", + "integrity": "sha512-RPkJ/IFi7sMUUVbZLqwWFtWw/zRDcfFsmrPSiTMrt5wb7AdxOr86EGQFvmGzef5QKV5IPBWWCujqVTw1RWX44A==", + "dev": true, + "requires": { + "@commitlint/config-validator": "^21.2.0", + "@commitlint/types": "^21.2.0", + "es-toolkit": "^1.46.0", + "global-directory": "^5.0.0", + "resolve-from": "^5.0.0" + } + }, + "@commitlint/rules": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.2.2.tgz", + "integrity": "sha512-eplQzyYkBjYB1HyyRj8hkcK11Y9DU9nuBz7uOKEd6NpE9NGDytLFCAnlRE+OoiK/5sHEJsaz2RGhuWBvYzIbNA==", + "dev": true, + "requires": { + "@commitlint/ensure": "^21.2.0", + "@commitlint/message": "^21.2.0", + "@commitlint/to-lines": "^21.0.1", + "@commitlint/types": "^21.2.0" + } + }, + "@commitlint/to-lines": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-21.0.1.tgz", + "integrity": "sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==", + "dev": true + }, + "@commitlint/top-level": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.2.0.tgz", + "integrity": "sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==", + "dev": true, + "requires": { + "escalade": "^3.2.0" + } + }, + "@commitlint/types": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.2.0.tgz", + "integrity": "sha512-7zVFCDB2reMvJH5dmbKnOQPjZEvjdJTH8jc0U/PIPU1r3/+vf5pD1HlfitV2MWsWXrvu7u39iY1lyLUPOaN0Gw==", + "dev": true, + "requires": { + "conventional-commits-parser": "^7.0.0", + "picocolors": "^1.1.1" + } + }, + "@conventional-changelog/git-client": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-3.1.2.tgz", + "integrity": "sha512-jZqwnJwf7nboIlAcw/mkOjVa6DexCcUOgT2oOQgkoi3z9vR8tGFkcMy2BFcYwjhL9sYcDDXkRQDayiDieCoW7A==", + "dev": true, + "requires": { + "@simple-libs/child-process-utils": "^2.0.0", + "@simple-libs/stream-utils": "^2.0.0", + "semver": "^7.5.2" + } + }, + "@conventional-changelog/template": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/template/-/template-1.4.0.tgz", + "integrity": "sha512-aalGyl7dbB5PArRebDIX43ZvBlXrYm9uWzGJ26t+4SzJVPsOuvfILGGbw5X4yX7i50YEmJ8zvbiWnqH/AAnZqg==", + "dev": true + }, + "@simple-libs/child-process-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-2.0.0.tgz", + "integrity": "sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==", + "dev": true, + "requires": { + "@simple-libs/stream-utils": "^2.0.0" + } + }, + "@simple-libs/stream-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-2.0.0.tgz", + "integrity": "sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==", + "dev": true + }, "@types/node": { "version": "24.13.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.4.tgz", @@ -362,6 +1626,18 @@ "undici-types": "~7.18.0" } }, + "ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -377,6 +1653,24 @@ "color-convert": "^2.0.1" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "argue-cli": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/argue-cli/-/argue-cli-3.2.0.tgz", + "integrity": "sha512-VipTB0gXgGIFO2Rg9yEVN5wLt2AurJcZqDbgmYSwPwsykhLrQhs240/bfceev4w68lI8JshoOJIk9uW7tFzROw==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -438,36 +1732,235 @@ "yargs": "17.7.2" } }, + "conventional-changelog-angular": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-9.4.0.tgz", + "integrity": "sha512-HdxRxuS8bBXVIuo4V82gvSwAXT0vYQUizrjs/izmPg5JdDstr8v8I5hduGL3iQbG+o310dUDxC4+LetuS5hu9w==", + "dev": true, + "requires": { + "@conventional-changelog/template": "^1.4.0" + } + }, + "conventional-changelog-conventionalcommits": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-10.4.0.tgz", + "integrity": "sha512-Rriac6ZrAlVm6cy9Bz4NSp+WMHpwNXoPIYex+HjCgduAVUSbnew29DQjQw0C4g9u3HtSYzGiGY+pdBXAZo+4aA==", + "dev": true, + "requires": { + "@conventional-changelog/template": "^1.4.0" + } + }, + "conventional-commits-parser": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-7.1.2.tgz", + "integrity": "sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==", + "dev": true, + "requires": { + "@simple-libs/stream-utils": "^2.0.0", + "argue-cli": "^3.1.0" + } + }, + "cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dev": true, + "requires": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + } + }, + "cosmiconfig-typescript-loader": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.3.0.tgz", + "integrity": "sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==", + "dev": true, + "requires": { + "jiti": "2.6.1" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-toolkit": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "dev": true + }, "escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true + }, + "global-directory": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-5.0.0.tgz", + "integrity": "sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==", + "dev": true, + "requires": { + "ini": "6.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true + }, + "jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, "prettier": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", @@ -480,6 +1973,18 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, "rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -489,6 +1994,12 @@ "tslib": "^2.1.0" } }, + "semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true + }, "shell-quote": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", @@ -524,6 +2035,12 @@ "has-flag": "^4.0.0" } }, + "tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true + }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", diff --git a/package.json b/package.json index 4b0d2ba..1a61dff 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,12 @@ "lint": "concurrently \"npm:lint:prettier\" \"npm:lint:types\"", "format": "prettier --write *.js *.d.ts", "test": "node --test", - "check": "concurrently \"npm:lint\" \"npm:test\"" + "check": "concurrently \"npm:lint\" \"npm:test\"", + "lint:commits": "commitlint --from origin/main --to HEAD --verbose" }, "devDependencies": { + "@commitlint/cli": "^21.2.2", + "@commitlint/config-conventional": "^21.2.2", "@types/node": "^24.13.4", "concurrently": "^9.2.4", "prettier": "^3.6.2", From f30a726f14820193fae0674c871c7ee6c88132e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Garn=C3=A6s?= Date: Tue, 15 Sep 2026 13:22:11 +0200 Subject: [PATCH 08/11] ci: enforce conventional commits semantic-release reads every commit that lands on main, so each pull request runs the same commitlint script that developers run locally. The commitlint version is pinned in package.json, so Dependabot keeps it current. Assisted-by: Claude --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0e9244..c53fb58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,3 +24,24 @@ jobs: - name: Lint and test run: npm run check + + commitlint: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v7 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Lint commit messages + run: npm run lint:commits From 23bc6266c2dda279abf63c95befe3873b2cceeac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Garn=C3=A6s?= Date: Mon, 14 Sep 2026 15:39:57 +0200 Subject: [PATCH 09/11] docs: add example consumer workflow Nightly schedule so releases of the action roll out without a consumer commit, manual dispatch for first install and debugging, and push on the workflow path so a new consumer is installed on merge. Assisted-by: Claude --- examples/upsun-github-env-sync.yml | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 examples/upsun-github-env-sync.yml diff --git a/examples/upsun-github-env-sync.yml b/examples/upsun-github-env-sync.yml new file mode 100644 index 0000000..61dc20c --- /dev/null +++ b/examples/upsun-github-env-sync.yml @@ -0,0 +1,31 @@ +# Copy to .github/workflows/upsun-github-env-sync.yml in the consumer repository. +name: Upsun GitHub Env Sync + +on: + schedule: + # Nightly, 04:00 UTC. Picks up new releases of the action. + - cron: "0 4 * * *" + workflow_dispatch: + push: + branches: + - main + paths: + - .github/workflows/upsun-github-env-sync.yml + +permissions: + contents: read + +concurrency: + group: upsun-github-env-sync + cancel-in-progress: false + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Install or update the activity script + uses: reload/upsun-github-env-sync@v1 + with: + upsun_project_id: ${{ vars.UPSUN_PROJECT_ID }} + upsun_api_token: ${{ secrets.UPSUN_API_TOKEN }} + github_deploy_token: ${{ secrets.GH_DEPLOY_TOKEN }} From b12157f45761a8095c2ed19d4d789cfe312c1cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Garn=C3=A6s?= Date: Mon, 14 Sep 2026 15:40:51 +0200 Subject: [PATCH 10/11] docs: rewrite README for action consumers and maintainers Replaces the manual CLI install instructions with the action-based install, documents the ownership rule, the version tags, the release process and the token expiry symptom. Assisted-by: Claude --- README.md | 190 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 104 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index f4069bc..a6ad054 100644 --- a/README.md +++ b/README.md @@ -1,126 +1,144 @@ -# Upsun Github Environment Synchonization +# Upsun GitHub Environment Sync -This document describes how to set up the GitHub deployment and environment -synchronization activity script on Upsun. +An Upsun activity script that syncs Upsun environments to GitHub deployments, and a GitHub Action that installs it in a project. -## Overview +## Contents -The activity script in `activity-script.js` automatically synchronizes Upsun -environment states with GitHub Deployments. It responds to environment lifecycle -events and creates/updates GitHub deployment statuses accordingly. +- `activity-script.js`: The activity script. Upsun runs it on environment events. +- `action.yml` and `setup.js`: The composite GitHub Action that installs or updates the script in a project. +- `examples/upsun-github-env-sync.yml`: A workflow file for consumer repositories. -### Event handling +## How it works -| Upsun Event | Activity State | GitHub Status | Details | -|-----------------------------|----------------|---------------|--------------------------| -| `environment.push` | pending | queued | Deployment queued | -| `environment.push` | in_progress | in_progress | Links to deployment log | -| `environment.push` | complete (success) | success | Links to environment url | -| `environment.push` | complete (failure) | failure | Links to deployment log | -| `environment.activate` | complete | success | Environment activated | -| `environment.domain.create` | complete | success | Environment url updated | -| `environment.domain.delete` | complete | success | Environment url updated | -| `environment.deactivate` | complete | inactive | Environment closed | -| `environment.delete` | complete | inactive | Environment deleted | +Upsun runs the activity script on these events. The script maps each event and activity state to a GitHub deployment status: -### Deployment creation +| Upsun event | Activity state | GitHub status | Details | +| --------------------------- | ------------------ | ------------- | ------------------------------- | +| `environment.push` | pending | queued | New deployment for the activity | +| `environment.push` | in_progress | in_progress | Links to the Upsun log | +| `environment.push` | complete (success) | success | Links to the environment URL | +| `environment.push` | complete (failure) | failure | Links to the Upsun log | +| `environment.redeploy` | pending | queued | Updates the latest deployment | +| `environment.redeploy` | in_progress | in_progress | Links to the Upsun log | +| `environment.redeploy` | complete (success) | success | Links to the environment URL | +| `environment.redeploy` | complete (failure) | failure | Links to the Upsun log | +| `environment.activate` | complete | success | Environment activated | +| `environment.domain.create` | complete | success | Environment URL updated | +| `environment.domain.delete` | complete | success | Environment URL updated | +| `environment.deactivate` | complete | inactive | Environment closed | +| `environment.delete` | complete | inactive | Environment deleted | -The script automatically creates or updates GitHub deployments when: -- Code is pushed to an environment -- An environment is activated for the first time -- A domain is added or removed for the environment +A push creates one GitHub deployment per Upsun activity. All other events update the latest deployment of the environment. -The script will: -1. Check if a deployment exists for the environment -2. Create one if it doesn't exist -3. Update the deployment status based on activity state +The action connects to the Upsun project with the Upsun CLI. It creates a `script` integration with the events above, or updates the one it created before. Then it sets three variables on the integration: -### URLs generated +- `GH_TOKEN` (sensitive): The GitHub token that the script uses. +- `GH_REPO`: The repository, taken from the workflow that runs the action. +- `UPSUN_GITHUB_ENV_SYNC_VERSION`: The installed release of this repository. -* Environment url: Extracted from the Upsun primary route -* Log url: Use the format `https://console.upsun.com/{OWNER_SLUG}/{PROJECT_ID}/-/log/{ACTIVITY_ID}` +## Install the script in a project -## Prerequisites +Do these steps once for each consumer repository. -1. **GitHub Personal Access Token** - - Create a token at: https://github.com/settings/personal-access-tokens/new - - Repository access: Select the repository for the project - - Permissions: - - Metadata: Read only (default) - - Deployments: Read and write - - Environments: Read and write - - Store the token securely +1. Create a fine-grained personal access token. Use a bot account if your organization has one, so the token does not stop when a person leaves. Give it access to the consumer repository only. Give it the longest lifetime that the organization policy permits. Give it these repository permissions: + - `Metadata`: Read-only + - `Contents`: Read-only + - `Pull requests`: Read-only + - `Deployments`: Read and write + - `Environments`: Read and write +2. In the consumer repository, create the repository variable `UPSUN_PROJECT_ID` with the Upsun project ID. +3. In the consumer repository, create the repository secret `GH_DEPLOY_TOKEN` with the token from step 1. +4. Make sure that the repository can read a secret `UPSUN_API_TOKEN` that holds an Upsun API token with access to the project. Create the token in the Upsun Console, under the account settings of a user or a dedicated API user. See [Upsun API tokens](https://developer.upsun.com/cli/api-tokens) for the steps in the Console. A GitHub organization secret lets all consumer repositories share one token. If the secret is an organization secret, ask an organization owner to give the repository access. +5. Copy `examples/upsun-github-env-sync.yml` to `.github/workflows/` in the consumer repository. If the default branch is not `main`, change the branch name in the file. +6. Merge the workflow file. The push installs the script. +7. Open the workflow run. Make sure that the last step ends with `Synchronized integration` and an ID. -2. **Upsun CLI** - - Install: `curl -fsSL https://raw.githubusercontent.com/platformsh/cli/main/installer.sh | bash` - - Login: `upsun login` +The workflow also runs every night at 04:00 UTC. This run installs new releases of the script. -## Development and validation +### Inputs -### 1. Install the activity script +| Input | Required | Description | +| --------------------- | -------- | -------------------------------------------------------------------------- | +| `upsun_project_id` | Yes | The Upsun project ID. | +| `upsun_api_token` | Yes | An Upsun API token with access to the project. | +| `github_deploy_token` | Yes | The GitHub token that the script uses. See step 1 for permissions. | +| `upsun_cli_version` | No | The Upsun CLI version to install, for example `5.11.0`. Default is latest. | -```bash -upsun integration:add \ - --type script \ - --file .platform/activity-scripts/upsun-github-env-sync/activity-script.js \ - --events='environment.push,environment.activate,environment.domain.create,environment.domain.delete,environment.deactivate,environment.delete' \ - --states='*' \ - --environments='*' +### Outputs + +| Output | Description | +| ---------------- | -------------------------------------------------- | +| `integration_id` | The ID of the integration that the action manages. | + +## Versions + +Each release gets a tag, for example `v1.2.0`. The tag `v1` always points to the latest release in the `v1` line. + +The example workflow uses `reload/upsun-github-env-sync@v1`. The nightly run then installs each new release without a change in the consumer repository. To stop on one release, use the exact tag instead: + +```yaml +uses: reload/upsun-github-env-sync@v1.2.0 ``` -### 2. Set required integration variables +## Troubleshooting + +### Using the Upsun CLI + +The commands in this section use the Upsun CLI on your computer. To install it, run the installer from [upsun/cli](https://github.com/upsun/cli). Then run `upsun login`. To list the integrations of a project and their IDs: ```bash -# Set GitHub token (required) -upsun api:curl /api/projects/[PROJECT_ID]/integrations/[INTEGRATION_ID]/variables -X POST --json="{ - \"name\": \"GH_TOKEN\", - \"value\": \"[GITHUB_TOKEN]\", - \"is_sensitive\": true -}" - -# Set GitHub repository (required - format: owner/repo, no []) -upsun api:curl /api/projects/[PROJECT_ID]/integrations/[INTEGRATION_ID]/variables -X POST --json="{ - \"name\": \"GH_REPO\", - \"value\": \"[GITHUB OWNER]/[GITHUB REPOSITORY]\" -}" +upsun integrations --project PROJECT_ID ``` -## Debugging +### Which integration the action manages -### View activity script logs +The action manages a script integration only if it has the variable `UPSUN_GITHUB_ENV_SYNC_VERSION`. The action creates this variable on the integration that it creates. The action does not read or change other integrations. -```bash -# View recent activity script executions -upsun integration:activities INTEGRATION_ID +If the project has no integration with the variable, the action creates one. If the project has one, the action updates it. If the project has more than one, the action stops with `Found 2 script integrations`. Delete the extra integrations and run the workflow again: -# View logs for a specific activity -upsun integration:activity:log INTEGRATION_ID ACTIVITY_ID +```bash +upsun integration:delete INTEGRATION_ID --project PROJECT_ID ``` -## Maintenance +### Replacing an integration installed by hand -### Update the Script +The action does not adopt an integration that you installed by hand. To replace it, delete it with the command above and run the workflow. -```bash -# After editing activity-script.js +### GitHub deployments stop updating + +The token in `GH_DEPLOY_TOKEN` has probably expired. The workflow does not find this error, because the workflow does not call GitHub with the token. Create a new token, update the secret, and run the workflow by hand. -# 1. Validate the changes -npm run activity-script:lint +### Reading the log of the script -# 2. Update the integration -upsun integration:update \ - --file .platform/activity-scripts/upsun-github-env-sync/activity-script.js \ - INTEGRATION_ID +```bash +upsun integration:activities INTEGRATION_ID --project PROJECT_ID +upsun integration:activity:log INTEGRATION_ID ACTIVITY_ID --project PROJECT_ID ``` -### Disable the integration +## Development + +The Node version is in `.nvmrc`. ```bash -upsun integration:delete INTEGRATION_ID +nvm use +npm ci +npm run check ``` +`npm run check` runs Prettier, the TypeScript check, and the tests. `npm run format` corrects the formatting. + +## Release + +Commit messages must follow [Conventional Commits](https://www.conventionalcommits.org/). CI lints every commit in a pull request. To lint your commits before you push, run `npm run lint:commits`. + +On each push to `main`, semantic-release reads the new commits. A `fix` commit makes a patch release. A `feat` commit makes a minor release. A commit with `!` after the type, or a `BREAKING CHANGE` footer, makes a major release. Other types make no release. + +A release creates a Git tag and a GitHub release, updates `CHANGELOG.md` and `package.json`, and moves the major tag. The package is not published to npm or another registry. Consumers use the action from the Git tag. + +The release commit is pushed to `main` with the secret `GH_RELEASE_TOKEN`. It holds a personal access token with `Contents: Read and write` on this repository, from an account in the bypass list of the ruleset on `main`. + ## References -- [Upsun Activity Scripts Documentation](https://docs.upsun.com/integrations/activity.html) -- [Upsun Activity Reference](https://docs.upsun.com/integrations/activity/reference.html) +- [Upsun activity scripts](https://developer.upsun.com/docs/integrations/activity) +- [Upsun activity reference](https://developer.upsun.com/docs/integrations/activity/reference) - [GitHub Deployments API](https://docs.github.com/en/rest/deployments/deployments) From 8056cd9da7a4e351a2a33c4b933d241ef39bc001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Garn=C3=A6s?= Date: Mon, 14 Sep 2026 16:43:55 +0200 Subject: [PATCH 11/11] test: build the failing deployment push activity with commits Since activities lost their default commits, this test took the environment-name fallback for the ref. Use the push helper like the other push tests so it covers a failing POST after a successful head-commit lookup. Assisted-by: Claude --- activity-script.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/activity-script.test.js b/activity-script.test.js index 3921815..f89d2bd 100644 --- a/activity-script.test.js +++ b/activity-script.test.js @@ -946,9 +946,8 @@ test("throws when latest deployment lookup fails", () => { test("throws when creating a deployment fails", () => { const deployments = []; const createDeploymentStatus = 500; - const activity = createActivity({ + const activity = createPushActivity({ id: "act-13", - type: "environment.push", state: "pending", }); const run = () =>