From 05d411714629047a24b5da741e4070b52fa9cf0c Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:02:20 -0500 Subject: [PATCH 01/15] update repo and file detection --- CHANGELOG.md | 7 +++++++ src/extension.ts | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6148b03..0b85eff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.4] - 2026-02-10 + +### Changed + +- GitGerbil will now wait for up to 5 seconds to detect a git repo when activating instead of immediately failing. +- File extensions like `.test.ts` will be correctly detected as `.ts` files now and scanned if the base extension is in the list of scanned file types. + ## [0.1.3] - 2026-02-08 ### Added diff --git a/src/extension.ts b/src/extension.ts index 7258109..dd6b37a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,5 +1,5 @@ import { checkComments, scanSecretKeys, validateFileName, type LineRange } from "./validate"; -import type { GitExtension, Repository } from "./types/git"; +import type { API, GitExtension, Repository } from "./types/git"; import * as commands from "./commands"; import * as vscode from "vscode"; @@ -43,7 +43,7 @@ async function checkFile(repo: Repository, uri: vscode.Uri): Promise { if (ignoredFiles.has(uri.fsPath.split("/").pop() ?? "")) return diagnostics.delete(uri); if ((await repo.checkIgnore([uri.fsPath])).size) return diagnostics.delete(uri); // ? if not a dotfile and the extension isnt in scannedFiles - if (!/^\.[^./\\]+$/.test(uri.fsPath.split("/").pop() ?? "") && !scannedFiles.has(/^[^.]+\.([^.]+)$/.exec(uri.fsPath)?.[1] ?? "")) return diagnostics.delete(uri); + if (!/^\.[^./\\]+$/.test(uri.fsPath.split("/").pop() ?? "") && !scannedFiles.has(uri.fsPath.split(".").pop() ?? "")) return diagnostics.delete(uri); const buffer = await vscode.workspace.fs.readFile(uri); const content = Buffer.from(buffer).toString("utf-8"); @@ -69,7 +69,15 @@ async function checkFile(repo: Repository, uri: vscode.Uri): Promise { else diagnostics.delete(uri); } -export function activate(context: vscode.ExtensionContext) { +async function waitForGitRepo(git: API, timeout = 5000) { + const start = Date.now(); + while (git.repositories.length === 0) { + if (Date.now() - start > timeout) throw new Error("Git repository not detected"); + await new Promise((res) => setTimeout(res, 100)); + } +} + +export async function activate(context: vscode.ExtensionContext) { const gitExtension = vscode.extensions.getExtension("vscode.git")?.exports; if (!gitExtension) throw new Error("Git extension not found"); @@ -77,6 +85,7 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(diagnostics); const git = gitExtension.getAPI(1); + await waitForGitRepo(git); const repo = git.repositories[0]; const scanningConfig = vscode.workspace.getConfiguration("gitgerbil"); @@ -117,9 +126,6 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand("gitgerbil.disableCommentScanning", commands.disableCommentScanning) ); - // TODO: add unit tests - if (!repo) throw new Error("No git repository found in the workspace"); - async function checkAllFiles(path: vscode.Uri) { const directory = await vscode.workspace.fs.readDirectory(path); From b9de6958825132a62295d979de7c120235bc436f Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:03:04 -0500 Subject: [PATCH 02/15] update commands --- CHANGELOG.md | 2 ++ package.json | 24 ++++++------------------ src/commands.ts | 43 +++++++++++++++++-------------------------- src/extension.ts | 11 ++++------- 4 files changed, 29 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b85eff..0871c5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - GitGerbil will now wait for up to 5 seconds to detect a git repo when activating instead of immediately failing. - File extensions like `.test.ts` will be correctly detected as `.ts` files now and scanned if the base extension is in the list of scanned file types. +- Replaced `enable` and `disable` commands (i.e. `gitgerbil.enableSecretScanning`) with `toggle` commands. +- Submitting an empty field when running `gitgerbil.setScannedFileTypes` will now reset to the default list of scanned file types instead of an empty list. ## [0.1.3] - 2026-02-08 diff --git a/package.json b/package.json index 27baee4..0e86b63 100644 --- a/package.json +++ b/package.json @@ -94,28 +94,16 @@ "title": "GitGerbil: Set Scanned File Types" }, { - "command": "gitgerbil.enableFilePathScanning", - "title": "GitGerbil: Enable File Path Scanning" + "command": "gitgerbil.toggleFilePathScanning", + "title": "GitGerbil: Toggle File Path Scanning" }, { - "command": "gitgerbil.disableFilePathScanning", - "title": "GitGerbil: Disable File Path Scanning" + "command": "gitgerbil.toggleSecretScanning", + "title": "GitGerbil: Toggle Secret Scanning" }, { - "command": "gitgerbil.enableSecretScanning", - "title": "GitGerbil: Enable Secret Scanning" - }, - { - "command": "gitgerbil.disableSecretScanning", - "title": "GitGerbil: Disable Secret Scanning" - }, - { - "command": "gitgerbil.enableCommentScanning", - "title": "GitGerbil: Enable Comment Scanning" - }, - { - "command": "gitgerbil.disableCommentScanning", - "title": "GitGerbil: Disable Comment Scanning" + "command": "gitgerbil.toggleCommentScanning", + "title": "GitGerbil: Toggle Comment Scanning" } ] } diff --git a/src/commands.ts b/src/commands.ts index 0b51fdf..7ac5eed 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,4 +1,5 @@ import * as vscode from "vscode"; +import { defaultScannedFiles } from "./extension"; export async function handleScannedFileTypes(): Promise { const config = vscode.workspace.getConfiguration("gitgerbil"); @@ -9,49 +10,39 @@ export async function handleScannedFileTypes(): Promise { prompt: "Enter file extensions to scan, separated by commas", value: value.join(", "), validateInput: (value) => { + if (!value) return; const extensions = value.split(",").map((ext) => ext.trim()); if (extensions.some((ext) => !/^[a-zA-Z0-9]+$/.test(ext))) return "File extensions must be alphanumeric and cannot contain dots or spaces"; } }); - if (!input) return; + if (input === undefined) return; + + const newValue = input.length === 0 ? (defaultScannedFiles as unknown as string[]) : input.split(",").map((ext) => ext.trim()); - const newValue = input.split(",").map((ext) => ext.trim()); await config.update("scannedFileTypes", newValue, vscode.ConfigurationTarget.Global); vscode.window.showInformationMessage(`Scanned file extensions updated.`); } -export async function enableFilePathScanning(): Promise { +export async function toggleFilePathScanning(): Promise { const config = vscode.workspace.getConfiguration("gitgerbil"); - await config.update("enableFilePathScanning", true, vscode.ConfigurationTarget.Global); - await vscode.window.showInformationMessage("File path scanning enabled."); -} + const newValue = !config.get("toggleFilePathScanning"); -export async function enableSecretScanning(): Promise { - const config = vscode.workspace.getConfiguration("gitgerbil"); - await config.update("enableSecretScanning", true, vscode.ConfigurationTarget.Global); - await vscode.window.showInformationMessage("Secret scanning enabled."); + await config.update("toggleFilePathScanning", newValue, vscode.ConfigurationTarget.Global); + await vscode.window.showInformationMessage(`File path scanning ${newValue ? "enabled" : "disabled"}.`); } -export async function enableCommentScanning(): Promise { +export async function toggleSecretScanning(): Promise { const config = vscode.workspace.getConfiguration("gitgerbil"); - await config.update("enableCommentScanning", true, vscode.ConfigurationTarget.Global); - await vscode.window.showInformationMessage("Comment scanning enabled."); -} + const newValue = !config.get("toggleSecretScanning"); -export async function disableFilePathScanning(): Promise { - const config = vscode.workspace.getConfiguration("gitgerbil"); - await config.update("enableFilePathScanning", false, vscode.ConfigurationTarget.Global); - await vscode.window.showInformationMessage("File path scanning disabled."); + await config.update("toggleSecretScanning", newValue, vscode.ConfigurationTarget.Global); + await vscode.window.showInformationMessage(`Secret scanning ${newValue ? "enabled" : "disabled"}.`); } -export async function disableSecretScanning(): Promise { +export async function toggleCommentScanning(): Promise { const config = vscode.workspace.getConfiguration("gitgerbil"); - await config.update("enableSecretScanning", false, vscode.ConfigurationTarget.Global); - await vscode.window.showInformationMessage("Secret scanning disabled."); -} + const newValue = !config.get("toggleCommentScanning"); -export async function disableCommentScanning(): Promise { - const config = vscode.workspace.getConfiguration("gitgerbil"); - await config.update("enableCommentScanning", false, vscode.ConfigurationTarget.Global); - await vscode.window.showInformationMessage("Comment scanning disabled."); + await config.update("toggleCommentScanning", newValue, vscode.ConfigurationTarget.Global); + await vscode.window.showInformationMessage(`Comment scanning ${newValue ? "enabled" : "disabled"}.`); } diff --git a/src/extension.ts b/src/extension.ts index dd6b37a..cefb17d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,7 +3,7 @@ import type { API, GitExtension, Repository } from "./types/git"; import * as commands from "./commands"; import * as vscode from "vscode"; -const defaultScannedFiles = ["ts", "js", "jsx", "tsx", "vue", "py", "rb", "go", "java", "php", "cs", "cpp", "c", "h", "rs", "html", "css", "scss", "less", "json", "yaml", "yml", "md"] as const; +export const defaultScannedFiles = ["ts", "js", "jsx", "tsx", "vue", "py", "rb", "go", "java", "php", "cs", "cpp", "c", "h", "rs", "html", "css", "scss", "less", "json", "yaml", "yml", "md"] as const; const ignoredFiles = new Set(["package-lock.json", "yarn.lock", "pnpm-lock.yaml", "Cargo.lock", "Gemfile.lock", "go.sum"]); const scannedFiles = new Set(); const scanningOptions = { @@ -118,12 +118,9 @@ export async function activate(context: vscode.ExtensionContext) { // * commands context.subscriptions.push( vscode.commands.registerCommand("gitgerbil.setScannedFileTypes", commands.handleScannedFileTypes), - vscode.commands.registerCommand("gitgerbil.enableFilePathScanning", commands.enableFilePathScanning), - vscode.commands.registerCommand("gitgerbil.enableSecretScanning", commands.enableSecretScanning), - vscode.commands.registerCommand("gitgerbil.enableCommentScanning", commands.enableCommentScanning), - vscode.commands.registerCommand("gitgerbil.disableFilePathScanning", commands.disableFilePathScanning), - vscode.commands.registerCommand("gitgerbil.disableSecretScanning", commands.disableSecretScanning), - vscode.commands.registerCommand("gitgerbil.disableCommentScanning", commands.disableCommentScanning) + vscode.commands.registerCommand("gitgerbil.toggleFilePathScanning", commands.toggleFilePathScanning), + vscode.commands.registerCommand("gitgerbil.toggleSecretScanning", commands.toggleSecretScanning), + vscode.commands.registerCommand("gitgerbil.toggleCommentScanning", commands.toggleCommentScanning) ); async function checkAllFiles(path: vscode.Uri) { From a22be0294aad63cff95c6970c5d27fc2fa75fcbd Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:03:36 -0500 Subject: [PATCH 03/15] fix #2 --- CHANGELOG.md | 2 ++ README.md | 2 ++ src/validate.ts | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0871c5f..a81b170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - File extensions like `.test.ts` will be correctly detected as `.ts` files now and scanned if the base extension is in the list of scanned file types. - Replaced `enable` and `disable` commands (i.e. `gitgerbil.enableSecretScanning`) with `toggle` commands. - Submitting an empty field when running `gitgerbil.setScannedFileTypes` will now reset to the default list of scanned file types instead of an empty list. +- Updated the README to mention `gitgerbil-ignore-file`. +- `.env.example` files will no longer be flagged by file path scanning. ## [0.1.3] - 2026-02-08 diff --git a/README.md b/README.md index de23a33..687f71d 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ Also also scans for TODO or FIXME comments in your code and gives a friendly rem > \[!TIP\] > > GitGerbil can have false positives. To ignore a line, add `// gitgerbil-ignore-line` above it (or whatever comment syntax your language uses). +> +> Or to ignore an entire file, add `// gitgerbil-ignore-file` at the top of the file. ## Extension Settings diff --git a/src/validate.ts b/src/validate.ts index fc55ca6..8002d47 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -7,7 +7,7 @@ import * as vscode from "vscode"; export function validateFileName(uri: vscode.Uri): 0 | 1 | 2 { const fileName = uri.fsPath.split("/").pop() ?? ""; const filePatterns = [ - /.*\.env.*/i, + /^\.env(?!\.example).*/i, /.*\.key.*/i, /.*\.pem.*/i, /.*\.p12.*/i, From be43d998b03889b1913c4479a6ec5ac88db797f2 Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:30:48 -0500 Subject: [PATCH 04/15] add strict secret scanning --- CHANGELOG.md | 13 ++++++++++++- package.json | 9 +++++++++ src/commands.ts | 20 ++++++++++++++------ src/extension.ts | 22 +++++++++++++--------- src/validate.ts | 26 +++++++++++++++++++++----- 5 files changed, 69 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a81b170..68c23d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.1.4] - 2026-02-10 +### Added + +- Added `enableStrictSecretScanning` setting (and corresponding `toggleStrictSecretScanning` command) that, when enabled, makes secret scanning skip files that don't have a common secret indicator (like "API_SECRET"). + ### Changed - GitGerbil will now wait for up to 5 seconds to detect a git repo when activating instead of immediately failing. - File extensions like `.test.ts` will be correctly detected as `.ts` files now and scanned if the base extension is in the list of scanned file types. -- Replaced `enable` and `disable` commands (i.e. `gitgerbil.enableSecretScanning`) with `toggle` commands. - Submitting an empty field when running `gitgerbil.setScannedFileTypes` will now reset to the default list of scanned file types instead of an empty list. - Updated the README to mention `gitgerbil-ignore-file`. - `.env.example` files will no longer be flagged by file path scanning. +### Removed + +- Replaced `enable` and `disable` commands (i.e. `gitgerbil.enableSecretScanning`) with `toggle` commands. + - `gitgerbil.toggleFilePathScanning` + - `gitgerbil.toggleSecretScanning` + - `gitgerbil.toggleStrictSecretScanning` + - `gitgerbil.toggleCommentScanning` + ## [0.1.3] - 2026-02-08 ### Added diff --git a/package.json b/package.json index 0e86b63..6cac67a 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,11 @@ "default": true, "description": "Enable or disable secret scanning in tracked git files. If enabled, errors will be shown where potential secrets are found." }, + "gitgerbil.enableStrictSecretScanning": { + "type": "boolean", + "default": true, + "description": "Enable or disable strict secret scanning in tracked git files. If enabled, secret scanning will only run if potential secret indicators are found in the content. Does nothing if secret scanning is disabled." + }, "gitgerbil.enableCommentScanning": { "type": "boolean", "default": true, @@ -101,6 +106,10 @@ "command": "gitgerbil.toggleSecretScanning", "title": "GitGerbil: Toggle Secret Scanning" }, + { + "command": "gitgerbil.toggleStrictSecretScanning", + "title": "GitGerbil: Toggle Strict Secret Scanning" + }, { "command": "gitgerbil.toggleCommentScanning", "title": "GitGerbil: Toggle Comment Scanning" diff --git a/src/commands.ts b/src/commands.ts index 7ac5eed..7e6c310 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -25,24 +25,32 @@ export async function handleScannedFileTypes(): Promise { export async function toggleFilePathScanning(): Promise { const config = vscode.workspace.getConfiguration("gitgerbil"); - const newValue = !config.get("toggleFilePathScanning"); + const newValue = !config.get("enableFilePathScanning"); - await config.update("toggleFilePathScanning", newValue, vscode.ConfigurationTarget.Global); + await config.update("enableFilePathScanning", newValue, vscode.ConfigurationTarget.Global); await vscode.window.showInformationMessage(`File path scanning ${newValue ? "enabled" : "disabled"}.`); } export async function toggleSecretScanning(): Promise { const config = vscode.workspace.getConfiguration("gitgerbil"); - const newValue = !config.get("toggleSecretScanning"); + const newValue = !config.get("enableSecretScanning"); - await config.update("toggleSecretScanning", newValue, vscode.ConfigurationTarget.Global); + await config.update("enableSecretScanning", newValue, vscode.ConfigurationTarget.Global); await vscode.window.showInformationMessage(`Secret scanning ${newValue ? "enabled" : "disabled"}.`); } +export async function toggleStrictSecretScanning(): Promise { + const config = vscode.workspace.getConfiguration("gitgerbil"); + const newValue = !config.get("enableStrictSecretScanning"); + + await config.update("enableStrictSecretScanning", newValue, vscode.ConfigurationTarget.Global); + await vscode.window.showInformationMessage(`Strict secret scanning ${newValue ? "enabled" : "disabled"}.`); +} + export async function toggleCommentScanning(): Promise { const config = vscode.workspace.getConfiguration("gitgerbil"); - const newValue = !config.get("toggleCommentScanning"); + const newValue = !config.get("enableCommentScanning"); - await config.update("toggleCommentScanning", newValue, vscode.ConfigurationTarget.Global); + await config.update("enableCommentScanning", newValue, vscode.ConfigurationTarget.Global); await vscode.window.showInformationMessage(`Comment scanning ${newValue ? "enabled" : "disabled"}.`); } diff --git a/src/extension.ts b/src/extension.ts index cefb17d..77a5c18 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,6 +9,7 @@ const scannedFiles = new Set(); const scanningOptions = { filePathScanning: true, secretScanning: true, + strictSecretScanning: true, commentScanning: true }; let diagnostics: vscode.DiagnosticCollection; @@ -47,17 +48,16 @@ async function checkFile(repo: Repository, uri: vscode.Uri): Promise { const buffer = await vscode.workspace.fs.readFile(uri); const content = Buffer.from(buffer).toString("utf-8"); - const contentLines = content.split("\n"); const fileDiagnostics: vscode.Diagnostic[] = []; - const fileIsIgnored = contentLines[0]?.includes("gitgerbil-ignore-file"); + const fileIsIgnored = content.split("\n")[0]?.includes("gitgerbil-ignore-file"); if (!fileIsIgnored && scanningOptions.filePathScanning) { const fileNameViolation = validateFileName(uri); if (fileNameViolation) fileDiagnostics.push(createDiagnostic(`${fileNameViolation === 1 ? "File" : "Folder"} name matches a sensitive pattern`, vscode.DiagnosticSeverity.Warning)); } if (!fileIsIgnored && scanningOptions.secretScanning) { - const secretResults = scanSecretKeys(contentLines); + const secretResults = scanSecretKeys(content, scanningOptions.strictSecretScanning); if (secretResults.length) fileDiagnostics.push(...secretResults.map(([range, message]) => createDiagnostic(message, vscode.DiagnosticSeverity.Error, range))); } if (!fileIsIgnored && scanningOptions.commentScanning) { @@ -94,6 +94,7 @@ export async function activate(context: vscode.ExtensionContext) { scanningOptions.filePathScanning = scanningConfig.get("enableFilePathScanning", scanningOptions.filePathScanning); scanningOptions.secretScanning = scanningConfig.get("enableSecretScanning", scanningOptions.secretScanning); scanningOptions.commentScanning = scanningConfig.get("enableCommentScanning", scanningOptions.commentScanning); + scanningOptions.strictSecretScanning = scanningConfig.get("enableStrictSecretScanning", scanningOptions.strictSecretScanning); // * check settings context.subscriptions.push( @@ -109,7 +110,9 @@ export async function activate(context: vscode.ExtensionContext) { } else if (event.affectsConfiguration("gitgerbil.enableFilePathScanning")) scanningOptions.filePathScanning = config.get("enableFilePathScanning", scanningOptions.filePathScanning); else if (event.affectsConfiguration("gitgerbil.enableSecretScanning")) scanningOptions.secretScanning = config.get("enableSecretScanning", scanningOptions.secretScanning); else if (event.affectsConfiguration("gitgerbil.enableCommentScanning")) scanningOptions.commentScanning = config.get("enableCommentScanning", scanningOptions.commentScanning); - + else if (event.affectsConfiguration("gitgerbil.enableStrictSecretScanning")) + scanningOptions.strictSecretScanning = config.get("enableStrictSecretScanning", scanningOptions.strictSecretScanning); + console.warn(scanningOptions.strictSecretScanning); const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; if (workspaceFolder) checkAllFiles(workspaceFolder.uri); }) @@ -120,7 +123,8 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand("gitgerbil.setScannedFileTypes", commands.handleScannedFileTypes), vscode.commands.registerCommand("gitgerbil.toggleFilePathScanning", commands.toggleFilePathScanning), vscode.commands.registerCommand("gitgerbil.toggleSecretScanning", commands.toggleSecretScanning), - vscode.commands.registerCommand("gitgerbil.toggleCommentScanning", commands.toggleCommentScanning) + vscode.commands.registerCommand("gitgerbil.toggleCommentScanning", commands.toggleCommentScanning), + vscode.commands.registerCommand("gitgerbil.toggleStrictSecretScanning", commands.toggleStrictSecretScanning) ); async function checkAllFiles(path: vscode.Uri) { @@ -139,10 +143,6 @@ export async function activate(context: vscode.ExtensionContext) { } } - // * check all files on actviation - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; - if (workspaceFolder) checkAllFiles(workspaceFolder.uri); - // * add watchers const watchFn = (uri: vscode.Uri) => { if (uri.path.includes(".gitignore") && workspaceFolder) checkAllFiles(workspaceFolder.uri); @@ -152,4 +152,8 @@ export async function activate(context: vscode.ExtensionContext) { watcher.onDidChange(watchFn); watcher.onDidCreate(watchFn); context.subscriptions.push(watcher); + + // * check all files on actviation + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + // if (workspaceFolder) await checkAllFiles(workspaceFolder.uri); } diff --git a/src/validate.ts b/src/validate.ts index 8002d47..3ed010c 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -35,13 +35,29 @@ export function validateFileName(uri: vscode.Uri): 0 | 1 | 2 { type LinePosition = [line: number, col: number]; export type LineRange = [from: LinePosition, to: LinePosition]; +const snakeCaseIndicators = ["access_key", "secret_key", "access_token", "api_key", "api_secret", "app_secret", "application_key", "app_key", "auth_token", "auth_secret"] as const; +// prettier-ignore +const indicators = [ + snakeCaseIndicators, // * snake_case + snakeCaseIndicators.map((i) => i.toUpperCase()), // * UPPER_SNAKE_CASE + snakeCaseIndicators.map((i) => i.split("_").map((part, index) => (index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1))).join("")), // * camelCase + snakeCaseIndicators.map((i) => i.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")), // * PascalCase + snakeCaseIndicators.map((i) => i.replace(/_/g, "-")), // * kebab-case + snakeCaseIndicators.map((i) => i.toUpperCase().replace(/_/g, "-")), // * UPPER-KEBAB-CASE + snakeCaseIndicators.map((i) => i.replace(/_/g, "")), // * flatcase + snakeCaseIndicators.map((i) => i.toUpperCase().replace(/_/g, "")) // * UPPERFLATCASE +].flat(); + /** Scans the content for potential secret keys based on predefined patterns. * @param content The content to scan for secret keys. * @returns An array of tuples containing the range of the detected secret key and the message. */ -export function scanSecretKeys(content: string[]): [range: LineRange, message: string][] { +export function scanSecretKeys(content: string, isStrict = true): [range: LineRange, message: string][] { + const contentLines = content.split("\n"); const results: [range: LineRange, message: string][] = []; + if (isStrict && !indicators.some((indicator) => content.includes(indicator))) return []; + const patterns = [ /[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/, // * jwt /sk_live_[0-9a-zA-Z]{24}/, // * stripe @@ -57,8 +73,8 @@ export function scanSecretKeys(content: string[]): [range: LineRange, message: s /(?=(?:[0-9a-fA-F]*[0-9]){4,})[0-9a-fA-F]{16,}/ // generic hex pattern ] as const; - for (let i = 0; i < content.length; i++) { - const line = content[i]; + for (let i = 0; i < contentLines.length; i++) { + const line = contentLines[i]; patternMatch: for (const pattern of patterns) { const match = pattern.exec(line); @@ -66,7 +82,7 @@ export function scanSecretKeys(content: string[]): [range: LineRange, message: s const matchedString = match[0]; const startIndex = match.index; - if (content[i - 1]?.includes("gitgerbil-ignore-line")) break patternMatch; + if (contentLines[i - 1]?.includes("gitgerbil-ignore-line")) break patternMatch; const startLine = i; const startCol = startIndex; @@ -110,7 +126,7 @@ export function checkComments(content: string): [range: LineRange, message: stri const startIndex = match.index; const precedingLines = content.slice(0, startIndex).split("\n"); - if (precedingLines[precedingLines.length - 2].includes("gitgerbil-ignore-line")) continue; + if (precedingLines[precedingLines.length - 2]?.includes("gitgerbil-ignore-line")) continue; const startLine = precedingLines.length - 1; const startCol = precedingLines[precedingLines.length - 1].length; From 394a9d2901d3e8038123fe45e98b4bc9e445ba84 Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 18:49:46 -0500 Subject: [PATCH 05/15] tweaks --- CHANGELOG.md | 3 +++ package.json | 5 ++++- src/commands.ts | 2 +- src/extension.ts | 5 +++-- src/validate.ts | 11 +++++------ 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68c23d6..430d11e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added `enableStrictSecretScanning` setting (and corresponding `toggleStrictSecretScanning` command) that, when enabled, makes secret scanning skip files that don't have a common secret indicator (like "API_SECRET"). +- Added `.svelte`, `.txt`, and `.toml` to the default list of scanned file extensions. ### Changed @@ -18,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Submitting an empty field when running `gitgerbil.setScannedFileTypes` will now reset to the default list of scanned file types instead of an empty list. - Updated the README to mention `gitgerbil-ignore-file`. - `.env.example` files will no longer be flagged by file path scanning. +- Fixed file name detection for files nested in subdirectories. ### Removed @@ -26,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `gitgerbil.toggleSecretScanning` - `gitgerbil.toggleStrictSecretScanning` - `gitgerbil.toggleCommentScanning` +- Removed SQL from comment scanning. ## [0.1.3] - 2026-02-08 diff --git a/package.json b/package.json index 6cac67a..176b834 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "jsx", "tsx", "vue", + "svelte", "py", "rb", "go", @@ -67,7 +68,9 @@ "json", "yaml", "yml", - "md" + "md", + "txt", + "toml" ], "description": "List of file extensions that will be scanned if any scanning options are enabled. Dotfiles are automatically included and do not need to be specified here." }, diff --git a/src/commands.ts b/src/commands.ts index 7e6c310..77f73de 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,5 +1,5 @@ -import * as vscode from "vscode"; import { defaultScannedFiles } from "./extension"; +import * as vscode from "vscode"; export async function handleScannedFileTypes(): Promise { const config = vscode.workspace.getConfiguration("gitgerbil"); diff --git a/src/extension.ts b/src/extension.ts index 77a5c18..821093e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,7 +3,8 @@ import type { API, GitExtension, Repository } from "./types/git"; import * as commands from "./commands"; import * as vscode from "vscode"; -export const defaultScannedFiles = ["ts", "js", "jsx", "tsx", "vue", "py", "rb", "go", "java", "php", "cs", "cpp", "c", "h", "rs", "html", "css", "scss", "less", "json", "yaml", "yml", "md"] as const; +// prettier-ignore +export const defaultScannedFiles = ["ts", "js", "jsx", "tsx", "vue", "svelte", "py", "rb", "go", "java", "php", "cs", "cpp", "c", "h", "rs", "html", "css", "scss", "less", "json", "yaml", "yml", "md", "txt", "toml"] as const; const ignoredFiles = new Set(["package-lock.json", "yarn.lock", "pnpm-lock.yaml", "Cargo.lock", "Gemfile.lock", "go.sum"]); const scannedFiles = new Set(); const scanningOptions = { @@ -155,5 +156,5 @@ export async function activate(context: vscode.ExtensionContext) { // * check all files on actviation const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; - // if (workspaceFolder) await checkAllFiles(workspaceFolder.uri); + if (workspaceFolder) await checkAllFiles(workspaceFolder.uri); } diff --git a/src/validate.ts b/src/validate.ts index 3ed010c..863da94 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -26,8 +26,8 @@ export function validateFileName(uri: vscode.Uri): 0 | 1 | 2 { const fileNameMatches = filePatterns.some((pattern) => pattern.test(fileName)); if (fileNameMatches) return 1; - const folderName = uri.fsPath.split("/").slice(-2, -1)[0] ?? ""; - const folderPatterns = [/node_modules/i, /vendor/i, /dist/i, /build/i, /out/i, /bin/i, /obj/i, /target/i, /logs/i, /tmp/i, /temp/i, /\.?venv/i, /__pycache__/i] as const; + const folderName = uri.fsPath.split("/").slice(0, -1).join("/"); + const folderPatterns = [/node_modules/i, /vendor/i, /dist/i, /build/i, /out/i, /bin/i, /obj/i, /target/i, /logs/i, /tmp/i, /temp/i, /venv/i, /__pycache__/i] as const; return folderPatterns.some((pattern) => pattern.test(folderName)) ? 2 : 0; } @@ -69,7 +69,7 @@ export function scanSecretKeys(content: string, isStrict = true): [range: LineRa /ghp_[0-9a-zA-Z]{36}/, // * github pat /github_pat_[0-9a-zA-Z]{40}/, // * github pat /sk-[0-9a-zA-Z]{48}/, // * openai - /(?=(?:[A-Za-z0-9_-]*[0-9_-]){4,})[A-Za-z0-9_-]{20,}={1,2}/, // generic base64 pattern + /(?=(?:[A-Za-z0-9_-]*[0-9_-]){4,})[A-Za-z0-9_-]{20,}={0,2}/, // generic base64 pattern /(?=(?:[0-9a-fA-F]*[0-9]){4,})[0-9a-fA-F]{16,}/ // generic hex pattern ] as const; @@ -112,9 +112,8 @@ export function checkComments(content: string): [range: LineRange, message: stri const commentPatterns = [ /\/\/.*/g, // * singleline comments /\/\*[\s\S]*?\*\//gm, // * multiline comments - /#.*$/g, // * python comments - //gm, // * html comments - /--.*$/gm // * sql comments + /#.*/g, // * python comments + //gm // * html comments ] as const; const commentHints = ["TODO", "FIXME", "HACK", "FIX", "todo", "fixme", "hack", "fix"] as const; From d5882e49983945734534caebfdc8431e6fc4da78 Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 18:49:58 -0500 Subject: [PATCH 06/15] add tests & CI workflow --- .github/workflows/ci.yml | 74 +++++++ .vscode/extensions.json | 9 +- .vscode/launch.json | 26 +-- .vscode/settings.json | 16 +- .vscode/tasks.json | 32 +-- .vscodeignore | 3 +- package.json | 9 +- src/tests/integration/extension.test.ts | 266 ++++++++++++++++++++++++ src/tests/integration/index.ts | 23 ++ src/tests/runTests.ts | 33 +++ src/tests/unit/comment.test.ts | 35 ++++ src/tests/unit/path.test.ts | 48 +++++ src/tests/unit/secret.test.ts | 51 +++++ tsconfig.json | 6 +- 14 files changed, 576 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 src/tests/integration/extension.test.ts create mode 100644 src/tests/integration/index.ts create mode 100644 src/tests/runTests.ts create mode 100644 src/tests/unit/comment.test.ts create mode 100644 src/tests/unit/path.test.ts create mode 100644 src/tests/unit/secret.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6e5ee37 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +name: CI + +on: + push: + branches: + - main + - dev + pull_request: + branches: + - main + - dev + +jobs: + compile: + timeout-minutes: 10 + runs-on: ubuntu-latest + name: Compile + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set Node.js version to 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install dependencies + run: npm install + + - name: Compile + run: npm run compile + + test: + timeout-minutes: 10 + runs-on: ubuntu-latest + name: Test + needs: compile + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set Node.js version to 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install dependencies + run: npm install + + - name: Run tests + run: npm test + + prettier: + timeout-minutes: 10 + runs-on: ubuntu-latest + name: Prettier Check + needs: compile + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set Node.js version to 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install dependencies + run: npm install + + - name: Run Prettier check + run: npm run prettier-check diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 186459d..dde8236 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,5 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "dbaeumer.vscode-eslint", - "ms-vscode.extension-test-runner" - ] + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": ["dbaeumer.vscode-eslint", "ms-vscode.extension-test-runner"] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 8880465..a0ca3cb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,19 +3,15 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "preLaunchTask": "${defaultBuildTask}" - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "${defaultBuildTask}" + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index afdab66..ffeaf91 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,11 @@ // Place your settings in this file to overwrite default and user settings. { - "files.exclude": { - "out": false // set this to true to hide the "out" folder with the compiled JS files - }, - "search.exclude": { - "out": true // set this to false to include "out" folder in search results - }, - // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3b17e53..078ff7e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,20 +1,20 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": { - "kind": "build", - "isDefault": true - } - } - ] + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] } diff --git a/.vscodeignore b/.vscodeignore index 7844cf6..95aa396 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,10 +1,9 @@ .vscode/** .vscode-test/** src/** +tests/** !src/assets/** .gitignore -.yarnrc -vsc-extension-quickstart.md **/tsconfig.json **/eslint.config.mjs **/*.map diff --git a/package.json b/package.json index 176b834..8aa032a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "gitgerbil", "displayName": "GitGerbil", "description": "Scan your project for potential secrets, sensitive information, and less-than-ideal files that you probably shouldn't commit.", - "version": "0.1.3", + "version": "0.1.4", "publisher": "KennethNg", "icon": "./src/assets/icon.png", "repository": { @@ -22,8 +22,11 @@ "vscode:prepublish": "npm run compile", "compile": "tsc -p ./", "watch": "tsc -watch -p ./", + "prettier-check": "prettier . --check", + "unit-test": "mocha ./out/tests/unit", + "integration-test": "node ./out/tests/runTests.js", "pretest": "npm run compile", - "test": "node ./out/test/runTests.js" + "test": "npm run unit-test && npm run integration-test" }, "devDependencies": { "@types/mocha": "^10.0.10", @@ -31,7 +34,7 @@ "@types/vscode": "^1.90.0", "@vscode/test-electron": "^2.5.2", "mocha": "^11.7.5", - "tsx": "^4.21.0", + "prettier": "^3.8.1", "typescript": "^5.8.3" }, "extensionDependencies": [ diff --git a/src/tests/integration/extension.test.ts b/src/tests/integration/extension.test.ts new file mode 100644 index 0000000..ec664d0 --- /dev/null +++ b/src/tests/integration/extension.test.ts @@ -0,0 +1,266 @@ +// gitgerbil-ignore-file + +import { defaultScannedFiles } from "../../extension"; +import { execSync } from "child_process"; +import { describe } from "mocha"; +import assert from "node:assert"; +import * as vscode from "vscode"; +import path from "path"; +import fs from "fs"; + +async function activateExtension() { + const extension = vscode.extensions.getExtension("KennethNg.gitgerbil"); + assert.ok(extension, "Extension not found"); + + await extension.activate(); + assert.ok(extension.isActive, "Extension failed to activate"); +} + +async function waitForDiagnostic(fileName: string, callback: (diagnostics: vscode.Diagnostic[]) => void) { + return new Promise((resolve) => { + vscode.languages.onDidChangeDiagnostics((event) => { + const uri = event.uris.find((uri) => uri.fsPath.endsWith(fileName)); + if (!uri) return; + + const diagnostics = vscode.languages.getDiagnostics(uri); + callback(diagnostics); + resolve(); + }); + }); +} + +const redkitten6sSupabaseKey = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZlbG1qa2VxZWttaGt4c3JycndiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njg1NzE4NTksImV4cCI6MjA4NDE0Nzg1OX0.cbspqjrqcdDkREd3tOlS2TcjknjIzUUeIcX_t8eNYfE"; +const redkitten6sYouTubeKey = "AIzaSyAVQKyYxMrhgHWR8f9LJms0GVpcufhMLwc"; + +describe("Extension Tests", function () { + this.timeout(10000); + + const workspace = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; + assert.ok(workspace, "No workspace found"); + + const createFiles = (files: { name: string; content: string }[]) => { + const folder = fs.mkdtempSync(path.join(workspace, "test-")); + + for (const file of files) { + const filePath = path.join(folder, file.name); + fs.writeFileSync(filePath, file.content); + } + + execSync(`git add .`, { cwd: folder }); + execSync(`git commit -m lgtm`, { cwd: folder }); + + return folder; + }; + + test("should activate extension", async function () { + await activateExtension(); + }); + + describe("File Path Scanning", function () { + test("should give diagnostic when file path is sensitive", async function () { + const folder = createFiles([{ name: ".env", content: "super secret env variables hehe\n" }]); + + await waitForDiagnostic(`${folder}/.env`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 1, "Expected one diagnostic for unsafe file"); + }); + }); + + test("should not give diagnostics when no file path issues", async function () { + const folder = createFiles([{ name: "README.md", content: "blah blah blah\n\nnothing to see here\n" }]); + + await waitForDiagnostic(`${folder}/README.md`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for safe file"); + }); + }); + + test("should not give diagnostics when sensitive file is .gitignore'd", async function () { + const folder = createFiles([ + { name: ".gitignore", content: "*.env\n" }, + { name: ".env", content: "super secret env variables hehe\n" } + ]); + + await waitForDiagnostic(`${folder}/.env`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for .gitignore'd sensitive file"); + }); + }); + + test("should not give diagnostics if file path scanning disabled", async function () { + const folder = createFiles([{ name: ".env", content: "# gitgerbil-ignore-file\n\nsuper secret env variables hehe\n" }]); + + await waitForDiagnostic(`${folder}/.env`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics when file path scanning disabled"); + }); + }); + }); + + describe("Secret Scanning", function () { + test("should give diagnostics when secret in file", async function () { + const folder = createFiles([ + { + name: "CLAUDE.md", + content: `blah blah blah\n\nnothing to see here\n\n\n\nAPI_KEY=${redkitten6sSupabaseKey}\n` + } + ]); + + await waitForDiagnostic(`${folder}/CLAUDE.md`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 1, "Expected one diagnostic for one secret in file"); + }); + }); + + test("should give multiple diagnostics when multiple secrets in file", async function () { + const folder = createFiles([ + { + name: "CLAUDE.md", + content: `blah blah blah\n\nnothing to see here\n\n\n\nAPI_KEY=${redkitten6sSupabaseKey}\nAPI_KEY=${redkitten6sSupabaseKey}\nAPI_KEY=${redkitten6sYouTubeKey}\n\nAPI_KEY=${redkitten6sYouTubeKey}\n` + } + ]); + + await waitForDiagnostic(`${folder}/CLAUDE.md`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 4, "Expected multiple diagnostics for multiple secrets in file"); + }); + }); + + test("should not give diagnostics when no secret in file", async function () { + const folder = createFiles([{ name: "README.md", content: "blah blah blah\n\nnothing to see here\n" }]); + + await waitForDiagnostic(`${folder}/README.md`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for safe file"); + }); + }); + + test("should not give diagnostics for ignored file type", async function () { + const folder = createFiles([ + { + name: "CLAUDE.md", + content: `blah blah blah\n\nnothing to see here\n\n\n\nAPI_KEY=${redkitten6sSupabaseKey}\n` + } + ]); + + vscode.workspace.getConfiguration("gitgerbil").update("scannedFileTypes", [], vscode.ConfigurationTarget.Global); + + await waitForDiagnostic(`${folder}/CLAUDE.md`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for ignored file"); + }); + }); + + test("should not give diagnostics for .gitignore'd file type", async function () { + const folder = createFiles([ + { + name: ".gitignore", + content: "*.md\n" + }, + { + name: "CLAUDE.md", + content: `blah blah blah\n\nnothing to see here\n\n\n\nAPI_KEY=${redkitten6sSupabaseKey}\n` + } + ]); + + await waitForDiagnostic(`${folder}/CLAUDE.md`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for .gitignore'd file"); + }); + }); + + test("should not give diagnostics if secret scanning disabled", async function () { + const folder = createFiles([ + { + name: "CLAUDE.md", + content: `blah blah blah\n\nnothing to see here\n\n\n\nAPI_KEY=${redkitten6sSupabaseKey}\n` + } + ]); + + vscode.workspace.getConfiguration("gitgerbil").update("enableSecretScanning", false, vscode.ConfigurationTarget.Global); + + await waitForDiagnostic(`${folder}/CLAUDE.md`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics when secret scanning disabled"); + }); + }); + + test("should not give diagnostics if strict secret scanning enabled and no indicators in file", async function () { + const folder = createFiles([ + { + name: "CLAUDE.md", + content: `blah blah blah\n\nnothing to see here\n\n\n\n${redkitten6sSupabaseKey}\n` + } + ]); + + await waitForDiagnostic(`${folder}/CLAUDE.md`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics when strict secret scanning enabled and no indicators in file"); + }); + }); + }); + + describe("Comment Scanning", function () { + test("should give diagnostics when hint comment in file", async function () { + const folder = createFiles([ + { + name: "index.ts", + content: `// TODO: commit api key\nconst apiKey = "";\n` + } + ]); + + await waitForDiagnostic(`${folder}/index.ts`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 1, "Expected one diagnostic for one hint comment"); + }); + }); + + test("should give multiple diagnostics when multiple hint comments in file", async function () { + const folder = createFiles([ + { + name: "index.ts", + content: `// TODO: commit api key\nconst apiKey = "";\n\n// FIXME: commit other api key\nconst otherApiKey = "";\n` + } + ]); + + await waitForDiagnostic(`${folder}/index.ts`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 2, "Expected multiple diagnostics for multiple hint comments"); + }); + }); + + test("should not give diagnostics when no hint comment in file", async function () { + const folder = createFiles([{ name: "index.ts", content: `const apiKey = "";\n` }]); + + await waitForDiagnostic(`${folder}/index.ts`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for file without hint comments"); + }); + }); + + test("should not give diagnostics for ignored file type", async function () { + const folder = createFiles([ + { + name: "index.ts", + content: `// TODO: commit api key\nconst apiKey = "";\n` + } + ]); + + vscode.workspace.getConfiguration("gitgerbil").update("scannedFileTypes", [], vscode.ConfigurationTarget.Global); + + await waitForDiagnostic(`${folder}/index.ts`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for ignored file"); + }); + }); + + test("should not give diagnostics if comment scanning disabled", async function () { + const folder = createFiles([ + { + name: "index.ts", + content: `// TODO: commit api key\nconst apiKey = "";\n` + } + ]); + + vscode.workspace.getConfiguration("gitgerbil").update("enableCommentScanning", false, vscode.ConfigurationTarget.Global); + + await waitForDiagnostic(`${folder}/index.ts`, (diagnostics) => { + assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics when comment scanning disabled"); + }); + }); + }); + + this.afterEach(function () { + vscode.workspace.getConfiguration("gitgerbil").update("scannedFileTypes", defaultScannedFiles, vscode.ConfigurationTarget.Global); + vscode.workspace.getConfiguration("gitgerbil").update("enableFilePathScanning", true, vscode.ConfigurationTarget.Global); + vscode.workspace.getConfiguration("gitgerbil").update("enableSecretScanning", true, vscode.ConfigurationTarget.Global); + vscode.workspace.getConfiguration("gitgerbil").update("enableStrictSecretScanning", true, vscode.ConfigurationTarget.Global); + vscode.workspace.getConfiguration("gitgerbil").update("enableCommentScanning", true, vscode.ConfigurationTarget.Global); + }); +}); diff --git a/src/tests/integration/index.ts b/src/tests/integration/index.ts new file mode 100644 index 0000000..c27610d --- /dev/null +++ b/src/tests/integration/index.ts @@ -0,0 +1,23 @@ +import * as path from "path"; +import { glob } from "glob"; +import Mocha from "mocha"; + +export function run(): Promise { + const mocha = new Mocha({ + ui: "tdd", + color: true + }); + + const testsRoot = path.resolve(__dirname); + + return new Promise(async (resolve, reject) => { + const files = await glob("**/*.test.js", { cwd: testsRoot }); + files.forEach((file) => mocha.addFile(path.resolve(testsRoot, file))); + + try { + mocha.run((failures) => (failures > 0 ? reject(new Error(`${failures} tests failed.`)) : resolve())); + } catch (err) { + reject(err); + } + }); +} diff --git a/src/tests/runTests.ts b/src/tests/runTests.ts new file mode 100644 index 0000000..657c1bf --- /dev/null +++ b/src/tests/runTests.ts @@ -0,0 +1,33 @@ +import { runTests } from "@vscode/test-electron"; +import { execSync } from "child_process"; +import * as path from "path"; +import fs from "fs"; +import os from "os"; + +async function main() { + try { + const extensionDevelopmentPath = path.resolve(__dirname, "../../"); + const extensionTestsPath = path.resolve(__dirname, "./integration/index"); + + const repoPath = fs.mkdtempSync(path.join(os.tmpdir(), "gitgerbil-test-repo-")); + execSync("git init", { cwd: repoPath }); + execSync('git config user.email "red@kitten.six"', { cwd: repoPath }); + execSync('git config user.name "RedKitten6"', { cwd: repoPath }); + + await runTests({ + extensionDevelopmentPath, + extensionTestsPath, + launchArgs: ["--disable-workspace-trust", repoPath] + }); + + fs.glob(repoPath, (error, matches) => { + if (error) return; + for (const folder of matches) fs.rmSync(folder, { recursive: true, force: true }); + }); + } catch (error) { + console.error("Failed to run tests", error); + process.exit(1); + } +} + +main(); diff --git a/src/tests/unit/comment.test.ts b/src/tests/unit/comment.test.ts new file mode 100644 index 0000000..6438a11 --- /dev/null +++ b/src/tests/unit/comment.test.ts @@ -0,0 +1,35 @@ +import { checkComments } from "../../validate"; +import { describe, test } from "mocha"; +import assert from "node:assert"; + +describe("Comment Scanning", function () { + test("should detect a singleline comment", function () { + const content = `// TODO: this is a comment\nconst x = 6;\n`; + assert.strictEqual(checkComments(content).length, 1); + }); + + test("should detect multiple singleline comments", function () { + const content = `// TODO: this is a comment\nconst x = 6;\n\n# HACK: this is definitely not syntactically correct\ny = 7;\n`; + assert.strictEqual(checkComments(content).length, 2); + }); + + test("should detect a multiline comment", function () { + const content = `/* FIXME: this is a\n multiline comment\n */\nconst x = 6;\n`; + assert.strictEqual(checkComments(content).length, 1); + }); + + test("should detect multiple multiline comments", function () { + const content = `/* FIXME: this is a\n multiline comment\n */\nconst x = 6;\n\n\ny = 7;\n`; + assert.strictEqual(checkComments(content).length, 2); + }); + + test("should not detect comments without hints", function () { + const content = `// This is a normal comment\nconst x = 6;\n`; + assert.strictEqual(checkComments(content).length, 0); + }); + + test("should ignore lines with gitgerbil-ignore-line", function () { + const content = `// gitgerbil-ignore-line\nTODO: add a 7\nconst x = 6;\n`; + assert.strictEqual(checkComments(content).length, 0); + }); +}); diff --git a/src/tests/unit/path.test.ts b/src/tests/unit/path.test.ts new file mode 100644 index 0000000..38465d3 --- /dev/null +++ b/src/tests/unit/path.test.ts @@ -0,0 +1,48 @@ +import { validateFileName } from "../../validate"; +import { describe, test } from "mocha"; +import assert from "node:assert"; + +describe("File Name Validation", function () { + test("should flag file names that match sensitive patterns", function () { + const violations = [".env", ".env.local", ".env.development", ".env.production", ".env.test", "creds.pem"]; + + for (const fileName of violations) { + assert.strictEqual(validateFileName({ fsPath: fileName } as any), 1, `Expected "${fileName}" to be flagged as a file name violation`); + } + }); + + test("should flag folder names that match sensitive patterns", function () { + const violations = [ + "node_modules/.bin/v-lint", + "node_modules/@kennething/v-lint/src/index.js", + "dist/index.js", + "build/vlint.wasm", + "out/hello", + "bin/hello", + "tmp/test.txt", + "logs/log.txt", + ".venv/Scripts/activate", + "__pycache__/index.cpython-310.pyc" + ]; + + for (const fileName of violations) { + assert.strictEqual(validateFileName({ fsPath: fileName } as any), 2, `Expected "${fileName}" to be flagged as a folder name violation`); + } + }); + + test("should not flag safe file names", function () { + const safeFileNames = ["index.js", "app.py", "README.md", "src/configuration.ts"]; + + for (const fileName of safeFileNames) { + assert.strictEqual(validateFileName({ fsPath: fileName } as any), 0, `Expected "${fileName}" to not be flagged as a file name violation`); + } + }); + + test("should not flag files in safe folders", function () { + const safeFilePaths = ["src/index.js", "lib/app.py", "docs/README.md"]; + + for (const filePath of safeFilePaths) { + assert.strictEqual(validateFileName({ fsPath: filePath } as any), 0, `Expected "${filePath}" to not be flagged as a folder name violation`); + } + }); +}); diff --git a/src/tests/unit/secret.test.ts b/src/tests/unit/secret.test.ts new file mode 100644 index 0000000..a7761cd --- /dev/null +++ b/src/tests/unit/secret.test.ts @@ -0,0 +1,51 @@ +// gitgerbil-ignore-file + +import { scanSecretKeys } from "../../validate"; +import { describe, test } from "mocha"; +import assert from "node:assert"; + +const redkitten6sSupabaseKey = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZlbG1qa2VxZWttaGt4c3JycndiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njg1NzE4NTksImV4cCI6MjA4NDE0Nzg1OX0.cbspqjrqcdDkREd3tOlS2TcjknjIzUUeIcX_t8eNYfE"; +const redkitten6sYouTubeKey = "AIzaSyAVQKyYxMrhgHWR8f9LJms0GVpcufhMLwc"; + +describe("Secret Detection", function () { + test("should detect a valid secret", function () { + const content = `API_KEY=${redkitten6sSupabaseKey}`; + assert.strictEqual(scanSecretKeys(content).length, 1); + }); + + test("should detect a valid secret with other text", function () { + const content = `const apiKey = [({"${redkitten6sSupabaseKey}"})]`; + assert.strictEqual(scanSecretKeys(content).length, 1); + }); + + test("should detect multiple secrets in the same file", function () { + const content = [ + `API_KEY=${redkitten6sSupabaseKey}`, + `API_KEY=${redkitten6sYouTubeKey}`, + "API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30", + `API_KEY=${redkitten6sSupabaseKey}` + ]; + assert.strictEqual(scanSecretKeys(content.join("\n")).length, content.length); + }); + + test("should detect a secret without indicators if strict mode is off", function () { + const content = `("${redkitten6sSupabaseKey}");`; + assert.strictEqual(scanSecretKeys(content, false).length, 1); + }); + + test("should not detect an invalid secret", function () { + const content = ["API_KEY=this is bob", "API_KEY=bob says hi", "API_KEY=this is bob when the train goes by", "API_KEY=splat"]; + assert.strictEqual(scanSecretKeys(content.join("\n")).length, 0); + }); + + test("should not detect a secret without indicators in strict mode", function () { + const content = `("${redkitten6sSupabaseKey}");`; + assert.strictEqual(scanSecretKeys(content).length, 0); + }); + + test("should ignore secrets on lines with gitgerbil-ignore-line", function () { + const content = `// gitgerbil-ignore-line\nAPI_KEY=${redkitten6sSupabaseKey}`; + assert.strictEqual(scanSecretKeys(content).length, 0); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index f5b6dd4..fda282d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,10 +7,6 @@ "sourceMap": true, "rootDir": "src", "skipLibCheck": true, - "strict": true /* enable all strict type-checking options */ - /* Additional Checks */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ + "strict": true } } From 6fe26e3f2f5d967ab325bbb452dbf353c20a68ee Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 18:54:43 -0500 Subject: [PATCH 07/15] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e5ee37..9b92fc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: run: npm install - name: Run tests - run: npm test + run: xvfb-run -a npm test prettier: timeout-minutes: 10 From bb1f5ae121cad32d6f929e40f4d1ab3bd3d1219f Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:11:21 -0500 Subject: [PATCH 08/15] test in prod yessir --- src/tests/integration/extension.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tests/integration/extension.test.ts b/src/tests/integration/extension.test.ts index ec664d0..489a86c 100644 --- a/src/tests/integration/extension.test.ts +++ b/src/tests/integration/extension.test.ts @@ -34,7 +34,7 @@ const redkitten6sSupabaseKey = const redkitten6sYouTubeKey = "AIzaSyAVQKyYxMrhgHWR8f9LJms0GVpcufhMLwc"; describe("Extension Tests", function () { - this.timeout(10000); + this.timeout(1000); const workspace = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; assert.ok(workspace, "No workspace found"); @@ -68,6 +68,9 @@ describe("Extension Tests", function () { test("should not give diagnostics when no file path issues", async function () { const folder = createFiles([{ name: "README.md", content: "blah blah blah\n\nnothing to see here\n" }]); + console.log("aaaaaa " + folder); + + console.log("bbbbb " + vscode.languages.getDiagnostics(vscode.Uri.file(path.join(folder, "README.md")))); await waitForDiagnostic(`${folder}/README.md`, (diagnostics) => { assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for safe file"); From e90e4bd405e43636c58c8fb987bd4249181ab782 Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:17:19 -0500 Subject: [PATCH 09/15] aaaaa --- src/tests/integration/extension.test.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/tests/integration/extension.test.ts b/src/tests/integration/extension.test.ts index 489a86c..85fa9c8 100644 --- a/src/tests/integration/extension.test.ts +++ b/src/tests/integration/extension.test.ts @@ -47,9 +47,7 @@ describe("Extension Tests", function () { fs.writeFileSync(filePath, file.content); } - execSync(`git add .`, { cwd: folder }); - execSync(`git commit -m lgtm`, { cwd: folder }); - + console.log(folder); return folder; }; @@ -68,9 +66,6 @@ describe("Extension Tests", function () { test("should not give diagnostics when no file path issues", async function () { const folder = createFiles([{ name: "README.md", content: "blah blah blah\n\nnothing to see here\n" }]); - console.log("aaaaaa " + folder); - - console.log("bbbbb " + vscode.languages.getDiagnostics(vscode.Uri.file(path.join(folder, "README.md")))); await waitForDiagnostic(`${folder}/README.md`, (diagnostics) => { assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for safe file"); From d931e077d14418156a7c7ef754f87f7b0a0e3567 Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:20:42 -0500 Subject: [PATCH 10/15] Update extension.test.ts --- src/tests/integration/extension.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tests/integration/extension.test.ts b/src/tests/integration/extension.test.ts index 85fa9c8..f1dd63d 100644 --- a/src/tests/integration/extension.test.ts +++ b/src/tests/integration/extension.test.ts @@ -48,6 +48,7 @@ describe("Extension Tests", function () { } console.log(folder); + console.log(execSync("ls -la " + folder).toString()); return folder; }; From 42f172bf09012dada95fc9b0b143a22da7fb0262 Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:24:43 -0500 Subject: [PATCH 11/15] Update extension.test.ts --- src/tests/integration/extension.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tests/integration/extension.test.ts b/src/tests/integration/extension.test.ts index f1dd63d..57f4a3b 100644 --- a/src/tests/integration/extension.test.ts +++ b/src/tests/integration/extension.test.ts @@ -18,12 +18,13 @@ async function activateExtension() { async function waitForDiagnostic(fileName: string, callback: (diagnostics: vscode.Diagnostic[]) => void) { return new Promise((resolve) => { - vscode.languages.onDidChangeDiagnostics((event) => { + const listener = vscode.languages.onDidChangeDiagnostics((event) => { const uri = event.uris.find((uri) => uri.fsPath.endsWith(fileName)); if (!uri) return; const diagnostics = vscode.languages.getDiagnostics(uri); callback(diagnostics); + listener.dispose(); resolve(); }); }); @@ -47,8 +48,6 @@ describe("Extension Tests", function () { fs.writeFileSync(filePath, file.content); } - console.log(folder); - console.log(execSync("ls -la " + folder).toString()); return folder; }; @@ -69,6 +68,7 @@ describe("Extension Tests", function () { const folder = createFiles([{ name: "README.md", content: "blah blah blah\n\nnothing to see here\n" }]); await waitForDiagnostic(`${folder}/README.md`, (diagnostics) => { + console.log("a"); assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for safe file"); }); }); @@ -80,6 +80,7 @@ describe("Extension Tests", function () { ]); await waitForDiagnostic(`${folder}/.env`, (diagnostics) => { + console.log("b"); assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for .gitignore'd sensitive file"); }); }); From ff969848cbe462494c89ad626127fc121eb48ecd Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:27:18 -0500 Subject: [PATCH 12/15] miracle sort but miracle commit --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b92fc6..29b1242 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,10 +20,10 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Set Node.js version to 22 + - name: Set Node.js version to 24 uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 - name: Install dependencies run: npm install @@ -33,7 +33,7 @@ jobs: test: timeout-minutes: 10 - runs-on: ubuntu-latest + runs-on: macos-latest name: Test needs: compile @@ -41,16 +41,16 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Set Node.js version to 22 + - name: Set Node.js version to 24 uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 - name: Install dependencies run: npm install - name: Run tests - run: xvfb-run -a npm test + run: npm test prettier: timeout-minutes: 10 @@ -62,10 +62,10 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Set Node.js version to 22 + - name: Set Node.js version to 24 uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 - name: Install dependencies run: npm install From 8b84295cb258f1e9e8161f10a7131b1d5ddb73e1 Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:29:12 -0500 Subject: [PATCH 13/15] please --- src/tests/integration/extension.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/tests/integration/extension.test.ts b/src/tests/integration/extension.test.ts index 57f4a3b..9cd771d 100644 --- a/src/tests/integration/extension.test.ts +++ b/src/tests/integration/extension.test.ts @@ -35,8 +35,6 @@ const redkitten6sSupabaseKey = const redkitten6sYouTubeKey = "AIzaSyAVQKyYxMrhgHWR8f9LJms0GVpcufhMLwc"; describe("Extension Tests", function () { - this.timeout(1000); - const workspace = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; assert.ok(workspace, "No workspace found"); @@ -68,7 +66,6 @@ describe("Extension Tests", function () { const folder = createFiles([{ name: "README.md", content: "blah blah blah\n\nnothing to see here\n" }]); await waitForDiagnostic(`${folder}/README.md`, (diagnostics) => { - console.log("a"); assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for safe file"); }); }); @@ -80,7 +77,6 @@ describe("Extension Tests", function () { ]); await waitForDiagnostic(`${folder}/.env`, (diagnostics) => { - console.log("b"); assert.strictEqual(diagnostics.length, 0, "Expected no diagnostics for .gitignore'd sensitive file"); }); }); From 611ed4dfb0b230fa77c64ec4f7368f79584addef Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:11:17 -0500 Subject: [PATCH 14/15] remove debug log --- src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 821093e..aaef558 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -113,7 +113,7 @@ export async function activate(context: vscode.ExtensionContext) { else if (event.affectsConfiguration("gitgerbil.enableCommentScanning")) scanningOptions.commentScanning = config.get("enableCommentScanning", scanningOptions.commentScanning); else if (event.affectsConfiguration("gitgerbil.enableStrictSecretScanning")) scanningOptions.strictSecretScanning = config.get("enableStrictSecretScanning", scanningOptions.strictSecretScanning); - console.warn(scanningOptions.strictSecretScanning); + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; if (workspaceFolder) checkAllFiles(workspaceFolder.uri); }) From f737f8586e8eacaa4edc86e6f4c41e56c038ea44 Mon Sep 17 00:00:00 2001 From: Kenneth Ng <145921389+kennething@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:12:09 -0500 Subject: [PATCH 15/15] Update ci.yml --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29b1242..df6f2ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,11 +4,9 @@ on: push: branches: - main - - dev pull_request: branches: - main - - dev jobs: compile: