From eff7b5b0cb8821734fcedbbb63fe9fbe6a8565aa Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 10 Oct 2026 06:13:01 +0000 Subject: [PATCH 1/8] ci: harden Codecov uploads and add release smoke tests - Fail CI when Codecov upload errors if CODECOV_TOKEN is present; skip uploads gracefully when the secret is unavailable (fork/Dependabot). - Split release into snapshot build, cross-platform binary/npm smoke tests, then GoReleaser publish and npm publish. - Add release dry-run workflow (actionlint + goreleaser snapshot) for PRs. - Document release checks in docs/dev.md. Co-authored-by: x_zhuo --- .github/workflows/ci.yml | 36 +++- .github/workflows/release-dry-run.yml | 72 ++++++++ .github/workflows/release-smoke-reusable.yml | 76 ++++++++ .github/workflows/release.yml | 50 +++++- docs/dev.md | 23 ++- scripts/npm-publish.js | 15 +- scripts/release-artifacts.js | 10 ++ scripts/release-smoke.js | 172 +++++++++++++++++++ scripts/release-smoke.test.js | 29 ++++ 9 files changed, 466 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/release-dry-run.yml create mode 100644 .github/workflows/release-smoke-reusable.yml create mode 100644 scripts/release-artifacts.js create mode 100644 scripts/release-smoke.js create mode 100644 scripts/release-smoke.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d79d73f..5d77f197 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,23 +55,37 @@ jobs: shell: bash run: gotestsum --junitfile test-results.xml -- -v -race -coverprofile=coverage.txt -covermode=atomic ./... + - name: Detect Codecov token + id: codecov_token + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + if [ -n "$CODECOV_TOKEN" ]; then + echo "present=true" >> "$GITHUB_OUTPUT" + else + echo "present=false" >> "$GITHUB_OUTPUT" + echo "Skipping Codecov upload: CODECOV_TOKEN is not available (e.g. fork or Dependabot PR)." + fi + - name: Upload test results to Codecov + if: steps.codecov_token.outputs.present == 'true' uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: token: ${{ secrets.CODECOV_TOKEN }} files: test-results.xml flags: unittests report_type: test_results - fail_ci_if_error: false + fail_ci_if_error: true - name: Upload coverage to Codecov + if: steps.codecov_token.outputs.present == 'true' uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.txt flags: unittests name: codecov-${{ matrix.os }} - fail_ci_if_error: false + fail_ci_if_error: true verbose: true integration: @@ -150,23 +164,37 @@ jobs: XSQL_TEST_PG_DSN: "postgres://postgres:postgres@127.0.0.1:5432/testdb?sslmode=disable" run: gotestsum --junitfile e2e-test-results.xml -- -v -tags=e2e -coverprofile=coverage-e2e.txt -covermode=atomic -coverpkg=github.com/zx06/xsql/internal/... ./tests/e2e/... + - name: Detect Codecov token + id: codecov_token + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + if [ -n "$CODECOV_TOKEN" ]; then + echo "present=true" >> "$GITHUB_OUTPUT" + else + echo "present=false" >> "$GITHUB_OUTPUT" + echo "Skipping Codecov upload: CODECOV_TOKEN is not available (e.g. fork or Dependabot PR)." + fi + - name: Upload integration test results to Codecov + if: steps.codecov_token.outputs.present == 'true' uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: token: ${{ secrets.CODECOV_TOKEN }} files: integration-test-results.xml,e2e-test-results.xml flags: integration,e2e report_type: test_results - fail_ci_if_error: false + fail_ci_if_error: true - name: Upload integration coverage to Codecov + if: steps.codecov_token.outputs.present == 'true' uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage-integration.txt,coverage-e2e.txt flags: integration,e2e name: codecov-integration - fail_ci_if_error: false + fail_ci_if_error: true verbose: true disable_search: true diff --git a/.github/workflows/release-dry-run.yml b/.github/workflows/release-dry-run.yml new file mode 100644 index 00000000..239b3197 --- /dev/null +++ b/.github/workflows/release-dry-run.yml @@ -0,0 +1,72 @@ +name: Release dry run + +on: + pull_request: + paths: + - ".github/workflows/release*.yml" + - ".goreleaser.yaml" + - "npm/**" + - "scripts/npm-publish.js" + - "scripts/release-smoke.js" + - "scripts/release-artifacts.js" + workflow_dispatch: + +permissions: + contents: read + +jobs: + actionlint: + name: actionlint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Run actionlint + run: | + bash <(curl -sSfL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) + ./actionlint -color + + build: + name: GoReleaser snapshot build + runs-on: ubuntu-latest + needs: actionlint + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + with: + go-version: "1.25" + cache: true + + - name: Set up Node.js + uses: actions/setup-node@v6.4.0 + with: + node-version: "24" + cache: "npm" + cache-dependency-path: webui/package-lock.json + + - name: GoReleaser snapshot build + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + with: + distribution: goreleaser + version: "~> v2" + args: build --snapshot --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload dist artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: goreleaser-dist + path: dist/ + retention-days: 3 + + smoke: + name: Release smoke tests + needs: build + uses: ./.github/workflows/release-smoke-reusable.yml + with: + dist-artifact-name: goreleaser-dist diff --git a/.github/workflows/release-smoke-reusable.yml b/.github/workflows/release-smoke-reusable.yml new file mode 100644 index 00000000..de514437 --- /dev/null +++ b/.github/workflows/release-smoke-reusable.yml @@ -0,0 +1,76 @@ +name: Release smoke (reusable) + +on: + workflow_call: + inputs: + dist-artifact-name: + description: Artifact name containing the GoReleaser dist/ directory + required: true + type: string + +jobs: + smoke-binary: + name: Binary smoke (${{ matrix.label }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - label: linux-amd64 + runner: ubuntu-latest + goos: linux + goarch: amd64 + - label: linux-arm64 + runner: ubuntu-24.04-arm + goos: linux + goarch: arm64 + - label: darwin-arm64 + runner: macos-latest + goos: darwin + goarch: arm64 + - label: windows-amd64 + runner: windows-latest + goos: windows + goarch: amd64 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up Node.js + uses: actions/setup-node@v6.4.0 + with: + node-version: "24" + + - name: Download GoReleaser dist + uses: actions/download-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: ${{ inputs.dist-artifact-name }} + path: dist + + - name: Smoke test binary + shell: bash + run: node scripts/release-smoke.js binary dist ${{ matrix.goos }} ${{ matrix.goarch }} + + smoke-npm: + name: npm smoke (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up Node.js + uses: actions/setup-node@v6.4.0 + with: + node-version: "24" + + - name: Download GoReleaser dist + uses: actions/download-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: ${{ inputs.dist-artifact-name }} + path: dist + + - name: Smoke test npm package + shell: bash + run: node scripts/release-smoke.js npm dist diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe1dfb3d..d5ec8903 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,8 +6,56 @@ on: - "v*" jobs: + build: + name: GoReleaser snapshot build + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + with: + go-version: "1.25" + cache: true + + - name: Set up Node.js + uses: actions/setup-node@v6.4.0 + with: + node-version: "24" + cache: "npm" + cache-dependency-path: webui/package-lock.json + + - name: GoReleaser snapshot build + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + with: + distribution: goreleaser + version: "~> v2" + args: build --snapshot --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload dist artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: goreleaser-dist + path: dist/ + retention-days: 7 + + smoke: + name: Release smoke tests + needs: build + uses: ./.github/workflows/release-smoke-reusable.yml + with: + dist-artifact-name: goreleaser-dist + release: - name: Release + name: Publish release + needs: smoke runs-on: ubuntu-latest permissions: contents: write diff --git a/docs/dev.md b/docs/dev.md index da441ec0..56e0012e 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -140,6 +140,27 @@ docs/ # 文档 - 单元测试(所有平台) - 集成测试(MySQL 8.0 + PostgreSQL 16) - 代码检查(golangci-lint) -- 代码覆盖率上报 +- 代码覆盖率上报(仓库内 PR / `main` 推送在配置了 `CODECOV_TOKEN` 时上传;fork / Dependabot PR 无 token 时会跳过上传,且不会因 Codecov 失败而阻塞) 详见 `.github/workflows/ci.yml`。 + +### 发布与发布前检查 + +正式发布由 tag 推送触发(`.github/workflows/release.yml`),顺序为: + +1. **GoReleaser snapshot 构建**(`goreleaser build --snapshot`),产出各平台二进制到 `dist/` 并上传 artifact。 +2. **冒烟测试**(`.github/workflows/release-smoke-reusable.yml`): + - 二进制:在 linux/amd64、linux/arm64、darwin/arm64、windows/amd64 上运行 `xsql version`、`xsql spec`、`xsql profile list`(脚本:`scripts/release-smoke.js binary`)。 + - npm:在 Linux / macOS / Windows 上从 `dist/` 填充平台包、`npm pack` 后本地安装 tarball,执行 `npx xsql version` 与 `npx xsql spec`(`scripts/release-smoke.js npm`)。 +3. **GoReleaser release**(`goreleaser release --clean`)——仅在上一步全部通过后创建 GitHub Release 并推送 Homebrew/Scoop 等。 +4. **npm 发布**(`scripts/npm-publish.js`)——从 GitHub Release 下载二进制后发布 `xsql-cli` 与各 `@zx06/xsql-cli-*` 平台包。 + +修改发布相关文件时,PR 会触发 **Release dry run**(`.github/workflows/release-dry-run.yml`):`actionlint` 校验 workflow、snapshot 构建与上述冒烟测试(不发布)。也可在 Actions 中手动 **workflow_dispatch** 该 workflow。 + +本地可复现 snapshot + 冒烟: + +```bash +goreleaser build --snapshot --clean +node scripts/release-smoke.js binary dist linux amd64 +node scripts/release-smoke.js npm dist +``` diff --git a/scripts/npm-publish.js b/scripts/npm-publish.js index 10d906f7..5b08b0ef 100644 --- a/scripts/npm-publish.js +++ b/scripts/npm-publish.js @@ -7,19 +7,12 @@ const https = require("https"); const http = require("http"); const { createWriteStream, mkdirSync } = require("fs"); +const { PLATFORM_MAP } = require("./release-artifacts"); + const REPO = "zx06/xsql"; const NPM_DIR = path.join(__dirname, "..", "npm"); const NPM_SCOPE = "@zx06"; -const PLATFORM_MAP = [ - { goos: "linux", goarch: "amd64", npm: "linux-x64", ext: "", archive: "tar.gz" }, - { goos: "linux", goarch: "arm64", npm: "linux-arm64", ext: "", archive: "tar.gz" }, - { goos: "darwin", goarch: "amd64", npm: "darwin-x64", ext: "", archive: "tar.gz" }, - { goos: "darwin", goarch: "arm64", npm: "darwin-arm64", ext: "", archive: "tar.gz" }, - { goos: "windows", goarch: "amd64", npm: "win32-x64", ext: ".exe", archive: "zip" }, - { goos: "windows", goarch: "arm64", npm: "win32-arm64", ext: ".exe", archive: "zip" }, -]; - function run(cmd, opts = {}) { console.log(`$ ${cmd}`); return execSync(cmd, { stdio: "inherit", ...opts }); @@ -178,5 +171,5 @@ module.exports = { buildPublishCommand, getPrereleaseTag, }; - - + + diff --git a/scripts/release-artifacts.js b/scripts/release-artifacts.js new file mode 100644 index 00000000..35201d2f --- /dev/null +++ b/scripts/release-artifacts.js @@ -0,0 +1,10 @@ +const PLATFORM_MAP = [ + { goos: "linux", goarch: "amd64", npm: "linux-x64", ext: "", archive: "tar.gz" }, + { goos: "linux", goarch: "arm64", npm: "linux-arm64", ext: "", archive: "tar.gz" }, + { goos: "darwin", goarch: "amd64", npm: "darwin-x64", ext: "", archive: "tar.gz" }, + { goos: "darwin", goarch: "arm64", npm: "darwin-arm64", ext: "", archive: "tar.gz" }, + { goos: "windows", goarch: "amd64", npm: "win32-x64", ext: ".exe", archive: "zip" }, + { goos: "windows", goarch: "arm64", npm: "win32-arm64", ext: ".exe", archive: "zip" }, +]; + +module.exports = { PLATFORM_MAP }; diff --git a/scripts/release-smoke.js b/scripts/release-smoke.js new file mode 100644 index 00000000..5d64ca87 --- /dev/null +++ b/scripts/release-smoke.js @@ -0,0 +1,172 @@ +#!/usr/bin/env node + +const { execFileSync } = require("child_process"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); + +const { PLATFORM_MAP } = require("./release-artifacts"); + +function readJSON(filePath) { + return JSON.parse(fs.readFileSync(filePath, "utf8")); +} + +function findBinaryArtifact(distDir, goos, goarch) { + const artifactsPath = path.join(distDir, "artifacts.json"); + if (!fs.existsSync(artifactsPath)) { + throw new Error(`missing ${artifactsPath}; run GoReleaser build first`); + } + const artifacts = readJSON(artifactsPath); + const match = artifacts.find( + (item) => + item.type === "Binary" && + item.goos === goos && + item.goarch === goarch && + item.path, + ); + if (!match) { + throw new Error(`no binary artifact for ${goos}/${goarch} in ${artifactsPath}`); + } + const relative = match.path.replace(/^dist[\\/]/, ""); + const binaryPath = path.isAbsolute(match.path) + ? match.path + : path.join(distDir, relative); + if (!fs.existsSync(binaryPath)) { + throw new Error(`binary not found at ${binaryPath}`); + } + return binaryPath; +} + +function runBinarySmoke(binaryPath) { + const commands = [ + ["version"], + ["spec", "--format", "json"], + ["profile", "list", "--format", "json"], + ]; + for (const args of commands) { + console.log(`> ${binaryPath} ${args.join(" ")}`); + execFileSync(binaryPath, args, { stdio: "inherit", env: process.env }); + } +} + +function copyBinaryToNpm(distDir, version) { + const repoRoot = path.join(__dirname, ".."); + const npmDir = path.join(repoRoot, "npm"); + for (const platform of PLATFORM_MAP) { + const src = findBinaryArtifact(distDir, platform.goos, platform.goarch); + const binDir = path.join(npmDir, platform.npm, "bin"); + const dest = path.join(binDir, `xsql${platform.ext}`); + fs.mkdirSync(binDir, { recursive: true }); + fs.copyFileSync(src, dest); + if (platform.ext === "") { + fs.chmodSync(dest, 0o755); + } + } + updateNpmVersions(npmDir, version); +} + +function updateNpmVersions(npmDir, version) { + const mainPkg = path.join(npmDir, "xsql", "package.json"); + const main = readJSON(mainPkg); + main.version = version; + if (main.optionalDependencies) { + for (const dep of Object.keys(main.optionalDependencies)) { + main.optionalDependencies[dep] = version; + } + } + fs.writeFileSync(mainPkg, JSON.stringify(main, null, 2) + "\n"); + for (const platform of PLATFORM_MAP) { + const pkgPath = path.join(npmDir, platform.npm, "package.json"); + const pkg = readJSON(pkgPath); + pkg.version = version; + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n"); + } +} + +function resolveVersion(distDir) { + const metadataPath = path.join(distDir, "metadata.json"); + if (fs.existsSync(metadataPath)) { + return readJSON(metadataPath).version; + } + return "0.0.0-smoke"; +} + +function packTarball(cwd) { + const output = execFileSync("npm", ["pack", "--json"], { + cwd, + encoding: "utf8", + }); + const parsed = JSON.parse(output); + const entry = Array.isArray(parsed) ? parsed[0] : parsed; + return path.resolve(cwd, entry.filename); +} + +function smokeNpmFromDist(distDir) { + const repoRoot = path.join(__dirname, ".."); + const npmDir = path.join(repoRoot, "npm"); + const version = resolveVersion(distDir); + copyBinaryToNpm(distDir, version); + + const key = `${os.platform()}-${os.arch()}`; + const platform = PLATFORM_MAP.find((p) => p.npm === key); + if (!platform) { + throw new Error(`unsupported npm smoke platform: ${key}`); + } + + const platformDir = path.join(npmDir, platform.npm); + const mainDir = path.join(npmDir, "xsql"); + const platformTgz = packTarball(platformDir); + const mainTgz = packTarball(mainDir); + + const workDir = fs.mkdtempSync(path.join(os.tmpdir(), "xsql-npm-smoke-")); + console.log(`npm smoke workdir: ${workDir}`); + execFileSync("npm", ["init", "-y"], { cwd: workDir, stdio: "inherit" }); + execFileSync("npm", ["install", platformTgz, mainTgz], { + cwd: workDir, + stdio: "inherit", + }); + execFileSync("npx", ["xsql", "version"], { cwd: workDir, stdio: "inherit" }); + execFileSync("npx", ["xsql", "spec", "--format", "json"], { + cwd: workDir, + stdio: "inherit", + }); +} + +function main() { + const [mode, ...rest] = process.argv.slice(2); + const distDir = path.resolve(rest[0] || "dist"); + + if (mode === "binary") { + const [goos, goarch] = rest.slice(1); + if (!goos || !goarch) { + console.error("Usage: node scripts/release-smoke.js binary "); + process.exit(1); + } + runBinarySmoke(findBinaryArtifact(distDir, goos, goarch)); + return; + } + + if (mode === "npm") { + smokeNpmFromDist(distDir); + return; + } + + console.error("Usage:"); + console.error(" node scripts/release-smoke.js binary [distDir] "); + console.error(" node scripts/release-smoke.js npm [distDir]"); + process.exit(1); +} + +if (require.main === module) { + try { + main(); + } catch (err) { + console.error(err.message || err); + process.exit(1); + } +} + +module.exports = { + findBinaryArtifact, + resolveVersion, +}; diff --git a/scripts/release-smoke.test.js b/scripts/release-smoke.test.js new file mode 100644 index 00000000..ae95ce04 --- /dev/null +++ b/scripts/release-smoke.test.js @@ -0,0 +1,29 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const test = require("node:test"); + +const { findBinaryArtifact, resolveVersion } = require("./release-smoke"); + +test("findBinaryArtifact resolves binary path from artifacts.json", () => { + const distDir = fs.mkdtempSync(path.join(os.tmpdir(), "xsql-dist-")); + const binaryRel = path.join("dist", "xsql_linux_amd64_v1", "xsql"); + const binaryAbs = path.join(distDir, "xsql_linux_amd64_v1", "xsql"); + fs.mkdirSync(path.dirname(binaryAbs), { recursive: true }); + fs.writeFileSync(binaryAbs, ""); + fs.writeFileSync( + path.join(distDir, "artifacts.json"), + JSON.stringify([ + { + type: "Binary", + goos: "linux", + goarch: "amd64", + path: binaryRel, + }, + ]), + ); + + assert.equal(findBinaryArtifact(distDir, "linux", "amd64"), binaryAbs); + assert.equal(resolveVersion(distDir), "0.0.0-smoke"); +}); From 0703d8bcabc8ecfbf4c2b335ab6fc74558711527 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 10 Oct 2026 06:18:01 +0000 Subject: [PATCH 2/8] fix(ci): use download-artifact v8 and stabilize Codecov CLI verify Co-authored-by: x_zhuo --- .github/workflows/ci.yml | 4 ++++ .github/workflows/release-smoke-reusable.yml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d77f197..feb065b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,6 +76,7 @@ jobs: flags: unittests report_type: test_results fail_ci_if_error: true + skip_validation: true - name: Upload coverage to Codecov if: steps.codecov_token.outputs.present == 'true' @@ -86,6 +87,7 @@ jobs: flags: unittests name: codecov-${{ matrix.os }} fail_ci_if_error: true + skip_validation: true verbose: true integration: @@ -185,6 +187,7 @@ jobs: flags: integration,e2e report_type: test_results fail_ci_if_error: true + skip_validation: true - name: Upload integration coverage to Codecov if: steps.codecov_token.outputs.present == 'true' @@ -195,6 +198,7 @@ jobs: flags: integration,e2e name: codecov-integration fail_ci_if_error: true + skip_validation: true verbose: true disable_search: true diff --git a/.github/workflows/release-smoke-reusable.yml b/.github/workflows/release-smoke-reusable.yml index de514437..2cc4f210 100644 --- a/.github/workflows/release-smoke-reusable.yml +++ b/.github/workflows/release-smoke-reusable.yml @@ -41,7 +41,7 @@ jobs: node-version: "24" - name: Download GoReleaser dist - uses: actions/download-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: ${{ inputs.dist-artifact-name }} path: dist @@ -66,7 +66,7 @@ jobs: node-version: "24" - name: Download GoReleaser dist - uses: actions/download-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: ${{ inputs.dist-artifact-name }} path: dist From d0950a7121b7730f28fe7e805d780b13493df03a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 10 Oct 2026 06:22:38 +0000 Subject: [PATCH 3/8] fix(ci): align artifact download v4 and Windows npm smoke Co-authored-by: x_zhuo --- .github/workflows/ci.yml | 1 + .github/workflows/release-smoke-reusable.yml | 4 +- scripts/release-smoke.js | 40 +++++++++++++------- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index feb065b4..9887dcae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,6 +57,7 @@ jobs: - name: Detect Codecov token id: codecov_token + shell: bash env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} run: | diff --git a/.github/workflows/release-smoke-reusable.yml b/.github/workflows/release-smoke-reusable.yml index 2cc4f210..a1190742 100644 --- a/.github/workflows/release-smoke-reusable.yml +++ b/.github/workflows/release-smoke-reusable.yml @@ -41,7 +41,7 @@ jobs: node-version: "24" - name: Download GoReleaser dist - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: ${{ inputs.dist-artifact-name }} path: dist @@ -66,7 +66,7 @@ jobs: node-version: "24" - name: Download GoReleaser dist - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: ${{ inputs.dist-artifact-name }} path: dist diff --git a/scripts/release-smoke.js b/scripts/release-smoke.js index 5d64ca87..b6c9f5bb 100644 --- a/scripts/release-smoke.js +++ b/scripts/release-smoke.js @@ -91,11 +91,29 @@ function resolveVersion(distDir) { return "0.0.0-smoke"; } +function runNpm(args, cwd) { + if (process.platform === "win32") { + return execFileSync("npm.cmd", args, { cwd, stdio: "inherit" }); + } + return execFileSync("npm", args, { cwd, stdio: "inherit" }); +} + +function runNpx(args, cwd) { + if (process.platform === "win32") { + return execFileSync("npx.cmd", args, { cwd, stdio: "inherit" }); + } + return execFileSync("npx", args, { cwd, stdio: "inherit" }); +} + function packTarball(cwd) { - const output = execFileSync("npm", ["pack", "--json"], { - cwd, - encoding: "utf8", - }); + const output = execFileSync( + process.platform === "win32" ? "npm.cmd" : "npm", + ["pack", "--json"], + { + cwd, + encoding: "utf8", + }, + ); const parsed = JSON.parse(output); const entry = Array.isArray(parsed) ? parsed[0] : parsed; return path.resolve(cwd, entry.filename); @@ -120,16 +138,10 @@ function smokeNpmFromDist(distDir) { const workDir = fs.mkdtempSync(path.join(os.tmpdir(), "xsql-npm-smoke-")); console.log(`npm smoke workdir: ${workDir}`); - execFileSync("npm", ["init", "-y"], { cwd: workDir, stdio: "inherit" }); - execFileSync("npm", ["install", platformTgz, mainTgz], { - cwd: workDir, - stdio: "inherit", - }); - execFileSync("npx", ["xsql", "version"], { cwd: workDir, stdio: "inherit" }); - execFileSync("npx", ["xsql", "spec", "--format", "json"], { - cwd: workDir, - stdio: "inherit", - }); + runNpm(["init", "-y"], workDir); + runNpm(["install", platformTgz, mainTgz], workDir); + runNpx(["xsql", "version"], workDir); + runNpx(["xsql", "spec", "--format", "json"], workDir); } function main() { From 7f3240391d6a4681075eb35aabc44c41201e3f97 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 10 Oct 2026 06:28:00 +0000 Subject: [PATCH 4/8] fix(release-smoke): restore executable bit after artifact download Co-authored-by: x_zhuo --- scripts/release-smoke.js | 43 ++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/scripts/release-smoke.js b/scripts/release-smoke.js index b6c9f5bb..68a65d5e 100644 --- a/scripts/release-smoke.js +++ b/scripts/release-smoke.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -const { execFileSync } = require("child_process"); +const { execFileSync, execSync } = require("child_process"); const fs = require("fs"); const os = require("os"); const path = require("path"); @@ -37,7 +37,15 @@ function findBinaryArtifact(distDir, goos, goarch) { return binaryPath; } +function ensureExecutable(binaryPath) { + if (process.platform === "win32") { + return; + } + fs.chmodSync(binaryPath, 0o755); +} + function runBinarySmoke(binaryPath) { + ensureExecutable(binaryPath); const commands = [ ["version"], ["spec", "--format", "json"], @@ -93,27 +101,36 @@ function resolveVersion(distDir) { function runNpm(args, cwd) { if (process.platform === "win32") { - return execFileSync("npm.cmd", args, { cwd, stdio: "inherit" }); + execSync(`npm ${args.map((a) => `"${a}"`).join(" ")}`, { + cwd, + stdio: "inherit", + shell: true, + }); + return; } - return execFileSync("npm", args, { cwd, stdio: "inherit" }); + execFileSync("npm", args, { cwd, stdio: "inherit" }); } function runNpx(args, cwd) { if (process.platform === "win32") { - return execFileSync("npx.cmd", args, { cwd, stdio: "inherit" }); + execSync(`npx ${args.map((a) => `"${a}"`).join(" ")}`, { + cwd, + stdio: "inherit", + shell: true, + }); + return; } - return execFileSync("npx", args, { cwd, stdio: "inherit" }); + execFileSync("npx", args, { cwd, stdio: "inherit" }); } function packTarball(cwd) { - const output = execFileSync( - process.platform === "win32" ? "npm.cmd" : "npm", - ["pack", "--json"], - { - cwd, - encoding: "utf8", - }, - ); + const output = + process.platform === "win32" + ? execSync("npm pack --json", { cwd, encoding: "utf8", shell: true }) + : execFileSync("npm", ["pack", "--json"], { + cwd, + encoding: "utf8", + }); const parsed = JSON.parse(output); const entry = Array.isArray(parsed) ? parsed[0] : parsed; return path.resolve(cwd, entry.filename); From 34b35a9f8ba0612b27238cd830729b3178cac78c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 10 Oct 2026 06:35:19 +0000 Subject: [PATCH 5/8] fix(security): address Sonar/CodeQL findings on release CI - Run actionlint via pinned go install instead of curl/bash. - Add least-privilege permissions (contents/actions) on release workflows. - Pin setup-node to full commit SHA in new/changed workflows. - Harden release-smoke.js: node: imports, platform allowlist, tight chmod, invoke npm/npx via absolute paths next to node. Co-authored-by: x_zhuo --- .github/workflows/release-dry-run.yml | 20 +++++- .github/workflows/release-smoke-reusable.yml | 19 +++++- .github/workflows/release.yml | 13 +++- scripts/release-smoke.js | 72 +++++++++++--------- scripts/release-smoke.test.js | 9 +++ 5 files changed, 91 insertions(+), 42 deletions(-) diff --git a/.github/workflows/release-dry-run.yml b/.github/workflows/release-dry-run.yml index 239b3197..068386ca 100644 --- a/.github/workflows/release-dry-run.yml +++ b/.github/workflows/release-dry-run.yml @@ -18,18 +18,29 @@ jobs: actionlint: name: actionlint runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Set up Go + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + with: + go-version: "1.25" + cache: false + - name: Run actionlint run: | - bash <(curl -sSfL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) - ./actionlint -color + go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.7 + "$(go env GOPATH)/bin/actionlint" -color build: name: GoReleaser snapshot build runs-on: ubuntu-latest needs: actionlint + permissions: + contents: read + actions: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -42,7 +53,7 @@ jobs: cache: true - name: Set up Node.js - uses: actions/setup-node@v6.4.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" cache: "npm" @@ -67,6 +78,9 @@ jobs: smoke: name: Release smoke tests needs: build + permissions: + contents: read + actions: read uses: ./.github/workflows/release-smoke-reusable.yml with: dist-artifact-name: goreleaser-dist diff --git a/.github/workflows/release-smoke-reusable.yml b/.github/workflows/release-smoke-reusable.yml index a1190742..4b971f78 100644 --- a/.github/workflows/release-smoke-reusable.yml +++ b/.github/workflows/release-smoke-reusable.yml @@ -8,10 +8,17 @@ on: required: true type: string +permissions: + contents: read + actions: read + jobs: smoke-binary: name: Binary smoke (${{ matrix.label }}) runs-on: ${{ matrix.runner }} + permissions: + contents: read + actions: read strategy: fail-fast: false matrix: @@ -36,7 +43,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Node.js - uses: actions/setup-node@v6.4.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" @@ -48,11 +55,17 @@ jobs: - name: Smoke test binary shell: bash - run: node scripts/release-smoke.js binary dist ${{ matrix.goos }} ${{ matrix.goarch }} + env: + SMOKE_GOOS: ${{ matrix.goos }} + SMOKE_GOARCH: ${{ matrix.goarch }} + run: node scripts/release-smoke.js binary dist "$SMOKE_GOOS" "$SMOKE_GOARCH" smoke-npm: name: npm smoke (${{ matrix.os }}) runs-on: ${{ matrix.os }} + permissions: + contents: read + actions: read strategy: fail-fast: false matrix: @@ -61,7 +74,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Node.js - uses: actions/setup-node@v6.4.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d5ec8903..310cbdf4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,12 +5,16 @@ on: tags: - "v*" +permissions: + contents: read + jobs: build: name: GoReleaser snapshot build runs-on: ubuntu-latest permissions: contents: read + actions: write steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -24,7 +28,7 @@ jobs: cache: true - name: Set up Node.js - uses: actions/setup-node@v6.4.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" cache: "npm" @@ -49,6 +53,9 @@ jobs: smoke: name: Release smoke tests needs: build + permissions: + contents: read + actions: read uses: ./.github/workflows/release-smoke-reusable.yml with: dist-artifact-name: goreleaser-dist @@ -72,7 +79,7 @@ jobs: cache: true - name: Set up Node.js - uses: actions/setup-node@v6.4.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" cache: "npm" @@ -100,7 +107,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Node.js - uses: actions/setup-node@v6.4.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "24" registry-url: "https://registry.npmjs.org" diff --git a/scripts/release-smoke.js b/scripts/release-smoke.js index 68a65d5e..da0bb391 100644 --- a/scripts/release-smoke.js +++ b/scripts/release-smoke.js @@ -1,17 +1,29 @@ #!/usr/bin/env node -const { execFileSync, execSync } = require("child_process"); -const fs = require("fs"); -const os = require("os"); -const path = require("path"); +const { execFileSync } = require("node:child_process"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); const { PLATFORM_MAP } = require("./release-artifacts"); +const ALLOWED_PLATFORMS = new Set( + PLATFORM_MAP.map((p) => `${p.goos}/${p.goarch}`), +); + function readJSON(filePath) { return JSON.parse(fs.readFileSync(filePath, "utf8")); } +function assertAllowedPlatform(goos, goarch) { + const key = `${goos}/${goarch}`; + if (!ALLOWED_PLATFORMS.has(key)) { + throw new Error(`unsupported smoke platform: ${key}`); + } +} + function findBinaryArtifact(distDir, goos, goarch) { + assertAllowedPlatform(goos, goarch); const artifactsPath = path.join(distDir, "artifacts.json"); if (!fs.existsSync(artifactsPath)) { throw new Error(`missing ${artifactsPath}; run GoReleaser build first`); @@ -37,15 +49,24 @@ function findBinaryArtifact(distDir, goos, goarch) { return binaryPath; } -function ensureExecutable(binaryPath) { +/** Owner/group/other read+execute only (no write for group/other). */ +function chmodExecutable(filePath) { if (process.platform === "win32") { return; } - fs.chmodSync(binaryPath, 0o755); + const stats = fs.statSync(filePath); + const withoutWrite = stats.mode & ~0o022; + const executable = withoutWrite | 0o111; + fs.chmodSync(filePath, executable & 0o755); +} + +function resolveNodeCliTool(toolName) { + const ext = process.platform === "win32" ? ".cmd" : ""; + return path.join(path.dirname(process.execPath), `${toolName}${ext}`); } function runBinarySmoke(binaryPath) { - ensureExecutable(binaryPath); + chmodExecutable(binaryPath); const commands = [ ["version"], ["spec", "--format", "json"], @@ -67,7 +88,7 @@ function copyBinaryToNpm(distDir, version) { fs.mkdirSync(binDir, { recursive: true }); fs.copyFileSync(src, dest); if (platform.ext === "") { - fs.chmodSync(dest, 0o755); + chmodExecutable(dest); } } updateNpmVersions(npmDir, version); @@ -100,37 +121,22 @@ function resolveVersion(distDir) { } function runNpm(args, cwd) { - if (process.platform === "win32") { - execSync(`npm ${args.map((a) => `"${a}"`).join(" ")}`, { - cwd, - stdio: "inherit", - shell: true, - }); - return; - } - execFileSync("npm", args, { cwd, stdio: "inherit" }); + execFileSync(resolveNodeCliTool("npm"), args, { cwd, stdio: "inherit" }); } function runNpx(args, cwd) { - if (process.platform === "win32") { - execSync(`npx ${args.map((a) => `"${a}"`).join(" ")}`, { - cwd, - stdio: "inherit", - shell: true, - }); - return; - } - execFileSync("npx", args, { cwd, stdio: "inherit" }); + execFileSync(resolveNodeCliTool("npx"), args, { cwd, stdio: "inherit" }); } function packTarball(cwd) { - const output = - process.platform === "win32" - ? execSync("npm pack --json", { cwd, encoding: "utf8", shell: true }) - : execFileSync("npm", ["pack", "--json"], { - cwd, - encoding: "utf8", - }); + const output = execFileSync(resolveNodeCliTool("npm"), ["pack", "--json"], { + cwd, + encoding: "utf8", + }); + return parsePackOutput(output, cwd); +} + +function parsePackOutput(output, cwd) { const parsed = JSON.parse(output); const entry = Array.isArray(parsed) ? parsed[0] : parsed; return path.resolve(cwd, entry.filename); diff --git a/scripts/release-smoke.test.js b/scripts/release-smoke.test.js index ae95ce04..8148d1d3 100644 --- a/scripts/release-smoke.test.js +++ b/scripts/release-smoke.test.js @@ -27,3 +27,12 @@ test("findBinaryArtifact resolves binary path from artifacts.json", () => { assert.equal(findBinaryArtifact(distDir, "linux", "amd64"), binaryAbs); assert.equal(resolveVersion(distDir), "0.0.0-smoke"); }); + +test("findBinaryArtifact rejects unknown platform", () => { + const distDir = fs.mkdtempSync(path.join(os.tmpdir(), "xsql-dist-")); + fs.writeFileSync(path.join(distDir, "artifacts.json"), "[]"); + assert.throws( + () => findBinaryArtifact(distDir, "plan9", "amd64"), + /unsupported smoke platform/, + ); +}); From 011bebdb0d3946d0b49449baddaf9c15fac38140 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 10 Oct 2026 06:42:09 +0000 Subject: [PATCH 6/8] fix(security): lock actionlint via go.sum and fix Windows npm exec Co-authored-by: x_zhuo --- .github/tools/go.mod | 19 +++++++++++++++++ .github/tools/go.sum | 30 +++++++++++++++++++++++++++ .github/tools/tools.go | 3 +++ .github/workflows/release-dry-run.yml | 6 +++--- scripts/release-smoke.js | 24 +++++++++++++++------ 5 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 .github/tools/go.mod create mode 100644 .github/tools/go.sum create mode 100644 .github/tools/tools.go diff --git a/.github/tools/go.mod b/.github/tools/go.mod new file mode 100644 index 00000000..acb908f8 --- /dev/null +++ b/.github/tools/go.mod @@ -0,0 +1,19 @@ +module github.com/zx06/xsql/.github/tools + +go 1.25.0 + +require github.com/rhysd/actionlint v1.7.7 + +require ( + github.com/bmatcuk/doublestar/v4 v4.8.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-shellwords v1.0.12 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/.github/tools/go.sum b/.github/tools/go.sum new file mode 100644 index 00000000..c1d42a63 --- /dev/null +++ b/.github/tools/go.sum @@ -0,0 +1,30 @@ +github.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ= +github.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/rhysd/actionlint v1.7.7 h1:0KgkoNTrYY7vmOCs9BW2AHxLvvpoY9nEUzgBHiPUr0k= +github.com/rhysd/actionlint v1.7.7/go.mod h1:AE6I6vJEkNaIfWqC2GNE5spIJNhxf8NCtLEKU4NnUXg= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/.github/tools/tools.go b/.github/tools/tools.go new file mode 100644 index 00000000..4e14c62f --- /dev/null +++ b/.github/tools/tools.go @@ -0,0 +1,3 @@ +package tools + +import _ "github.com/rhysd/actionlint/cmd/actionlint" diff --git a/.github/workflows/release-dry-run.yml b/.github/workflows/release-dry-run.yml index 068386ca..a8dd41a6 100644 --- a/.github/workflows/release-dry-run.yml +++ b/.github/workflows/release-dry-run.yml @@ -9,6 +9,7 @@ on: - "scripts/npm-publish.js" - "scripts/release-smoke.js" - "scripts/release-artifacts.js" + - ".github/tools/**" workflow_dispatch: permissions: @@ -30,9 +31,8 @@ jobs: cache: false - name: Run actionlint - run: | - go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.7 - "$(go env GOPATH)/bin/actionlint" -color + working-directory: .github/tools + run: go run github.com/rhysd/actionlint/cmd/actionlint -color ../.. build: name: GoReleaser snapshot build diff --git a/scripts/release-smoke.js b/scripts/release-smoke.js index da0bb391..cf1b8360 100644 --- a/scripts/release-smoke.js +++ b/scripts/release-smoke.js @@ -120,19 +120,31 @@ function resolveVersion(distDir) { return "0.0.0-smoke"; } +function runNodeCliTool(toolName, args, cwd, options = {}) { + const toolPath = resolveNodeCliTool(toolName); + const execOptions = { cwd, ...options }; + if (process.platform === "win32") { + execFileSync(toolPath, args, { ...execOptions, shell: true }); + return; + } + execFileSync(toolPath, args, execOptions); +} + function runNpm(args, cwd) { - execFileSync(resolveNodeCliTool("npm"), args, { cwd, stdio: "inherit" }); + runNodeCliTool("npm", args, cwd, { stdio: "inherit" }); } function runNpx(args, cwd) { - execFileSync(resolveNodeCliTool("npx"), args, { cwd, stdio: "inherit" }); + runNodeCliTool("npx", args, cwd, { stdio: "inherit" }); } function packTarball(cwd) { - const output = execFileSync(resolveNodeCliTool("npm"), ["pack", "--json"], { - cwd, - encoding: "utf8", - }); + const toolPath = resolveNodeCliTool("npm"); + const execOptions = { cwd, encoding: "utf8" }; + const output = + process.platform === "win32" + ? execFileSync(toolPath, ["pack", "--json"], { ...execOptions, shell: true }) + : execFileSync(toolPath, ["pack", "--json"], execOptions); return parsePackOutput(output, cwd); } From 62bd251e957a3d5fb8aab57c26bd265da114f90c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 10 Oct 2026 06:48:33 +0000 Subject: [PATCH 7/8] fix(ci): pass workflow directory to actionlint Co-authored-by: x_zhuo --- .github/workflows/release-dry-run.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-dry-run.yml b/.github/workflows/release-dry-run.yml index a8dd41a6..1f9298e7 100644 --- a/.github/workflows/release-dry-run.yml +++ b/.github/workflows/release-dry-run.yml @@ -32,7 +32,7 @@ jobs: - name: Run actionlint working-directory: .github/tools - run: go run github.com/rhysd/actionlint/cmd/actionlint -color ../.. + run: go run github.com/rhysd/actionlint/cmd/actionlint -color "${{ github.workspace }}/.github/workflows" build: name: GoReleaser snapshot build From 4b86eccf6295b03b34b1c109f943d78b4f9b7527 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 10 Oct 2026 06:53:52 +0000 Subject: [PATCH 8/8] fix(ci): pass workflow files explicitly to actionlint Co-authored-by: x_zhuo --- .github/workflows/release-dry-run.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-dry-run.yml b/.github/workflows/release-dry-run.yml index 1f9298e7..4d6c6377 100644 --- a/.github/workflows/release-dry-run.yml +++ b/.github/workflows/release-dry-run.yml @@ -31,8 +31,13 @@ jobs: cache: false - name: Run actionlint - working-directory: .github/tools - run: go run github.com/rhysd/actionlint/cmd/actionlint -color "${{ github.workspace }}/.github/workflows" + env: + WORKFLOW_DIR: ${{ github.workspace }}/.github/workflows + run: | + shopt -s nullglob + mapfile -t workflow_files < <(printf '%s\n' "$WORKFLOW_DIR"/*.yml "$WORKFLOW_DIR"/*.yaml) + cd .github/tools + go run github.com/rhysd/actionlint/cmd/actionlint -color "${workflow_files[@]}" build: name: GoReleaser snapshot build