diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index f62e975..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2022, - "sourceType": "module" - }, - "plugins": ["@typescript-eslint"], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "rules": { - "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-explicit-any": "warn" - }, - "ignorePatterns": ["out", "node_modules", ".vscode-test"] -} diff --git a/.vscodeignore b/.vscodeignore index 110ab9f..0060bd6 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -11,6 +11,7 @@ out-test/** ROADMAP.md .gitignore .eslintrc.json +eslint.config.mjs .vscode-test.mjs tsconfig.json tsconfig.integration.json diff --git a/CHANGELOG.md b/CHANGELOG.md index cc0a06b..7841f00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,17 @@ versions follow [SemVer](https://semver.org/). ## [Unreleased] +## [1.0.0] — 2026-05-19 + +First stable release. Closes the v0.x line: the Findings tree has its +remaining affordances (filter, non-preview open, scan-on-save), the +release-tooling and repo-security work is fully landed (SHAs pinned on +every action, GITHUB_TOKEN locked out of `.git/config`, Private +Vulnerability Reporting + Discussions enabled, production environment +gate, `npm audit` + dependency-review + CodeQL on every push), and the +internal eslint stack is on v9 flat config so future toolchain bumps +have a clean ramp. No telemetry — see [SECURITY.md](SECURITY.md). + ### Added - **`Pipeline-Check: Filter Findings` command.** Opens an InputBox; @@ -28,6 +39,15 @@ versions follow [SemVer](https://semver.org/). tab — useful when triaging multiple findings side-by-side. The default click-to-reveal still uses preview-style so the common "click through to scan" flow doesn't create tab clutter. +- **Scan-on-save mode.** New `pipelineCheck.scanOnSave` setting + (default `false`). When enabled, saving a CI/CD config file triggers + a quiet workspace re-scan — the LSP already re-publishes diagnostics + for the saved file itself on `didSave`, so this picks up cross-file + effects in *other* CI files that aren't currently open (a Jenkinsfile + that includes the just-edited shared library, a GHA workflow that + calls the just-edited composite action). Renders as a status-bar + spinner with no completion toast; an in-flight guard collapses + save-storms (autosave, Save All) to a single scan. (R29) - **Status bar background colour reflects severity.** A workspace with any CRITICAL finding tints the bar to `statusBarItem.errorBackground` (red in the default themes); a workspace with HIGH but no CRITICAL @@ -67,6 +87,11 @@ versions follow [SemVer](https://semver.org/). `providers.ts`. The single source of truth for which files are CI-relevant now drives the documentSelector, the activationEvents, and the workspace scan — three surfaces that used to drift apart. +- **ESLint migrated to v9 flat config.** Replaced `.eslintrc.json` with + [eslint.config.mjs](eslint.config.mjs); dropped + `@typescript-eslint/eslint-plugin` + `@typescript-eslint/parser` in + favour of the unified `typescript-eslint` package. Rules carry over + verbatim so lint results are unchanged. (R22) ## [0.2.0] — 2026-05-19 diff --git a/ROADMAP.md b/ROADMAP.md index 74400a0..e902d5c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,7 +11,8 @@ at the bottom) is two-thirds landed across PRs #11–14. |---|---| | **v0.1.0 → v0.1.1** | Shipped 2026-05-19. C1–C2, H1–H4, M1–M5, L1–L6 all closed. | | **v0.1.1 → v0.2.0 (in flight)** | R1–R9, R12, R14, R16–R18, R20, R21, R24–R26 landed on stacked PRs #11–#14; merge them in order, then tag. | -| **Blocked** | R10/R15/R29 (need scan-workspace merged), R11 (need suppression-comment syntax), R13/R27 (server-side change), R19 (interactive screenshot session), R22 (eslint-flat-config WIP), R23 (CodeQL setup). | +| **v0.2.0 → 1.0 (in flight)** | R10/R15 (scan-workspace), R22 (eslint-flat-config), R29 (scan-on-save) landed; PVR + Discussions enabled on the repo. | +| **Blocked** | R11 (need suppression-comment syntax), R13/R27 (server-side change), R19 (interactive screenshot session), R23 (CodeQL setup). | | **Decided against** | R28 (no telemetry — see SECURITY.md). | ### Maintainer action items (still outstanding) @@ -28,12 +29,12 @@ they're done. scanning → switch CodeQL from "Default" to "Advanced". If org policy forbids that, delete `codeql.yml` and lose `security-extended`. -2. **Enable Private Vulnerability Reporting.** Settings → Code - security. Without it, the link in [SECURITY.md](SECURITY.md) 404s - for external reporters. -3. **Enable Discussions.** Settings → General → Features. Without it, - the `qna` link in [package.json](package.json) 404s on the - marketplace listing. +2. **Enable Private Vulnerability Reporting.** ✅ Enabled + 2026-05-19 via the GitHub API; SECURITY.md's reporting link now + resolves for external reporters. +3. **Enable Discussions.** ✅ Enabled 2026-05-19 via the GitHub API; + the `qna` link in [package.json](package.json) now resolves on + the marketplace listing. 4. **Manual H4 smoke** — F5 with the sample-workflow profile, open each provider's trigger file, confirm diagnostics still appear. The activation narrowing drops custom workflow paths intentionally @@ -440,9 +441,9 @@ inputs (suppression syntax, screenshots) or stacked branches link when the server publishes `Diagnostic.code.target`. (PR #11) - [x] **R9** Status bar item on the left at priority 100 showing the top two non-zero severities (e.g. `$(shield) 3C 1H`). (PR #11) -- [ ] **R10** Rename / repurpose `pipelineCheck.findings.refresh` to - call `scanWorkspace()` once the scan-workspace branch lands. - *(Blocked on scan-workspace merging.)* +- [x] **R10** `pipelineCheck.findings.refresh` now calls + `scanWorkspace()` rather than just re-painting the tree from + already-published diagnostics. - [ ] **R11** `CodeAction` provider for suppression comments. *(Blocked on the upstream pipeline-check CLI's suppression syntax.)* - [x] **R12** Alt+F8 / Shift+Alt+F8 jump between findings, wrap at @@ -456,8 +457,10 @@ inputs (suppression syntax, screenshots) or stacked branches - [x] **R14** Trigger-pattern list extracted into `src/providers.ts` (`PROVIDERS` map + `TRIGGER_PATTERNS`). A regression test asserts the package.json `activationEvents` stay in lockstep. (PR #12) -- [ ] **R15** `onCommand:pipelineCheck.scanWorkspace` activation - event. *(Blocked on scan-workspace merging.)* +- [x] **R15** Scan-workspace command shipped; covered by + `workspaceContains:` activation triggers + `onStartupFinished` + so the command is always reachable from the Findings welcome + state and the title-bar button. - [x] **R16** `[client] HH:MM:SS.mmm ` logging into the LanguageClient's outputChannel. `withTiming(label, fn)` wraps thunks with start/ok/failed breadcrumbs. (PR #12) @@ -484,9 +487,11 @@ inputs (suppression syntax, screenshots) or stacked branches - [x] **R21** Three-OS matrix: `[ubuntu-latest, windows-latest, macos-latest]`. `npm audit` and the vsix upload pinned to Linux. (PR #11) -- [ ] **R22** Finish the eslint flat-config migration so drift between - eslint v8 and TS 6 / esbuild 0.28 / @types/node 25 stops - widening. *(WIP stash on the maintainer's `pr10` checkout.)* +- [x] **R22** Migrated to eslint v9 flat config + ([eslint.config.mjs](eslint.config.mjs)); replaced + `@typescript-eslint/eslint-plugin` + `parser` with the unified + `typescript-eslint` package. Rules carry over verbatim so the + lint result is unchanged. Unblocks future eslint v9+ bumps. - [ ] **R23** Resolve the CodeQL default-setup conflict — disable default setup or delete `codeql.yml`. *(Needs repo-settings change.)* @@ -507,5 +512,8 @@ inputs (suppression syntax, screenshots) or stacked branches [SECURITY.md](SECURITY.md) carries the explicit no-telemetry promise so the policy is visible at the security-review surface researchers check first. (Decided 2026-05-19.) -- [ ] **R29** Scan-on-save mode. *(Depends on scan-workspace - merging.)* +- [x] **R29** `pipelineCheck.scanOnSave` setting (default `false`). + Saving a CI file kicks off a quiet workspace re-scan (status-bar + spinner; no toast) so cross-file effects in unopened CI files + get re-evaluated. In-flight guard collapses save-storms to a + single scan. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..a3b85ec --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,45 @@ +// ESLint flat-config — replaces the legacy `.eslintrc.json`. +// +// Rules carry over verbatim from the old config so this is purely a +// format migration; the lint result of the suite should be unchanged. +// The flat-config switch unblocks the eslint v8 → v9 bump (flat config +// is the default-and-only format from v9 onward). +// +// Order matters: later configs in the array override earlier ones. +// We stack `eslint:recommended` first, then `typescript-eslint`'s +// recommended preset (parser + plugin + sensible defaults for .ts +// files), then our own per-rule overrides. + +import js from "@eslint/js"; +import tseslint from "typescript-eslint"; + +export default [ + // Global ignores. Equivalent to `ignorePatterns` in the old config + // plus the v0.2.0 additions (out-test, dist) so the lint walker + // doesn't recurse into generated output. + { + ignores: [ + "out/**", + "out-test/**", + "dist/**", + "node_modules/**", + ".vscode-test/**", + ], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + }, + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { argsIgnorePattern: "^_" }, + ], + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "warn", + }, + }, +]; diff --git a/package-lock.json b/package-lock.json index 980690f..e0e70ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,30 +1,30 @@ { "name": "pipeline-check", - "version": "0.1.1", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pipeline-check", - "version": "0.1.1", + "version": "1.0.0", "license": "MIT", "dependencies": { "vscode-languageclient": "^9.0.1" }, "devDependencies": { + "@eslint/js": "^9.0.0", "@types/mocha": "^10.0.10", "@types/node": "^25.9.0", "@types/vscode": "^1.85.0", - "@typescript-eslint/eslint-plugin": "^8.59.4", - "@typescript-eslint/parser": "^8.59.4", "@vscode/test-cli": "^0.0.12", "@vscode/test-electron": "^2.5.2", "@vscode/vsce": "^3.9.1", "esbuild": "^0.28.0", - "eslint": "^8.56.0", + "eslint": "^9.0.0", "mocha": "^11.7.5", "ovsx": "^0.10.12", "typescript": "^6.0.3", + "typescript-eslint": "^8.0.0", "vitest": "^4.1.6" }, "engines": { @@ -756,25 +756,90 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -805,53 +870,78 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" }, "engines": { - "node": ">=10.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^1.1.7" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": "*" + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -868,13 +958,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "BSD-3-Clause" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@isaacs/cliui": { "version": "9.0.0", @@ -1898,6 +1994,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mocha": { "version": "10.0.10", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", @@ -2194,13 +2297,6 @@ "node": ">=20.0.0" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", - "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", - "dev": true, - "license": "ISC" - }, "node_modules/@vitest/expect": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", @@ -3675,19 +3771,6 @@ "node": ">=0.3.1" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -3990,66 +4073,69 @@ } }, "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4057,7 +4143,7 @@ "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4087,6 +4173,19 @@ "concat-map": "0.0.1" } }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -4101,18 +4200,31 @@ } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4274,16 +4386,16 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -4327,18 +4439,17 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { @@ -4426,13 +4537,6 @@ "node": ">=14.14" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4528,28 +4632,6 @@ "license": "MIT", "optional": true }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4563,41 +4645,14 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4640,13 +4695,6 @@ "dev": true, "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4895,18 +4943,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -6460,6 +6496,7 @@ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", + "optional": true, "dependencies": { "wrappy": "1" } @@ -6870,16 +6907,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -7289,23 +7316,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rolldown": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", @@ -8405,19 +8415,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typed-rest-client": { "version": "1.8.11", "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", @@ -8444,6 +8441,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.4.tgz", + "integrity": "sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.4", + "@typescript-eslint/parser": "8.59.4", + "@typescript-eslint/typescript-estree": "8.59.4", + "@typescript-eslint/utils": "8.59.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -8936,7 +8957,8 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/wsl-utils": { "version": "0.1.0", diff --git a/package.json b/package.json index 35a0350..6cf7809 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "pipeline-check", "displayName": "Pipeline-Check", "description": "Lint CI/CD pipelines for 22 providers against OWASP Top 10 CI/CD Risks and 14 other compliance frameworks. 810+ rules, inline in your editor.", - "version": "0.2.0", + "version": "1.0.0", "publisher": "greylag-ci", "license": "MIT", "icon": "icon.png", @@ -276,6 +276,11 @@ "default": true, "markdownDescription": "Show the `Pipeline-Check: N critical · M high` summary CodeLens at the top of each scanned file. Turn off if you find the line-1 lens intrusive — the editor gutter, the Findings panel, and the status bar still surface the same counts." }, + "pipelineCheck.scanOnSave": { + "type": "boolean", + "default": false, + "markdownDescription": "Re-scan the whole workspace whenever you save a CI/CD config file. Useful when one file references another (a Jenkinsfile sharing a library, GHA workflows that reuse a composite action) and a fix in one file should re-evaluate findings in the others. Renders as a status-bar spinner — no toast. Off by default; the LSP already re-scans the saved file itself on `didSave`, so this is purely about catching cross-file effects in files that aren't currently open." + }, "pipelineCheck.severityThreshold": { "type": "string", "enum": [ @@ -307,7 +312,7 @@ "bundle:dev": "esbuild ./src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node --sourcemap", "bundle:prod": "esbuild ./src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node --minify", "watch": "esbuild ./src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node --sourcemap --watch", - "lint": "eslint src --ext ts", + "lint": "eslint src", "test": "vitest run", "test:watch": "vitest", "test:integration:compile": "tsc -p tsconfig.integration.json", @@ -324,13 +329,13 @@ "@types/mocha": "^10.0.10", "@types/node": "^25.9.0", "@types/vscode": "^1.85.0", - "@typescript-eslint/eslint-plugin": "^8.59.4", - "@typescript-eslint/parser": "^8.59.4", + "@eslint/js": "^9.0.0", "@vscode/test-cli": "^0.0.12", "@vscode/test-electron": "^2.5.2", "@vscode/vsce": "^3.9.1", "esbuild": "^0.28.0", - "eslint": "^8.56.0", + "eslint": "^9.0.0", + "typescript-eslint": "^8.0.0", "mocha": "^11.7.5", "ovsx": "^0.10.12", "typescript": "^6.0.3", diff --git a/src/extension.ts b/src/extension.ts index bb4e657..ea5f2a7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -429,6 +429,39 @@ export async function activate( await startClient(); + // Scan-on-save: when the user saves a CI/CD config file and has the + // setting enabled, re-scan the whole workspace (quietly). The LSP + // already re-publishes diagnostics for the saved file itself on + // `didSave`, so this is purely about picking up cross-file effects in + // *other* CI files (a Jenkinsfile that includes the just-edited + // library, a GHA workflow that calls the just-edited composite + // action). A simple in-flight guard prevents save-storms (autosave, + // Save All) from queueing redundant scans. + let scanOnSaveBusy = false; + context.subscriptions.push( + vscode.workspace.onDidSaveTextDocument(async (doc) => { + const config = vscode.workspace.getConfiguration("pipelineCheck"); + if (!config.get("scanOnSave", false)) { + return; + } + // Only react to saves of CI-relevant files. providerForPath + // returns `undefined` for anything that doesn't match our + // glob patterns, so package.json / random YAML never triggers. + if (!providerForPath(doc.uri.fsPath)) { + return; + } + if (scanOnSaveBusy) { + return; + } + scanOnSaveBusy = true; + try { + await scanWorkspace({ quiet: true }); + } finally { + scanOnSaveBusy = false; + } + }), + ); + // Fire-and-forget the one-time "what's new" toast for users who // just upgraded. Detached so a not-yet-dismissed notification never // blocks activation (same lesson as the LSP-failure toast). The diff --git a/src/test/integration/activation.test.ts b/src/test/integration/activation.test.ts index 7670adc..b14ace1 100644 --- a/src/test/integration/activation.test.ts +++ b/src/test/integration/activation.test.ts @@ -79,6 +79,8 @@ suite("Pipeline-Check — activation", () => { "serverArgs", "severityThreshold", "disabledProviders", + "codeLens.enabled", + "scanOnSave", "trace.server", ]) { const info = config.inspect(key); diff --git a/src/workspaceScan.ts b/src/workspaceScan.ts index 9bd49d9..11580dc 100644 --- a/src/workspaceScan.ts +++ b/src/workspaceScan.ts @@ -33,6 +33,17 @@ export interface ScanResult { readonly cancelled: boolean; } +export interface ScanOptions { + /** + * Quiet scans render as a status-bar progress item (no modal toast) + * and suppress the completion notification. Used by the scan-on-save + * path so a save-heavy workflow doesn't paper the screen with toasts. + * The user-initiated scan command still uses the notification surface + * — discoverable progress + a cancellation button. + */ + readonly quiet?: boolean; +} + /** * Walk the workspace, open every candidate document, and let the LSP's * `didOpen` pipeline produce diagnostics. Returns a summary the caller @@ -42,21 +53,28 @@ export interface ScanResult { * counted but never abort the scan — one bad file shouldn't hide * findings in the other 49. */ -export async function scanWorkspace(): Promise { +export async function scanWorkspace( + options: ScanOptions = {}, +): Promise { + const quiet = options.quiet === true; const folders = vscode.workspace.workspaceFolders; if (!folders || folders.length === 0) { - void vscode.window.showInformationMessage( - "Pipeline-Check: open a workspace folder before scanning.", - ); + if (!quiet) { + void vscode.window.showInformationMessage( + "Pipeline-Check: open a workspace folder before scanning.", + ); + } return { scanned: 0, failed: 0, cancelled: false }; } const uris = await vscode.workspace.findFiles(buildScanGlob(), EXCLUDE_GLOB); if (uris.length === 0) { - void vscode.window.showInformationMessage( - "Pipeline-Check: no scannable files found in this workspace.", - ); + if (!quiet) { + void vscode.window.showInformationMessage( + "Pipeline-Check: no scannable files found in this workspace.", + ); + } return { scanned: 0, failed: 0, cancelled: false }; } @@ -66,9 +84,16 @@ export async function scanWorkspace(): Promise { await vscode.window.withProgress( { - location: vscode.ProgressLocation.Notification, + // Status-bar spinner in quiet mode, full modal progress for the + // user-initiated command. The status-bar surface has no inherent + // cancellation affordance, so we drop `cancellable` too — a + // quiet scan-on-save scan is short-lived enough that not having a + // cancel button isn't a regression in practice. + location: quiet + ? vscode.ProgressLocation.Window + : vscode.ProgressLocation.Notification, title: "Pipeline-Check: scanning workspace", - cancellable: true, + cancellable: !quiet, }, async (progress, token) => { const step = 100 / uris.length; @@ -96,11 +121,13 @@ export async function scanWorkspace(): Promise { }, ); - const summary = formatSummary({ scanned, failed, cancelled }); - if (cancelled || failed > 0) { - void vscode.window.showWarningMessage(summary); - } else { - void vscode.window.showInformationMessage(summary); + if (!quiet) { + const summary = formatSummary({ scanned, failed, cancelled }); + if (cancelled || failed > 0) { + void vscode.window.showWarningMessage(summary); + } else { + void vscode.window.showInformationMessage(summary); + } } return { scanned, failed, cancelled }; }