diff --git a/.circleci/config.yml b/.circleci/config.yml index 0e7c9bff56..be4d60b3ad 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -436,7 +436,7 @@ commands: parameters: items: type: string - default: go gradle python pipenv elixir composer gradle@9 maven sbt dotnet uv + default: go gradle python pipenv elixir composer gradle@9 maven dotnet uv steps: - run: name: Installing Rosetta @@ -460,6 +460,16 @@ commands: - run: name: Upgrade npm to satisfy package.json engine requirements (>=11.10) command: npm install -g npm@^11.10 + - restore_cache: + keys: + - acceptance-{{ checksum "scripts/install-sbt.sh" }} + - run: + name: Installing sbt 1.x + command: ./scripts/install-sbt.sh + - save_cache: + key: acceptance-{{ checksum "scripts/install-sbt.sh" }} + paths: + - ~/sbt install-deps-macos-build: parameters: @@ -1323,7 +1333,6 @@ workflows: - release-npm: name: upload npm context: - - team-hammerhead-common-deploy-tokens - devex_cli_docker_hub requires: - upload github @@ -1855,7 +1864,9 @@ jobs: version: << pipeline.parameters.aws_version >> - run: name: Pre-Publishing - command: make release-pre + command: | + export NPM_ID_TOKEN=$(circleci run oidc get --claims '{"aud": "npm:registry.npmjs.org"}') + make release-pre - failed-release-notification npm-validation: @@ -1988,7 +1999,9 @@ jobs: deployment: stable - run: name: Publish to npm - command: ./release-scripts/upload-artifacts.sh npm + command: | + export NPM_ID_TOKEN=$(circleci run oidc get --claims '{"aud": "npm:registry.npmjs.org"}') + ./release-scripts/upload-artifacts.sh npm - failed-release-notification trigger-building-distribution-channels: diff --git a/cliv2-private/go.mod b/cliv2-private/go.mod index b93d05a8d8..f90f60128b 100644 --- a/cliv2-private/go.mod +++ b/cliv2-private/go.mod @@ -4,7 +4,7 @@ go 1.26.4 require ( github.com/snyk/cli/cliv2 v0.0.0 - github.com/snyk/remy-cli-extension v1.20.1 + github.com/snyk/remy-cli-extension v1.23.0 ) require ( @@ -216,7 +216,7 @@ require ( github.com/snyk/container-cli v0.0.0-20260213211631-cd2b2cf8f3ea // indirect github.com/snyk/dep-graph/go v0.0.0-20260127160647-c836da762c62 // indirect github.com/snyk/error-catalog-golang-public v0.0.0-20260505112649-a5103d411663 // indirect - github.com/snyk/go-application-framework v0.6.2 // indirect + github.com/snyk/go-application-framework v0.6.5-0.20260706141127-9483f3b67782 // indirect github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65 // indirect github.com/snyk/policy-engine v1.1.4 // indirect github.com/snyk/snyk-iac-capture v0.6.5 // indirect diff --git a/cliv2-private/go.sum b/cliv2-private/go.sum index f34c10a4bf..36e8468bd7 100644 --- a/cliv2-private/go.sum +++ b/cliv2-private/go.sum @@ -585,14 +585,14 @@ github.com/snyk/dep-graph/go v0.0.0-20260127160647-c836da762c62 h1:kgZNQ5ztI4+n3 github.com/snyk/dep-graph/go v0.0.0-20260127160647-c836da762c62/go.mod h1:hTr91da/4ze2nk9q6ZW1BmfM2Z8rLUZSEZ3kK+6WGpc= github.com/snyk/error-catalog-golang-public v0.0.0-20260505112649-a5103d411663 h1:j2ZPhi78wKIHTiL9EFTNVXMIbsk56FVF2d5Sy1ZwSYk= github.com/snyk/error-catalog-golang-public v0.0.0-20260505112649-a5103d411663/go.mod h1:Ytttq7Pw4vOCu9NtRQaOeDU2dhBYUyNBe6kX4+nIIQ4= -github.com/snyk/go-application-framework v0.6.2 h1:grVccp5mzmctL2hpo9fTnonAEUeMqX+tGczXnECu3j4= -github.com/snyk/go-application-framework v0.6.2/go.mod h1:0YC7xCETnFTdz6rq8OQPL0aWekMLOkBQu1FE4/cReMA= +github.com/snyk/go-application-framework v0.6.5-0.20260706141127-9483f3b67782 h1:ErhPyGnqAYJeVAqB+tdvEYpdBdfXwDkTxHRmHLdeLqg= +github.com/snyk/go-application-framework v0.6.5-0.20260706141127-9483f3b67782/go.mod h1:0YC7xCETnFTdz6rq8OQPL0aWekMLOkBQu1FE4/cReMA= github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65 h1:CEQuYv0Go6MEyRCD3YjLYM2u3Oxkx8GpCpFBd4rUTUk= github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65/go.mod h1:88KbbvGYlmLgee4OcQ19yr0bNpXpOr2kciOthaSzCAg= github.com/snyk/policy-engine v1.1.4 h1:0XpaMpl7ixSk4+dlpHYg2iKEBuv+5Ci+QIcbsmhktao= github.com/snyk/policy-engine v1.1.4/go.mod h1:yAlMDZhzORiZcbJCW0GEk/nHkc4DBDKp8BRoiGTWkBE= -github.com/snyk/remy-cli-extension v1.20.1 h1:Y5h3oqbKghkkObRqwvF8qkX6OtmmKDAbh+OTW7oNppc= -github.com/snyk/remy-cli-extension v1.20.1/go.mod h1:qpL64pAGKDAbApnP8CLIAIOLPD6R+Ge6qA4m0L2auKo= +github.com/snyk/remy-cli-extension v1.23.0 h1:cjfoltDIsJlunC2Zsx9hPN8EuJAAPUCZFDJvyg6Qj90= +github.com/snyk/remy-cli-extension v1.23.0/go.mod h1:qpL64pAGKDAbApnP8CLIAIOLPD6R+Ge6qA4m0L2auKo= github.com/snyk/snyk-iac-capture v0.6.5 h1:992DXCAJSN97KtUh8T5ndaWwd/6ZCal2bDkRXqM1u/E= github.com/snyk/snyk-iac-capture v0.6.5/go.mod h1:e47i55EmM0F69ZxyFHC4sCi7vyaJW6DLoaamJJCzWGk= github.com/snyk/snyk-ls v0.0.0-20260626083941-77c2abaeaaaa h1:qKLDBzWOoLLn06e69Cw7MLb6TtInLN+Y6IhD+oYVxdw= diff --git a/cliv2/go.mod b/cliv2/go.mod index b8a6a77bec..8a76fb94a1 100644 --- a/cliv2/go.mod +++ b/cliv2/go.mod @@ -22,7 +22,7 @@ require ( github.com/snyk/code-client-go v1.27.0 github.com/snyk/container-cli v0.0.0-20260213211631-cd2b2cf8f3ea github.com/snyk/error-catalog-golang-public v0.0.0-20260505112649-a5103d411663 - github.com/snyk/go-application-framework v0.6.2 + github.com/snyk/go-application-framework v0.6.5-0.20260706141127-9483f3b67782 github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65 github.com/snyk/snyk-iac-capture v0.6.5 github.com/snyk/snyk-ls v0.0.0-20260626083941-77c2abaeaaaa diff --git a/cliv2/go.sum b/cliv2/go.sum index 3dc006a0ba..d789320b64 100644 --- a/cliv2/go.sum +++ b/cliv2/go.sum @@ -547,8 +547,8 @@ github.com/snyk/dep-graph/go v0.0.0-20260127160647-c836da762c62 h1:kgZNQ5ztI4+n3 github.com/snyk/dep-graph/go v0.0.0-20260127160647-c836da762c62/go.mod h1:hTr91da/4ze2nk9q6ZW1BmfM2Z8rLUZSEZ3kK+6WGpc= github.com/snyk/error-catalog-golang-public v0.0.0-20260505112649-a5103d411663 h1:j2ZPhi78wKIHTiL9EFTNVXMIbsk56FVF2d5Sy1ZwSYk= github.com/snyk/error-catalog-golang-public v0.0.0-20260505112649-a5103d411663/go.mod h1:Ytttq7Pw4vOCu9NtRQaOeDU2dhBYUyNBe6kX4+nIIQ4= -github.com/snyk/go-application-framework v0.6.2 h1:grVccp5mzmctL2hpo9fTnonAEUeMqX+tGczXnECu3j4= -github.com/snyk/go-application-framework v0.6.2/go.mod h1:0YC7xCETnFTdz6rq8OQPL0aWekMLOkBQu1FE4/cReMA= +github.com/snyk/go-application-framework v0.6.5-0.20260706141127-9483f3b67782 h1:ErhPyGnqAYJeVAqB+tdvEYpdBdfXwDkTxHRmHLdeLqg= +github.com/snyk/go-application-framework v0.6.5-0.20260706141127-9483f3b67782/go.mod h1:0YC7xCETnFTdz6rq8OQPL0aWekMLOkBQu1FE4/cReMA= github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65 h1:CEQuYv0Go6MEyRCD3YjLYM2u3Oxkx8GpCpFBd4rUTUk= github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65/go.mod h1:88KbbvGYlmLgee4OcQ19yr0bNpXpOr2kciOthaSzCAg= github.com/snyk/policy-engine v1.1.4 h1:0XpaMpl7ixSk4+dlpHYg2iKEBuv+5Ci+QIcbsmhktao= diff --git a/cliv2/pkg/core/main.go b/cliv2/pkg/core/main.go index 701d23a330..0df2f9ba93 100644 --- a/cliv2/pkg/core/main.go +++ b/cliv2/pkg/core/main.go @@ -37,8 +37,8 @@ import ( "github.com/snyk/cli/cliv2/internal/cliv2" "github.com/snyk/cli/cliv2/internal/constants" - cliv2utils "github.com/snyk/cli/cliv2/internal/utils" persona "github.com/snyk/cli/cliv2/internal/persona" + cliv2utils "github.com/snyk/cli/cliv2/internal/utils" localworkflows "github.com/snyk/go-application-framework/pkg/local_workflows" "github.com/snyk/go-application-framework/pkg/local_workflows/config_utils" diff --git a/release-scripts/upload-artifacts.sh b/release-scripts/upload-artifacts.sh index d63670a2a9..be82a84a8b 100755 --- a/release-scripts/upload-artifacts.sh +++ b/release-scripts/upload-artifacts.sh @@ -112,13 +112,11 @@ upload_npm() { if [ "${DRY_RUN}" == true ]; then echo "DRY RUN: uploading to npm..." - npm config set '//registry.npmjs.org/:_authToken' "${HAMMERHEAD_NPM_TOKEN}" npm publish --dry-run ${npm_tag_arg} ./binary-releases/snyk-fix.tgz npm publish --dry-run ${npm_tag_arg} ./binary-releases/snyk-protect.tgz npm publish --dry-run ${npm_tag_arg} ./binary-releases/snyk.tgz else echo "Uploading to npm..." - npm config set '//registry.npmjs.org/:_authToken' "${HAMMERHEAD_NPM_TOKEN}" npm publish ${npm_tag_arg} ./binary-releases/snyk-fix.tgz npm publish ${npm_tag_arg} ./binary-releases/snyk-protect.tgz npm publish ${npm_tag_arg} ./binary-releases/snyk.tgz diff --git a/scripts/install-sbt.sh b/scripts/install-sbt.sh new file mode 100755 index 0000000000..75ce1411bf --- /dev/null +++ b/scripts/install-sbt.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# pinned sbt 1.x for acceptance tests; snyk-sbt-plugin does not support sbt 2.x +set -euo pipefail + +SBT_VERSION="1.12.11" +SBT_SHA256="5f972a79d2a5bf8f29141a74c35e686bb0860a6b6ec677af892f94cf9b124645" +SBT_HOME="${SBT_HOME:-${HOME}/sbt}" + +download() { + if command -v wget >/dev/null 2>&1; then + wget -qO "$2" "$1" + else + curl -fsSL -o "$2" "$1" + fi +} + +if [ ! -x "${SBT_HOME}/bin/sbt" ] || [[ "$("${SBT_HOME}/bin/sbt" --version 2>&1 || true)" != *"${SBT_VERSION}"* ]]; then + echo "Installing sbt ${SBT_VERSION} to ${SBT_HOME}" + base="https://github.com/sbt/sbt/releases/download/v${SBT_VERSION}" + tarball="/tmp/sbt-${SBT_VERSION}.tgz" + download "${base}/sbt-${SBT_VERSION}.tgz" "${tarball}" + if command -v sha256sum >/dev/null 2>&1; then + actual=$(sha256sum "${tarball}" | awk '{print $1}') + else + actual=$(shasum -a 256 "${tarball}" | awk '{print $1}') + fi + if [ "${actual}" != "${SBT_SHA256}" ]; then + echo "ERROR: checksum verification failed for ${tarball}" + echo "Expected: ${SBT_SHA256}" + echo "Actual: ${actual}" + exit 1 + fi + rm -rf "${SBT_HOME}" + tar xzf "${tarball}" -C "${HOME}" +fi + +if [ -n "${BASH_ENV:-}" ]; then + echo "export PATH=\"${SBT_HOME}/bin:\$PATH\"" >> "$BASH_ENV" +fi +export PATH="${SBT_HOME}/bin:${PATH}" +sbt --version diff --git a/test/jest/acceptance/doctor.spec.ts b/test/jest/acceptance/doctor.spec.ts new file mode 100644 index 0000000000..1e6373fa41 --- /dev/null +++ b/test/jest/acceptance/doctor.spec.ts @@ -0,0 +1,64 @@ +import { runSnykCLI } from '../util/runSnykCLI'; + +jest.setTimeout(1000 * 120); + +describe('snyk doctor', () => { + const env = { + ...process.env, + SNYK_DISABLE_ANALYTICS: '1', + }; + + describe('use doctor on the current system (live checks)', () => { + it('outputs a human readable report', async () => { + const { code, stdout } = await runSnykCLI('doctor --live', { env }); + + expect(code).toBe(0); + expect(stdout).toContain('Snyk Doctor Diagnostic Report'); + }); + }); + + describe('use doctor on the debug logs of another system', () => { + const fs = require('fs'); + const os = require('os'); + const path = require('path'); + + const sampleLog = [ + '2026-06-10T13:10:38Z main - < response [0x1]: 401 Unauthorized', + '2026-06-10T13:10:38Z main - ------------ Summary ------------', + '2026-06-10T13:10:38Z main - ------------ Errors ------------', + '2026-06-10T13:10:38Z main - Authentication error (SNYK-0005)', + '2026-06-10T13:10:38Z main - Exit Code: 2', + ].join('\n'); + + let logFile: string; + let tmpDir: string; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'doctor-test-')); + logFile = path.join(tmpDir, 'debug.log'); + fs.writeFileSync(logFile, sampleLog); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + it('analyzes the debug log and reports findings', async () => { + const { code, stdout } = await runSnykCLI(`doctor --input=${logFile}`, { + env, + }); + + expect(code).toBe(0); + expect(stdout).toContain('Snyk Doctor Diagnostic Report'); + }); + + it('--input with non-existent file', async () => { + const { code } = await runSnykCLI( + 'doctor --input=/tmp/does-not-exist-snyk-doctor-test.log', + { env }, + ); + + expect(code).not.toBe(0); + }); + }); +}); diff --git a/test/jest/acceptance/snyk-test/unified-test-api-equivalence.spec.ts b/test/jest/acceptance/snyk-test/unified-test-api-equivalence.spec.ts index 52a6dc2a0d..5ccb8cd2af 100644 --- a/test/jest/acceptance/snyk-test/unified-test-api-equivalence.spec.ts +++ b/test/jest/acceptance/snyk-test/unified-test-api-equivalence.spec.ts @@ -13,30 +13,34 @@ * Starter corpus. Expand per resolveDepgraphs-rollout-plan.md. */ -import { fakeServer } from '../../../acceptance/fake-server'; +import { + fakeServer, + getFirstIPv4Address, +} from '../../../acceptance/fake-server'; import { createProjectFromWorkspace } from '../../util/createProject'; -import { getServerPort } from '../../util/getServerPort'; +import { getAvailableServerPort } from '../../util/getServerPort'; import { assertEquivalent, runBothFlows } from './equivalenceHelpers'; -jest.setTimeout(1000 * 60 * 3); +jest.setTimeout(1000 * 60 * 5); describe('snyk test — unified test API equivalence (FF off vs on)', () => { let server; let env: Record; - beforeAll((done) => { - const port = getServerPort(process); + beforeAll(async () => { + const port = await getAvailableServerPort(process); const baseApi = '/v1'; + const fakeServerIp = getFirstIPv4Address(); env = { ...process.env, - SNYK_API: 'http://localhost:' + port + baseApi, - SNYK_HOST: 'http://localhost:' + port, + SNYK_API: `http://${fakeServerIp}:${port}${baseApi}`, + SNYK_HOST: `http://${fakeServerIp}:${port}`, SNYK_TOKEN: '123456789', SNYK_DISABLE_ANALYTICS: '1', SNYK_HTTP_PROTOCOL_UPGRADE: '0', }; server = fakeServer(baseApi, env.SNYK_TOKEN); - server.listen(port, () => done()); + await server.listenPromise(port); }); afterAll((done) => { diff --git a/test/jest/util/runCommand.ts b/test/jest/util/runCommand.ts index eb0d662e0a..d4d80d4920 100644 --- a/test/jest/util/runCommand.ts +++ b/test/jest/util/runCommand.ts @@ -24,6 +24,8 @@ const runCommand = ( ): Promise => { return new Promise((resolve, reject) => { const child = spawn(command, args, options); + // default pipe stdin blocks commands that read until EOF (e.g. doctor) + child.stdin?.end(); const stdout: Buffer[] = []; const stderr: Buffer[] = [];