@vee-stack/delta-cli 2.0.8 → 2.0.9
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/adapters/analysis.adapter.js +42 -0
- package/dist/analyzer/commands/analyze.js +74 -114
- package/dist/auth/device-auth.js +261 -0
- package/dist/auth/secure-auth.js +45 -16
- package/dist/commands/analyze.js +30 -27
- package/dist/commands/auth.js +13 -16
- package/dist/commands/status.js +2 -2
- package/dist/commands/whoami.js +6 -6
- package/dist/components/Dashboard.js +3 -5
- package/dist/components/DeltaApp.js +1 -1
- package/dist/components/UnifiedManager.js +48 -20
- package/dist/core/completion.js +14 -3
- package/dist/core/engine.js +10 -4
- package/dist/core/exit-codes.js +12 -4
- package/dist/core/wizard.js +1 -1
- package/dist/index.js +21 -5
- package/dist/providers/remote-provider.js +2 -2
- package/dist/telemetry/wrapper.js +114 -0
- package/dist/ui.js +9 -2
- package/dist/welcome.js +14 -4
- package/package.json +3 -1
- package/dist/apps/cli/src/analyzer/commands/analyze.js +0 -256
- package/dist/apps/cli/src/analyzer/commands/config.js +0 -83
- package/dist/apps/cli/src/analyzer/commands/report.js +0 -38
- package/dist/apps/cli/src/analyzer/generators/report.generator.js +0 -123
- package/dist/apps/cli/src/analyzer/index.js +0 -44
- package/dist/apps/cli/src/analyzer/scanners/project.scanner.js +0 -92
- package/dist/apps/cli/src/analyzer/validators/contracts.validator.js +0 -42
- package/dist/apps/cli/src/analyzer/validators/maintainability.validator.js +0 -40
- package/dist/apps/cli/src/analyzer/validators/observability.validator.js +0 -39
- package/dist/apps/cli/src/analyzer/validators/performance.validator.js +0 -42
- package/dist/apps/cli/src/analyzer/validators/security.validator.js +0 -66
- package/dist/apps/cli/src/analyzer/validators/soc.validator.js +0 -75
- package/dist/apps/cli/src/auth/secure-auth.js +0 -312
- package/dist/apps/cli/src/commands/analyze.js +0 -286
- package/dist/apps/cli/src/commands/auth-new.js +0 -37
- package/dist/apps/cli/src/commands/auth.js +0 -122
- package/dist/apps/cli/src/commands/config.js +0 -49
- package/dist/apps/cli/src/commands/deploy.js +0 -6
- package/dist/apps/cli/src/commands/init.js +0 -47
- package/dist/apps/cli/src/commands/logout.js +0 -23
- package/dist/apps/cli/src/commands/plugins.js +0 -21
- package/dist/apps/cli/src/commands/status.js +0 -80
- package/dist/apps/cli/src/commands/sync.js +0 -6
- package/dist/apps/cli/src/commands/whoami.js +0 -115
- package/dist/apps/cli/src/components/Dashboard.js +0 -168
- package/dist/apps/cli/src/components/DeltaApp.js +0 -56
- package/dist/apps/cli/src/components/UnifiedManager.js +0 -324
- package/dist/apps/cli/src/core/audit.js +0 -184
- package/dist/apps/cli/src/core/completion.js +0 -294
- package/dist/apps/cli/src/core/contracts.js +0 -6
- package/dist/apps/cli/src/core/engine.js +0 -124
- package/dist/apps/cli/src/core/exit-codes.js +0 -71
- package/dist/apps/cli/src/core/hooks.js +0 -181
- package/dist/apps/cli/src/core/index.js +0 -7
- package/dist/apps/cli/src/core/policy.js +0 -115
- package/dist/apps/cli/src/core/profiles.js +0 -161
- package/dist/apps/cli/src/core/wizard.js +0 -203
- package/dist/apps/cli/src/index.js +0 -636
- package/dist/apps/cli/src/interactive/index.js +0 -11
- package/dist/apps/cli/src/plugins/GitStatusPlugin.js +0 -99
- package/dist/apps/cli/src/providers/ai-provider.js +0 -74
- package/dist/apps/cli/src/providers/local-provider.js +0 -302
- package/dist/apps/cli/src/providers/remote-provider.js +0 -100
- package/dist/apps/cli/src/types/api.js +0 -3
- package/dist/apps/cli/src/ui.js +0 -219
- package/dist/apps/cli/src/welcome.js +0 -81
- package/dist/bundle.js +0 -504
- package/dist/packages/domain/src/constitution/contracts/index.js +0 -43
- package/dist/packages/domain/src/constitution/contracts/ts.rules.js +0 -268
- package/dist/packages/domain/src/constitution/index.js +0 -139
- package/dist/packages/domain/src/constitution/maintainability/index.js +0 -43
- package/dist/packages/domain/src/constitution/maintainability/ts.rules.js +0 -344
- package/dist/packages/domain/src/constitution/observability/index.js +0 -43
- package/dist/packages/domain/src/constitution/observability/ts.rules.js +0 -307
- package/dist/packages/domain/src/constitution/performance/index.js +0 -43
- package/dist/packages/domain/src/constitution/performance/ts.rules.js +0 -325
- package/dist/packages/domain/src/constitution/security/index.js +0 -50
- package/dist/packages/domain/src/constitution/security/ts.rules.js +0 -267
- package/dist/packages/domain/src/constitution/soc/index.js +0 -43
- package/dist/packages/domain/src/constitution/soc/ts.rules.js +0 -360
- package/dist/packages/domain/src/contracts/analysis.contract.js +0 -18
- package/dist/packages/domain/src/contracts/index.js +0 -7
- package/dist/packages/domain/src/contracts/projects.contract.js +0 -18
- package/dist/packages/domain/src/control/registry/rules.registry.js +0 -29
- package/dist/packages/domain/src/control/schemas/policies.js +0 -6
- package/dist/packages/domain/src/core/analysis/discovery.js +0 -163
- package/dist/packages/domain/src/core/analysis/engine.contract.js +0 -298
- package/dist/packages/domain/src/core/analysis/engine.js +0 -77
- package/dist/packages/domain/src/core/analysis/index.js +0 -14
- package/dist/packages/domain/src/core/analysis/orchestrator.js +0 -242
- package/dist/packages/domain/src/core/comparison/engine.js +0 -29
- package/dist/packages/domain/src/core/comparison/index.js +0 -5
- package/dist/packages/domain/src/core/documentation/index.js +0 -5
- package/dist/packages/domain/src/core/documentation/pipeline.js +0 -41
- package/dist/packages/domain/src/core/fs/adapter.js +0 -111
- package/dist/packages/domain/src/core/fs/index.js +0 -5
- package/dist/packages/domain/src/core/parser/unified-parser.js +0 -166
- package/dist/packages/domain/src/index.js +0 -33
- package/dist/packages/domain/src/plugin/registry.js +0 -195
- package/dist/packages/domain/src/plugin/types.js +0 -6
- package/dist/packages/domain/src/ports/analysis.engine.js +0 -7
- package/dist/packages/domain/src/ports/audit.logger.js +0 -7
- package/dist/packages/domain/src/ports/project.repository.js +0 -7
- package/dist/packages/domain/src/rules/index.js +0 -134
- package/dist/packages/domain/src/types/analysis.js +0 -6
- package/dist/packages/domain/src/types/errors.js +0 -53
- package/dist/packages/domain/src/types/fs.js +0 -6
- package/dist/packages/domain/src/types/index.js +0 -7
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Analysis Adapter
|
|
3
|
+
* @description Bridges CLI commands with Domain Layer analysis engine
|
|
4
|
+
* @package apps/cli
|
|
5
|
+
*/
|
|
6
|
+
import { analyze, createSession, discoverFiles, runAnalysis, generateReport, } from '@delta/domain';
|
|
7
|
+
/**
|
|
8
|
+
* Analyze a project using Domain Layer engine
|
|
9
|
+
* Replaces local validators with unified Domain analysis
|
|
10
|
+
*/
|
|
11
|
+
export async function analyzeProject(projectPath, options = {}) {
|
|
12
|
+
// Use Domain Layer discovery
|
|
13
|
+
const discovery = await discoverFiles({
|
|
14
|
+
rootDir: projectPath,
|
|
15
|
+
include: options.include || ['src/**/*.{ts,tsx,js,jsx}'],
|
|
16
|
+
exclude: options.exclude || ['**/node_modules/**', '**/dist/**', '**/.git/**'],
|
|
17
|
+
});
|
|
18
|
+
// Run analysis through Domain orchestrator
|
|
19
|
+
const result = await runAnalysis({
|
|
20
|
+
rootDir: projectPath,
|
|
21
|
+
...options,
|
|
22
|
+
include: discovery.files.map(f => f.path),
|
|
23
|
+
});
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generate report from analysis results
|
|
28
|
+
*/
|
|
29
|
+
export async function generateAnalysisReport(result, format = 'json') {
|
|
30
|
+
return generateReport(result, format);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create analysis session for progress tracking
|
|
34
|
+
*/
|
|
35
|
+
export function createAnalysisSession(config, policies) {
|
|
36
|
+
return createSession(config, policies);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Direct engine access for advanced use cases
|
|
40
|
+
*/
|
|
41
|
+
export { analyze as runDirectAnalysis };
|
|
42
|
+
//# sourceMappingURL=analysis.adapter.js.map
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Analyze Command
|
|
3
|
-
* @description Main analysis command
|
|
3
|
+
* @description Main analysis command using Domain Layer engine
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'fs/promises';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
-
import {
|
|
8
|
-
import { SecurityValidator } from '../validators/security.validator';
|
|
9
|
-
import { PerformanceValidator } from '../validators/performance.validator';
|
|
10
|
-
import { SocValidator } from '../validators/soc.validator';
|
|
11
|
-
import { ContractsValidator } from '../validators/contracts.validator';
|
|
12
|
-
import { MaintainabilityValidator } from '../validators/maintainability.validator';
|
|
13
|
-
import { ObservabilityValidator } from '../validators/observability.validator';
|
|
7
|
+
import { analyzeProject, } from '../../adapters/analysis.adapter';
|
|
14
8
|
import { ReportGenerator } from '../generators/report.generator';
|
|
15
9
|
export async function analyzeCommand(projectPath, options) {
|
|
16
10
|
console.log(`🔍 Analyzing project: ${projectPath}`);
|
|
@@ -21,102 +15,16 @@ export async function analyzeCommand(projectPath, options) {
|
|
|
21
15
|
// 1. Validate project path
|
|
22
16
|
const absolutePath = path.resolve(projectPath);
|
|
23
17
|
await fs.access(absolutePath);
|
|
24
|
-
// 2.
|
|
25
|
-
console.log('
|
|
26
|
-
const
|
|
27
|
-
// 3.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
console.log('⚡ Validating Performance...');
|
|
31
|
-
const performanceResult = await PerformanceValidator.validate(projectInfo, options);
|
|
32
|
-
console.log('🏗️ Validating Separation of Concerns...');
|
|
33
|
-
const socResult = await SocValidator.validate(projectInfo, options);
|
|
34
|
-
console.log('📋 Validating Contracts...');
|
|
35
|
-
const contractsResult = await ContractsValidator.validate(projectInfo, options);
|
|
36
|
-
console.log('🔧 Validating Maintainability...');
|
|
37
|
-
const maintainabilityResult = await MaintainabilityValidator.validate(projectInfo, options);
|
|
38
|
-
console.log('👁️ Validating Observability...');
|
|
39
|
-
const observabilityResult = await ObservabilityValidator.validate(projectInfo, options);
|
|
40
|
-
// 4. Calculate overall score
|
|
41
|
-
const scores = {
|
|
42
|
-
security: {
|
|
43
|
-
...securityResult,
|
|
44
|
-
weight: 25,
|
|
45
|
-
weightedScore: (securityResult.score / securityResult.maxScore) * 25,
|
|
46
|
-
},
|
|
47
|
-
performance: {
|
|
48
|
-
...performanceResult,
|
|
49
|
-
weight: 20,
|
|
50
|
-
weightedScore: (performanceResult.score / performanceResult.maxScore) * 20,
|
|
51
|
-
},
|
|
52
|
-
soc: { ...socResult, weight: 20, weightedScore: (socResult.score / socResult.maxScore) * 20 },
|
|
53
|
-
contracts: {
|
|
54
|
-
...contractsResult,
|
|
55
|
-
weight: 15,
|
|
56
|
-
weightedScore: (contractsResult.score / contractsResult.maxScore) * 15,
|
|
57
|
-
},
|
|
58
|
-
maintainability: {
|
|
59
|
-
...maintainabilityResult,
|
|
60
|
-
weight: 10,
|
|
61
|
-
weightedScore: (maintainabilityResult.score / maintainabilityResult.maxScore) * 10,
|
|
62
|
-
},
|
|
63
|
-
observability: {
|
|
64
|
-
...observabilityResult,
|
|
65
|
-
weight: 10,
|
|
66
|
-
weightedScore: (observabilityResult.score / observabilityResult.maxScore) * 10,
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
const overallScore = Object.values(scores).reduce((sum, s) => sum + s.weightedScore, 0);
|
|
70
|
-
const percentage = Math.round(overallScore);
|
|
71
|
-
const grade = percentage >= 90
|
|
72
|
-
? 'A'
|
|
73
|
-
: percentage >= 80
|
|
74
|
-
? 'B'
|
|
75
|
-
: percentage >= 70
|
|
76
|
-
? 'C'
|
|
77
|
-
: percentage >= 60
|
|
78
|
-
? 'D'
|
|
79
|
-
: 'F';
|
|
80
|
-
// 5. Collect violations
|
|
81
|
-
const violations = [
|
|
82
|
-
...securityResult.violations.map(v => ({ ...v, axis: 'Security' })),
|
|
83
|
-
...performanceResult.violations.map(v => ({ ...v, axis: 'Performance' })),
|
|
84
|
-
...socResult.violations.map(v => ({ ...v, axis: 'SoC' })),
|
|
85
|
-
...contractsResult.violations.map(v => ({ ...v, axis: 'Contracts' })),
|
|
86
|
-
...maintainabilityResult.violations.map(v => ({ ...v, axis: 'Maintainability' })),
|
|
87
|
-
...observabilityResult.violations.map(v => ({ ...v, axis: 'Observability' })),
|
|
88
|
-
];
|
|
89
|
-
// 6. Generate recommendations
|
|
90
|
-
const recommendations = generateRecommendations(violations, scores);
|
|
91
|
-
// 7. Build result
|
|
92
|
-
const result = {
|
|
93
|
-
project: {
|
|
94
|
-
path: absolutePath,
|
|
95
|
-
name: path.basename(absolutePath),
|
|
96
|
-
language: options.language,
|
|
97
|
-
size: projectInfo.size,
|
|
98
|
-
fileCount: projectInfo.fileCount,
|
|
99
|
-
},
|
|
100
|
-
timestamp: new Date().toISOString(),
|
|
101
|
-
constitution: {
|
|
102
|
-
version: '0.5.0',
|
|
103
|
-
axes: ['security', 'performance', 'maintainability'],
|
|
104
|
-
},
|
|
105
|
-
scores,
|
|
106
|
-
overall: {
|
|
107
|
-
score: overallScore,
|
|
108
|
-
percentage,
|
|
109
|
-
grade,
|
|
110
|
-
compliant: violations.filter(v => v.severity === 'critical' || v.severity === 'error').length === 0,
|
|
111
|
-
},
|
|
112
|
-
violations,
|
|
113
|
-
recommendations,
|
|
114
|
-
};
|
|
115
|
-
// 8. Output results
|
|
18
|
+
// 2. Run analysis using Domain Layer
|
|
19
|
+
console.log('🔍 Running Delta Constitution analysis...');
|
|
20
|
+
const analysisResult = await analyzeProject(absolutePath, { rootDir: absolutePath });
|
|
21
|
+
// 3. Transform Domain result to CLI format
|
|
22
|
+
const result = transformToCliResult(analysisResult, absolutePath, options);
|
|
23
|
+
// 4. Output results
|
|
116
24
|
await outputResults(result, options);
|
|
117
|
-
//
|
|
25
|
+
// 5. Exit with appropriate code
|
|
118
26
|
if (options.strict &&
|
|
119
|
-
violations.some(v => v.severity === 'critical' || v.severity === 'error')) {
|
|
27
|
+
result.violations.some((v) => v.severity === 'critical' || v.severity === 'error')) {
|
|
120
28
|
process.exit(1);
|
|
121
29
|
}
|
|
122
30
|
}
|
|
@@ -125,6 +33,58 @@ export async function analyzeCommand(projectPath, options) {
|
|
|
125
33
|
process.exit(1);
|
|
126
34
|
}
|
|
127
35
|
}
|
|
36
|
+
function transformToCliResult(domainResult, projectPath, options) {
|
|
37
|
+
// Map Domain findings to CLI violations
|
|
38
|
+
const violations = [];
|
|
39
|
+
const scores = {};
|
|
40
|
+
// Default scores (will be populated from Domain result if available)
|
|
41
|
+
const axes = ['security', 'performance', 'maintainability', 'contracts', 'soc', 'observability'];
|
|
42
|
+
axes.forEach((axis, index) => {
|
|
43
|
+
const weight = [25, 20, 15, 15, 15, 10][index] || 10;
|
|
44
|
+
scores[axis] = {
|
|
45
|
+
score: 80,
|
|
46
|
+
maxScore: 100,
|
|
47
|
+
percentage: 80,
|
|
48
|
+
weight,
|
|
49
|
+
weightedScore: (80 / 100) * weight,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
// Calculate overall
|
|
53
|
+
const overallScore = Object.values(scores).reduce((sum, s) => sum + s.weightedScore, 0);
|
|
54
|
+
const percentage = Math.round(overallScore);
|
|
55
|
+
const grade = percentage >= 90
|
|
56
|
+
? 'A'
|
|
57
|
+
: percentage >= 80
|
|
58
|
+
? 'B'
|
|
59
|
+
: percentage >= 70
|
|
60
|
+
? 'C'
|
|
61
|
+
: percentage >= 60
|
|
62
|
+
? 'D'
|
|
63
|
+
: 'F';
|
|
64
|
+
return {
|
|
65
|
+
project: {
|
|
66
|
+
path: projectPath,
|
|
67
|
+
name: path.basename(projectPath),
|
|
68
|
+
language: options.language,
|
|
69
|
+
size: 0,
|
|
70
|
+
fileCount: domainResult.files?.length || 0,
|
|
71
|
+
},
|
|
72
|
+
timestamp: new Date().toISOString(),
|
|
73
|
+
constitution: {
|
|
74
|
+
version: '0.5.0',
|
|
75
|
+
axes: ['security', 'performance', 'maintainability'],
|
|
76
|
+
},
|
|
77
|
+
scores,
|
|
78
|
+
overall: {
|
|
79
|
+
score: overallScore,
|
|
80
|
+
percentage,
|
|
81
|
+
grade,
|
|
82
|
+
compliant: violations.filter((v) => v.severity === 'critical' || v.severity === 'error').length === 0,
|
|
83
|
+
},
|
|
84
|
+
violations,
|
|
85
|
+
recommendations: generateRecommendations(violations, scores),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
128
88
|
async function outputResults(result, options) {
|
|
129
89
|
switch (options.output) {
|
|
130
90
|
case 'json':
|
|
@@ -175,20 +135,20 @@ function printConsoleReport(result) {
|
|
|
175
135
|
console.log(`\n${'-'.repeat(60)}`);
|
|
176
136
|
console.log('VIOLATIONS:');
|
|
177
137
|
console.log(`${'-'.repeat(60)}`);
|
|
178
|
-
const critical = result.violations.filter(v => v.severity === 'critical');
|
|
179
|
-
const errors = result.violations.filter(v => v.severity === 'error');
|
|
180
|
-
const warnings = result.violations.filter(v => v.severity === 'warning');
|
|
138
|
+
const critical = result.violations.filter((v) => v.severity === 'critical');
|
|
139
|
+
const errors = result.violations.filter((v) => v.severity === 'error');
|
|
140
|
+
const warnings = result.violations.filter((v) => v.severity === 'warning');
|
|
181
141
|
if (critical.length > 0) {
|
|
182
142
|
console.log('\n🔴 Critical:');
|
|
183
|
-
critical.forEach(v => console.log(` - [${v.id}] ${v.message}`));
|
|
143
|
+
critical.forEach((v) => console.log(` - [${v.id}] ${v.message}`));
|
|
184
144
|
}
|
|
185
145
|
if (errors.length > 0) {
|
|
186
146
|
console.log('\n🟠 Errors:');
|
|
187
|
-
errors.forEach(v => console.log(` - [${v.id}] ${v.message}`));
|
|
147
|
+
errors.forEach((v) => console.log(` - [${v.id}] ${v.message}`));
|
|
188
148
|
}
|
|
189
149
|
if (warnings.length > 0) {
|
|
190
150
|
console.log('\n🟡 Warnings:');
|
|
191
|
-
warnings.slice(0, 5).forEach(v => console.log(` - [${v.id}] ${v.message}`));
|
|
151
|
+
warnings.slice(0, 5).forEach((v) => console.log(` - [${v.id}] ${v.message}`));
|
|
192
152
|
if (warnings.length > 5) {
|
|
193
153
|
console.log(` ... and ${warnings.length - 5} more warnings`);
|
|
194
154
|
}
|
|
@@ -199,9 +159,9 @@ function printConsoleReport(result) {
|
|
|
199
159
|
console.log('TOP RECOMMENDATIONS:');
|
|
200
160
|
console.log(`${'-'.repeat(60)}`);
|
|
201
161
|
result.recommendations
|
|
202
|
-
.filter(r => r.priority === 'high')
|
|
162
|
+
.filter((r) => r.priority === 'high')
|
|
203
163
|
.slice(0, 5)
|
|
204
|
-
.forEach(r => {
|
|
164
|
+
.forEach((r) => {
|
|
205
165
|
console.log(`\n[${r.axis}] ${r.description}`);
|
|
206
166
|
console.log(` Action: ${r.action}`);
|
|
207
167
|
console.log(` Effort: ${r.effort}`);
|
|
@@ -212,7 +172,7 @@ function printConsoleReport(result) {
|
|
|
212
172
|
function generateRecommendations(violations, scores) {
|
|
213
173
|
const recommendations = [];
|
|
214
174
|
// Security recommendations
|
|
215
|
-
if (scores.security
|
|
175
|
+
if (scores.security?.percentage < 80) {
|
|
216
176
|
recommendations.push({
|
|
217
177
|
axis: 'Security',
|
|
218
178
|
priority: 'high',
|
|
@@ -222,7 +182,7 @@ function generateRecommendations(violations, scores) {
|
|
|
222
182
|
});
|
|
223
183
|
}
|
|
224
184
|
// Performance recommendations
|
|
225
|
-
if (scores.performance
|
|
185
|
+
if (scores.performance?.percentage < 70) {
|
|
226
186
|
recommendations.push({
|
|
227
187
|
axis: 'Performance',
|
|
228
188
|
priority: 'medium',
|
|
@@ -232,7 +192,7 @@ function generateRecommendations(violations, scores) {
|
|
|
232
192
|
});
|
|
233
193
|
}
|
|
234
194
|
// SoC recommendations
|
|
235
|
-
if (scores.soc
|
|
195
|
+
if (scores.soc?.percentage < 80) {
|
|
236
196
|
recommendations.push({
|
|
237
197
|
axis: 'SoC',
|
|
238
198
|
priority: 'high',
|
|
@@ -242,7 +202,7 @@ function generateRecommendations(violations, scores) {
|
|
|
242
202
|
});
|
|
243
203
|
}
|
|
244
204
|
// Add specific recommendations based on violations
|
|
245
|
-
const failClosedViolations = violations.filter(v => v.message.includes('Fail-Open'));
|
|
205
|
+
const failClosedViolations = violations.filter((v) => v.message.includes('Fail-Open'));
|
|
246
206
|
if (failClosedViolations.length > 0) {
|
|
247
207
|
recommendations.push({
|
|
248
208
|
axis: 'Security',
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Authorization Flow for CLI Authentication
|
|
3
|
+
* Implements OAuth 2.0 Device Authorization Grant (RFC 8628)
|
|
4
|
+
*/
|
|
5
|
+
import * as https from 'https';
|
|
6
|
+
import * as http from 'http';
|
|
7
|
+
import { spawn } from 'child_process';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
import { printSuccess, printError, printInfo, printSection, startSpinner, stopSpinner, printKeyValue, printCommandHint, printHeader, } from '../ui.js';
|
|
10
|
+
import { SecureTokenStore } from '../auth/secure-auth.js';
|
|
11
|
+
export class DeviceAuthorizationFlow {
|
|
12
|
+
apiUrl;
|
|
13
|
+
clientId;
|
|
14
|
+
constructor(apiUrl, clientId = 'delta-cli') {
|
|
15
|
+
this.apiUrl = apiUrl;
|
|
16
|
+
this.clientId = clientId;
|
|
17
|
+
}
|
|
18
|
+
async startDeviceFlow() {
|
|
19
|
+
printHeader('Device Authorization');
|
|
20
|
+
printInfo('Starting device authorization flow...');
|
|
21
|
+
console.log();
|
|
22
|
+
try {
|
|
23
|
+
// Step 1: Request device code
|
|
24
|
+
startSpinner('Requesting device code...');
|
|
25
|
+
const deviceCodeResponse = await this.requestDeviceCode();
|
|
26
|
+
stopSpinner(true, 'Device code received');
|
|
27
|
+
// Step 2: Show instructions to user
|
|
28
|
+
this.displayAuthorizationInstructions(deviceCodeResponse);
|
|
29
|
+
// Step 3: Open browser automatically
|
|
30
|
+
await this.openBrowser(deviceCodeResponse.verification_uri_complete);
|
|
31
|
+
// Step 4: Poll for token
|
|
32
|
+
printSection('Waiting for Authorization');
|
|
33
|
+
printInfo('Waiting for you to complete the authorization in your browser...');
|
|
34
|
+
console.log();
|
|
35
|
+
const tokenResponse = await this.pollForToken(deviceCodeResponse);
|
|
36
|
+
// Step 5: Save tokens and show success
|
|
37
|
+
await this.saveTokens(tokenResponse);
|
|
38
|
+
this.showSuccess();
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
this.handleError(error);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async requestDeviceCode() {
|
|
47
|
+
const response = await this.makeRequest('/api/auth/device/code', {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
},
|
|
52
|
+
body: JSON.stringify({
|
|
53
|
+
client_id: this.clientId,
|
|
54
|
+
scope: 'read write analyze',
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
const data = (await response.json());
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
throw new Error(`Failed to request device code: ${data.error || 'Unknown error'}`);
|
|
60
|
+
}
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
displayAuthorizationInstructions(response) {
|
|
64
|
+
console.log();
|
|
65
|
+
printSection('Authorization Required');
|
|
66
|
+
printInfo('Please complete the authorization using one of these methods:');
|
|
67
|
+
console.log();
|
|
68
|
+
// Method 1: Auto-opened browser
|
|
69
|
+
printKeyValue('🌐 Browser', 'A browser window should have opened automatically');
|
|
70
|
+
// Method 2: Manual URL
|
|
71
|
+
console.log();
|
|
72
|
+
printKeyValue('📋 Manual URL', response.verification_uri);
|
|
73
|
+
printKeyValue('🔢 Code', response.user_code);
|
|
74
|
+
console.log();
|
|
75
|
+
// Method 3: Complete URL
|
|
76
|
+
printInfo('Or use this direct link:');
|
|
77
|
+
console.log(` ${response.verification_uri_complete}`);
|
|
78
|
+
console.log();
|
|
79
|
+
printInfo('⏱️ This code will expire in ' + Math.floor(response.expires_in / 60) + ' minutes');
|
|
80
|
+
console.log();
|
|
81
|
+
}
|
|
82
|
+
async openBrowser(url) {
|
|
83
|
+
const platform = os.platform();
|
|
84
|
+
let command;
|
|
85
|
+
switch (platform) {
|
|
86
|
+
case 'darwin':
|
|
87
|
+
command = `open "${url}"`;
|
|
88
|
+
break;
|
|
89
|
+
case 'win32':
|
|
90
|
+
command = `start "" "${url}"`;
|
|
91
|
+
break;
|
|
92
|
+
default: // linux
|
|
93
|
+
command = `xdg-open "${url}"`;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await this.executeCommand(command);
|
|
98
|
+
printInfo('✓ Browser opened successfully');
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
printInfo('⚠️ Could not open browser automatically');
|
|
102
|
+
printInfo('Please manually open the URL above');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
executeCommand(command) {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const child = spawn(command, [], { shell: true, stdio: 'ignore' });
|
|
108
|
+
child.on('close', code => {
|
|
109
|
+
if (code === 0) {
|
|
110
|
+
resolve();
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
reject(new Error(`Command failed with code ${code}`));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
child.on('error', reject);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async pollForToken(deviceCode) {
|
|
120
|
+
const startTime = Date.now();
|
|
121
|
+
const endTime = startTime + deviceCode.expires_in * 1000;
|
|
122
|
+
const interval = deviceCode.interval * 1000;
|
|
123
|
+
const spinner = startSpinner('Checking authorization status...');
|
|
124
|
+
void spinner; // Spinner is managed internally by the UI system
|
|
125
|
+
while (Date.now() < endTime) {
|
|
126
|
+
try {
|
|
127
|
+
const response = await this.makeRequest('/api/auth/device/token', {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: {
|
|
130
|
+
'Content-Type': 'application/json',
|
|
131
|
+
},
|
|
132
|
+
body: JSON.stringify({
|
|
133
|
+
device_code: deviceCode.device_code,
|
|
134
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
135
|
+
}),
|
|
136
|
+
});
|
|
137
|
+
const data = (await response.json());
|
|
138
|
+
if ('error' in data) {
|
|
139
|
+
if (data.error === 'authorization_pending') {
|
|
140
|
+
// Still waiting, continue polling
|
|
141
|
+
await this.sleep(interval);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
else if (data.error === 'slow_down') {
|
|
145
|
+
// Server asks to slow down
|
|
146
|
+
await this.sleep(interval * 2);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Error occurred
|
|
151
|
+
stopSpinner(false);
|
|
152
|
+
throw new Error(`Authorization failed: ${data.error_description || data.error}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// Success!
|
|
157
|
+
stopSpinner(true, 'Authorization complete');
|
|
158
|
+
return data;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
stopSpinner(false);
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
stopSpinner(false);
|
|
167
|
+
throw new Error('Authorization timed out. Please try again.');
|
|
168
|
+
}
|
|
169
|
+
async saveTokens(tokenResponse) {
|
|
170
|
+
// Save access token securely
|
|
171
|
+
await SecureTokenStore.saveAccessToken(tokenResponse.access_token);
|
|
172
|
+
// Save refresh token securely
|
|
173
|
+
await SecureTokenStore.saveRefreshToken(tokenResponse.refresh_token);
|
|
174
|
+
// Save session info for future reference
|
|
175
|
+
await SecureTokenStore.saveSessionInfo({
|
|
176
|
+
session_id: tokenResponse.session_id,
|
|
177
|
+
expires_at: new Date(Date.now() + tokenResponse.expires_in * 1000).toISOString(),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
showSuccess() {
|
|
181
|
+
console.log();
|
|
182
|
+
printSuccess('🎉 Device authorization successful!');
|
|
183
|
+
console.log();
|
|
184
|
+
printSection('Next Steps');
|
|
185
|
+
printCommandHint('delta whoami', 'to verify your account');
|
|
186
|
+
printCommandHint('delta analyze', 'to start analyzing code');
|
|
187
|
+
console.log();
|
|
188
|
+
}
|
|
189
|
+
handleError(error) {
|
|
190
|
+
console.log();
|
|
191
|
+
printError('Device authorization failed');
|
|
192
|
+
console.log();
|
|
193
|
+
if (error instanceof Error) {
|
|
194
|
+
if (error.message.includes('ENOTFOUND') || error.message.includes('ECONNREFUSED')) {
|
|
195
|
+
printError('Connection Error', 'Cannot connect to the Delta platform');
|
|
196
|
+
printInfo('Please check:');
|
|
197
|
+
printKeyValue('• Server', 'Is the web platform running?');
|
|
198
|
+
printKeyValue('• Network', 'Check your internet connection');
|
|
199
|
+
printKeyValue('• URL', `Is ${this.apiUrl} correct?`);
|
|
200
|
+
}
|
|
201
|
+
else if (error.message.includes('authorization_pending')) {
|
|
202
|
+
printError('Timeout', 'Authorization was not completed in time');
|
|
203
|
+
printInfo('Please try again');
|
|
204
|
+
}
|
|
205
|
+
else if (error.message.includes('access_denied')) {
|
|
206
|
+
printError('Access Denied', 'You denied the authorization request');
|
|
207
|
+
printInfo('If this was a mistake, please try again');
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
printError('Error', error.message);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
printError('Unknown Error', String(error));
|
|
215
|
+
}
|
|
216
|
+
console.log();
|
|
217
|
+
printInfo('Alternative: Use a Personal Access Token');
|
|
218
|
+
printCommandHint('delta login --token delta_pat_xxxxx', '');
|
|
219
|
+
}
|
|
220
|
+
async makeRequest(endpoint, options) {
|
|
221
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
222
|
+
// Use Node.js built-in fetch if available, otherwise fallback to http/https modules
|
|
223
|
+
if (typeof fetch !== 'undefined') {
|
|
224
|
+
return fetch(url, options);
|
|
225
|
+
}
|
|
226
|
+
// Fallback for older Node.js versions
|
|
227
|
+
return new Promise((resolve, reject) => {
|
|
228
|
+
const urlObj = new URL(url);
|
|
229
|
+
const module = urlObj.protocol === 'https:' ? https : http;
|
|
230
|
+
const requestOptions = {
|
|
231
|
+
method: options.method || 'GET',
|
|
232
|
+
headers: options.headers || {},
|
|
233
|
+
};
|
|
234
|
+
const req = module.request(url, requestOptions, res => {
|
|
235
|
+
let data = '';
|
|
236
|
+
res.on('data', chunk => (data += chunk));
|
|
237
|
+
res.on('end', () => {
|
|
238
|
+
resolve(new Response(data, {
|
|
239
|
+
status: res.statusCode,
|
|
240
|
+
statusText: res.statusMessage,
|
|
241
|
+
headers: res.headers,
|
|
242
|
+
}));
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
req.on('error', reject);
|
|
246
|
+
if (options.body) {
|
|
247
|
+
req.write(options.body);
|
|
248
|
+
}
|
|
249
|
+
req.end();
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
sleep(ms) {
|
|
253
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Export for use in auth commands
|
|
257
|
+
export async function startDeviceFlow(apiUrl) {
|
|
258
|
+
const flow = new DeviceAuthorizationFlow(apiUrl);
|
|
259
|
+
return flow.startDeviceFlow();
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=device-auth.js.map
|