@vee-stack/delta-cli 2.0.3 → 2.0.5
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/analyzer/commands/analyze.js +260 -0
- package/dist/analyzer/commands/config.js +83 -0
- package/dist/analyzer/commands/report.js +38 -0
- package/dist/analyzer/generators/report.generator.js +123 -0
- package/dist/analyzer/index.js +44 -0
- package/dist/analyzer/scanners/project.scanner.js +92 -0
- package/dist/analyzer/validators/contracts.validator.js +42 -0
- package/dist/analyzer/validators/maintainability.validator.js +40 -0
- package/dist/analyzer/validators/observability.validator.js +39 -0
- package/dist/analyzer/validators/performance.validator.js +42 -0
- package/dist/analyzer/validators/security.validator.js +66 -0
- package/dist/analyzer/validators/soc.validator.js +75 -0
- package/dist/apps/cli/src/analyzer/commands/analyze.js +256 -0
- package/dist/apps/cli/src/analyzer/commands/config.js +83 -0
- package/dist/apps/cli/src/analyzer/commands/report.js +38 -0
- package/dist/apps/cli/src/analyzer/generators/report.generator.js +123 -0
- package/dist/apps/cli/src/analyzer/index.js +44 -0
- package/dist/apps/cli/src/analyzer/scanners/project.scanner.js +92 -0
- package/dist/apps/cli/src/analyzer/validators/contracts.validator.js +42 -0
- package/dist/apps/cli/src/analyzer/validators/maintainability.validator.js +40 -0
- package/dist/apps/cli/src/analyzer/validators/observability.validator.js +39 -0
- package/dist/apps/cli/src/analyzer/validators/performance.validator.js +42 -0
- package/dist/apps/cli/src/analyzer/validators/security.validator.js +66 -0
- package/dist/apps/cli/src/analyzer/validators/soc.validator.js +75 -0
- package/dist/apps/cli/src/auth/secure-auth.js +312 -0
- package/dist/apps/cli/src/commands/analyze.js +286 -0
- package/dist/apps/cli/src/commands/auth-new.js +37 -0
- package/dist/apps/cli/src/commands/auth.js +122 -0
- package/dist/apps/cli/src/commands/config.js +49 -0
- package/dist/apps/cli/src/commands/deploy.js +6 -0
- package/dist/apps/cli/src/commands/init.js +47 -0
- package/dist/apps/cli/src/commands/logout.js +23 -0
- package/dist/apps/cli/src/commands/plugins.js +21 -0
- package/dist/apps/cli/src/commands/status.js +80 -0
- package/dist/apps/cli/src/commands/sync.js +6 -0
- package/dist/apps/cli/src/commands/whoami.js +115 -0
- package/dist/apps/cli/src/components/Dashboard.js +168 -0
- package/dist/apps/cli/src/components/DeltaApp.js +56 -0
- package/dist/apps/cli/src/components/UnifiedManager.js +324 -0
- package/dist/apps/cli/src/core/audit.js +184 -0
- package/dist/apps/cli/src/core/completion.js +294 -0
- package/dist/apps/cli/src/core/contracts.js +6 -0
- package/dist/apps/cli/src/core/engine.js +124 -0
- package/dist/apps/cli/src/core/exit-codes.js +71 -0
- package/dist/apps/cli/src/core/hooks.js +181 -0
- package/dist/apps/cli/src/core/index.js +7 -0
- package/dist/apps/cli/src/core/policy.js +115 -0
- package/dist/apps/cli/src/core/profiles.js +161 -0
- package/dist/apps/cli/src/core/wizard.js +203 -0
- package/dist/apps/cli/src/index.js +636 -0
- package/dist/apps/cli/src/interactive/index.js +11 -0
- package/dist/apps/cli/src/plugins/GitStatusPlugin.js +99 -0
- package/dist/apps/cli/src/providers/ai-provider.js +74 -0
- package/dist/apps/cli/src/providers/local-provider.js +302 -0
- package/dist/apps/cli/src/providers/remote-provider.js +100 -0
- package/dist/apps/cli/src/types/api.js +3 -0
- package/dist/apps/cli/src/ui.js +219 -0
- package/dist/apps/cli/src/welcome.js +81 -0
- package/dist/auth/secure-auth.js +418 -0
- package/dist/bundle.js +45 -46
- package/dist/commands/analyze.js +363 -0
- package/dist/commands/auth-new.js +37 -0
- package/dist/commands/auth.js +133 -0
- package/dist/commands/config.js +50 -0
- package/dist/commands/deploy.js +6 -0
- package/dist/commands/init.js +47 -0
- package/dist/commands/logout.js +30 -0
- package/dist/commands/plugins.js +21 -0
- package/dist/commands/status.js +82 -0
- package/dist/commands/sync.js +6 -0
- package/dist/commands/whoami.js +71 -0
- package/dist/components/Dashboard.js +169 -0
- package/dist/components/DeltaApp.js +57 -0
- package/dist/components/UnifiedManager.js +344 -0
- package/dist/core/audit.js +184 -0
- package/dist/core/completion.js +294 -0
- package/dist/core/contracts.js +6 -0
- package/dist/core/engine.js +124 -0
- package/dist/core/exit-codes.js +71 -0
- package/dist/core/hooks.js +181 -0
- package/dist/core/index.js +7 -0
- package/dist/core/policy.js +115 -0
- package/dist/core/profiles.js +161 -0
- package/dist/core/wizard.js +203 -0
- package/dist/index.js +387 -0
- package/dist/interactive/index.js +11 -0
- package/dist/packages/domain/src/constitution/contracts/index.js +43 -0
- package/dist/packages/domain/src/constitution/contracts/ts.rules.js +268 -0
- package/dist/packages/domain/src/constitution/index.js +139 -0
- package/dist/packages/domain/src/constitution/maintainability/index.js +43 -0
- package/dist/packages/domain/src/constitution/maintainability/ts.rules.js +344 -0
- package/dist/packages/domain/src/constitution/observability/index.js +43 -0
- package/dist/packages/domain/src/constitution/observability/ts.rules.js +307 -0
- package/dist/packages/domain/src/constitution/performance/index.js +43 -0
- package/dist/packages/domain/src/constitution/performance/ts.rules.js +325 -0
- package/dist/packages/domain/src/constitution/security/index.js +50 -0
- package/dist/packages/domain/src/constitution/security/ts.rules.js +267 -0
- package/dist/packages/domain/src/constitution/soc/index.js +43 -0
- package/dist/packages/domain/src/constitution/soc/ts.rules.js +360 -0
- package/dist/packages/domain/src/contracts/analysis.contract.js +18 -0
- package/dist/packages/domain/src/contracts/index.js +7 -0
- package/dist/packages/domain/src/contracts/projects.contract.js +18 -0
- package/dist/packages/domain/src/control/registry/rules.registry.js +29 -0
- package/dist/packages/domain/src/control/schemas/policies.js +6 -0
- package/dist/packages/domain/src/core/analysis/discovery.js +163 -0
- package/dist/packages/domain/src/core/analysis/engine.contract.js +298 -0
- package/dist/packages/domain/src/core/analysis/engine.js +77 -0
- package/dist/packages/domain/src/core/analysis/index.js +14 -0
- package/dist/packages/domain/src/core/analysis/orchestrator.js +242 -0
- package/dist/packages/domain/src/core/comparison/engine.js +29 -0
- package/dist/packages/domain/src/core/comparison/index.js +5 -0
- package/dist/packages/domain/src/core/documentation/index.js +5 -0
- package/dist/packages/domain/src/core/documentation/pipeline.js +41 -0
- package/dist/packages/domain/src/core/fs/adapter.js +111 -0
- package/dist/packages/domain/src/core/fs/index.js +5 -0
- package/dist/packages/domain/src/core/parser/unified-parser.js +166 -0
- package/dist/packages/domain/src/index.js +33 -0
- package/dist/packages/domain/src/plugin/registry.js +195 -0
- package/dist/packages/domain/src/plugin/types.js +6 -0
- package/dist/packages/domain/src/ports/analysis.engine.js +7 -0
- package/dist/packages/domain/src/ports/audit.logger.js +7 -0
- package/dist/packages/domain/src/ports/project.repository.js +7 -0
- package/dist/packages/domain/src/rules/index.js +134 -0
- package/dist/packages/domain/src/types/analysis.js +6 -0
- package/dist/packages/domain/src/types/errors.js +53 -0
- package/dist/packages/domain/src/types/fs.js +6 -0
- package/dist/packages/domain/src/types/index.js +7 -0
- package/dist/plugins/GitStatusPlugin.js +93 -0
- package/dist/providers/ai-provider.js +74 -0
- package/dist/providers/local-provider.js +304 -0
- package/dist/providers/remote-provider.js +100 -0
- package/dist/types/api.js +3 -0
- package/dist/ui.js +219 -0
- package/dist/welcome.js +81 -0
- package/package.json +18 -18
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure Authentication System with OAuth2 + Keychain Integration
|
|
3
|
+
* Features:
|
|
4
|
+
* - OAuth2 PKCE flow
|
|
5
|
+
* - Secure token storage in OS keychain
|
|
6
|
+
* - Automatic token refresh
|
|
7
|
+
* - Session management
|
|
8
|
+
* - PAT (Personal Access Token) support for CI/CD
|
|
9
|
+
*/
|
|
10
|
+
import keytar from 'keytar';
|
|
11
|
+
import fetch from 'node-fetch';
|
|
12
|
+
import open from 'open';
|
|
13
|
+
import { createServer } from 'http';
|
|
14
|
+
import { URL } from 'url';
|
|
15
|
+
import crypto from 'crypto';
|
|
16
|
+
import chalk from 'chalk';
|
|
17
|
+
import { printSuccess, printError, printInfo, startSpinner, stopSpinner } from '../ui.js';
|
|
18
|
+
const SERVICE_NAME = 'delta-cli';
|
|
19
|
+
const ACCOUNT_NAME = 'user-token';
|
|
20
|
+
const REFRESH_ACCOUNT = 'refresh-token';
|
|
21
|
+
// PKCE Code Generator
|
|
22
|
+
function generatePKCE() {
|
|
23
|
+
const verifier = crypto.randomBytes(32).toString('base64url');
|
|
24
|
+
const challenge = crypto
|
|
25
|
+
.createHash('sha256')
|
|
26
|
+
.update(verifier)
|
|
27
|
+
.digest('base64url');
|
|
28
|
+
return { verifier, challenge };
|
|
29
|
+
}
|
|
30
|
+
// Secure Token Storage
|
|
31
|
+
export class SecureTokenStore {
|
|
32
|
+
static async saveAccessToken(token) {
|
|
33
|
+
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, token);
|
|
34
|
+
}
|
|
35
|
+
static async getAccessToken() {
|
|
36
|
+
return await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
37
|
+
}
|
|
38
|
+
static async saveRefreshToken(token) {
|
|
39
|
+
await keytar.setPassword(SERVICE_NAME, REFRESH_ACCOUNT, token);
|
|
40
|
+
}
|
|
41
|
+
static async getRefreshToken() {
|
|
42
|
+
return await keytar.getPassword(SERVICE_NAME, REFRESH_ACCOUNT);
|
|
43
|
+
}
|
|
44
|
+
static async clearTokens() {
|
|
45
|
+
await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
46
|
+
await keytar.deletePassword(SERVICE_NAME, REFRESH_ACCOUNT);
|
|
47
|
+
}
|
|
48
|
+
static async hasTokens() {
|
|
49
|
+
const token = await this.getAccessToken();
|
|
50
|
+
return !!token;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// OAuth2 Flow Handler
|
|
54
|
+
export async function startOAuthFlow(method = 'oauth') {
|
|
55
|
+
startSpinner('Starting OAuth2 authentication flow...');
|
|
56
|
+
try {
|
|
57
|
+
const { verifier, challenge } = generatePKCE();
|
|
58
|
+
const redirectUri = 'http://localhost:3456/callback';
|
|
59
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
60
|
+
// Build OAuth URL
|
|
61
|
+
const oauthUrl = new URL('https://api.delta.dev/auth/authorize');
|
|
62
|
+
oauthUrl.searchParams.set('response_type', 'code');
|
|
63
|
+
oauthUrl.searchParams.set('client_id', 'delta-cli');
|
|
64
|
+
oauthUrl.searchParams.set('redirect_uri', redirectUri);
|
|
65
|
+
oauthUrl.searchParams.set('code_challenge', challenge);
|
|
66
|
+
oauthUrl.searchParams.set('code_challenge_method', 'S256');
|
|
67
|
+
oauthUrl.searchParams.set('state', state);
|
|
68
|
+
oauthUrl.searchParams.set('scope', 'read write analyze');
|
|
69
|
+
if (method === 'github') {
|
|
70
|
+
oauthUrl.searchParams.set('provider', 'github');
|
|
71
|
+
}
|
|
72
|
+
else if (method === 'google') {
|
|
73
|
+
oauthUrl.searchParams.set('provider', 'google');
|
|
74
|
+
}
|
|
75
|
+
stopSpinner(true, 'OAuth server ready');
|
|
76
|
+
// Open browser
|
|
77
|
+
printInfo('Opening browser for authentication...');
|
|
78
|
+
await open(oauthUrl.toString());
|
|
79
|
+
// Start local server to capture callback
|
|
80
|
+
const authCode = await new Promise((resolve, reject) => {
|
|
81
|
+
const server = createServer(async (req, res) => {
|
|
82
|
+
const url = new URL(req.url || '/', `http://localhost:3456`);
|
|
83
|
+
if (url.pathname === '/callback') {
|
|
84
|
+
const code = url.searchParams.get('code');
|
|
85
|
+
const returnedState = url.searchParams.get('state');
|
|
86
|
+
const error = url.searchParams.get('error');
|
|
87
|
+
if (error) {
|
|
88
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
89
|
+
res.end(`
|
|
90
|
+
<html>
|
|
91
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
92
|
+
<h1 style="color: #e74c3c;">❌ Authentication Failed</h1>
|
|
93
|
+
<p>${error}</p>
|
|
94
|
+
<p>You can close this window.</p>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
|
97
|
+
`);
|
|
98
|
+
reject(new Error(error));
|
|
99
|
+
server.close();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (!code || returnedState !== state) {
|
|
103
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
104
|
+
res.end(`
|
|
105
|
+
<html>
|
|
106
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
107
|
+
<h1 style="color: #e74c3c;">❌ Invalid Response</h1>
|
|
108
|
+
<p>You can close this window.</p>
|
|
109
|
+
</body>
|
|
110
|
+
</html>
|
|
111
|
+
`);
|
|
112
|
+
reject(new Error('Invalid OAuth response'));
|
|
113
|
+
server.close();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
117
|
+
res.end(`
|
|
118
|
+
<html>
|
|
119
|
+
<head>
|
|
120
|
+
<style>
|
|
121
|
+
body {
|
|
122
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
123
|
+
text-align: center;
|
|
124
|
+
padding: 50px;
|
|
125
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
126
|
+
color: white;
|
|
127
|
+
min-height: 100vh;
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
justify-content: center;
|
|
131
|
+
}
|
|
132
|
+
.container {
|
|
133
|
+
background: rgba(255,255,255,0.1);
|
|
134
|
+
backdrop-filter: blur(10px);
|
|
135
|
+
padding: 40px;
|
|
136
|
+
border-radius: 20px;
|
|
137
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
138
|
+
}
|
|
139
|
+
h1 { margin: 0 0 20px; font-size: 48px; }
|
|
140
|
+
.check { font-size: 64px; margin-bottom: 20px; }
|
|
141
|
+
p { font-size: 18px; opacity: 0.9; }
|
|
142
|
+
</style>
|
|
143
|
+
</head>
|
|
144
|
+
<body>
|
|
145
|
+
<div class="container">
|
|
146
|
+
<div class="check">✓</div>
|
|
147
|
+
<h1>Successfully Signed In!</h1>
|
|
148
|
+
<p>You can close this window and return to the terminal.</p>
|
|
149
|
+
</div>
|
|
150
|
+
</body>
|
|
151
|
+
</html>
|
|
152
|
+
`);
|
|
153
|
+
resolve(code);
|
|
154
|
+
server.close();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
server.listen(3456, () => {
|
|
158
|
+
console.log(chalk.dim(' Waiting for authentication...'));
|
|
159
|
+
});
|
|
160
|
+
// Timeout after 5 minutes
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
server.close();
|
|
163
|
+
reject(new Error('Authentication timeout'));
|
|
164
|
+
}, 5 * 60 * 1000);
|
|
165
|
+
});
|
|
166
|
+
// Exchange code for tokens
|
|
167
|
+
startSpinner('Exchanging authorization code...');
|
|
168
|
+
const tokenResponse = await fetch('https://api.delta.dev/auth/token', {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: { 'Content-Type': 'application/json' },
|
|
171
|
+
body: JSON.stringify({
|
|
172
|
+
grant_type: 'authorization_code',
|
|
173
|
+
code: authCode,
|
|
174
|
+
redirect_uri: redirectUri,
|
|
175
|
+
client_id: 'delta-cli',
|
|
176
|
+
code_verifier: verifier,
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
if (!tokenResponse.ok) {
|
|
180
|
+
throw new Error('Failed to exchange authorization code');
|
|
181
|
+
}
|
|
182
|
+
const tokens = await tokenResponse.json();
|
|
183
|
+
// Store tokens securely
|
|
184
|
+
await SecureTokenStore.saveAccessToken(tokens.access_token);
|
|
185
|
+
await SecureTokenStore.saveRefreshToken(tokens.refresh_token);
|
|
186
|
+
stopSpinner(true, 'Authentication successful!');
|
|
187
|
+
printSuccess('Successfully authenticated with Delta Cloud');
|
|
188
|
+
printInfo(`Token expires in ${tokens.expires_in} seconds`);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
stopSpinner(false, 'Authentication failed');
|
|
193
|
+
printError('OAuth2 authentication failed', error.message);
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// PAT Authentication for CI/CD
|
|
198
|
+
export async function authenticateWithPAT(token) {
|
|
199
|
+
startSpinner('Validating Personal Access Token...');
|
|
200
|
+
try {
|
|
201
|
+
// Validate token
|
|
202
|
+
const response = await fetch('https://api.delta.dev/auth/verify', {
|
|
203
|
+
headers: {
|
|
204
|
+
'Authorization': `Bearer ${token}`,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
throw new Error('Invalid token');
|
|
209
|
+
}
|
|
210
|
+
const data = await response.json();
|
|
211
|
+
// Store token
|
|
212
|
+
await SecureTokenStore.saveAccessToken(token);
|
|
213
|
+
stopSpinner(true, 'Token validated');
|
|
214
|
+
printSuccess(`Authenticated as ${data.user.email}`);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
stopSpinner(false, 'Token validation failed');
|
|
219
|
+
printError('PAT authentication failed', error.message);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Token Refresh
|
|
224
|
+
export async function refreshAccessToken() {
|
|
225
|
+
const refreshToken = await SecureTokenStore.getRefreshToken();
|
|
226
|
+
if (!refreshToken) {
|
|
227
|
+
printError('No refresh token available');
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const response = await fetch('https://api.delta.dev/auth/refresh', {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
headers: { 'Content-Type': 'application/json' },
|
|
234
|
+
body: JSON.stringify({
|
|
235
|
+
grant_type: 'refresh_token',
|
|
236
|
+
refresh_token: refreshToken,
|
|
237
|
+
client_id: 'delta-cli',
|
|
238
|
+
}),
|
|
239
|
+
});
|
|
240
|
+
if (!response.ok) {
|
|
241
|
+
throw new Error('Token refresh failed');
|
|
242
|
+
}
|
|
243
|
+
const tokens = await response.json();
|
|
244
|
+
await SecureTokenStore.saveAccessToken(tokens.access_token);
|
|
245
|
+
if (tokens.refresh_token) {
|
|
246
|
+
await SecureTokenStore.saveRefreshToken(tokens.refresh_token);
|
|
247
|
+
}
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
printError('Failed to refresh token', error.message);
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Logout
|
|
256
|
+
export async function logout() {
|
|
257
|
+
startSpinner('Clearing credentials...');
|
|
258
|
+
try {
|
|
259
|
+
await SecureTokenStore.clearTokens();
|
|
260
|
+
stopSpinner(true, 'Logged out');
|
|
261
|
+
printSuccess('Successfully logged out');
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
stopSpinner(false, 'Logout failed');
|
|
265
|
+
printError('Failed to clear credentials', error.message);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Auth Status
|
|
269
|
+
export async function getAuthStatus() {
|
|
270
|
+
const token = await SecureTokenStore.getAccessToken();
|
|
271
|
+
if (!token) {
|
|
272
|
+
return { authenticated: false };
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const response = await fetch('https://api.delta.dev/auth/me', {
|
|
276
|
+
headers: {
|
|
277
|
+
'Authorization': `Bearer ${token}`,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
if (!response.ok) {
|
|
281
|
+
// Try to refresh token
|
|
282
|
+
const refreshed = await refreshAccessToken();
|
|
283
|
+
if (!refreshed) {
|
|
284
|
+
return { authenticated: false };
|
|
285
|
+
}
|
|
286
|
+
// Retry with new token
|
|
287
|
+
const newToken = await SecureTokenStore.getAccessToken();
|
|
288
|
+
const retryResponse = await fetch('https://api.delta.dev/auth/me', {
|
|
289
|
+
headers: {
|
|
290
|
+
'Authorization': `Bearer ${newToken}`,
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
if (!retryResponse.ok) {
|
|
294
|
+
return { authenticated: false };
|
|
295
|
+
}
|
|
296
|
+
const userData = await retryResponse.json();
|
|
297
|
+
return {
|
|
298
|
+
authenticated: true,
|
|
299
|
+
user: userData,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const userData = await response.json();
|
|
303
|
+
return {
|
|
304
|
+
authenticated: true,
|
|
305
|
+
user: userData,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return { authenticated: false };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=secure-auth.js.map
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze Command - Run local analysis with optional upload to cloud
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'fs/promises';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import * as crypto from 'crypto';
|
|
7
|
+
import { loadConfig } from './auth.js';
|
|
8
|
+
export async function analyzeCommand(projectPath, options) {
|
|
9
|
+
const targetPath = path.resolve(projectPath);
|
|
10
|
+
console.log('🔍 Running analysis...');
|
|
11
|
+
console.log(` Path: ${targetPath}`);
|
|
12
|
+
// Simple file discovery
|
|
13
|
+
const files = await discoverFiles(targetPath, {
|
|
14
|
+
include: options.include ? [options.include] : ['**/*.{ts,tsx,js,jsx}'],
|
|
15
|
+
exclude: options.exclude ? [options.exclude].concat(['node_modules', 'dist', '.git']) : ['node_modules', 'dist', '.git'],
|
|
16
|
+
maxSize: parseInt(options.maxSize, 10),
|
|
17
|
+
});
|
|
18
|
+
console.log(` Found ${files.length} files to analyze`);
|
|
19
|
+
// Run analysis
|
|
20
|
+
const startTime = Date.now();
|
|
21
|
+
const summary = {
|
|
22
|
+
totalFiles: files.length,
|
|
23
|
+
successfulParses: files.length,
|
|
24
|
+
failedParses: 0,
|
|
25
|
+
totalLines: 0,
|
|
26
|
+
totalFunctions: 0,
|
|
27
|
+
totalClasses: 0,
|
|
28
|
+
totalDuration: 0,
|
|
29
|
+
errors: [],
|
|
30
|
+
};
|
|
31
|
+
const findings = [];
|
|
32
|
+
for (const file of files) {
|
|
33
|
+
try {
|
|
34
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
35
|
+
const lines = content.split('\n');
|
|
36
|
+
summary.totalLines += lines.length;
|
|
37
|
+
// Simple heuristics for demo
|
|
38
|
+
if (content.includes('function '))
|
|
39
|
+
summary.totalFunctions++;
|
|
40
|
+
if (content.includes('class '))
|
|
41
|
+
summary.totalClasses++;
|
|
42
|
+
// Find potential issues
|
|
43
|
+
if (content.includes('eval(')) {
|
|
44
|
+
findings.push({
|
|
45
|
+
file: path.relative(targetPath, file),
|
|
46
|
+
type: 'security',
|
|
47
|
+
severity: 'high',
|
|
48
|
+
message: 'Use of eval() detected',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (content.includes('console.log')) {
|
|
52
|
+
findings.push({
|
|
53
|
+
file: path.relative(targetPath, file),
|
|
54
|
+
type: 'maintainability',
|
|
55
|
+
severity: 'low',
|
|
56
|
+
message: 'Console.log statement found',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
summary.failedParses++;
|
|
62
|
+
summary.successfulParses--;
|
|
63
|
+
summary.errors.push({
|
|
64
|
+
file: path.relative(targetPath, file),
|
|
65
|
+
error: error instanceof Error ? error.message : String(error),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
summary.totalDuration = Date.now() - startTime;
|
|
70
|
+
// Build report
|
|
71
|
+
const report = {
|
|
72
|
+
schema_version: '1.0',
|
|
73
|
+
engine_version: '0.5.0',
|
|
74
|
+
created_at: new Date().toISOString(),
|
|
75
|
+
project: {
|
|
76
|
+
name: options.projectName || path.basename(targetPath),
|
|
77
|
+
root_fingerprint: '',
|
|
78
|
+
path: targetPath,
|
|
79
|
+
},
|
|
80
|
+
tools_used: [
|
|
81
|
+
{ id: 'delta-core', version: '0.5.0', adapter_version: '0.5.0' },
|
|
82
|
+
],
|
|
83
|
+
summary: {
|
|
84
|
+
total: findings.length,
|
|
85
|
+
high: findings.filter(f => f.severity === 'high').length,
|
|
86
|
+
medium: findings.filter(f => f.severity === 'medium').length,
|
|
87
|
+
low: findings.filter(f => f.severity === 'low').length,
|
|
88
|
+
},
|
|
89
|
+
findings,
|
|
90
|
+
metadata: summary,
|
|
91
|
+
};
|
|
92
|
+
// Save report to file
|
|
93
|
+
const outputDir = path.resolve(options.output);
|
|
94
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
95
|
+
const reportFile = path.join(outputDir, `report-${Date.now()}.json`);
|
|
96
|
+
await fs.writeFile(reportFile, JSON.stringify(report, null, 2));
|
|
97
|
+
console.log('\n✅ Analysis complete');
|
|
98
|
+
console.log(` Files analyzed: ${summary.totalFiles}`);
|
|
99
|
+
console.log(` Total lines: ${summary.totalLines.toLocaleString()}`);
|
|
100
|
+
console.log(` Functions: ${summary.totalFunctions}`);
|
|
101
|
+
console.log(` Classes: ${summary.totalClasses}`);
|
|
102
|
+
console.log(` Duration: ${(summary.totalDuration / 1000).toFixed(2)}s`);
|
|
103
|
+
console.log(` Findings: ${findings.length} (${report.summary.high} high, ${report.summary.medium} medium, ${report.summary.low} low)`);
|
|
104
|
+
console.log(` Report saved: ${reportFile}`);
|
|
105
|
+
// Upload to cloud if requested
|
|
106
|
+
if (options.upload) {
|
|
107
|
+
console.log('\n☁️ Uploading report to Delta cloud...');
|
|
108
|
+
await uploadReport(report);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function uploadReport(report) {
|
|
112
|
+
const config = await loadConfig();
|
|
113
|
+
if (!config.pat) {
|
|
114
|
+
console.error('❌ Error: Not authenticated. Run: delta login --token <pat>');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
const apiUrl = process.env.DELTA_API_URL || config.apiUrl || 'https://api.delta.dev';
|
|
118
|
+
const reportJson = JSON.stringify(report);
|
|
119
|
+
const reportHash = crypto.createHash('sha256').update(reportJson).digest('hex');
|
|
120
|
+
try {
|
|
121
|
+
// Step 1: Init upload session
|
|
122
|
+
const initResponse = await fetch(`${apiUrl}/api/upload/init`, {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
headers: {
|
|
125
|
+
'Authorization': `Bearer ${config.pat}`,
|
|
126
|
+
'Content-Type': 'application/json',
|
|
127
|
+
'Idempotency-Key': crypto.randomUUID(),
|
|
128
|
+
},
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
schemaVersion: '1.0',
|
|
131
|
+
engineVersion: '0.5.0',
|
|
132
|
+
reportHash: reportHash,
|
|
133
|
+
reportSizeBytes: Buffer.byteLength(reportJson),
|
|
134
|
+
toolsUsed: [{ id: 'delta-core', version: '0.5.0', adapterVersion: '0.5.0' }],
|
|
135
|
+
}),
|
|
136
|
+
});
|
|
137
|
+
if (!initResponse.ok) {
|
|
138
|
+
let payload = undefined;
|
|
139
|
+
try {
|
|
140
|
+
payload = await initResponse.json();
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
payload = await initResponse.text().catch(() => undefined);
|
|
144
|
+
}
|
|
145
|
+
const details = payload?.error?.details ? `\nDetails: ${JSON.stringify(payload.error.details)}` : '';
|
|
146
|
+
const message = payload?.error?.message || `Init failed: ${initResponse.status}`;
|
|
147
|
+
throw new Error(message + details);
|
|
148
|
+
}
|
|
149
|
+
const initData = await initResponse.json();
|
|
150
|
+
if (!initData.success) {
|
|
151
|
+
throw new Error(initData.error?.message || 'Upload init failed');
|
|
152
|
+
}
|
|
153
|
+
const uploadId = initData.data.uploadId;
|
|
154
|
+
const secret = initData.data.signing.secret;
|
|
155
|
+
console.log(` Upload session: ${uploadId}`);
|
|
156
|
+
// Step 2: Sign and upload report
|
|
157
|
+
const signature = crypto.createHmac('sha256', secret).update(reportJson).digest('hex');
|
|
158
|
+
const uploadResponse = await fetch(`${apiUrl}/api/upload/${uploadId}`, {
|
|
159
|
+
method: 'PUT',
|
|
160
|
+
headers: {
|
|
161
|
+
'Authorization': `Bearer ${config.pat}`,
|
|
162
|
+
'Content-Type': 'application/json',
|
|
163
|
+
},
|
|
164
|
+
body: JSON.stringify({
|
|
165
|
+
report: Object.assign({}, report, {
|
|
166
|
+
integrity: {
|
|
167
|
+
report_hash: reportHash,
|
|
168
|
+
signature: signature,
|
|
169
|
+
algorithm: 'HMAC-SHA256',
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
}),
|
|
173
|
+
});
|
|
174
|
+
if (!uploadResponse.ok) {
|
|
175
|
+
let payload = undefined;
|
|
176
|
+
try {
|
|
177
|
+
payload = await uploadResponse.json();
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
payload = await uploadResponse.text().catch(() => undefined);
|
|
181
|
+
}
|
|
182
|
+
const details = payload?.error?.details ? `\nDetails: ${JSON.stringify(payload.error.details)}` : '';
|
|
183
|
+
const message = payload?.error?.message || `Upload failed: ${uploadResponse.status}`;
|
|
184
|
+
throw new Error(message + details);
|
|
185
|
+
}
|
|
186
|
+
console.log(' Report uploaded');
|
|
187
|
+
// Step 3: Finalize
|
|
188
|
+
const finalizeResponse = await fetch(`${apiUrl}/api/upload/${uploadId}/finalize`, {
|
|
189
|
+
method: 'POST',
|
|
190
|
+
headers: {
|
|
191
|
+
'Authorization': `Bearer ${config.pat}`,
|
|
192
|
+
'Content-Type': 'application/json',
|
|
193
|
+
'Idempotency-Key': crypto.randomUUID(),
|
|
194
|
+
},
|
|
195
|
+
body: '{}',
|
|
196
|
+
});
|
|
197
|
+
if (!finalizeResponse.ok) {
|
|
198
|
+
let payload = undefined;
|
|
199
|
+
try {
|
|
200
|
+
payload = await finalizeResponse.json();
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
payload = await finalizeResponse.text().catch(() => undefined);
|
|
204
|
+
}
|
|
205
|
+
const details = payload?.error?.details ? `\nDetails: ${JSON.stringify(payload.error.details)}` : '';
|
|
206
|
+
const message = payload?.error?.message || `Finalize failed: ${finalizeResponse.status}`;
|
|
207
|
+
throw new Error(message + details);
|
|
208
|
+
}
|
|
209
|
+
const finalizeData = await finalizeResponse.json();
|
|
210
|
+
if (!finalizeData.success) {
|
|
211
|
+
throw new Error(finalizeData.error?.message || 'Upload finalize failed');
|
|
212
|
+
}
|
|
213
|
+
console.log(`✅ Report finalized: ${finalizeData.data.reportId}`);
|
|
214
|
+
console.log(` Quota updated`);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
console.error('❌ Upload failed:', error instanceof Error ? error.message : String(error));
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async function discoverFiles(rootPath, options) {
|
|
222
|
+
const files = [];
|
|
223
|
+
async function walk(dir) {
|
|
224
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
225
|
+
for (const entry of entries) {
|
|
226
|
+
const fullPath = path.join(dir, entry.name);
|
|
227
|
+
if (entry.isDirectory()) {
|
|
228
|
+
// Skip excluded directories
|
|
229
|
+
if (options.exclude.some(e => entry.name.includes(e)))
|
|
230
|
+
continue;
|
|
231
|
+
await walk(fullPath);
|
|
232
|
+
}
|
|
233
|
+
else if (entry.isFile()) {
|
|
234
|
+
// Check include patterns - support glob patterns like **/*.{ts,tsx}
|
|
235
|
+
const shouldInclude = options.include.some(pattern => {
|
|
236
|
+
// Handle brace expansion: **/*.{ts,tsx} → match **/*.ts or **/*.tsx
|
|
237
|
+
if (pattern.includes('{') && pattern.includes('}')) {
|
|
238
|
+
const braceMatch = pattern.match(/\{([^}]+)\}/);
|
|
239
|
+
if (braceMatch) {
|
|
240
|
+
const extensions = braceMatch[1].split(',');
|
|
241
|
+
const basePattern = pattern.replace(/\{[^}]+\}/, '');
|
|
242
|
+
return extensions.some(ext => {
|
|
243
|
+
const fullPattern = basePattern + ext;
|
|
244
|
+
return matchGlob(fullPath, fullPattern);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return matchGlob(fullPath, pattern);
|
|
249
|
+
});
|
|
250
|
+
if (shouldInclude) {
|
|
251
|
+
// Check file size
|
|
252
|
+
try {
|
|
253
|
+
const stats = await fs.stat(fullPath);
|
|
254
|
+
if (stats.size <= options.maxSize) {
|
|
255
|
+
files.push(fullPath);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// Skip files we can't stat
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
await walk(rootPath);
|
|
266
|
+
return files;
|
|
267
|
+
}
|
|
268
|
+
function matchGlob(filePath, pattern) {
|
|
269
|
+
// Convert glob pattern to regex
|
|
270
|
+
// **/*.ts → matches any .ts file in any directory
|
|
271
|
+
// *.ts → matches .ts files in current directory
|
|
272
|
+
const fileName = filePath.split(/[/\\]/).pop() || '';
|
|
273
|
+
if (pattern.includes('**')) {
|
|
274
|
+
// Match any directory depth
|
|
275
|
+
const ext = pattern.replace('**/*', '');
|
|
276
|
+
return fileName.endsWith(ext);
|
|
277
|
+
}
|
|
278
|
+
else if (pattern.includes('*')) {
|
|
279
|
+
// Simple wildcard
|
|
280
|
+
const ext = pattern.replace('*', '');
|
|
281
|
+
return fileName.endsWith(ext);
|
|
282
|
+
}
|
|
283
|
+
// Exact match
|
|
284
|
+
return filePath.includes(pattern);
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { startOAuthFlow, authenticateWithPAT, SecureTokenStore } from '../auth/secure-auth.js';
|
|
3
|
+
import { printSuccess, printInfo } from '../ui.js';
|
|
4
|
+
import prompts from 'prompts';
|
|
5
|
+
export async function loginCommand(options) {
|
|
6
|
+
// Check if already authenticated
|
|
7
|
+
if (await SecureTokenStore.hasTokens()) {
|
|
8
|
+
const overwrite = await prompts({
|
|
9
|
+
type: 'confirm',
|
|
10
|
+
name: 'value',
|
|
11
|
+
message: chalk.yellow('Already authenticated. Overwrite existing credentials?'),
|
|
12
|
+
initial: false,
|
|
13
|
+
});
|
|
14
|
+
if (!overwrite.value) {
|
|
15
|
+
printInfo('Login cancelled');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (options.token) {
|
|
20
|
+
// PAT authentication
|
|
21
|
+
await authenticateWithPAT(options.token);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
// OAuth2 flow
|
|
25
|
+
const method = options.method || 'oauth';
|
|
26
|
+
const success = await startOAuthFlow(method);
|
|
27
|
+
if (success) {
|
|
28
|
+
printSuccess('You are now logged in!');
|
|
29
|
+
printInfo('Run "delta whoami" to see your account info');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function logoutCommand() {
|
|
34
|
+
const { logout } = await import('../auth/secure-auth.js');
|
|
35
|
+
await logout();
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=auth-new.js.map
|