@inspecto-dev/cli 0.2.0-alpha.4 → 0.2.0-alpha.6

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 (41) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/.turbo/turbo-test.log +16 -21
  3. package/CHANGELOG.md +12 -0
  4. package/README.md +58 -11
  5. package/bin/inspecto.js +5 -1
  6. package/dist/bin.d.ts +5 -1
  7. package/dist/bin.js +89 -50
  8. package/dist/{chunk-EUCQCD3Y.js → chunk-PDDFPQJS.js} +1954 -1053
  9. package/dist/index.d.ts +128 -2
  10. package/dist/index.js +15 -3
  11. package/package.json +2 -1
  12. package/src/bin.ts +139 -67
  13. package/src/commands/apply.ts +114 -0
  14. package/src/commands/detect.ts +59 -0
  15. package/src/commands/doctor.ts +225 -72
  16. package/src/commands/init.ts +106 -183
  17. package/src/commands/plan.ts +41 -0
  18. package/src/detect/build-tool.ts +107 -3
  19. package/src/index.ts +13 -2
  20. package/src/inject/ast-injector.ts +20 -9
  21. package/src/inject/extension.ts +3 -1
  22. package/src/inject/strategies/vite.ts +2 -1
  23. package/src/instructions.ts +60 -46
  24. package/src/onboarding/apply.ts +325 -0
  25. package/src/onboarding/context.ts +36 -0
  26. package/src/onboarding/planner.ts +278 -0
  27. package/src/prompts.ts +54 -11
  28. package/src/types.ts +95 -0
  29. package/src/utils/fs.ts +2 -1
  30. package/src/utils/logger.ts +9 -0
  31. package/src/utils/output.ts +40 -0
  32. package/tests/apply.test.ts +537 -0
  33. package/tests/ast-injector.test.ts +50 -0
  34. package/tests/build-tool.test.ts +3 -5
  35. package/tests/detect.test.ts +94 -0
  36. package/tests/doctor.test.ts +224 -0
  37. package/tests/init.test.ts +333 -0
  38. package/tests/instructions.test.ts +61 -0
  39. package/tests/logger.test.ts +100 -0
  40. package/tests/plan.test.ts +713 -0
  41. package/tests/workspace-build-tool.test.ts +75 -0
package/dist/index.d.ts CHANGED
@@ -2,6 +2,94 @@
2
2
  type PackageManager = 'bun' | 'pnpm' | 'yarn' | 'npm';
3
3
  /** Supported build tools (v1) */
4
4
  type BuildTool = 'vite' | 'webpack' | 'rspack' | 'rsbuild' | 'esbuild' | 'rollup';
5
+ /** Machine-readable status for onboarding commands */
6
+ type CommandStatus = 'ok' | 'warning' | 'blocked' | 'error';
7
+ /** Structured message emitted by onboarding commands */
8
+ interface CommandMessage {
9
+ code: string;
10
+ message: string;
11
+ }
12
+ interface OnboardingProvider {
13
+ id: string;
14
+ label: string;
15
+ supported: boolean;
16
+ preferredMode: 'cli' | 'extension';
17
+ }
18
+ /** Detected build tool with its config path */
19
+ interface BuildToolDetection {
20
+ tool: BuildTool;
21
+ configPath: string;
22
+ /** Human-readable label like "Vite (vite.config.ts)" */
23
+ label: string;
24
+ /** Whether this is a legacy rspack version (< 0.4.0) */
25
+ isLegacyRspack?: boolean;
26
+ /** Whether this is Webpack 4.x */
27
+ isLegacyWebpack?: boolean;
28
+ /** Relative package path when using --packages */
29
+ packagePath?: string;
30
+ }
31
+ /** Machine-readable detection output for skill-first onboarding */
32
+ interface DetectionResult {
33
+ status: CommandStatus;
34
+ warnings: CommandMessage[];
35
+ blockers: CommandMessage[];
36
+ project: {
37
+ root: string;
38
+ packageManager: PackageManager;
39
+ };
40
+ environment: {
41
+ frameworks: string[];
42
+ unsupportedFrameworks: string[];
43
+ buildTools: BuildToolDetection[];
44
+ unsupportedBuildTools: string[];
45
+ ides: Array<{
46
+ ide: string;
47
+ supported: boolean;
48
+ }>;
49
+ providers: OnboardingProvider[];
50
+ };
51
+ }
52
+ /** Machine-readable onboarding plan output */
53
+ interface PlanResult {
54
+ status: CommandStatus;
55
+ warnings: CommandMessage[];
56
+ blockers: CommandMessage[];
57
+ strategy: 'supported' | 'manual' | 'unsupported';
58
+ actions: Array<{
59
+ type: 'install_dependency' | 'modify_file' | 'install_extension' | 'manual_step';
60
+ target: string;
61
+ description: string;
62
+ }>;
63
+ defaults: {
64
+ provider?: string;
65
+ ide?: string;
66
+ shared: boolean;
67
+ extension: boolean;
68
+ };
69
+ }
70
+ /** A single doctor diagnostic check/result */
71
+ interface DoctorDiagnostic {
72
+ code: string;
73
+ status: 'ok' | 'warning' | 'error';
74
+ message: string;
75
+ hints: string[];
76
+ details?: Record<string, unknown>;
77
+ }
78
+ /** Machine-readable diagnostics output for `inspecto doctor` */
79
+ interface DoctorResult {
80
+ status: CommandStatus;
81
+ summary: {
82
+ errors: number;
83
+ warnings: number;
84
+ };
85
+ project: {
86
+ root: string;
87
+ packageManager?: PackageManager;
88
+ };
89
+ errors: DoctorDiagnostic[];
90
+ warnings: DoctorDiagnostic[];
91
+ checks: DoctorDiagnostic[];
92
+ }
5
93
  /** Options passed to `inspecto init` */
6
94
  interface InitOptions {
7
95
  shared: boolean;
@@ -30,12 +118,50 @@ interface InstallLock {
30
118
  mutations: Mutation[];
31
119
  }
32
120
 
121
+ interface ApplyOnboardingResult {
122
+ status: CommandStatus;
123
+ mutations: Mutation[];
124
+ postInstall: {
125
+ installFailed: boolean;
126
+ injectionFailed: boolean;
127
+ manualExtensionInstallNeeded: boolean;
128
+ nextSteps: string[];
129
+ };
130
+ }
131
+
132
+ interface ApplyCommandOptions {
133
+ json?: boolean;
134
+ shared?: boolean;
135
+ skipInstall?: boolean;
136
+ dryRun?: boolean;
137
+ noExtension?: boolean;
138
+ }
139
+ interface ApplyCommandResult extends ApplyOnboardingResult {
140
+ plan: PlanResult;
141
+ }
142
+ declare function apply(options?: ApplyCommandOptions): Promise<ApplyCommandResult>;
143
+
144
+ declare function detect(json?: boolean): Promise<DetectionResult>;
145
+
33
146
  declare function init(options: InitOptions): Promise<void>;
34
147
 
35
- declare function doctor(): Promise<void>;
148
+ interface DoctorCommandOptions {
149
+ json?: boolean;
150
+ }
151
+ declare function collectDoctorResult(root?: string): Promise<DoctorResult>;
152
+ declare function doctor(options?: DoctorCommandOptions | boolean): Promise<DoctorResult>;
153
+
154
+ declare function plan(json?: boolean): Promise<PlanResult>;
36
155
 
37
156
  declare function teardown(): Promise<void>;
38
157
 
158
+ interface ReportCommandErrorOptions {
159
+ debug?: boolean;
160
+ json?: boolean;
161
+ }
162
+ declare function writeCommandOutput<T>(result: T, json: boolean, renderText: (value: T) => void): T;
163
+ declare function reportCommandError(error: unknown, options?: ReportCommandErrorOptions): void;
164
+
39
165
  type Framework = 'react' | 'vue';
40
166
 
41
- export { type BuildTool, type Framework, type InitOptions, type InstallLock, type PackageManager, doctor, init, teardown };
167
+ export { type BuildTool, type DoctorDiagnostic, type DoctorResult, type Framework, type InitOptions, type InstallLock, type PackageManager, apply, collectDoctorResult, detect, doctor, init, plan, reportCommandError, teardown, writeCommandOutput };
package/dist/index.js CHANGED
@@ -1,10 +1,22 @@
1
1
  import {
2
+ apply,
3
+ collectDoctorResult,
4
+ detect,
2
5
  doctor,
3
6
  init,
4
- teardown
5
- } from "./chunk-EUCQCD3Y.js";
7
+ plan,
8
+ reportCommandError,
9
+ teardown,
10
+ writeCommandOutput
11
+ } from "./chunk-PDDFPQJS.js";
6
12
  export {
13
+ apply,
14
+ collectDoctorResult,
15
+ detect,
7
16
  doctor,
8
17
  init,
9
- teardown
18
+ plan,
19
+ reportCommandError,
20
+ teardown,
21
+ writeCommandOutput
10
22
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inspecto-dev/cli",
3
- "version": "0.2.0-alpha.4",
3
+ "version": "0.2.0-alpha.6",
4
4
  "description": "CLI tools for Inspecto onboarding and lifecycle management",
5
5
  "keywords": [
6
6
  "inspecto",
@@ -17,6 +17,7 @@
17
17
  "dependencies": {
18
18
  "cac": "^6.7.14",
19
19
  "magicast": "^0.5.2",
20
+ "ora": "^9.3.0",
20
21
  "picocolors": "^1.0.0",
21
22
  "prompts": "^2.4.2",
22
23
  "@inspecto-dev/types": "0.2.0-alpha.4"
package/src/bin.ts CHANGED
@@ -4,22 +4,27 @@
4
4
  // v1 scope: VS Code | React + Vue | Vite + Webpack + Rspack + esbuild + Rollup
5
5
  // ============================================================
6
6
  import { cac, type CAC } from 'cac'
7
+ import { fileURLToPath } from 'node:url'
7
8
  import { createRequire } from 'node:module'
9
+ import { apply } from './commands/apply.js'
10
+ import { detect } from './commands/detect.js'
8
11
  import { init } from './commands/init.js'
9
12
  import { doctor } from './commands/doctor.js'
13
+ import { plan } from './commands/plan.js'
10
14
  import { teardown } from './commands/teardown.js'
11
- import { log } from './utils/logger.js'
15
+ import { reportCommandError } from './utils/output.js'
12
16
 
13
17
  const require = createRequire(import.meta.url)
14
18
  const { version } = require('../package.json')
15
19
 
16
- // cac object
17
- const cli: CAC = cac('inspecto')
18
-
19
20
  interface GlobalOptions {
20
21
  debug?: boolean
21
22
  }
22
23
 
24
+ interface JsonCommandOptions extends GlobalOptions {
25
+ json?: boolean
26
+ }
27
+
23
28
  interface InitCommandOptions extends GlobalOptions {
24
29
  shared?: boolean
25
30
  skipInstall?: boolean
@@ -30,74 +35,141 @@ interface InitCommandOptions extends GlobalOptions {
30
35
  force?: boolean
31
36
  }
32
37
 
33
- cli
34
- .command('init', 'Set up Inspecto in your project')
35
- .option('--shared', 'Share .inspecto/settings.json with your team via Git', { default: false })
36
- .option('--skip-install', 'Skip npm dependency installation', { default: false })
37
- .option('--dry-run', 'Preview changes without modifying files', { default: false })
38
- .option('--provider <provider>', 'Set default provider (e.g. copilot, claude-code)')
39
- .option('--no-extension', 'Skip VS Code extension installation', { default: false })
40
- .option('--packages <names>', '(Monorepo) Comma-separated list of packages to inject')
41
- .option('--force', 'Force initialization even if environment is unsupported', { default: false })
42
- .option('--debug', 'Enable debug mode to show full error traces', { default: false })
43
- .action(async (options: InitCommandOptions) => {
44
- try {
45
- await init({
46
- shared: options.shared ?? false,
47
- skipInstall: options.skipInstall ?? false,
48
- dryRun: options.dryRun ?? false,
49
- ...(options.provider && { provider: options.provider }), // Changed from prefer to provider
50
- noExtension: options.noExtension ?? false,
51
- ...(options.packages && {
52
- packages: options.packages.split(',').map((s: string) => s.trim()),
53
- }),
54
- force: options.force ?? false,
55
- })
56
- } catch (err) {
57
- log.error(err instanceof Error ? err.message : String(err))
58
- if (options.debug && err instanceof Error && err.stack) {
59
- console.error(err.stack)
60
- }
61
- process.exit(1)
62
- }
38
+ interface ApplyCommandOptions extends JsonCommandOptions {
39
+ shared?: boolean
40
+ skipInstall?: boolean
41
+ dryRun?: boolean
42
+ extension?: boolean
43
+ }
44
+
45
+ function exitWithError(error: unknown, options: JsonCommandOptions = {}): never {
46
+ reportCommandError(error, {
47
+ debug: options.debug ?? false,
48
+ json: options.json ?? false,
63
49
  })
50
+ process.exit(1)
51
+ }
64
52
 
65
- cli
66
- .command('doctor', 'Diagnose your Inspecto installation')
67
- .option('--debug', 'Enable debug mode to show full error traces', { default: false })
68
- .action(async (options: GlobalOptions) => {
69
- try {
70
- await doctor()
71
- } catch (err) {
72
- log.error(err instanceof Error ? err.message : String(err))
73
- if (options.debug && err instanceof Error && err.stack) {
74
- console.error(err.stack)
53
+ export function createCli(_argv: readonly string[] = process.argv): CAC {
54
+ const cli: CAC = cac('inspecto')
55
+
56
+ cli
57
+ .command('init', 'Set up Inspecto in your project')
58
+ .option('--shared', 'Share .inspecto/settings.json with your team via Git', { default: false })
59
+ .option('--skip-install', 'Skip npm dependency installation', { default: false })
60
+ .option('--dry-run', 'Preview changes without modifying files', { default: false })
61
+ .option('--provider <provider>', 'Set default provider (e.g. copilot, claude-code)')
62
+ .option('--no-extension', 'Skip VS Code extension installation', { default: false })
63
+ .option('--packages <names>', '(Monorepo) Comma-separated list of packages to inject')
64
+ .option('--force', 'Force initialization even if environment is unsupported', {
65
+ default: false,
66
+ })
67
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
68
+ .action(async (options: InitCommandOptions) => {
69
+ try {
70
+ await init({
71
+ shared: options.shared ?? false,
72
+ skipInstall: options.skipInstall ?? false,
73
+ dryRun: options.dryRun ?? false,
74
+ ...(options.provider && { provider: options.provider }),
75
+ noExtension: options.noExtension ?? false,
76
+ ...(options.packages && {
77
+ packages: options.packages.split(',').map((s: string) => s.trim()),
78
+ }),
79
+ force: options.force ?? false,
80
+ })
81
+ } catch (error) {
82
+ exitWithError(error, options)
75
83
  }
76
- process.exit(1)
77
- }
78
- })
84
+ })
79
85
 
80
- cli
81
- .command('teardown', 'Remove Inspecto from your project')
82
- .option('--debug', 'Enable debug mode to show full error traces', { default: false })
83
- .action(async (options: GlobalOptions) => {
84
- try {
85
- await teardown()
86
- } catch (err) {
87
- log.error(err instanceof Error ? err.message : String(err))
88
- if (options.debug && err instanceof Error && err.stack) {
89
- console.error(err.stack)
86
+ cli
87
+ .command('doctor', 'Diagnose your Inspecto installation')
88
+ .option('--json', 'Print machine-readable JSON output', { default: false })
89
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
90
+ .action(async (options: JsonCommandOptions) => {
91
+ try {
92
+ await doctor({ json: options.json ?? false })
93
+ } catch (error) {
94
+ exitWithError(error, options)
90
95
  }
91
- process.exit(1)
92
- }
93
- })
96
+ })
94
97
 
95
- cli.help()
96
- cli.version(version)
98
+ cli
99
+ .command('detect', 'Detect whether the current project can be onboarded automatically')
100
+ .option('--json', 'Print machine-readable JSON output', { default: false })
101
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
102
+ .action(async (options: JsonCommandOptions) => {
103
+ try {
104
+ await detect(options.json ?? false)
105
+ } catch (error) {
106
+ exitWithError(error, options)
107
+ }
108
+ })
97
109
 
98
- try {
99
- cli.parse()
100
- } catch (err) {
101
- log.error(err instanceof Error ? err.message : String(err))
102
- process.exit(1)
110
+ cli
111
+ .command('plan', 'Preview the onboarding plan for the current project')
112
+ .option('--json', 'Print machine-readable JSON output', { default: false })
113
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
114
+ .action(async (options: JsonCommandOptions) => {
115
+ try {
116
+ await plan(options.json ?? false)
117
+ } catch (error) {
118
+ exitWithError(error, options)
119
+ }
120
+ })
121
+
122
+ cli
123
+ .command('apply', 'Apply the onboarding plan to the current project')
124
+ .option('--json', 'Print machine-readable JSON output', { default: false })
125
+ .option('--shared', 'Write shared Inspecto settings instead of local-only settings')
126
+ .option('--skip-install', 'Skip npm dependency installation')
127
+ .option('--dry-run', 'Preview changes without modifying files')
128
+ .option('--no-extension', 'Skip IDE extension installation')
129
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
130
+ .action(async (options: ApplyCommandOptions) => {
131
+ try {
132
+ await apply({
133
+ json: options.json ?? false,
134
+ ...(options.shared !== undefined && { shared: options.shared }),
135
+ ...(options.skipInstall !== undefined && { skipInstall: options.skipInstall }),
136
+ ...(options.dryRun !== undefined && { dryRun: options.dryRun }),
137
+ ...(options.extension === false && { noExtension: true }),
138
+ })
139
+ } catch (error) {
140
+ exitWithError(error, options)
141
+ }
142
+ })
143
+
144
+ cli
145
+ .command('teardown', 'Remove Inspecto from your project')
146
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
147
+ .action(async (options: GlobalOptions) => {
148
+ try {
149
+ await teardown()
150
+ } catch (error) {
151
+ exitWithError(error, options)
152
+ }
153
+ })
154
+
155
+ cli.help()
156
+ cli.version(version)
157
+
158
+ return cli
159
+ }
160
+
161
+ export async function runCli(argv: readonly string[] = process.argv): Promise<void> {
162
+ const cli = createCli(argv)
163
+ const parsedArgv = [...argv]
164
+
165
+ try {
166
+ await cli.parse(parsedArgv)
167
+ } catch (error) {
168
+ exitWithError(error, { json: argv.includes('--json') })
169
+ }
170
+ }
171
+
172
+ const entryPath = process.argv[1]
173
+ if (entryPath && fileURLToPath(import.meta.url) === entryPath) {
174
+ void runCli()
103
175
  }
@@ -0,0 +1,114 @@
1
+ import { applyOnboardingPlan, type ApplyOnboardingResult } from '../onboarding/apply.js'
2
+ import { buildOnboardingContext } from '../onboarding/context.js'
3
+ import { createPlanResult } from '../onboarding/planner.js'
4
+ import { log } from '../utils/logger.js'
5
+ import { writeCommandOutput } from '../utils/output.js'
6
+ import type { PlanResult } from '../types.js'
7
+
8
+ export interface ApplyCommandOptions {
9
+ json?: boolean
10
+ shared?: boolean
11
+ skipInstall?: boolean
12
+ dryRun?: boolean
13
+ noExtension?: boolean
14
+ }
15
+
16
+ export interface ApplyCommandResult extends ApplyOnboardingResult {
17
+ plan: PlanResult
18
+ }
19
+
20
+ function getProviderDefault(
21
+ providerId?: string,
22
+ preferredMode?: 'cli' | 'extension',
23
+ ): string | undefined {
24
+ if (!providerId) return undefined
25
+ const mode = preferredMode ?? (providerId === 'coco' ? 'cli' : 'extension')
26
+ return `${providerId}.${mode}`
27
+ }
28
+
29
+ function statusRank(status: ApplyCommandResult['status']): number {
30
+ switch (status) {
31
+ case 'error':
32
+ return 3
33
+ case 'blocked':
34
+ return 2
35
+ case 'warning':
36
+ return 1
37
+ case 'ok':
38
+ default:
39
+ return 0
40
+ }
41
+ }
42
+
43
+ function mergeStatus(
44
+ planStatus: PlanResult['status'],
45
+ applyStatus: ApplyOnboardingResult['status'],
46
+ ): ApplyCommandResult['status'] {
47
+ return statusRank(planStatus) >= statusRank(applyStatus) ? planStatus : applyStatus
48
+ }
49
+
50
+ function printApplyResult(result: ApplyCommandResult): void {
51
+ const manualSteps = result.postInstall.nextSteps.filter(
52
+ step => !result.plan.blockers.some(blocker => blocker.message === step),
53
+ )
54
+
55
+ log.header('Inspecto Apply')
56
+ log.info(`Status: ${result.status}`)
57
+ log.info(`Strategy: ${result.plan.strategy}`)
58
+
59
+ for (const blocker of result.plan.blockers) {
60
+ log.error(blocker.message)
61
+ }
62
+ for (const warning of result.plan.warnings) {
63
+ log.warn(warning.message)
64
+ }
65
+
66
+ if (manualSteps.length > 0 || result.plan.blockers.length > 0) {
67
+ log.blank()
68
+ log.warn('──────── Manual Steps Required ────────')
69
+ manualSteps.forEach(step => log.error(step))
70
+ return
71
+ }
72
+
73
+ if (result.plan.warnings.length > 0) {
74
+ return
75
+ }
76
+
77
+ log.ready('Ready! Hold Alt + Click any element to inspect.')
78
+ }
79
+
80
+ export async function apply(options: ApplyCommandOptions = {}): Promise<ApplyCommandResult> {
81
+ const root = process.cwd()
82
+ const context = await buildOnboardingContext(root)
83
+ const plan = createPlanResult(context)
84
+ const selectedProvider =
85
+ context.providers.find(provider => provider.id === plan.defaults.provider) ?? null
86
+ const selectedIDE =
87
+ context.ides.find(ide => ide.ide === plan.defaults.ide) ??
88
+ context.ides.find(ide => ide.supported) ??
89
+ null
90
+
91
+ const applyResult = await applyOnboardingPlan({
92
+ repoRoot: root,
93
+ projectRoot: root,
94
+ packageManager: context.packageManager,
95
+ supportedBuildTargets: context.buildTools.supported,
96
+ options: {
97
+ shared: options.shared ?? plan.defaults.shared,
98
+ skipInstall: options.skipInstall ?? false,
99
+ dryRun: options.dryRun ?? false,
100
+ noExtension: options.noExtension ?? !plan.defaults.extension,
101
+ quiet: options.json ?? false,
102
+ },
103
+ selectedIDE,
104
+ providerDefault: getProviderDefault(plan.defaults.provider, selectedProvider?.preferredMode),
105
+ plan,
106
+ })
107
+
108
+ const result: ApplyCommandResult = {
109
+ ...applyResult,
110
+ status: mergeStatus(plan.status, applyResult.status),
111
+ plan,
112
+ }
113
+ return writeCommandOutput(result, options.json ?? false, printApplyResult)
114
+ }
@@ -0,0 +1,59 @@
1
+ import { log } from '../utils/logger.js'
2
+ import { writeCommandOutput } from '../utils/output.js'
3
+ import { createDetectionResult } from '../onboarding/planner.js'
4
+ import type { DetectionResult } from '../types.js'
5
+
6
+ function printDetectionResult(result: DetectionResult): void {
7
+ const suppressedCodes = new Set([
8
+ 'unsupported-build-tool',
9
+ 'unsupported-build-tool-present',
10
+ 'unsupported-framework',
11
+ 'unsupported-framework-present',
12
+ ])
13
+
14
+ log.header('Inspecto Detect')
15
+ log.info(`Status: ${result.status}`)
16
+ log.info(`Root: ${result.project.root}`)
17
+ log.info(`Package manager: ${result.project.packageManager}`)
18
+
19
+ if (result.environment.frameworks.length > 0) {
20
+ log.success(`Supported frameworks: ${result.environment.frameworks.join(', ')}`)
21
+ }
22
+ if (result.environment.unsupportedFrameworks.length > 0) {
23
+ log.warn(`Unsupported frameworks: ${result.environment.unsupportedFrameworks.join(', ')}`)
24
+ }
25
+ if (result.environment.buildTools.length > 0) {
26
+ log.success(
27
+ `Supported build tools: ${result.environment.buildTools.map(tool => tool.label).join(', ')}`,
28
+ )
29
+ }
30
+ if (result.environment.unsupportedBuildTools.length > 0) {
31
+ log.warn(`Unsupported build tools: ${result.environment.unsupportedBuildTools.join(', ')}`)
32
+ }
33
+
34
+ const supportedIdes = result.environment.ides.filter(ide => ide.supported).map(ide => ide.ide)
35
+ if (supportedIdes.length > 0) {
36
+ log.success(`Supported IDEs: ${supportedIdes.join(', ')}`)
37
+ }
38
+
39
+ const supportedProviders = result.environment.providers
40
+ .filter(provider => provider.supported)
41
+ .map(provider => provider.label)
42
+ if (supportedProviders.length > 0) {
43
+ log.success(`Supported providers: ${supportedProviders.join(', ')}`)
44
+ }
45
+
46
+ for (const blocker of result.blockers) {
47
+ if (suppressedCodes.has(blocker.code)) continue
48
+ log.error(blocker.message)
49
+ }
50
+ for (const warning of result.warnings) {
51
+ if (suppressedCodes.has(warning.code)) continue
52
+ log.warn(warning.message)
53
+ }
54
+ }
55
+
56
+ export async function detect(json = false): Promise<DetectionResult> {
57
+ const result = await createDetectionResult(process.cwd())
58
+ return writeCommandOutput(result, json, printDetectionResult)
59
+ }