codeslick-cli 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -259,6 +259,7 @@ The `.codeslick.json` file controls how CodeSlick scans your code.
259
259
  | `autofix` | boolean | `false` | Enable auto-fix (experimental) |
260
260
  | `exclude` | string[] | See above | Glob patterns to exclude from scanning |
261
261
  | `languages` | string[] | All | Languages to scan: `javascript`, `typescript`, `python`, `java` |
262
+ | `telemetry` | boolean | `true` | Enable anonymous usage analytics |
262
263
 
263
264
  ### Severity Thresholds
264
265
 
@@ -533,20 +534,27 @@ MIT License - see [LICENSE](../../LICENSE) for details.
533
534
  - **Issues**: https://github.com/VitorLourenco/codeslick2/issues
534
535
  - **Email**: support@codeslick.dev
535
536
 
536
- ## What's New in v1.0
537
+ ## What's New in v1.1
538
+
539
+ - **Update Notifications** - CLI notifies you when a new version is available
540
+ - **Anonymous Telemetry** - Usage stats for dashboard analytics (disable with `cs config set telemetry false`)
541
+ - **Improved SSRF Detection** - Internal API routes (`/api/...`) no longer trigger false positives
542
+ - **Fixed Critical Sorting** - CRITICAL issues now correctly appear first in reports
543
+ - **Markdown Reports** - Auto-generates detailed reports for large scans (>20 files or >30 issues)
544
+
545
+ ### v1.0 Features
537
546
 
538
547
  - **Staged Files by Default** - Fast pre-commit scans (<1s for most commits)
539
548
  - **Quick Mode** - Skip TypeScript type checking with `--quick` for even faster scans
540
549
  - **Smart Output** - Only shows CRITICAL and HIGH issues by default (use `--verbose` for all)
541
- - **Markdown Reports** - Auto-generates detailed reports for large scans (>20 files or >30 issues)
542
550
  - **268 Security Checks** - OWASP Top 10:2025 compliant
543
551
 
544
552
  ## Roadmap
545
553
 
546
- ### v1.1 (Q2 2026)
547
- - Auto-fix support (--fix flag)
554
+ ### v1.2 (Coming Soon)
548
555
  - Custom rule configuration
549
556
  - IDE integration (VS Code extension)
557
+ - Enhanced auto-fix support
550
558
 
551
559
  ---
552
560
 
package/bin/codeslick.cjs CHANGED
@@ -25,8 +25,13 @@ const { scanCommand } = require('../dist/packages/cli/src/commands/scan');
25
25
  const { initCommand } = require('../dist/packages/cli/src/commands/init');
26
26
  const { configCommand } = require('../dist/packages/cli/src/commands/config');
27
27
  const { loginCommand, logoutCommand, whoamiCommand } = require('../dist/packages/cli/src/commands/auth');
28
+ const { startBackgroundUpdateCheck } = require('../dist/packages/cli/src/utils/version-check');
28
29
  const { version } = require('../package.json');
29
30
 
31
+ // Start version check in background (non-blocking)
32
+ // Will print notification at the end if update is available
33
+ void startBackgroundUpdateCheck();
34
+
30
35
  // Detect if running as 'cs' or 'codeslick'
31
36
  const scriptName = process.argv[1].includes('/cs') ? 'cs' : 'codeslick';
32
37
 
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Version Check Utility
3
+ *
4
+ * Checks npm registry for newer versions and notifies users.
5
+ * Uses a 24-hour cache to avoid slowing down every command.
6
+ */
7
+ /**
8
+ * Get the currently installed version
9
+ */
10
+ export declare function getInstalledVersion(): string;
11
+ /**
12
+ * Check for updates and return info if available
13
+ * Uses cache to avoid checking on every command
14
+ */
15
+ export declare function checkForUpdates(): Promise<{
16
+ hasUpdate: boolean;
17
+ currentVersion: string;
18
+ latestVersion: string;
19
+ } | null>;
20
+ /**
21
+ * Print update notification if a new version is available
22
+ * Non-blocking - runs in background
23
+ */
24
+ export declare function printUpdateNotification(): Promise<void>;
25
+ /**
26
+ * Start update check in background (non-blocking)
27
+ * Returns a promise that resolves when check is complete
28
+ */
29
+ export declare function startBackgroundUpdateCheck(): Promise<void>;
30
+ //# sourceMappingURL=version-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../../../../src/utils/version-check.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAO5C;AA6ED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,IAAI,CAAC,CAqCR;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAe7D;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC,CAE1D"}
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ /**
3
+ * Version Check Utility
4
+ *
5
+ * Checks npm registry for newer versions and notifies users.
6
+ * Uses a 24-hour cache to avoid slowing down every command.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.getInstalledVersion = getInstalledVersion;
13
+ exports.checkForUpdates = checkForUpdates;
14
+ exports.printUpdateNotification = printUpdateNotification;
15
+ exports.startBackgroundUpdateCheck = startBackgroundUpdateCheck;
16
+ const os_1 = require("os");
17
+ const path_1 = require("path");
18
+ const fs_1 = require("fs");
19
+ const chalk_1 = __importDefault(require("chalk"));
20
+ const PACKAGE_NAME = 'codeslick-cli';
21
+ const CACHE_FILE = (0, path_1.join)((0, os_1.homedir)(), '.codeslick', 'version-cache.json');
22
+ const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours
23
+ const NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
24
+ const CHECK_TIMEOUT_MS = 3000; // 3 seconds - don't slow down the CLI
25
+ /**
26
+ * Get the currently installed version
27
+ */
28
+ function getInstalledVersion() {
29
+ try {
30
+ const pkg = require('../../package.json');
31
+ return pkg.version;
32
+ }
33
+ catch {
34
+ return '0.0.0';
35
+ }
36
+ }
37
+ /**
38
+ * Compare two semver versions
39
+ * Returns: 1 if a > b, -1 if a < b, 0 if equal
40
+ */
41
+ function compareVersions(a, b) {
42
+ const partsA = a.split('.').map(Number);
43
+ const partsB = b.split('.').map(Number);
44
+ for (let i = 0; i < 3; i++) {
45
+ const numA = partsA[i] || 0;
46
+ const numB = partsB[i] || 0;
47
+ if (numA > numB)
48
+ return 1;
49
+ if (numA < numB)
50
+ return -1;
51
+ }
52
+ return 0;
53
+ }
54
+ /**
55
+ * Read cached version info
56
+ */
57
+ function readCache() {
58
+ try {
59
+ if (!(0, fs_1.existsSync)(CACHE_FILE))
60
+ return null;
61
+ const data = JSON.parse((0, fs_1.readFileSync)(CACHE_FILE, 'utf-8'));
62
+ return data;
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
68
+ /**
69
+ * Write version info to cache
70
+ */
71
+ function writeCache(latestVersion) {
72
+ try {
73
+ const cacheDir = (0, path_1.join)((0, os_1.homedir)(), '.codeslick');
74
+ if (!(0, fs_1.existsSync)(cacheDir)) {
75
+ (0, fs_1.mkdirSync)(cacheDir, { recursive: true });
76
+ }
77
+ const cache = {
78
+ latestVersion,
79
+ checkedAt: Date.now(),
80
+ };
81
+ (0, fs_1.writeFileSync)(CACHE_FILE, JSON.stringify(cache, null, 2));
82
+ }
83
+ catch {
84
+ // Silently ignore cache write errors
85
+ }
86
+ }
87
+ /**
88
+ * Fetch latest version from npm registry
89
+ */
90
+ async function fetchLatestVersion() {
91
+ try {
92
+ const controller = new AbortController();
93
+ const timeoutId = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
94
+ const response = await fetch(NPM_REGISTRY_URL, {
95
+ signal: controller.signal,
96
+ headers: {
97
+ 'Accept': 'application/json',
98
+ },
99
+ });
100
+ clearTimeout(timeoutId);
101
+ if (!response.ok)
102
+ return null;
103
+ const data = await response.json();
104
+ return data.version || null;
105
+ }
106
+ catch {
107
+ return null;
108
+ }
109
+ }
110
+ /**
111
+ * Check for updates and return info if available
112
+ * Uses cache to avoid checking on every command
113
+ */
114
+ async function checkForUpdates() {
115
+ try {
116
+ const currentVersion = getInstalledVersion();
117
+ // Check cache first
118
+ const cache = readCache();
119
+ const now = Date.now();
120
+ let latestVersion = null;
121
+ if (cache && (now - cache.checkedAt) < CACHE_DURATION_MS) {
122
+ // Use cached version
123
+ latestVersion = cache.latestVersion;
124
+ }
125
+ else {
126
+ // Fetch from npm (fire and forget - don't block)
127
+ latestVersion = await fetchLatestVersion();
128
+ if (latestVersion) {
129
+ writeCache(latestVersion);
130
+ }
131
+ else if (cache) {
132
+ // Use stale cache if fetch failed
133
+ latestVersion = cache.latestVersion;
134
+ }
135
+ }
136
+ if (!latestVersion)
137
+ return null;
138
+ const hasUpdate = compareVersions(latestVersion, currentVersion) > 0;
139
+ return {
140
+ hasUpdate,
141
+ currentVersion,
142
+ latestVersion,
143
+ };
144
+ }
145
+ catch {
146
+ return null;
147
+ }
148
+ }
149
+ /**
150
+ * Print update notification if a new version is available
151
+ * Non-blocking - runs in background
152
+ */
153
+ async function printUpdateNotification() {
154
+ try {
155
+ const result = await checkForUpdates();
156
+ if (result?.hasUpdate) {
157
+ console.log('');
158
+ console.log(chalk_1.default.cyan('┌─────────────────────────────────────────────────┐'));
159
+ console.log(chalk_1.default.cyan('│') + chalk_1.default.yellow(` Update available: ${result.currentVersion} → ${result.latestVersion}`.padEnd(47)) + chalk_1.default.cyan('│'));
160
+ console.log(chalk_1.default.cyan('│') + chalk_1.default.gray(' Run: ') + chalk_1.default.green('npm install -g codeslick-cli'.padEnd(39)) + chalk_1.default.cyan('│'));
161
+ console.log(chalk_1.default.cyan('└─────────────────────────────────────────────────┘'));
162
+ console.log('');
163
+ }
164
+ }
165
+ catch {
166
+ // Silently ignore errors - don't disrupt user workflow
167
+ }
168
+ }
169
+ /**
170
+ * Start update check in background (non-blocking)
171
+ * Returns a promise that resolves when check is complete
172
+ */
173
+ function startBackgroundUpdateCheck() {
174
+ return printUpdateNotification().catch(() => { });
175
+ }
176
+ //# sourceMappingURL=version-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../../../../src/utils/version-check.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeslick-cli",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "CodeSlick CLI tool for pre-commit security scanning",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Version Check Utility
3
+ *
4
+ * Checks npm registry for newer versions and notifies users.
5
+ * Uses a 24-hour cache to avoid slowing down every command.
6
+ */
7
+
8
+ import { homedir } from 'os';
9
+ import { join } from 'path';
10
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
11
+ import chalk from 'chalk';
12
+
13
+ const PACKAGE_NAME = 'codeslick-cli';
14
+ const CACHE_FILE = join(homedir(), '.codeslick', 'version-cache.json');
15
+ const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours
16
+ const NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
17
+ const CHECK_TIMEOUT_MS = 3000; // 3 seconds - don't slow down the CLI
18
+
19
+ interface VersionCache {
20
+ latestVersion: string;
21
+ checkedAt: number;
22
+ }
23
+
24
+ /**
25
+ * Get the currently installed version
26
+ */
27
+ export function getInstalledVersion(): string {
28
+ try {
29
+ const pkg = require('../../package.json');
30
+ return pkg.version;
31
+ } catch {
32
+ return '0.0.0';
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Compare two semver versions
38
+ * Returns: 1 if a > b, -1 if a < b, 0 if equal
39
+ */
40
+ function compareVersions(a: string, b: string): number {
41
+ const partsA = a.split('.').map(Number);
42
+ const partsB = b.split('.').map(Number);
43
+
44
+ for (let i = 0; i < 3; i++) {
45
+ const numA = partsA[i] || 0;
46
+ const numB = partsB[i] || 0;
47
+ if (numA > numB) return 1;
48
+ if (numA < numB) return -1;
49
+ }
50
+ return 0;
51
+ }
52
+
53
+ /**
54
+ * Read cached version info
55
+ */
56
+ function readCache(): VersionCache | null {
57
+ try {
58
+ if (!existsSync(CACHE_FILE)) return null;
59
+ const data = JSON.parse(readFileSync(CACHE_FILE, 'utf-8'));
60
+ return data as VersionCache;
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Write version info to cache
68
+ */
69
+ function writeCache(latestVersion: string): void {
70
+ try {
71
+ const cacheDir = join(homedir(), '.codeslick');
72
+ if (!existsSync(cacheDir)) {
73
+ mkdirSync(cacheDir, { recursive: true });
74
+ }
75
+ const cache: VersionCache = {
76
+ latestVersion,
77
+ checkedAt: Date.now(),
78
+ };
79
+ writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
80
+ } catch {
81
+ // Silently ignore cache write errors
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Fetch latest version from npm registry
87
+ */
88
+ async function fetchLatestVersion(): Promise<string | null> {
89
+ try {
90
+ const controller = new AbortController();
91
+ const timeoutId = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
92
+
93
+ const response = await fetch(NPM_REGISTRY_URL, {
94
+ signal: controller.signal,
95
+ headers: {
96
+ 'Accept': 'application/json',
97
+ },
98
+ });
99
+
100
+ clearTimeout(timeoutId);
101
+
102
+ if (!response.ok) return null;
103
+
104
+ const data = await response.json() as { version?: string };
105
+ return data.version || null;
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Check for updates and return info if available
113
+ * Uses cache to avoid checking on every command
114
+ */
115
+ export async function checkForUpdates(): Promise<{
116
+ hasUpdate: boolean;
117
+ currentVersion: string;
118
+ latestVersion: string;
119
+ } | null> {
120
+ try {
121
+ const currentVersion = getInstalledVersion();
122
+
123
+ // Check cache first
124
+ const cache = readCache();
125
+ const now = Date.now();
126
+
127
+ let latestVersion: string | null = null;
128
+
129
+ if (cache && (now - cache.checkedAt) < CACHE_DURATION_MS) {
130
+ // Use cached version
131
+ latestVersion = cache.latestVersion;
132
+ } else {
133
+ // Fetch from npm (fire and forget - don't block)
134
+ latestVersion = await fetchLatestVersion();
135
+
136
+ if (latestVersion) {
137
+ writeCache(latestVersion);
138
+ } else if (cache) {
139
+ // Use stale cache if fetch failed
140
+ latestVersion = cache.latestVersion;
141
+ }
142
+ }
143
+
144
+ if (!latestVersion) return null;
145
+
146
+ const hasUpdate = compareVersions(latestVersion, currentVersion) > 0;
147
+
148
+ return {
149
+ hasUpdate,
150
+ currentVersion,
151
+ latestVersion,
152
+ };
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Print update notification if a new version is available
160
+ * Non-blocking - runs in background
161
+ */
162
+ export async function printUpdateNotification(): Promise<void> {
163
+ try {
164
+ const result = await checkForUpdates();
165
+
166
+ if (result?.hasUpdate) {
167
+ console.log('');
168
+ console.log(chalk.cyan('┌─────────────────────────────────────────────────┐'));
169
+ console.log(chalk.cyan('│') + chalk.yellow(` Update available: ${result.currentVersion} → ${result.latestVersion}`.padEnd(47)) + chalk.cyan('│'));
170
+ console.log(chalk.cyan('│') + chalk.gray(' Run: ') + chalk.green('npm install -g codeslick-cli'.padEnd(39)) + chalk.cyan('│'));
171
+ console.log(chalk.cyan('└─────────────────────────────────────────────────┘'));
172
+ console.log('');
173
+ }
174
+ } catch {
175
+ // Silently ignore errors - don't disrupt user workflow
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Start update check in background (non-blocking)
181
+ * Returns a promise that resolves when check is complete
182
+ */
183
+ export function startBackgroundUpdateCheck(): Promise<void> {
184
+ return printUpdateNotification().catch(() => {});
185
+ }