fcis 0.1.0 → 0.2.1
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/.plans/003-code-cleanup-consolidation.md +242 -0
- package/.plans/004-directory-depth-rollup.md +408 -0
- package/.plans/005-code-refinements.md +210 -0
- package/.plans/006-minor-refinements.md +149 -0
- package/.plans/007-compositional-function-scoring.md +514 -0
- package/README.md +214 -132
- package/TECHNICAL.md +125 -2
- package/dist/cli.js +599 -328
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +409 -240
- package/dist/index.js.map +1 -1
- package/docs/images/fc-is-submarine.webp +0 -0
- package/package.json +3 -1
- package/src/cli-utils.ts +201 -0
- package/src/cli.ts +105 -118
- package/src/detection/markers.ts +0 -222
- package/src/extraction/extract-functions.ts +106 -2
- package/src/extraction/extractor.ts +35 -74
- package/src/reporting/report-console.ts +188 -102
- package/src/reporting/report-json.ts +26 -3
- package/src/scoring/scorer.ts +425 -160
- package/src/types.ts +9 -2
- package/tests/classifier.test.ts +0 -1
- package/tests/cli.test.ts +356 -0
- package/tests/detect-markers.test.ts +1 -3
- package/tests/extractor.test.ts +95 -1
- package/tests/integration.test.ts +344 -0
- package/tests/report-console.test.ts +92 -0
- package/tests/scorer.test.ts +886 -0
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fcis",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Functional Core, Imperative Shell analyzer for TypeScript codebases",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"chalk": "^5.3.0",
|
|
24
24
|
"cleye": "^1.3.2",
|
|
25
|
+
"fcis": "0.2.1",
|
|
26
|
+
"jsonc-parser": "^3.3.1",
|
|
25
27
|
"ts-morph": "^24.0.0",
|
|
26
28
|
"ts-pattern": "^5.6.0",
|
|
27
29
|
"zod": "^3.24.0"
|
package/src/cli-utils.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Utilities - Pure Functions
|
|
3
|
+
*
|
|
4
|
+
* Extracted pure functions for CLI validation and configuration building.
|
|
5
|
+
* These are separated from cli.ts to enable unit testing without triggering
|
|
6
|
+
* the CLI entry point.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { z } from 'zod'
|
|
10
|
+
import type { AnalyzerConfig } from './types.js'
|
|
11
|
+
|
|
12
|
+
const VALID_FORMATS = ['console', 'json', 'summary'] as const
|
|
13
|
+
type OutputFormat = (typeof VALID_FORMATS)[number]
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* CLI flags as parsed by cleye
|
|
17
|
+
* Note: cleye automatically adds `version` and `help` flags
|
|
18
|
+
*/
|
|
19
|
+
export type CliFlags = {
|
|
20
|
+
json: boolean
|
|
21
|
+
output: string | undefined
|
|
22
|
+
minHealth: number | undefined
|
|
23
|
+
minPurity: number | undefined
|
|
24
|
+
minQuality: number | undefined
|
|
25
|
+
files: string | undefined
|
|
26
|
+
format: string
|
|
27
|
+
quiet: boolean
|
|
28
|
+
verbose: boolean
|
|
29
|
+
dirDepth: number | undefined
|
|
30
|
+
version: boolean | undefined
|
|
31
|
+
help: boolean | undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validation result type
|
|
36
|
+
*/
|
|
37
|
+
export type ValidationResult = { valid: true } | { valid: false; error: string }
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validate a threshold value (0-100)
|
|
41
|
+
*/
|
|
42
|
+
const ThresholdSchema = z.number().min(0).max(100)
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validate a positive integer (for --dir-depth)
|
|
46
|
+
*/
|
|
47
|
+
const PositiveIntegerSchema = z.number().int().min(1)
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validate CLI flags (pure function)
|
|
51
|
+
*/
|
|
52
|
+
export function validateCliFlags(flags: CliFlags): ValidationResult {
|
|
53
|
+
// Validate threshold values
|
|
54
|
+
if (flags.minHealth !== undefined) {
|
|
55
|
+
const result = ThresholdSchema.safeParse(flags.minHealth)
|
|
56
|
+
if (!result.success) {
|
|
57
|
+
return {
|
|
58
|
+
valid: false,
|
|
59
|
+
error: '--min-health must be a number between 0 and 100',
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (flags.minPurity !== undefined) {
|
|
65
|
+
const result = ThresholdSchema.safeParse(flags.minPurity)
|
|
66
|
+
if (!result.success) {
|
|
67
|
+
return {
|
|
68
|
+
valid: false,
|
|
69
|
+
error: '--min-purity must be a number between 0 and 100',
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (flags.minQuality !== undefined) {
|
|
75
|
+
const result = ThresholdSchema.safeParse(flags.minQuality)
|
|
76
|
+
if (!result.success) {
|
|
77
|
+
return {
|
|
78
|
+
valid: false,
|
|
79
|
+
error: '--min-quality must be a number between 0 and 100',
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Validate format
|
|
85
|
+
if (!VALID_FORMATS.includes(flags.format as OutputFormat)) {
|
|
86
|
+
return {
|
|
87
|
+
valid: false,
|
|
88
|
+
error: `--format must be one of: ${VALID_FORMATS.join(', ')}`,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Validate dir-depth
|
|
93
|
+
if (flags.dirDepth !== undefined) {
|
|
94
|
+
const result = PositiveIntegerSchema.safeParse(flags.dirDepth)
|
|
95
|
+
if (!result.success) {
|
|
96
|
+
return {
|
|
97
|
+
valid: false,
|
|
98
|
+
error: '--dir-depth must be a positive integer (1 or greater)',
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { valid: true }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Validate a glob pattern (pure function)
|
|
108
|
+
*/
|
|
109
|
+
export function validateGlobPattern(pattern: string): ValidationResult {
|
|
110
|
+
if (!pattern || pattern.trim() === '') {
|
|
111
|
+
return { valid: false, error: 'Glob pattern cannot be empty' }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (pattern.startsWith('/')) {
|
|
115
|
+
return {
|
|
116
|
+
valid: false,
|
|
117
|
+
error: 'Glob pattern cannot be an absolute path (starting with /)',
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check for unbalanced brackets
|
|
122
|
+
let bracketDepth = 0
|
|
123
|
+
let braceDepth = 0
|
|
124
|
+
for (const char of pattern) {
|
|
125
|
+
if (char === '[') bracketDepth++
|
|
126
|
+
if (char === ']') bracketDepth--
|
|
127
|
+
if (char === '{') braceDepth++
|
|
128
|
+
if (char === '}') braceDepth--
|
|
129
|
+
if (bracketDepth < 0 || braceDepth < 0) {
|
|
130
|
+
return { valid: false, error: 'Glob pattern has unbalanced brackets' }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (bracketDepth !== 0 || braceDepth !== 0) {
|
|
134
|
+
return { valid: false, error: 'Glob pattern has unbalanced brackets' }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { valid: true }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Build AnalyzerConfig from CLI flags (pure function)
|
|
142
|
+
*/
|
|
143
|
+
export function buildAnalyzerConfig(
|
|
144
|
+
tsconfigPath: string,
|
|
145
|
+
flags: CliFlags,
|
|
146
|
+
): AnalyzerConfig {
|
|
147
|
+
const config: AnalyzerConfig = {
|
|
148
|
+
tsconfigPath,
|
|
149
|
+
format: flags.format as 'console' | 'json' | 'summary',
|
|
150
|
+
quiet: flags.quiet,
|
|
151
|
+
verbose: flags.verbose,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (flags.files !== undefined) {
|
|
155
|
+
config.filesGlob = flags.files
|
|
156
|
+
}
|
|
157
|
+
if (flags.minHealth !== undefined) {
|
|
158
|
+
config.minHealth = flags.minHealth
|
|
159
|
+
}
|
|
160
|
+
if (flags.minPurity !== undefined) {
|
|
161
|
+
config.minPurity = flags.minPurity
|
|
162
|
+
}
|
|
163
|
+
if (flags.minQuality !== undefined) {
|
|
164
|
+
config.minQuality = flags.minQuality
|
|
165
|
+
}
|
|
166
|
+
if (flags.output !== undefined) {
|
|
167
|
+
config.outputPath = flags.output
|
|
168
|
+
}
|
|
169
|
+
if (flags.dirDepth !== undefined) {
|
|
170
|
+
config.dirDepth = flags.dirDepth
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return config
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Build threshold config from CLI flags (pure function)
|
|
178
|
+
*/
|
|
179
|
+
export function buildThresholdConfig(flags: CliFlags): {
|
|
180
|
+
minHealth?: number
|
|
181
|
+
minPurity?: number
|
|
182
|
+
minQuality?: number
|
|
183
|
+
} {
|
|
184
|
+
const config: {
|
|
185
|
+
minHealth?: number
|
|
186
|
+
minPurity?: number
|
|
187
|
+
minQuality?: number
|
|
188
|
+
} = {}
|
|
189
|
+
|
|
190
|
+
if (flags.minHealth !== undefined) {
|
|
191
|
+
config.minHealth = flags.minHealth
|
|
192
|
+
}
|
|
193
|
+
if (flags.minPurity !== undefined) {
|
|
194
|
+
config.minPurity = flags.minPurity
|
|
195
|
+
}
|
|
196
|
+
if (flags.minQuality !== undefined) {
|
|
197
|
+
config.minQuality = flags.minQuality
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return config
|
|
201
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -12,31 +12,100 @@
|
|
|
12
12
|
* - 3: Analysis error (zero files could be analyzed)
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { createRequire } from 'node:module'
|
|
15
16
|
import { cli } from 'cleye'
|
|
16
|
-
import { z } from 'zod'
|
|
17
17
|
import chalk from 'chalk'
|
|
18
18
|
import * as fs from 'node:fs'
|
|
19
19
|
import * as path from 'node:path'
|
|
20
20
|
|
|
21
21
|
import { analyze, checkThresholds } from './analyzer.js'
|
|
22
|
-
import {
|
|
22
|
+
import { validateTsconfigContent } from './extraction/extractor.js'
|
|
23
23
|
import {
|
|
24
24
|
printConsoleReport,
|
|
25
25
|
generateSummaryLine,
|
|
26
26
|
} from './reporting/report-console.js'
|
|
27
27
|
import { generateJsonReport, writeJsonReport } from './reporting/report-json.js'
|
|
28
|
-
import type {
|
|
28
|
+
import type { ProjectScore } from './types.js'
|
|
29
|
+
import {
|
|
30
|
+
validateCliFlags,
|
|
31
|
+
validateGlobPattern,
|
|
32
|
+
buildAnalyzerConfig,
|
|
33
|
+
buildThresholdConfig,
|
|
34
|
+
type CliFlags,
|
|
35
|
+
type ValidationResult,
|
|
36
|
+
} from './cli-utils.js'
|
|
29
37
|
|
|
30
38
|
const EXIT_SUCCESS = 0
|
|
31
39
|
const EXIT_THRESHOLD_FAILED = 1
|
|
32
40
|
const EXIT_CONFIG_ERROR = 2
|
|
33
41
|
const EXIT_ANALYSIS_ERROR = 3
|
|
34
42
|
|
|
35
|
-
|
|
43
|
+
// Read version from package.json to maintain single source of truth
|
|
44
|
+
const require = createRequire(import.meta.url)
|
|
45
|
+
const pkg = require('../package.json') as { version: string }
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Handle analysis output based on flags (shell function - performs I/O)
|
|
49
|
+
*/
|
|
50
|
+
function handleAnalysisOutput(score: ProjectScore, flags: CliFlags): void {
|
|
51
|
+
if (!flags.quiet) {
|
|
52
|
+
if (flags.json || flags.format === 'json') {
|
|
53
|
+
const jsonOptions: { pretty: boolean; dirDepth?: number } = {
|
|
54
|
+
pretty: true,
|
|
55
|
+
}
|
|
56
|
+
if (flags.dirDepth !== undefined) {
|
|
57
|
+
jsonOptions.dirDepth = flags.dirDepth
|
|
58
|
+
}
|
|
59
|
+
console.log(generateJsonReport(score, jsonOptions))
|
|
60
|
+
} else if (flags.format === 'summary') {
|
|
61
|
+
console.log(generateSummaryLine(score))
|
|
62
|
+
} else {
|
|
63
|
+
const reportOptions: { verbose?: boolean; dirDepth?: number } = {
|
|
64
|
+
verbose: flags.verbose,
|
|
65
|
+
}
|
|
66
|
+
if (flags.dirDepth !== undefined) {
|
|
67
|
+
reportOptions.dirDepth = flags.dirDepth
|
|
68
|
+
}
|
|
69
|
+
printConsoleReport(score, reportOptions)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Write JSON to file if requested
|
|
74
|
+
if (flags.output) {
|
|
75
|
+
const outputOptions: { pretty: boolean; dirDepth?: number } = {
|
|
76
|
+
pretty: true,
|
|
77
|
+
}
|
|
78
|
+
if (flags.dirDepth !== undefined) {
|
|
79
|
+
outputOptions.dirDepth = flags.dirDepth
|
|
80
|
+
}
|
|
81
|
+
writeJsonReport(score, flags.output, outputOptions)
|
|
82
|
+
if (!flags.quiet && flags.format !== 'json') {
|
|
83
|
+
console.log(chalk.green(`Report written to: ${flags.output}`))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Ensure output directory exists (shell function - performs I/O)
|
|
90
|
+
*/
|
|
91
|
+
function ensureOutputDirectory(outputPath: string): ValidationResult {
|
|
92
|
+
const outputDir = path.dirname(path.resolve(outputPath))
|
|
93
|
+
if (!fs.existsSync(outputDir)) {
|
|
94
|
+
try {
|
|
95
|
+
fs.mkdirSync(outputDir, { recursive: true })
|
|
96
|
+
} catch {
|
|
97
|
+
return {
|
|
98
|
+
valid: false,
|
|
99
|
+
error: `Cannot create output directory: ${outputDir}`,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { valid: true }
|
|
104
|
+
}
|
|
36
105
|
|
|
37
106
|
const argv = cli({
|
|
38
107
|
name: 'fcis',
|
|
39
|
-
version:
|
|
108
|
+
version: pkg.version,
|
|
40
109
|
flags: {
|
|
41
110
|
json: {
|
|
42
111
|
type: Boolean,
|
|
@@ -83,6 +152,11 @@ const argv = cli({
|
|
|
83
152
|
description: 'Show per-file scores and all classified functions',
|
|
84
153
|
default: false,
|
|
85
154
|
},
|
|
155
|
+
dirDepth: {
|
|
156
|
+
type: Number,
|
|
157
|
+
description:
|
|
158
|
+
'Roll up directory metrics to depth N (e.g., 1 for top-level)',
|
|
159
|
+
},
|
|
86
160
|
},
|
|
87
161
|
parameters: ['<tsconfig>'],
|
|
88
162
|
help: {
|
|
@@ -102,13 +176,14 @@ async function main(): Promise<void> {
|
|
|
102
176
|
|
|
103
177
|
const tsconfigPath = args.tsconfig
|
|
104
178
|
|
|
105
|
-
// Validate tsconfig
|
|
179
|
+
// Validate tsconfig path provided
|
|
106
180
|
if (!tsconfigPath) {
|
|
107
181
|
console.error(chalk.red('Error: tsconfig path is required'))
|
|
108
182
|
console.error('Usage: fcis <tsconfig> [options]')
|
|
109
183
|
process.exit(EXIT_CONFIG_ERROR)
|
|
110
184
|
}
|
|
111
185
|
|
|
186
|
+
// Validate tsconfig exists
|
|
112
187
|
const absoluteTsconfigPath = path.resolve(tsconfigPath)
|
|
113
188
|
if (!fs.existsSync(absoluteTsconfigPath)) {
|
|
114
189
|
console.error(
|
|
@@ -117,71 +192,39 @@ async function main(): Promise<void> {
|
|
|
117
192
|
process.exit(EXIT_CONFIG_ERROR)
|
|
118
193
|
}
|
|
119
194
|
|
|
120
|
-
// Validate tsconfig
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
JSON.parse(strippedContent)
|
|
125
|
-
} catch (e) {
|
|
195
|
+
// Validate tsconfig content
|
|
196
|
+
const tsconfigContent = fs.readFileSync(absoluteTsconfigPath, 'utf-8')
|
|
197
|
+
const tsconfigValidation = validateTsconfigContent(tsconfigContent)
|
|
198
|
+
if (!tsconfigValidation.valid) {
|
|
126
199
|
console.error(
|
|
127
200
|
chalk.red(`Error: Invalid tsconfig.json at ${absoluteTsconfigPath}`),
|
|
128
201
|
)
|
|
129
|
-
console.error(
|
|
202
|
+
console.error(tsconfigValidation.error)
|
|
130
203
|
process.exit(EXIT_CONFIG_ERROR)
|
|
131
204
|
}
|
|
132
205
|
|
|
133
|
-
// Validate
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
chalk.red('Error: --min-health must be a number between 0 and 100'),
|
|
139
|
-
)
|
|
140
|
-
process.exit(EXIT_CONFIG_ERROR)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (flags.minPurity !== undefined) {
|
|
145
|
-
const result = ThresholdSchema.safeParse(flags.minPurity)
|
|
146
|
-
if (!result.success) {
|
|
147
|
-
console.error(
|
|
148
|
-
chalk.red('Error: --min-purity must be a number between 0 and 100'),
|
|
149
|
-
)
|
|
150
|
-
process.exit(EXIT_CONFIG_ERROR)
|
|
151
|
-
}
|
|
206
|
+
// Validate CLI flags
|
|
207
|
+
const flagsValidation = validateCliFlags(flags)
|
|
208
|
+
if (!flagsValidation.valid) {
|
|
209
|
+
console.error(chalk.red(`Error: ${flagsValidation.error}`))
|
|
210
|
+
process.exit(EXIT_CONFIG_ERROR)
|
|
152
211
|
}
|
|
153
212
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
)
|
|
213
|
+
// Validate glob pattern if provided
|
|
214
|
+
if (flags.files !== undefined) {
|
|
215
|
+
const globValidation = validateGlobPattern(flags.files)
|
|
216
|
+
if (!globValidation.valid) {
|
|
217
|
+
console.error(chalk.red(`Error: ${globValidation.error}`))
|
|
160
218
|
process.exit(EXIT_CONFIG_ERROR)
|
|
161
219
|
}
|
|
162
220
|
}
|
|
163
221
|
|
|
164
|
-
// Validate format
|
|
165
|
-
const validFormats = ['console', 'json', 'summary']
|
|
166
|
-
if (!validFormats.includes(flags.format)) {
|
|
167
|
-
console.error(
|
|
168
|
-
chalk.red(`Error: --format must be one of: ${validFormats.join(', ')}`),
|
|
169
|
-
)
|
|
170
|
-
process.exit(EXIT_CONFIG_ERROR)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
222
|
// Validate output path if provided
|
|
174
223
|
if (flags.output) {
|
|
175
|
-
const
|
|
176
|
-
if (!
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
} catch (e) {
|
|
180
|
-
console.error(
|
|
181
|
-
chalk.red(`Error: Cannot create output directory: ${outputDir}`),
|
|
182
|
-
)
|
|
183
|
-
process.exit(EXIT_CONFIG_ERROR)
|
|
184
|
-
}
|
|
224
|
+
const outputValidation = ensureOutputDirectory(flags.output)
|
|
225
|
+
if (!outputValidation.valid) {
|
|
226
|
+
console.error(chalk.red(`Error: ${outputValidation.error}`))
|
|
227
|
+
process.exit(EXIT_CONFIG_ERROR)
|
|
185
228
|
}
|
|
186
229
|
}
|
|
187
230
|
|
|
@@ -191,69 +234,14 @@ async function main(): Promise<void> {
|
|
|
191
234
|
console.log(chalk.gray(`Analyzing ${process.cwd()}...`))
|
|
192
235
|
}
|
|
193
236
|
|
|
194
|
-
|
|
195
|
-
const config: AnalyzerConfig = {
|
|
196
|
-
tsconfigPath: absoluteTsconfigPath,
|
|
197
|
-
format: flags.format as 'console' | 'json' | 'summary',
|
|
198
|
-
quiet: flags.quiet,
|
|
199
|
-
verbose: flags.verbose,
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Only add optional properties if they are defined
|
|
203
|
-
if (flags.files !== undefined) {
|
|
204
|
-
config.filesGlob = flags.files
|
|
205
|
-
}
|
|
206
|
-
if (flags.minHealth !== undefined) {
|
|
207
|
-
config.minHealth = flags.minHealth
|
|
208
|
-
}
|
|
209
|
-
if (flags.minPurity !== undefined) {
|
|
210
|
-
config.minPurity = flags.minPurity
|
|
211
|
-
}
|
|
212
|
-
if (flags.minQuality !== undefined) {
|
|
213
|
-
config.minQuality = flags.minQuality
|
|
214
|
-
}
|
|
215
|
-
if (flags.output !== undefined) {
|
|
216
|
-
config.outputPath = flags.output
|
|
217
|
-
}
|
|
218
|
-
|
|
237
|
+
const config = buildAnalyzerConfig(absoluteTsconfigPath, flags)
|
|
219
238
|
const score = await analyze(config)
|
|
220
239
|
|
|
221
|
-
// Output results
|
|
222
|
-
|
|
223
|
-
if (flags.json || flags.format === 'json') {
|
|
224
|
-
console.log(generateJsonReport(score, { pretty: true }))
|
|
225
|
-
} else if (flags.format === 'summary') {
|
|
226
|
-
console.log(generateSummaryLine(score))
|
|
227
|
-
} else {
|
|
228
|
-
printConsoleReport(score, { verbose: flags.verbose })
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Write JSON to file if requested
|
|
233
|
-
if (flags.output) {
|
|
234
|
-
writeJsonReport(score, flags.output, { pretty: true })
|
|
235
|
-
if (!flags.quiet && flags.format !== 'json') {
|
|
236
|
-
console.log(chalk.green(`Report written to: ${flags.output}`))
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Check thresholds - build object only with defined values
|
|
241
|
-
const thresholdConfig: {
|
|
242
|
-
minHealth?: number
|
|
243
|
-
minPurity?: number
|
|
244
|
-
minQuality?: number
|
|
245
|
-
} = {}
|
|
246
|
-
|
|
247
|
-
if (flags.minHealth !== undefined) {
|
|
248
|
-
thresholdConfig.minHealth = flags.minHealth
|
|
249
|
-
}
|
|
250
|
-
if (flags.minPurity !== undefined) {
|
|
251
|
-
thresholdConfig.minPurity = flags.minPurity
|
|
252
|
-
}
|
|
253
|
-
if (flags.minQuality !== undefined) {
|
|
254
|
-
thresholdConfig.minQuality = flags.minQuality
|
|
255
|
-
}
|
|
240
|
+
// Output results
|
|
241
|
+
handleAnalysisOutput(score, flags)
|
|
256
242
|
|
|
243
|
+
// Check thresholds
|
|
244
|
+
const thresholdConfig = buildThresholdConfig(flags)
|
|
257
245
|
const thresholdResult = checkThresholds(score, thresholdConfig)
|
|
258
246
|
|
|
259
247
|
if (!thresholdResult.passed) {
|
|
@@ -274,7 +262,6 @@ async function main(): Promise<void> {
|
|
|
274
262
|
console.error(error instanceof Error ? error.message : String(error))
|
|
275
263
|
}
|
|
276
264
|
|
|
277
|
-
// Check if it's a "no files" error
|
|
278
265
|
if (error instanceof Error && error.message.includes('No source files')) {
|
|
279
266
|
process.exit(EXIT_ANALYSIS_ERROR)
|
|
280
267
|
}
|