flagwatch 1.0.0

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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/dist/analyzer/analyze-flags.d.ts +3 -0
  4. package/dist/analyzer/analyze-flags.d.ts.map +1 -0
  5. package/dist/analyzer/analyze-flags.js +165 -0
  6. package/dist/analyzer/analyze-flags.js.map +1 -0
  7. package/dist/cli/parse-args.d.ts +5 -0
  8. package/dist/cli/parse-args.d.ts.map +1 -0
  9. package/dist/cli/parse-args.js +122 -0
  10. package/dist/cli/parse-args.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +65 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config/load-config.d.ts +3 -0
  16. package/dist/config/load-config.d.ts.map +1 -0
  17. package/dist/config/load-config.js +144 -0
  18. package/dist/config/load-config.js.map +1 -0
  19. package/dist/detector/detect-flags.d.ts +4 -0
  20. package/dist/detector/detect-flags.d.ts.map +1 -0
  21. package/dist/detector/detect-flags.js +128 -0
  22. package/dist/detector/detect-flags.js.map +1 -0
  23. package/dist/errors.d.ts +23 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/errors.js +51 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/index.d.ts +12 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +56 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/logger.d.ts +15 -0
  32. package/dist/logger.d.ts.map +1 -0
  33. package/dist/logger.js +45 -0
  34. package/dist/logger.js.map +1 -0
  35. package/dist/reporter/report-results.d.ts +3 -0
  36. package/dist/reporter/report-results.d.ts.map +1 -0
  37. package/dist/reporter/report-results.js +80 -0
  38. package/dist/reporter/report-results.js.map +1 -0
  39. package/dist/scanner/glob-matcher.d.ts +3 -0
  40. package/dist/scanner/glob-matcher.d.ts.map +1 -0
  41. package/dist/scanner/glob-matcher.js +44 -0
  42. package/dist/scanner/glob-matcher.js.map +1 -0
  43. package/dist/scanner/scan-files.d.ts +2 -0
  44. package/dist/scanner/scan-files.d.ts.map +1 -0
  45. package/dist/scanner/scan-files.js +90 -0
  46. package/dist/scanner/scan-files.js.map +1 -0
  47. package/dist/types.d.ts +51 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +3 -0
  50. package/dist/types.js.map +1 -0
  51. package/package.json +40 -0
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.matchesGlob = matchesGlob;
4
+ exports.matchesAnyGlob = matchesAnyGlob;
5
+ function escapeRegex(str) {
6
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
7
+ }
8
+ function globToRegex(pattern) {
9
+ const parts = pattern.split('/');
10
+ const regexParts = parts.map((part) => {
11
+ if (part === '**') {
12
+ return '.*';
13
+ }
14
+ let processed = part;
15
+ // Handle brace patterns first (before escaping)
16
+ processed = processed.replace(/\{([^}]+)\}/g, (_match, content) => {
17
+ const alternatives = content.split(',').map((alt) => escapeRegex(alt.trim()));
18
+ return `(${alternatives.join('|')})`;
19
+ });
20
+ // Replace ** wildcards
21
+ processed = processed.replace(/\*\*/g, 'PLACEHOLDER_DOUBLE_STAR');
22
+ // Replace * wildcards
23
+ processed = processed.replace(/\*/g, 'PLACEHOLDER_SINGLE_STAR');
24
+ // Escape all special characters
25
+ processed = escapeRegex(processed);
26
+ // Restore our placeholders with actual regex
27
+ processed = processed.replace(/PLACEHOLDER_DOUBLE_STAR/g, '.*');
28
+ processed = processed.replace(/PLACEHOLDER_SINGLE_STAR/g, '[^/]*');
29
+ // Unescape the patterns we created from braces
30
+ processed = processed.replace(/\\\(/g, '(').replace(/\\\)/g, ')').replace(/\\\|/g, '|');
31
+ return processed;
32
+ });
33
+ const regexStr = `^${regexParts.join('/')}$`;
34
+ return new RegExp(regexStr);
35
+ }
36
+ function matchesGlob(filePath, pattern) {
37
+ const normalizedPath = filePath.replace(/\\/g, '/');
38
+ const regex = globToRegex(pattern);
39
+ return regex.test(normalizedPath);
40
+ }
41
+ function matchesAnyGlob(filePath, patterns) {
42
+ return patterns.some((pattern) => matchesGlob(filePath, pattern));
43
+ }
44
+ //# sourceMappingURL=glob-matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob-matcher.js","sourceRoot":"","sources":["../../src/scanner/glob-matcher.ts"],"names":[],"mappings":";;AA0CA,kCAIC;AAED,wCAEC;AAlDD,SAAS,WAAW,CAAC,GAAW;IAC9B,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IAClC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACpC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,SAAS,GAAG,IAAI,CAAC;QAErB,gDAAgD;QAChD,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;YAChE,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YACtF,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QACvC,CAAC,CAAC,CAAC;QAEH,uBAAuB;QACvB,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAC;QAElE,sBAAsB;QACtB,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,yBAAyB,CAAC,CAAC;QAEhE,gCAAgC;QAChC,SAAS,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;QAEnC,6CAA6C;QAC7C,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,0BAA0B,EAAE,IAAI,CAAC,CAAC;QAChE,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,0BAA0B,EAAE,OAAO,CAAC,CAAC;QAEnE,+CAA+C;QAC/C,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAExF,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC7C,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC9B,CAAC;AAED,SAAgB,WAAW,CAAC,QAAgB,EAAE,OAAe;IAC3D,MAAM,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,OAAO,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AACpC,CAAC;AAED,SAAgB,cAAc,CAAC,QAAgB,EAAE,QAAkB;IACjE,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AACpE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function scanFiles(rootPath: string, includePatterns: string[], excludePatterns: string[]): string[];
2
+ //# sourceMappingURL=scan-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan-files.d.ts","sourceRoot":"","sources":["../../src/scanner/scan-files.ts"],"names":[],"mappings":"AA0DA,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EAAE,EACzB,eAAe,EAAE,MAAM,EAAE,GACxB,MAAM,EAAE,CAkBV"}
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.scanFiles = scanFiles;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const glob_matcher_1 = require("./glob-matcher");
40
+ const errors_1 = require("../errors");
41
+ function shouldIncludeFile(filePath, includePatterns, excludePatterns) {
42
+ const normalizedPath = filePath.replace(/\\/g, '/');
43
+ if (excludePatterns.length > 0 && (0, glob_matcher_1.matchesAnyGlob)(normalizedPath, excludePatterns)) {
44
+ return false;
45
+ }
46
+ if (includePatterns.length === 0) {
47
+ return true;
48
+ }
49
+ return (0, glob_matcher_1.matchesAnyGlob)(normalizedPath, includePatterns);
50
+ }
51
+ function scanDirectory(dirPath, includePatterns, excludePatterns, files) {
52
+ try {
53
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
54
+ const sortedEntries = entries.sort((a, b) => {
55
+ return a.name.localeCompare(b.name);
56
+ });
57
+ for (const entry of sortedEntries) {
58
+ const fullPath = path.join(dirPath, entry.name);
59
+ if (entry.isDirectory()) {
60
+ if (shouldIncludeFile(fullPath, includePatterns, excludePatterns)) {
61
+ scanDirectory(fullPath, includePatterns, excludePatterns, files);
62
+ }
63
+ }
64
+ else if (entry.isFile()) {
65
+ if (shouldIncludeFile(fullPath, includePatterns, excludePatterns)) {
66
+ files.push(fullPath);
67
+ }
68
+ }
69
+ }
70
+ }
71
+ catch (error) {
72
+ throw new errors_1.FileScanError(`Failed to scan directory: ${dirPath}`, dirPath, error instanceof Error ? error : undefined);
73
+ }
74
+ }
75
+ function scanFiles(rootPath, includePatterns, excludePatterns) {
76
+ const files = [];
77
+ if (!fs.existsSync(rootPath)) {
78
+ throw new errors_1.FileScanError(`Root path does not exist: ${rootPath}`, rootPath);
79
+ }
80
+ const stats = fs.statSync(rootPath);
81
+ if (stats.isFile()) {
82
+ if (shouldIncludeFile(rootPath, includePatterns, excludePatterns)) {
83
+ files.push(rootPath);
84
+ }
85
+ return files;
86
+ }
87
+ scanDirectory(rootPath, includePatterns, excludePatterns, files);
88
+ return files.sort();
89
+ }
90
+ //# sourceMappingURL=scan-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan-files.js","sourceRoot":"","sources":["../../src/scanner/scan-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ export interface FlagwatchConfig {
2
+ include: string[];
3
+ exclude: string[];
4
+ flagPatterns: string[];
5
+ envVarPrefixes: string[];
6
+ strict: boolean;
7
+ }
8
+ export interface FlagReference {
9
+ name: string;
10
+ file: string;
11
+ line: number;
12
+ column: number;
13
+ pattern: string;
14
+ }
15
+ export interface FlagDefinition {
16
+ name: string;
17
+ file: string;
18
+ line: number;
19
+ column: number;
20
+ }
21
+ export interface DeadConditional {
22
+ file: string;
23
+ line: number;
24
+ column: number;
25
+ condition: string;
26
+ alwaysTrue: boolean;
27
+ alwaysFalse: boolean;
28
+ }
29
+ export interface AnalysisResult {
30
+ flagsDetected: number;
31
+ flagsUnused: FlagDefinition[];
32
+ flagsMissing: FlagReference[];
33
+ deadConditionals: DeadConditional[];
34
+ allFlags: FlagReference[];
35
+ }
36
+ export interface CliOptions {
37
+ ci: boolean;
38
+ json: boolean;
39
+ config?: string;
40
+ ignore: string[];
41
+ strict: boolean;
42
+ verbose: boolean;
43
+ help: boolean;
44
+ version: boolean;
45
+ }
46
+ export interface ReporterOptions {
47
+ ci: boolean;
48
+ json: boolean;
49
+ verbose: boolean;
50
+ }
51
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "flagwatch",
3
+ "version": "1.0.0",
4
+ "description": "CI-first visibility into feature flags and conditional code paths",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "flagwatch": "dist/cli.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/cli.js",
12
+ "test": "jest",
13
+ "test:watch": "jest --watch",
14
+ "test:coverage": "jest --coverage"
15
+ },
16
+ "keywords": [
17
+ "feature-flags",
18
+ "static-analysis",
19
+ "ci",
20
+ "code-analysis",
21
+ "dead-code"
22
+ ],
23
+ "author": "",
24
+ "license": "MIT",
25
+ "engines": {
26
+ "node": ">=14.0.0"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "devDependencies": {
34
+ "@types/jest": "^30.0.0",
35
+ "@types/node": "^25.0.3",
36
+ "jest": "^30.2.0",
37
+ "ts-jest": "^29.4.6",
38
+ "typescript": "^5.9.3"
39
+ }
40
+ }