flagshark 1.2.0 → 1.3.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.
- package/dist/cli.js +90 -4
- package/package.json +4 -3
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import {
|
|
4
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
5
|
+
import { parse as parseYaml } from "yaml";
|
|
6
|
+
import { scanRepo, FlagsharkConfigSchema } from "@flagshark/core";
|
|
5
7
|
|
|
6
8
|
// src/formatter.ts
|
|
7
9
|
var VERSION = "1.2.0";
|
|
@@ -49,6 +51,9 @@ function formatText(result, options) {
|
|
|
49
51
|
lines.push("");
|
|
50
52
|
const langCount = Object.keys(result.languageBreakdown).length;
|
|
51
53
|
lines.push(`Scanned ${result.filesScanned} files across ${langCount} language${langCount === 1 ? "" : "s"}`);
|
|
54
|
+
if (result.excludedCount && result.excludedCount > 0) {
|
|
55
|
+
lines.push(`(${result.excludedCount} excluded via .flagsharkignore + excludes)`);
|
|
56
|
+
}
|
|
52
57
|
if (result.totalFlags === 0) {
|
|
53
58
|
lines.push("No feature flags detected.");
|
|
54
59
|
lines.push("");
|
|
@@ -85,6 +90,13 @@ function formatText(result, options) {
|
|
|
85
90
|
lines.push("Automate cleanup \u2192 https://flagshark.com");
|
|
86
91
|
lines.push("Open source CLI \u2192 https://github.com/FlagShark/flagshark");
|
|
87
92
|
}
|
|
93
|
+
if (result.excludedPaths && result.excludedPaths.length > 0) {
|
|
94
|
+
lines.push("");
|
|
95
|
+
lines.push(`Excluded files (${result.excludedPaths.length}):`);
|
|
96
|
+
for (const p of result.excludedPaths) {
|
|
97
|
+
lines.push(` ${p}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
88
100
|
return lines.join("\n");
|
|
89
101
|
}
|
|
90
102
|
function formatJson(result) {
|
|
@@ -110,6 +122,7 @@ function formatJson(result) {
|
|
|
110
122
|
detectedProviders: result.detectedProviders,
|
|
111
123
|
languages,
|
|
112
124
|
flags,
|
|
125
|
+
excludedPaths: result.excludedPaths,
|
|
113
126
|
scanDuration: result.scanDuration,
|
|
114
127
|
links: {
|
|
115
128
|
dashboard: "https://flagshark.com",
|
|
@@ -121,7 +134,7 @@ function formatJson(result) {
|
|
|
121
134
|
}
|
|
122
135
|
|
|
123
136
|
// src/cli.ts
|
|
124
|
-
var VERSION2 = "1.
|
|
137
|
+
var VERSION2 = "1.3.0";
|
|
125
138
|
var HELP_TEXT = `
|
|
126
139
|
flagshark scan [options]
|
|
127
140
|
|
|
@@ -132,19 +145,30 @@ Options:
|
|
|
132
145
|
--verbose Show all stale flags (not just top 10)
|
|
133
146
|
--help Show help
|
|
134
147
|
--version Show version
|
|
148
|
+
|
|
149
|
+
Configuration:
|
|
150
|
+
--config <path> Use this config file (overrides .flagshark.yml discovery)
|
|
151
|
+
--no-config Skip config file discovery
|
|
152
|
+
--no-ignore-file Skip .flagsharkignore discovery
|
|
153
|
+
--show-excluded Show excluded files in text output
|
|
135
154
|
`.trim();
|
|
136
155
|
function parseArgs(argv) {
|
|
137
156
|
const args = {
|
|
138
157
|
json: false,
|
|
139
158
|
diff: null,
|
|
140
|
-
threshold:
|
|
159
|
+
threshold: void 0,
|
|
141
160
|
verbose: false,
|
|
142
161
|
help: false,
|
|
143
162
|
version: false
|
|
144
163
|
};
|
|
145
164
|
let i = 2;
|
|
146
165
|
while (i < argv.length) {
|
|
147
|
-
|
|
166
|
+
let arg = argv[i];
|
|
167
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
168
|
+
const eqIdx = arg.indexOf("=");
|
|
169
|
+
argv.splice(i, 1, arg.slice(0, eqIdx), arg.slice(eqIdx + 1));
|
|
170
|
+
arg = argv[i];
|
|
171
|
+
}
|
|
148
172
|
switch (arg) {
|
|
149
173
|
case "--json":
|
|
150
174
|
args.json = true;
|
|
@@ -174,6 +198,32 @@ function parseArgs(argv) {
|
|
|
174
198
|
case "-v":
|
|
175
199
|
args.version = true;
|
|
176
200
|
break;
|
|
201
|
+
case "--engine": {
|
|
202
|
+
const value = argv[++i];
|
|
203
|
+
if (value !== "regex" && value !== "tree-sitter") {
|
|
204
|
+
process.stderr.write(`Error: --engine must be 'regex' or 'tree-sitter', got '${value}'
|
|
205
|
+
`);
|
|
206
|
+
process.exit(2);
|
|
207
|
+
}
|
|
208
|
+
args.engine = value;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case "--config":
|
|
212
|
+
i++;
|
|
213
|
+
args.configPath = argv[i];
|
|
214
|
+
if (!args.configPath) {
|
|
215
|
+
throw new Error("--config requires a file path argument");
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
case "--no-config":
|
|
219
|
+
args.noConfig = true;
|
|
220
|
+
break;
|
|
221
|
+
case "--no-ignore-file":
|
|
222
|
+
args.noIgnoreFile = true;
|
|
223
|
+
break;
|
|
224
|
+
case "--show-excluded":
|
|
225
|
+
args.showExcluded = true;
|
|
226
|
+
break;
|
|
177
227
|
case "scan":
|
|
178
228
|
break;
|
|
179
229
|
default:
|
|
@@ -212,12 +262,48 @@ async function main() {
|
|
|
212
262
|
} else {
|
|
213
263
|
logger.info("Scanning current directory...");
|
|
214
264
|
}
|
|
265
|
+
let configOverride;
|
|
266
|
+
if (args.configPath) {
|
|
267
|
+
if (!existsSync(args.configPath)) {
|
|
268
|
+
process.stderr.write(`Error: config file not found: ${args.configPath}
|
|
269
|
+
`);
|
|
270
|
+
process.exit(2);
|
|
271
|
+
}
|
|
272
|
+
const raw = readFileSync(args.configPath, "utf-8");
|
|
273
|
+
const parsed = parseYaml(raw);
|
|
274
|
+
const configResult = FlagsharkConfigSchema.safeParse(parsed);
|
|
275
|
+
if (!configResult.success) {
|
|
276
|
+
process.stderr.write(`Error: invalid config at ${args.configPath}: ${configResult.error.message}
|
|
277
|
+
`);
|
|
278
|
+
process.exit(2);
|
|
279
|
+
}
|
|
280
|
+
configOverride = configResult.data;
|
|
281
|
+
}
|
|
215
282
|
const result = await scanRepo({
|
|
216
283
|
cwd: process.cwd(),
|
|
217
284
|
threshold: args.threshold,
|
|
218
285
|
diff: args.diff ?? void 0,
|
|
286
|
+
engine: args.engine,
|
|
287
|
+
config: configOverride,
|
|
288
|
+
noConfig: args.noConfig,
|
|
289
|
+
noIgnoreFile: args.noIgnoreFile,
|
|
290
|
+
collectExcludedPaths: args.showExcluded,
|
|
219
291
|
logger
|
|
220
292
|
});
|
|
293
|
+
if (args.verbose && result.effectiveExcludes) {
|
|
294
|
+
const r = result.effectiveExcludes;
|
|
295
|
+
const allRules = [
|
|
296
|
+
...r.paths.map((p) => `excludes.paths: ${p}`),
|
|
297
|
+
...r.files.map((p) => `excludes.files: ${p}`),
|
|
298
|
+
...r.presets.flatMap((name, i) => [`excludes.presets[${i}]: ${name}`]),
|
|
299
|
+
...r.ignoreFile.map((p) => `.flagsharkignore: ${p}`)
|
|
300
|
+
];
|
|
301
|
+
if (allRules.length > 0) {
|
|
302
|
+
process.stderr.write("Effective excludes:\n");
|
|
303
|
+
for (const rule of allRules) process.stderr.write(` ${rule}
|
|
304
|
+
`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
221
307
|
const output = args.json ? formatJson(result) + "\n" : formatText(result, { json: false, verbose: args.verbose, maxDisplay: 10 }) + "\n";
|
|
222
308
|
const exitCode = result.staleFlags.length > 0 ? 1 : 0;
|
|
223
309
|
if (process.stdout.write(output)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flagshark",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Find stale feature flags in your codebase",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
"main": "./dist/cli.js",
|
|
18
18
|
"files": ["dist/", "bin/"],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "esbuild src/cli.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/cli.js --external:zod --external:@flagshark/core",
|
|
20
|
+
"build": "esbuild src/cli.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/cli.js --external:zod --external:@flagshark/core --external:yaml",
|
|
21
21
|
"test": "vitest run",
|
|
22
22
|
"typecheck": "tsc --noEmit"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@flagshark/core": "1.
|
|
25
|
+
"@flagshark/core": "1.2.0",
|
|
26
|
+
"yaml": "^2.4.0",
|
|
26
27
|
"zod": "^3.23.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|