diff --git a/.github/workflows/skill-release.yml b/.github/workflows/skill-release.yml index 3c325e1..c6c0942 100644 --- a/.github/workflows/skill-release.yml +++ b/.github/workflows/skill-release.yml @@ -1685,6 +1685,10 @@ jobs: contents: read env: CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }} + AWS_REGION: eu-north-1 steps: - name: Check if publishable if: needs.release-tag.outputs.publish_clawhub != 'true' @@ -1704,52 +1708,12 @@ jobs: - name: Install clawhub CLI if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != '' - run: | - npm ci --prefix .github/clawhub-cli - echo "${GITHUB_WORKSPACE}/.github/clawhub-cli/node_modules/.bin" >> "$GITHUB_PATH" + run: bash scripts/ci/install_clawhub_cli.sh - name: Patch clawhub publish payload workaround # Temporary: clawhub@0.7.0 publish payload is missing acceptLicenseTerms. if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != '' - run: | - node <<'NODE' - const fs = require("node:fs"); - const path = require("node:path"); - - const npmRoot = path.join(process.env.GITHUB_WORKSPACE, ".github", "clawhub-cli", "node_modules"); - const publishScriptPath = path.join( - npmRoot, - "clawhub", - "dist", - "cli", - "commands", - "publish.js" - ); - - if (!fs.existsSync(publishScriptPath)) { - throw new Error(`clawhub publish script not found: ${publishScriptPath}`); - } - - const original = fs.readFileSync(publishScriptPath, "utf8"); - if (original.includes("acceptLicenseTerms: true")) { - console.log(`[patch-clawhub] Already patched: ${publishScriptPath}`); - process.exit(0); - } - - const payloadPattern = /changelog,\r?\n(\s*)tags,/; - if (!payloadPattern.test(original)) { - throw new Error( - `[patch-clawhub] Could not find expected publish payload pattern in ${publishScriptPath}` - ); - } - - const patched = original.replace( - payloadPattern, - (_, indent) => `changelog,\n${indent}acceptLicenseTerms: true,\n${indent}tags,` - ); - fs.writeFileSync(publishScriptPath, patched, "utf8"); - console.log(`[patch-clawhub] Patched: ${publishScriptPath}`); - NODE + run: node scripts/ci/patch_clawhub_publish_payload.mjs - name: Login to ClawHub if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != '' @@ -1831,6 +1795,10 @@ jobs: contents: read env: CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }} + AWS_REGION: eu-north-1 steps: - name: Parse tag id: parse @@ -1895,51 +1863,11 @@ jobs: run: node scripts/ci/validate_skill_install_docs.mjs --skills "${{ steps.parse.outputs.skill_path }}" - name: Install clawhub CLI - run: | - npm ci --prefix .github/clawhub-cli - echo "${GITHUB_WORKSPACE}/.github/clawhub-cli/node_modules/.bin" >> "$GITHUB_PATH" + run: bash scripts/ci/install_clawhub_cli.sh - name: Patch clawhub publish payload workaround # Temporary: clawhub@0.7.0 publish payload is missing acceptLicenseTerms. - run: | - node <<'NODE' - const fs = require("node:fs"); - const path = require("node:path"); - - const npmRoot = path.join(process.env.GITHUB_WORKSPACE, ".github", "clawhub-cli", "node_modules"); - const publishScriptPath = path.join( - npmRoot, - "clawhub", - "dist", - "cli", - "commands", - "publish.js" - ); - - if (!fs.existsSync(publishScriptPath)) { - throw new Error(`clawhub publish script not found: ${publishScriptPath}`); - } - - const original = fs.readFileSync(publishScriptPath, "utf8"); - if (original.includes("acceptLicenseTerms: true")) { - console.log(`[patch-clawhub] Already patched: ${publishScriptPath}`); - process.exit(0); - } - - const payloadPattern = /changelog,\r?\n(\s*)tags,/; - if (!payloadPattern.test(original)) { - throw new Error( - `[patch-clawhub] Could not find expected publish payload pattern in ${publishScriptPath}` - ); - } - - const patched = original.replace( - payloadPattern, - (_, indent) => `changelog,\n${indent}acceptLicenseTerms: true,\n${indent}tags,` - ); - fs.writeFileSync(publishScriptPath, patched, "utf8"); - console.log(`[patch-clawhub] Patched: ${publishScriptPath}`); - NODE + run: node scripts/ci/patch_clawhub_publish_payload.mjs - name: Login to ClawHub run: | diff --git a/scripts/ci/install_clawhub_cli.sh b/scripts/ci/install_clawhub_cli.sh new file mode 100644 index 0000000..d656083 --- /dev/null +++ b/scripts/ci/install_clawhub_cli.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +CLI_PREFIX="${CLAWHUB_CLI_PREFIX:-.github/clawhub-cli}" +CODEARTIFACT_DOMAIN="${CODEARTIFACT_DOMAIN:-prompt-security}" +CODEARTIFACT_DOMAIN_OWNER="${CODEARTIFACT_DOMAIN_OWNER:-443370709039}" +CODEARTIFACT_REPOSITORY="${CODEARTIFACT_REPOSITORY:-npm-proxy}" +AWS_REGION="${AWS_REGION:-${AWS_DEFAULT_REGION:-eu-north-1}}" + +if ! command -v aws >/dev/null 2>&1; then + echo "::error::aws CLI is required to authenticate npm against CodeArtifact" + exit 1 +fi + +if ! aws sts get-caller-identity >/dev/null 2>&1; then + echo "::error::AWS credentials are required before installing the CodeArtifact-pinned clawhub CLI" + exit 1 +fi + +aws codeartifact login \ + --tool npm \ + --domain "$CODEARTIFACT_DOMAIN" \ + --domain-owner "$CODEARTIFACT_DOMAIN_OWNER" \ + --repository "$CODEARTIFACT_REPOSITORY" \ + --region "$AWS_REGION" + +npm ci --prefix "$CLI_PREFIX" + +if [ -n "${GITHUB_PATH:-}" ]; then + workspace="${GITHUB_WORKSPACE:-$(pwd)}" + echo "${workspace}/${CLI_PREFIX}/node_modules/.bin" >> "$GITHUB_PATH" +fi diff --git a/scripts/ci/patch_clawhub_publish_payload.mjs b/scripts/ci/patch_clawhub_publish_payload.mjs new file mode 100644 index 0000000..ee962c8 --- /dev/null +++ b/scripts/ci/patch_clawhub_publish_payload.mjs @@ -0,0 +1,35 @@ +import fs from "node:fs"; +import path from "node:path"; + +const workspace = process.env.GITHUB_WORKSPACE || process.cwd(); +const npmRoot = path.join(workspace, ".github", "clawhub-cli", "node_modules"); +const publishScriptPath = path.join( + npmRoot, + "clawhub", + "dist", + "cli", + "commands", + "publish.js", +); + +if (!fs.existsSync(publishScriptPath)) { + throw new Error(`clawhub publish script not found: ${publishScriptPath}`); +} + +const original = fs.readFileSync(publishScriptPath, "utf8"); +if (original.includes("acceptLicenseTerms: true")) { + console.log(`[patch-clawhub] Already patched: ${publishScriptPath}`); + process.exit(0); +} + +const payloadPattern = /changelog,\r?\n(\s*)tags,/; +if (!payloadPattern.test(original)) { + throw new Error(`[patch-clawhub] Could not find expected publish payload pattern in ${publishScriptPath}`); +} + +const patched = original.replace( + payloadPattern, + (_, indent) => `changelog,\n${indent}acceptLicenseTerms: true,\n${indent}tags,`, +); +fs.writeFileSync(publishScriptPath, patched, "utf8"); +console.log(`[patch-clawhub] Patched: ${publishScriptPath}`); diff --git a/scripts/test-skill-release-workflow.mjs b/scripts/test-skill-release-workflow.mjs index 7b105bc..636f368 100644 --- a/scripts/test-skill-release-workflow.mjs +++ b/scripts/test-skill-release-workflow.mjs @@ -3,8 +3,12 @@ import { readFile } from 'node:fs/promises'; const workflowPath = new URL('../.github/workflows/skill-release.yml', import.meta.url); const ciWorkflowPath = new URL('../.github/workflows/ci.yml', import.meta.url); +const installClawhubCliPath = new URL('./ci/install_clawhub_cli.sh', import.meta.url); +const patchClawhubPayloadPath = new URL('./ci/patch_clawhub_publish_payload.mjs', import.meta.url); const workflow = await readFile(workflowPath, 'utf8'); const ciWorkflow = await readFile(ciWorkflowPath, 'utf8'); +const installClawhubCli = await readFile(installClawhubCliPath, 'utf8'); +const patchClawhubPayload = await readFile(patchClawhubPayloadPath, 'utf8'); assert.match( workflow, @@ -315,6 +319,68 @@ assert.match( 'ClawHub publish must use the resolved ClawHub slug', ); +assert.equal( + workflow.match(/bash scripts\/ci\/install_clawhub_cli\.sh/g)?.length, + 2, + 'ClawHub publish and republish jobs must share the same pinned CLI installer', +); + +assert.equal( + workflow.match(/node scripts\/ci\/patch_clawhub_publish_payload\.mjs/g)?.length, + 2, + 'ClawHub publish and republish jobs must share the same payload patch helper', +); + +assert.doesNotMatch( + workflow, + /npm ci --prefix \.github\/clawhub-cli/, + 'ClawHub CLI installation must not be duplicated inline in the workflow', +); + +assert.doesNotMatch( + workflow, + /node <<'NODE'[\s\S]*acceptLicenseTerms: true/, + 'ClawHub payload patching must not be duplicated inline in the workflow', +); + +for (const secret of ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN']) { + assert.match( + workflow, + new RegExp(`${secret}: \\$\\{\\{ secrets\\.${secret} \\}\\}`), + `ClawHub jobs must expose ${secret} for CodeArtifact npm authentication`, + ); +} + +assert.match( + installClawhubCli, + /aws codeartifact login[\s\S]*--domain "\$CODEARTIFACT_DOMAIN"[\s\S]*--domain-owner "\$CODEARTIFACT_DOMAIN_OWNER"[\s\S]*--repository "\$CODEARTIFACT_REPOSITORY"[\s\S]*--region "\$AWS_REGION"/, + 'ClawHub CLI installer must authenticate npm against CodeArtifact before npm ci', +); + +assert.match( + installClawhubCli, + /npm ci --prefix "\$CLI_PREFIX"/, + 'ClawHub CLI installer must install from the committed lockfile prefix', +); + +assert.match( + installClawhubCli, + /"\$\{workspace\}\/\$\{CLI_PREFIX\}\/node_modules\/\.bin" >> "\$GITHUB_PATH"/, + 'ClawHub CLI installer must expose the pinned clawhub binary on GITHUB_PATH', +); + +assert.match( + patchClawhubPayload, + /const payloadPattern = \/changelog,\\r\?\\n\(\\s\*\)tags,\/;/, + 'ClawHub payload patch helper must target the expected publish payload shape', +); + +assert.match( + patchClawhubPayload, + /acceptLicenseTerms: true/, + 'ClawHub payload patch helper must preserve the acceptLicenseTerms workaround', +); + assert.doesNotMatch( workflow, /clawhub inspect "\$SKILL_NAME" --version "\$VERSION" --json/,