@turboops/cli 1.0.0-dev.579 → 1.0.0-dev.582
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/index.js +769 -171
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import chalk8 from "chalk";
|
|
|
6
6
|
|
|
7
7
|
// src/services/config.ts
|
|
8
8
|
import Conf from "conf";
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
9
11
|
|
|
10
12
|
// src/utils/logger.ts
|
|
11
13
|
import chalk from "chalk";
|
|
@@ -133,9 +135,9 @@ function loadPackageJson() {
|
|
|
133
135
|
join(__dirname, "../package.json")
|
|
134
136
|
// From dist/
|
|
135
137
|
];
|
|
136
|
-
for (const
|
|
137
|
-
if (existsSync(
|
|
138
|
-
return require2(
|
|
138
|
+
for (const path2 of paths) {
|
|
139
|
+
if (existsSync(path2)) {
|
|
140
|
+
return require2(path2);
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
return { version: "0.0.0", name: "@turboops/cli" };
|
|
@@ -196,90 +198,150 @@ var API_URLS = {
|
|
|
196
198
|
production: "https://api.turbo-ops.de",
|
|
197
199
|
dev: "https://api.dev.turbo-ops.de"
|
|
198
200
|
};
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
var
|
|
205
|
-
apiUrl: API_URLS.production,
|
|
201
|
+
var APP_URLS = {
|
|
202
|
+
production: "https://turbo-ops.de",
|
|
203
|
+
dev: "https://dev.turbo-ops.de"
|
|
204
|
+
};
|
|
205
|
+
var LOCAL_CONFIG_FILE = ".turboops.json";
|
|
206
|
+
var globalDefaults = {
|
|
206
207
|
token: null,
|
|
207
|
-
project: null,
|
|
208
208
|
userId: null
|
|
209
209
|
};
|
|
210
|
-
var
|
|
210
|
+
var globalConfig = new Conf({
|
|
211
211
|
projectName: "turboops-cli",
|
|
212
|
-
defaults
|
|
212
|
+
defaults: globalDefaults
|
|
213
213
|
});
|
|
214
|
+
function findProjectRoot() {
|
|
215
|
+
let currentDir = process.cwd();
|
|
216
|
+
const root = path.parse(currentDir).root;
|
|
217
|
+
while (currentDir !== root) {
|
|
218
|
+
if (fs.existsSync(path.join(currentDir, LOCAL_CONFIG_FILE))) {
|
|
219
|
+
return currentDir;
|
|
220
|
+
}
|
|
221
|
+
if (fs.existsSync(path.join(currentDir, ".git"))) {
|
|
222
|
+
return currentDir;
|
|
223
|
+
}
|
|
224
|
+
if (fs.existsSync(path.join(currentDir, "package.json"))) {
|
|
225
|
+
return currentDir;
|
|
226
|
+
}
|
|
227
|
+
currentDir = path.dirname(currentDir);
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
function getLocalConfigPath() {
|
|
232
|
+
const projectRoot = findProjectRoot();
|
|
233
|
+
if (!projectRoot) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
return path.join(projectRoot, LOCAL_CONFIG_FILE);
|
|
237
|
+
}
|
|
238
|
+
function readLocalConfig() {
|
|
239
|
+
const configPath = getLocalConfigPath();
|
|
240
|
+
if (!configPath || !fs.existsSync(configPath)) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
245
|
+
return JSON.parse(content);
|
|
246
|
+
} catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function writeLocalConfig(config) {
|
|
251
|
+
const projectRoot = findProjectRoot() || process.cwd();
|
|
252
|
+
const configPath = path.join(projectRoot, LOCAL_CONFIG_FILE);
|
|
253
|
+
try {
|
|
254
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
255
|
+
return true;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
logger.error(`Failed to write ${LOCAL_CONFIG_FILE}: ${error}`);
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function getApiUrl() {
|
|
262
|
+
const version = getCurrentVersion();
|
|
263
|
+
const isDevVersion = version.includes("-dev");
|
|
264
|
+
return isDevVersion ? API_URLS.dev : API_URLS.production;
|
|
265
|
+
}
|
|
214
266
|
var configService = {
|
|
215
267
|
/**
|
|
216
|
-
* Get API URL
|
|
217
|
-
* Priority: ENV variable > stored config > version-based default
|
|
268
|
+
* Get API URL (fixed based on CLI version)
|
|
218
269
|
*/
|
|
219
270
|
getApiUrl() {
|
|
220
271
|
if (process.env.TURBOOPS_API_URL) {
|
|
221
272
|
return process.env.TURBOOPS_API_URL;
|
|
222
273
|
}
|
|
223
|
-
|
|
224
|
-
if (storedUrl === "https://api.turboops.io" || storedUrl === API_URLS.production) {
|
|
225
|
-
return getDefaultApiUrl();
|
|
226
|
-
}
|
|
227
|
-
return storedUrl;
|
|
274
|
+
return getApiUrl();
|
|
228
275
|
},
|
|
229
276
|
/**
|
|
230
|
-
*
|
|
277
|
+
* Get App URL (frontend)
|
|
278
|
+
* Returns the appropriate frontend URL based on CLI version
|
|
231
279
|
*/
|
|
232
|
-
|
|
233
|
-
|
|
280
|
+
getAppUrl() {
|
|
281
|
+
const version = getCurrentVersion();
|
|
282
|
+
const isDevVersion = version.includes("-dev");
|
|
283
|
+
return isDevVersion ? APP_URLS.dev : APP_URLS.production;
|
|
234
284
|
},
|
|
235
285
|
/**
|
|
236
|
-
* Get authentication token
|
|
286
|
+
* Get authentication token (global - user session)
|
|
237
287
|
*/
|
|
238
288
|
getToken() {
|
|
239
|
-
return process.env.TURBOOPS_TOKEN ||
|
|
289
|
+
return process.env.TURBOOPS_TOKEN || globalConfig.get("token");
|
|
240
290
|
},
|
|
241
291
|
/**
|
|
242
|
-
* Set authentication token
|
|
292
|
+
* Set authentication token (global - user session)
|
|
243
293
|
*/
|
|
244
294
|
setToken(token) {
|
|
245
|
-
|
|
295
|
+
globalConfig.set("token", token);
|
|
246
296
|
},
|
|
247
297
|
/**
|
|
248
298
|
* Clear authentication token
|
|
249
299
|
*/
|
|
250
300
|
clearToken() {
|
|
251
|
-
|
|
252
|
-
|
|
301
|
+
globalConfig.set("token", null);
|
|
302
|
+
globalConfig.set("userId", null);
|
|
253
303
|
},
|
|
254
304
|
/**
|
|
255
|
-
* Get current project
|
|
305
|
+
* Get current project (local - project specific)
|
|
256
306
|
*/
|
|
257
307
|
getProject() {
|
|
258
|
-
|
|
308
|
+
if (process.env.TURBOOPS_PROJECT) {
|
|
309
|
+
return process.env.TURBOOPS_PROJECT;
|
|
310
|
+
}
|
|
311
|
+
const localConfig = readLocalConfig();
|
|
312
|
+
return localConfig?.project || null;
|
|
259
313
|
},
|
|
260
314
|
/**
|
|
261
|
-
* Set current project
|
|
315
|
+
* Set current project (local - writes to .turboops.json)
|
|
262
316
|
*/
|
|
263
317
|
setProject(project) {
|
|
264
|
-
|
|
318
|
+
const localConfig = readLocalConfig() || { project: "" };
|
|
319
|
+
localConfig.project = project;
|
|
320
|
+
writeLocalConfig(localConfig);
|
|
265
321
|
},
|
|
266
322
|
/**
|
|
267
323
|
* Clear current project
|
|
268
324
|
*/
|
|
269
325
|
clearProject() {
|
|
270
|
-
|
|
326
|
+
const configPath = getLocalConfigPath();
|
|
327
|
+
if (configPath && fs.existsSync(configPath)) {
|
|
328
|
+
try {
|
|
329
|
+
fs.unlinkSync(configPath);
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
271
333
|
},
|
|
272
334
|
/**
|
|
273
|
-
* Get user ID
|
|
335
|
+
* Get user ID (global - user session)
|
|
274
336
|
*/
|
|
275
337
|
getUserId() {
|
|
276
|
-
return
|
|
338
|
+
return globalConfig.get("userId");
|
|
277
339
|
},
|
|
278
340
|
/**
|
|
279
|
-
* Set user ID
|
|
341
|
+
* Set user ID (global - user session)
|
|
280
342
|
*/
|
|
281
343
|
setUserId(userId) {
|
|
282
|
-
|
|
344
|
+
globalConfig.set("userId", userId);
|
|
283
345
|
},
|
|
284
346
|
/**
|
|
285
347
|
* Check if user is authenticated
|
|
@@ -293,6 +355,19 @@ var configService = {
|
|
|
293
355
|
hasProject() {
|
|
294
356
|
return !!this.getProject();
|
|
295
357
|
},
|
|
358
|
+
/**
|
|
359
|
+
* Check if local config exists
|
|
360
|
+
*/
|
|
361
|
+
hasLocalConfig() {
|
|
362
|
+
const configPath = getLocalConfigPath();
|
|
363
|
+
return !!configPath && fs.existsSync(configPath);
|
|
364
|
+
},
|
|
365
|
+
/**
|
|
366
|
+
* Get local config file path (for display purposes)
|
|
367
|
+
*/
|
|
368
|
+
getLocalConfigPath() {
|
|
369
|
+
return getLocalConfigPath();
|
|
370
|
+
},
|
|
296
371
|
/**
|
|
297
372
|
* Get all configuration
|
|
298
373
|
*/
|
|
@@ -305,17 +380,25 @@ var configService = {
|
|
|
305
380
|
};
|
|
306
381
|
},
|
|
307
382
|
/**
|
|
308
|
-
* Clear all configuration
|
|
383
|
+
* Clear all configuration (both global and local)
|
|
309
384
|
*/
|
|
310
385
|
clearAll() {
|
|
311
|
-
|
|
386
|
+
globalConfig.clear();
|
|
387
|
+
this.clearProject();
|
|
312
388
|
},
|
|
313
389
|
/**
|
|
314
|
-
* Check if using a project token (turbo_xxx format)
|
|
390
|
+
* Check if using a project token (turbo_xxx format, but not turbo_cli_xxx)
|
|
315
391
|
*/
|
|
316
392
|
isProjectToken() {
|
|
317
393
|
const token = this.getToken();
|
|
318
|
-
return !!token && token.startsWith("turbo_");
|
|
394
|
+
return !!token && token.startsWith("turbo_") && !token.startsWith("turbo_cli_");
|
|
395
|
+
},
|
|
396
|
+
/**
|
|
397
|
+
* Check if using a CLI session token (turbo_cli_xxx format)
|
|
398
|
+
*/
|
|
399
|
+
isCliSessionToken() {
|
|
400
|
+
const token = this.getToken();
|
|
401
|
+
return !!token && token.startsWith("turbo_cli_");
|
|
319
402
|
},
|
|
320
403
|
/**
|
|
321
404
|
* Show current configuration
|
|
@@ -323,12 +406,15 @@ var configService = {
|
|
|
323
406
|
show() {
|
|
324
407
|
const data = this.getAll();
|
|
325
408
|
const isProjectToken2 = this.isProjectToken();
|
|
409
|
+
const isCliToken = this.isCliSessionToken();
|
|
410
|
+
const localConfigPath = this.getLocalConfigPath();
|
|
326
411
|
logger.header("Configuration");
|
|
327
412
|
logger.table({
|
|
328
413
|
"API URL": data.apiUrl,
|
|
329
414
|
Token: data.token || "Not set",
|
|
330
|
-
"Token Type": isProjectToken2 ? "Project Token" : data.token ? "User Token" : "N/A",
|
|
415
|
+
"Token Type": isProjectToken2 ? "Project Token (CI/CD)" : isCliToken ? "CLI Session Token" : data.token ? "User Token" : "N/A",
|
|
331
416
|
Project: data.project || (isProjectToken2 ? "(from token)" : "Not set"),
|
|
417
|
+
"Project Config": localConfigPath || "(not found)",
|
|
332
418
|
"User ID": data.userId || (isProjectToken2 ? "(N/A for project token)" : "Not set")
|
|
333
419
|
});
|
|
334
420
|
}
|
|
@@ -336,7 +422,6 @@ var configService = {
|
|
|
336
422
|
|
|
337
423
|
// src/commands/login.ts
|
|
338
424
|
import { Command } from "commander";
|
|
339
|
-
import prompts from "prompts";
|
|
340
425
|
|
|
341
426
|
// src/services/api.ts
|
|
342
427
|
function isProjectToken(token) {
|
|
@@ -347,7 +432,7 @@ var apiClient = {
|
|
|
347
432
|
/**
|
|
348
433
|
* Make an API request
|
|
349
434
|
*/
|
|
350
|
-
async request(method,
|
|
435
|
+
async request(method, path2, body) {
|
|
351
436
|
const apiUrl = configService.getApiUrl();
|
|
352
437
|
const token = configService.getToken();
|
|
353
438
|
if (!token) {
|
|
@@ -356,7 +441,7 @@ var apiClient = {
|
|
|
356
441
|
status: 401
|
|
357
442
|
};
|
|
358
443
|
}
|
|
359
|
-
const url = `${apiUrl}${
|
|
444
|
+
const url = `${apiUrl}${path2}`;
|
|
360
445
|
const headers = {
|
|
361
446
|
"Content-Type": "application/json",
|
|
362
447
|
Authorization: `Bearer ${token}`
|
|
@@ -501,23 +586,66 @@ var apiClient = {
|
|
|
501
586
|
return this.request("GET", `/deployment/projects/by-slug/${slug}`);
|
|
502
587
|
},
|
|
503
588
|
/**
|
|
504
|
-
*
|
|
589
|
+
* Create a new project
|
|
590
|
+
*/
|
|
591
|
+
async createProject(data) {
|
|
592
|
+
const payload = {
|
|
593
|
+
name: data.name,
|
|
594
|
+
slug: data.slug
|
|
595
|
+
};
|
|
596
|
+
if (data.customer) payload.customer = data.customer;
|
|
597
|
+
if (data.description) payload.description = data.description;
|
|
598
|
+
if (data.repositoryUrl) payload.repositoryUrl = data.repositoryUrl;
|
|
599
|
+
return this.request("POST", "/deployment/projects/simple", payload);
|
|
600
|
+
},
|
|
601
|
+
/**
|
|
602
|
+
* Get all customers
|
|
603
|
+
*/
|
|
604
|
+
async getCustomers() {
|
|
605
|
+
return this.request("GET", "/customer");
|
|
606
|
+
},
|
|
607
|
+
/**
|
|
608
|
+
* Get environments (stages) for project
|
|
505
609
|
*/
|
|
506
610
|
async getEnvironments(projectId) {
|
|
507
611
|
return this.request(
|
|
508
612
|
"GET",
|
|
509
|
-
`/deployment/
|
|
613
|
+
`/deployment/stages?projectId=${projectId}`
|
|
510
614
|
);
|
|
511
615
|
},
|
|
512
616
|
/**
|
|
513
|
-
* Get environment by slug
|
|
617
|
+
* Get environment (stage) by slug
|
|
514
618
|
*/
|
|
515
619
|
async getEnvironmentBySlug(projectId, slug) {
|
|
516
620
|
return this.request(
|
|
517
621
|
"GET",
|
|
518
|
-
`/deployment/
|
|
622
|
+
`/deployment/stages/by-slug/${projectId}/${slug}`
|
|
519
623
|
);
|
|
520
624
|
},
|
|
625
|
+
/**
|
|
626
|
+
* Create a new stage
|
|
627
|
+
*/
|
|
628
|
+
async createStage(data) {
|
|
629
|
+
return this.request("POST", "/deployment/stages/simple", data);
|
|
630
|
+
},
|
|
631
|
+
/**
|
|
632
|
+
* Get deployment-ready servers
|
|
633
|
+
*/
|
|
634
|
+
async getDeploymentServers() {
|
|
635
|
+
return this.request("GET", "/server?deploymentReady=true");
|
|
636
|
+
},
|
|
637
|
+
/**
|
|
638
|
+
* Generate CI/CD pipeline configuration
|
|
639
|
+
*/
|
|
640
|
+
async generatePipeline(projectId, type) {
|
|
641
|
+
return this.request("GET", `/deployment/projects/${projectId}/pipeline/${type}`);
|
|
642
|
+
},
|
|
643
|
+
/**
|
|
644
|
+
* Get required secrets for pipeline
|
|
645
|
+
*/
|
|
646
|
+
async getPipelineSecrets(projectId, type) {
|
|
647
|
+
return this.request("GET", `/deployment/projects/${projectId}/pipeline/${type}/secrets`);
|
|
648
|
+
},
|
|
521
649
|
/**
|
|
522
650
|
* Trigger deployment
|
|
523
651
|
*/
|
|
@@ -548,7 +676,7 @@ var apiClient = {
|
|
|
548
676
|
async restart(environmentId) {
|
|
549
677
|
return this.request(
|
|
550
678
|
"POST",
|
|
551
|
-
`/deployment/
|
|
679
|
+
`/deployment/stages/${environmentId}/restart`
|
|
552
680
|
);
|
|
553
681
|
},
|
|
554
682
|
/**
|
|
@@ -557,7 +685,7 @@ var apiClient = {
|
|
|
557
685
|
async stop(environmentId) {
|
|
558
686
|
return this.request(
|
|
559
687
|
"POST",
|
|
560
|
-
`/deployment/
|
|
688
|
+
`/deployment/stages/${environmentId}/stop`
|
|
561
689
|
);
|
|
562
690
|
},
|
|
563
691
|
/**
|
|
@@ -566,7 +694,7 @@ var apiClient = {
|
|
|
566
694
|
async wake(environmentId) {
|
|
567
695
|
return this.request(
|
|
568
696
|
"POST",
|
|
569
|
-
`/deployment/
|
|
697
|
+
`/deployment/stages/${environmentId}/wake`
|
|
570
698
|
);
|
|
571
699
|
},
|
|
572
700
|
/**
|
|
@@ -581,7 +709,7 @@ var apiClient = {
|
|
|
581
709
|
async getEnvVars(environmentId) {
|
|
582
710
|
return this.request(
|
|
583
711
|
"GET",
|
|
584
|
-
`/deployment/
|
|
712
|
+
`/deployment/stages/${environmentId}/env-vars`
|
|
585
713
|
);
|
|
586
714
|
},
|
|
587
715
|
/**
|
|
@@ -590,7 +718,7 @@ var apiClient = {
|
|
|
590
718
|
async setEnvVar(environmentId, key, value, secret) {
|
|
591
719
|
return this.request(
|
|
592
720
|
"PUT",
|
|
593
|
-
`/deployment/
|
|
721
|
+
`/deployment/stages/${environmentId}/env-vars`,
|
|
594
722
|
{
|
|
595
723
|
key,
|
|
596
724
|
value,
|
|
@@ -604,7 +732,7 @@ var apiClient = {
|
|
|
604
732
|
async deleteEnvVar(environmentId, key) {
|
|
605
733
|
return this.request(
|
|
606
734
|
"DELETE",
|
|
607
|
-
`/deployment/
|
|
735
|
+
`/deployment/stages/${environmentId}/env-vars/${key}`
|
|
608
736
|
);
|
|
609
737
|
},
|
|
610
738
|
/**
|
|
@@ -634,6 +762,137 @@ var apiClient = {
|
|
|
634
762
|
}
|
|
635
763
|
};
|
|
636
764
|
|
|
765
|
+
// src/services/auth.ts
|
|
766
|
+
import os from "os";
|
|
767
|
+
var authService = {
|
|
768
|
+
/**
|
|
769
|
+
* Start browser-based authentication flow
|
|
770
|
+
* Returns the auth code and URL for the browser
|
|
771
|
+
*/
|
|
772
|
+
async initAuth() {
|
|
773
|
+
const apiUrl = configService.getApiUrl();
|
|
774
|
+
const version = getCurrentVersion();
|
|
775
|
+
const platform = os.platform();
|
|
776
|
+
const hostname = os.hostname();
|
|
777
|
+
try {
|
|
778
|
+
const response = await fetch(`${apiUrl}/cli/auth/init`, {
|
|
779
|
+
body: JSON.stringify({
|
|
780
|
+
cliVersion: version,
|
|
781
|
+
hostname,
|
|
782
|
+
platform
|
|
783
|
+
}),
|
|
784
|
+
headers: {
|
|
785
|
+
"Content-Type": "application/json"
|
|
786
|
+
},
|
|
787
|
+
method: "POST"
|
|
788
|
+
});
|
|
789
|
+
if (!response.ok) {
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
return await response.json();
|
|
793
|
+
} catch {
|
|
794
|
+
return null;
|
|
795
|
+
}
|
|
796
|
+
},
|
|
797
|
+
/**
|
|
798
|
+
* Poll for authentication status
|
|
799
|
+
*/
|
|
800
|
+
async pollAuth(code, pollToken) {
|
|
801
|
+
const apiUrl = configService.getApiUrl();
|
|
802
|
+
try {
|
|
803
|
+
const response = await fetch(`${apiUrl}/cli/auth/poll/${code}`, {
|
|
804
|
+
headers: {
|
|
805
|
+
"X-Poll-Token": pollToken
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
if (!response.ok) {
|
|
809
|
+
return { status: "expired" };
|
|
810
|
+
}
|
|
811
|
+
return await response.json();
|
|
812
|
+
} catch {
|
|
813
|
+
return { status: "expired" };
|
|
814
|
+
}
|
|
815
|
+
},
|
|
816
|
+
/**
|
|
817
|
+
* Validate a CLI token
|
|
818
|
+
*/
|
|
819
|
+
async validateToken(token) {
|
|
820
|
+
const apiUrl = configService.getApiUrl();
|
|
821
|
+
try {
|
|
822
|
+
const response = await fetch(`${apiUrl}/cli/auth/validate`, {
|
|
823
|
+
headers: {
|
|
824
|
+
Authorization: `Bearer ${token}`
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
if (!response.ok) {
|
|
828
|
+
return { valid: false };
|
|
829
|
+
}
|
|
830
|
+
return await response.json();
|
|
831
|
+
} catch {
|
|
832
|
+
return { valid: false };
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
/**
|
|
836
|
+
* Perform browser-based login
|
|
837
|
+
* Opens browser, waits for user to authorize, returns user info
|
|
838
|
+
*/
|
|
839
|
+
async browserLogin() {
|
|
840
|
+
const open = (await import("open")).default;
|
|
841
|
+
logger.info("Initialisiere Anmeldung...");
|
|
842
|
+
const authInit = await this.initAuth();
|
|
843
|
+
if (!authInit) {
|
|
844
|
+
return { error: "Konnte Authentifizierung nicht initialisieren", success: false };
|
|
845
|
+
}
|
|
846
|
+
logger.newline();
|
|
847
|
+
logger.info("Bitte autorisieren Sie die CLI in Ihrem Browser.");
|
|
848
|
+
logger.newline();
|
|
849
|
+
logger.info(`Autorisierungscode: ${authInit.code}`);
|
|
850
|
+
logger.newline();
|
|
851
|
+
try {
|
|
852
|
+
await open(authInit.authUrl);
|
|
853
|
+
logger.info("Browser wurde ge\xF6ffnet...");
|
|
854
|
+
} catch {
|
|
855
|
+
logger.warning("Browser konnte nicht ge\xF6ffnet werden.");
|
|
856
|
+
logger.info(`Bitte \xF6ffnen Sie diese URL manuell: ${authInit.authUrl}`);
|
|
857
|
+
}
|
|
858
|
+
logger.newline();
|
|
859
|
+
logger.info("Warte auf Autorisierung...");
|
|
860
|
+
const maxAttempts = 150;
|
|
861
|
+
const pollInterval = 2e3;
|
|
862
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
863
|
+
const result = await this.pollAuth(authInit.code, authInit.pollToken);
|
|
864
|
+
if (result.status === "authorized" && result.token) {
|
|
865
|
+
configService.setToken(result.token);
|
|
866
|
+
if (result.user) {
|
|
867
|
+
configService.setUserId(result.user.id);
|
|
868
|
+
}
|
|
869
|
+
return {
|
|
870
|
+
success: true,
|
|
871
|
+
user: result.user
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
if (result.status === "expired" || result.status === "consumed") {
|
|
875
|
+
return { error: "Autorisierungscode abgelaufen oder bereits verwendet", success: false };
|
|
876
|
+
}
|
|
877
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
878
|
+
}
|
|
879
|
+
return { error: "Zeit\xFCberschreitung bei der Anmeldung", success: false };
|
|
880
|
+
},
|
|
881
|
+
/**
|
|
882
|
+
* Check if current token is a CLI token and validate it
|
|
883
|
+
*/
|
|
884
|
+
async checkCliToken() {
|
|
885
|
+
const token = configService.getToken();
|
|
886
|
+
if (!token) {
|
|
887
|
+
return { valid: false };
|
|
888
|
+
}
|
|
889
|
+
if (!token.startsWith("turbo_cli_")) {
|
|
890
|
+
return { valid: true };
|
|
891
|
+
}
|
|
892
|
+
return await this.validateToken(token);
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
|
|
637
896
|
// src/utils/spinner.ts
|
|
638
897
|
import ora from "ora";
|
|
639
898
|
function createSpinner(text) {
|
|
@@ -660,60 +919,59 @@ var loginCommand = new Command("login").description("Authenticate with TurboOps
|
|
|
660
919
|
logger.header("TurboOps Login");
|
|
661
920
|
if (options.token) {
|
|
662
921
|
configService.setToken(options.token);
|
|
663
|
-
|
|
922
|
+
if (options.token.startsWith("turbo_cli_")) {
|
|
923
|
+
const { data: data2, error: error2 } = await withSpinner("Verifying token...", async () => {
|
|
924
|
+
const result2 = await authService.validateToken(options.token);
|
|
925
|
+
if (result2.valid && result2.user) {
|
|
926
|
+
return { data: result2.user, error: void 0 };
|
|
927
|
+
}
|
|
928
|
+
return { data: void 0, error: "Invalid token" };
|
|
929
|
+
});
|
|
930
|
+
if (error2 || !data2) {
|
|
931
|
+
configService.clearToken();
|
|
932
|
+
logger.error(`Authentication failed: ${error2 || "Invalid token"}`);
|
|
933
|
+
process.exit(10 /* AUTH_ERROR */);
|
|
934
|
+
}
|
|
935
|
+
configService.setUserId(data2.id);
|
|
936
|
+
addJsonData({
|
|
937
|
+
authenticated: true,
|
|
938
|
+
user: { email: data2.email, id: data2.id }
|
|
939
|
+
});
|
|
940
|
+
logger.success(`Authenticated as ${data2.email}`);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const { data, error } = await withSpinner(
|
|
664
944
|
"Verifying token...",
|
|
665
945
|
() => apiClient.whoami()
|
|
666
946
|
);
|
|
667
|
-
if (
|
|
947
|
+
if (error || !data) {
|
|
668
948
|
configService.clearToken();
|
|
669
|
-
logger.error(`Authentication failed: ${
|
|
949
|
+
logger.error(`Authentication failed: ${error || "Invalid token"}`);
|
|
670
950
|
process.exit(10 /* AUTH_ERROR */);
|
|
671
951
|
}
|
|
672
|
-
configService.setUserId(
|
|
952
|
+
configService.setUserId(data.id);
|
|
673
953
|
addJsonData({
|
|
674
954
|
authenticated: true,
|
|
675
|
-
user: {
|
|
955
|
+
user: { email: data.email, id: data.id }
|
|
676
956
|
});
|
|
677
|
-
logger.success(`Authenticated as ${
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
const response = await prompts([
|
|
681
|
-
{
|
|
682
|
-
type: "text",
|
|
683
|
-
name: "email",
|
|
684
|
-
message: "Email:",
|
|
685
|
-
validate: (value) => value.includes("@") || "Please enter a valid email"
|
|
686
|
-
},
|
|
687
|
-
{
|
|
688
|
-
type: "password",
|
|
689
|
-
name: "password",
|
|
690
|
-
message: "Password:"
|
|
691
|
-
}
|
|
692
|
-
]);
|
|
693
|
-
if (!response.email || !response.password) {
|
|
694
|
-
logger.warning("Login cancelled");
|
|
695
|
-
addJsonData({ authenticated: false, cancelled: true });
|
|
957
|
+
logger.success(`Authenticated as ${data.email}`);
|
|
696
958
|
return;
|
|
697
959
|
}
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
(
|
|
701
|
-
|
|
702
|
-
if (error || !data) {
|
|
703
|
-
logger.error(`Login failed: ${error || "Unknown error"}`);
|
|
704
|
-
addJsonData({ authenticated: false, error: error || "Unknown error" });
|
|
960
|
+
const result = await authService.browserLogin();
|
|
961
|
+
if (!result.success) {
|
|
962
|
+
logger.error(`Login failed: ${result.error || "Unknown error"}`);
|
|
963
|
+
addJsonData({ authenticated: false, error: result.error || "Unknown error" });
|
|
705
964
|
process.exit(10 /* AUTH_ERROR */);
|
|
706
965
|
}
|
|
707
|
-
configService.setToken(data.token);
|
|
708
|
-
configService.setUserId(data.user.id);
|
|
709
966
|
addJsonData({
|
|
710
967
|
authenticated: true,
|
|
711
|
-
user: {
|
|
968
|
+
user: result.user ? { email: result.user.email, fullName: result.user.fullName, id: result.user.id } : void 0
|
|
712
969
|
});
|
|
713
|
-
logger.success(`Logged in as ${data.user.email}`);
|
|
714
970
|
logger.newline();
|
|
715
|
-
logger.
|
|
716
|
-
logger.
|
|
971
|
+
logger.success(`Angemeldet als ${result.user?.email || "Unknown"}`);
|
|
972
|
+
logger.newline();
|
|
973
|
+
logger.info("Ihre Anmeldedaten wurden gespeichert.");
|
|
974
|
+
logger.info("F\xFChren Sie `turbo whoami` aus, um Ihre Authentifizierung zu \xFCberpr\xFCfen.");
|
|
717
975
|
});
|
|
718
976
|
var logoutCommand = new Command("logout").description("Remove stored authentication").action(() => {
|
|
719
977
|
configService.clearToken();
|
|
@@ -726,6 +984,36 @@ var whoamiCommand = new Command("whoami").description("Show current authenticate
|
|
|
726
984
|
addJsonData({ authenticated: false });
|
|
727
985
|
process.exit(10 /* AUTH_ERROR */);
|
|
728
986
|
}
|
|
987
|
+
const token = configService.getToken();
|
|
988
|
+
if (token?.startsWith("turbo_cli_")) {
|
|
989
|
+
const { data: data2, error: error2 } = await withSpinner("Fetching user info...", async () => {
|
|
990
|
+
const result = await authService.validateToken(token);
|
|
991
|
+
if (result.valid && result.user) {
|
|
992
|
+
return { data: result.user, error: void 0 };
|
|
993
|
+
}
|
|
994
|
+
return { data: void 0, error: "Invalid token" };
|
|
995
|
+
});
|
|
996
|
+
if (error2 || !data2) {
|
|
997
|
+
logger.error(`Failed to fetch user info: ${error2 || "Unknown error"}`);
|
|
998
|
+
addJsonData({ authenticated: false, error: error2 || "Unknown error" });
|
|
999
|
+
process.exit(13 /* API_ERROR */);
|
|
1000
|
+
}
|
|
1001
|
+
addJsonData({
|
|
1002
|
+
authenticated: true,
|
|
1003
|
+
user: {
|
|
1004
|
+
email: data2.email,
|
|
1005
|
+
id: data2.id,
|
|
1006
|
+
name: data2.fullName || null
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
logger.header("Current User");
|
|
1010
|
+
logger.table({
|
|
1011
|
+
Email: data2.email,
|
|
1012
|
+
ID: data2.id,
|
|
1013
|
+
Name: data2.fullName || "-"
|
|
1014
|
+
});
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
729
1017
|
const { data, error } = await withSpinner(
|
|
730
1018
|
"Fetching user info...",
|
|
731
1019
|
() => apiClient.whoami()
|
|
@@ -738,15 +1026,15 @@ var whoamiCommand = new Command("whoami").description("Show current authenticate
|
|
|
738
1026
|
addJsonData({
|
|
739
1027
|
authenticated: true,
|
|
740
1028
|
user: {
|
|
741
|
-
id: data.id,
|
|
742
1029
|
email: data.email,
|
|
1030
|
+
id: data.id,
|
|
743
1031
|
name: data.name || null
|
|
744
1032
|
}
|
|
745
1033
|
});
|
|
746
1034
|
logger.header("Current User");
|
|
747
1035
|
logger.table({
|
|
748
|
-
ID: data.id,
|
|
749
1036
|
Email: data.email,
|
|
1037
|
+
ID: data.id,
|
|
750
1038
|
Name: data.name || "-"
|
|
751
1039
|
});
|
|
752
1040
|
});
|
|
@@ -1034,11 +1322,77 @@ var statusCommand = new Command3("status").description("Show deployment status")
|
|
|
1034
1322
|
});
|
|
1035
1323
|
}
|
|
1036
1324
|
});
|
|
1037
|
-
var configCommand = new Command3("config").description("
|
|
1038
|
-
|
|
1039
|
-
|
|
1325
|
+
var configCommand = new Command3("config").description("Manage configuration");
|
|
1326
|
+
configCommand.command("show", { isDefault: true }).description("Show current configuration").action(() => {
|
|
1327
|
+
const config = configService.getAll();
|
|
1328
|
+
addJsonData({ config });
|
|
1040
1329
|
configService.show();
|
|
1041
1330
|
});
|
|
1331
|
+
configCommand.command("set").description("Set a configuration value").argument("<key>", "Configuration key (token, project)").argument("<value>", "Value to set").action((key, value) => {
|
|
1332
|
+
switch (key.toLowerCase()) {
|
|
1333
|
+
case "token":
|
|
1334
|
+
configService.setToken(value);
|
|
1335
|
+
logger.success("Token saved (global)");
|
|
1336
|
+
addJsonData({ key: "token", saved: true, scope: "global" });
|
|
1337
|
+
break;
|
|
1338
|
+
case "project":
|
|
1339
|
+
configService.setProject(value);
|
|
1340
|
+
logger.success(`Project saved to .turboops.json`);
|
|
1341
|
+
addJsonData({ key: "project", value, saved: true, scope: "local" });
|
|
1342
|
+
break;
|
|
1343
|
+
default:
|
|
1344
|
+
logger.error(`Unknown config key: ${key}`);
|
|
1345
|
+
logger.info("Available keys: token, project");
|
|
1346
|
+
addJsonData({ error: `Unknown config key: ${key}` });
|
|
1347
|
+
process.exit(14 /* VALIDATION_ERROR */);
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
configCommand.command("get").description("Get a configuration value").argument("<key>", "Configuration key (token, project, api-url)").action((key) => {
|
|
1351
|
+
let value = null;
|
|
1352
|
+
switch (key.toLowerCase()) {
|
|
1353
|
+
case "token":
|
|
1354
|
+
value = configService.getToken();
|
|
1355
|
+
if (value) {
|
|
1356
|
+
console.log("***");
|
|
1357
|
+
addJsonData({ key: "token", set: true });
|
|
1358
|
+
} else {
|
|
1359
|
+
console.log("(not set)");
|
|
1360
|
+
addJsonData({ key: "token", set: false });
|
|
1361
|
+
}
|
|
1362
|
+
return;
|
|
1363
|
+
case "project":
|
|
1364
|
+
value = configService.getProject();
|
|
1365
|
+
break;
|
|
1366
|
+
case "api-url":
|
|
1367
|
+
value = configService.getApiUrl();
|
|
1368
|
+
break;
|
|
1369
|
+
default:
|
|
1370
|
+
logger.error(`Unknown config key: ${key}`);
|
|
1371
|
+
logger.info("Available keys: token, project, api-url");
|
|
1372
|
+
addJsonData({ error: `Unknown config key: ${key}` });
|
|
1373
|
+
process.exit(14 /* VALIDATION_ERROR */);
|
|
1374
|
+
}
|
|
1375
|
+
console.log(value || "(not set)");
|
|
1376
|
+
addJsonData({ key, value });
|
|
1377
|
+
});
|
|
1378
|
+
configCommand.command("clear").description("Clear configuration").option("--all", "Clear all configuration").option("--token", "Clear token only").option("--project", "Clear project only").action((opts) => {
|
|
1379
|
+
if (opts.all) {
|
|
1380
|
+
configService.clearAll();
|
|
1381
|
+
logger.success("All configuration cleared");
|
|
1382
|
+
addJsonData({ cleared: "all" });
|
|
1383
|
+
} else if (opts.token) {
|
|
1384
|
+
configService.clearToken();
|
|
1385
|
+
logger.success("Token cleared");
|
|
1386
|
+
addJsonData({ cleared: "token" });
|
|
1387
|
+
} else if (opts.project) {
|
|
1388
|
+
configService.clearProject();
|
|
1389
|
+
logger.success("Project configuration cleared");
|
|
1390
|
+
addJsonData({ cleared: "project" });
|
|
1391
|
+
} else {
|
|
1392
|
+
logger.error("Specify what to clear: --all, --token, or --project");
|
|
1393
|
+
process.exit(14 /* VALIDATION_ERROR */);
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1042
1396
|
function getStatusColor(status) {
|
|
1043
1397
|
switch (status) {
|
|
1044
1398
|
case "healthy":
|
|
@@ -1151,130 +1505,377 @@ envCommand.addCommand(envUnsetCommand);
|
|
|
1151
1505
|
|
|
1152
1506
|
// src/commands/init.ts
|
|
1153
1507
|
import { Command as Command5 } from "commander";
|
|
1154
|
-
import
|
|
1508
|
+
import prompts from "prompts";
|
|
1155
1509
|
import chalk6 from "chalk";
|
|
1156
1510
|
var initCommand = new Command5("init").description("Initialize TurboOps project in current directory").action(async () => {
|
|
1157
1511
|
logger.header("TurboOps Project Initialization");
|
|
1158
1512
|
if (!configService.isAuthenticated()) {
|
|
1159
|
-
logger.warning("
|
|
1513
|
+
logger.warning("Nicht authentifiziert. Bitte melden Sie sich zuerst an.");
|
|
1160
1514
|
logger.newline();
|
|
1161
|
-
const { shouldLogin } = await
|
|
1162
|
-
|
|
1515
|
+
const { shouldLogin } = await prompts({
|
|
1516
|
+
initial: true,
|
|
1517
|
+
message: "M\xF6chten Sie sich jetzt anmelden?",
|
|
1163
1518
|
name: "shouldLogin",
|
|
1164
|
-
|
|
1165
|
-
initial: true
|
|
1519
|
+
type: "confirm"
|
|
1166
1520
|
});
|
|
1167
1521
|
if (!shouldLogin) {
|
|
1168
|
-
logger.info("
|
|
1522
|
+
logger.info("F\xFChren Sie `turbo login` aus, wenn Sie bereit sind.");
|
|
1169
1523
|
addJsonData({ initialized: false, reason: "not_authenticated" });
|
|
1170
1524
|
return;
|
|
1171
1525
|
}
|
|
1172
|
-
const
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
name: "email",
|
|
1176
|
-
message: "Email:",
|
|
1177
|
-
validate: (value) => value.includes("@") || "Please enter a valid email"
|
|
1178
|
-
},
|
|
1179
|
-
{
|
|
1180
|
-
type: "password",
|
|
1181
|
-
name: "password",
|
|
1182
|
-
message: "Password:"
|
|
1183
|
-
}
|
|
1184
|
-
]);
|
|
1185
|
-
if (!credentials.email || !credentials.password) {
|
|
1186
|
-
logger.warning("Login cancelled");
|
|
1187
|
-
addJsonData({ initialized: false, reason: "login_cancelled" });
|
|
1188
|
-
return;
|
|
1189
|
-
}
|
|
1190
|
-
const { data, error } = await withSpinner(
|
|
1191
|
-
"Logging in...",
|
|
1192
|
-
() => apiClient.login(credentials.email, credentials.password)
|
|
1193
|
-
);
|
|
1194
|
-
if (error || !data) {
|
|
1195
|
-
logger.error(`Login failed: ${error || "Unknown error"}`);
|
|
1526
|
+
const result = await authService.browserLogin();
|
|
1527
|
+
if (!result.success) {
|
|
1528
|
+
logger.error(`Anmeldung fehlgeschlagen: ${result.error || "Unbekannter Fehler"}`);
|
|
1196
1529
|
addJsonData({
|
|
1530
|
+
error: result.error || "Unknown error",
|
|
1197
1531
|
initialized: false,
|
|
1198
|
-
reason: "login_failed"
|
|
1199
|
-
error: error || "Unknown error"
|
|
1532
|
+
reason: "login_failed"
|
|
1200
1533
|
});
|
|
1201
1534
|
process.exit(10 /* AUTH_ERROR */);
|
|
1202
1535
|
}
|
|
1203
|
-
|
|
1204
|
-
configService.setUserId(data.user.id);
|
|
1205
|
-
logger.success(`Logged in as ${data.user.email}`);
|
|
1536
|
+
logger.success(`Angemeldet als ${result.user?.email || "Unknown"}`);
|
|
1206
1537
|
}
|
|
1207
1538
|
logger.newline();
|
|
1208
|
-
const { projectSlug } = await
|
|
1209
|
-
|
|
1539
|
+
const { projectSlug } = await prompts({
|
|
1540
|
+
hint: "Der Slug Ihres TurboOps-Projekts",
|
|
1541
|
+
message: "Projekt-Slug:",
|
|
1210
1542
|
name: "projectSlug",
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
validate: (value) => value.length > 0 || "Project slug is required"
|
|
1543
|
+
type: "text",
|
|
1544
|
+
validate: (value) => value.length > 0 || "Projekt-Slug ist erforderlich"
|
|
1214
1545
|
});
|
|
1215
1546
|
if (!projectSlug) {
|
|
1216
|
-
logger.warning("
|
|
1547
|
+
logger.warning("Initialisierung abgebrochen");
|
|
1217
1548
|
addJsonData({ initialized: false, reason: "cancelled" });
|
|
1218
1549
|
return;
|
|
1219
1550
|
}
|
|
1220
1551
|
const { data: project, error: projectError } = await withSpinner(
|
|
1221
|
-
"
|
|
1552
|
+
"Suche Projekt...",
|
|
1222
1553
|
() => apiClient.getProject(projectSlug)
|
|
1223
1554
|
);
|
|
1224
|
-
if (
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1555
|
+
if (project) {
|
|
1556
|
+
await setupProject(project);
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
logger.newline();
|
|
1560
|
+
logger.warning(`Projekt "${projectSlug}" nicht gefunden.`);
|
|
1561
|
+
const { shouldCreate } = await prompts({
|
|
1562
|
+
initial: true,
|
|
1563
|
+
message: "M\xF6chten Sie ein neues Projekt anlegen?",
|
|
1564
|
+
name: "shouldCreate",
|
|
1565
|
+
type: "confirm"
|
|
1566
|
+
});
|
|
1567
|
+
if (!shouldCreate) {
|
|
1568
|
+
logger.info("Initialisierung abgebrochen.");
|
|
1569
|
+
addJsonData({ initialized: false, reason: "project_not_found" });
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
await createNewProject(projectSlug);
|
|
1573
|
+
});
|
|
1574
|
+
async function setupProject(project) {
|
|
1575
|
+
configService.setProject(project.slug);
|
|
1576
|
+
const { data: environments } = await apiClient.getEnvironments(project.id);
|
|
1577
|
+
if (!environments || environments.length === 0) {
|
|
1578
|
+
logger.newline();
|
|
1579
|
+
const { shouldCreateStage } = await prompts({
|
|
1580
|
+
initial: true,
|
|
1581
|
+
message: "Das Projekt hat noch keine Stages. M\xF6chten Sie jetzt eine erstellen?",
|
|
1582
|
+
name: "shouldCreateStage",
|
|
1583
|
+
type: "confirm"
|
|
1584
|
+
});
|
|
1585
|
+
if (shouldCreateStage) {
|
|
1586
|
+
await createFirstStage(project.id, project.slug);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
const fs2 = await import("fs/promises");
|
|
1590
|
+
const path2 = await import("path");
|
|
1591
|
+
const gitlabCiPath = path2.join(process.cwd(), ".gitlab-ci.yml");
|
|
1592
|
+
let hasGitLabPipeline = false;
|
|
1593
|
+
try {
|
|
1594
|
+
await fs2.access(gitlabCiPath);
|
|
1595
|
+
hasGitLabPipeline = true;
|
|
1596
|
+
} catch {
|
|
1597
|
+
}
|
|
1598
|
+
if (!hasGitLabPipeline) {
|
|
1599
|
+
logger.newline();
|
|
1600
|
+
const { shouldCreatePipeline } = await prompts({
|
|
1601
|
+
initial: false,
|
|
1602
|
+
message: "M\xF6chten Sie eine CI/CD Pipeline anlegen?",
|
|
1603
|
+
name: "shouldCreatePipeline",
|
|
1604
|
+
type: "confirm"
|
|
1605
|
+
});
|
|
1606
|
+
if (shouldCreatePipeline) {
|
|
1607
|
+
await createPipeline(project.id);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
await showFinalSummary(project);
|
|
1611
|
+
}
|
|
1612
|
+
async function createNewProject(slug) {
|
|
1613
|
+
logger.newline();
|
|
1614
|
+
logger.header("Neues Projekt erstellen");
|
|
1615
|
+
const { data: customers } = await withSpinner(
|
|
1616
|
+
"Lade verf\xFCgbare Kunden...",
|
|
1617
|
+
() => apiClient.getCustomers()
|
|
1618
|
+
);
|
|
1619
|
+
const promptQuestions = [
|
|
1620
|
+
{
|
|
1621
|
+
initial: slug.charAt(0).toUpperCase() + slug.slice(1).replace(/-/g, " "),
|
|
1622
|
+
message: "Projektname:",
|
|
1623
|
+
name: "name",
|
|
1624
|
+
type: "text",
|
|
1625
|
+
validate: (value) => value.length > 0 || "Projektname ist erforderlich"
|
|
1626
|
+
}
|
|
1627
|
+
];
|
|
1628
|
+
if (customers && customers.length > 0) {
|
|
1629
|
+
promptQuestions.push({
|
|
1630
|
+
choices: [
|
|
1631
|
+
{ title: "(Kein Kunde)", value: "" },
|
|
1632
|
+
...customers.map((c) => ({ title: c.name, value: c.id }))
|
|
1633
|
+
],
|
|
1634
|
+
message: "Kunde (optional):",
|
|
1635
|
+
name: "customer",
|
|
1636
|
+
type: "select"
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
promptQuestions.push(
|
|
1640
|
+
{
|
|
1641
|
+
message: "Beschreibung (optional):",
|
|
1642
|
+
name: "description",
|
|
1643
|
+
type: "text"
|
|
1644
|
+
},
|
|
1645
|
+
{
|
|
1646
|
+
message: "Repository URL (optional):",
|
|
1647
|
+
name: "repositoryUrl",
|
|
1648
|
+
type: "text"
|
|
1649
|
+
}
|
|
1650
|
+
);
|
|
1651
|
+
const projectDetails = await prompts(promptQuestions);
|
|
1652
|
+
if (!projectDetails.name) {
|
|
1653
|
+
logger.warning("Projekterstellung abgebrochen");
|
|
1654
|
+
addJsonData({ initialized: false, reason: "cancelled" });
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
const { data: newProject, error: createError } = await withSpinner(
|
|
1658
|
+
"Erstelle Projekt...",
|
|
1659
|
+
() => apiClient.createProject({
|
|
1660
|
+
customer: projectDetails.customer || void 0,
|
|
1661
|
+
description: projectDetails.description || void 0,
|
|
1662
|
+
name: projectDetails.name,
|
|
1663
|
+
repositoryUrl: projectDetails.repositoryUrl || void 0,
|
|
1664
|
+
slug
|
|
1665
|
+
})
|
|
1666
|
+
);
|
|
1667
|
+
if (createError || !newProject) {
|
|
1668
|
+
logger.error(`Projekt konnte nicht erstellt werden: ${createError || "Unbekannter Fehler"}`);
|
|
1231
1669
|
addJsonData({
|
|
1670
|
+
error: createError || "Unknown error",
|
|
1232
1671
|
initialized: false,
|
|
1233
|
-
reason: "
|
|
1234
|
-
projectSlug
|
|
1672
|
+
reason: "create_failed"
|
|
1235
1673
|
});
|
|
1236
|
-
process.exit(
|
|
1674
|
+
process.exit(13 /* API_ERROR */);
|
|
1675
|
+
}
|
|
1676
|
+
logger.success(`Projekt "${newProject.name}" wurde erstellt!`);
|
|
1677
|
+
configService.setProject(newProject.slug);
|
|
1678
|
+
logger.newline();
|
|
1679
|
+
const { shouldCreateStage } = await prompts({
|
|
1680
|
+
initial: true,
|
|
1681
|
+
message: "M\xF6chten Sie jetzt die erste Stage anlegen?",
|
|
1682
|
+
name: "shouldCreateStage",
|
|
1683
|
+
type: "confirm"
|
|
1684
|
+
});
|
|
1685
|
+
if (shouldCreateStage) {
|
|
1686
|
+
await createFirstStage(newProject.id, newProject.slug);
|
|
1687
|
+
}
|
|
1688
|
+
logger.newline();
|
|
1689
|
+
const { shouldCreatePipeline } = await prompts({
|
|
1690
|
+
initial: true,
|
|
1691
|
+
message: "M\xF6chten Sie eine CI/CD Pipeline anlegen?",
|
|
1692
|
+
name: "shouldCreatePipeline",
|
|
1693
|
+
type: "confirm"
|
|
1694
|
+
});
|
|
1695
|
+
if (shouldCreatePipeline) {
|
|
1696
|
+
await createPipeline(newProject.id);
|
|
1697
|
+
}
|
|
1698
|
+
await showFinalSummary(newProject);
|
|
1699
|
+
}
|
|
1700
|
+
async function createFirstStage(projectId, projectSlug) {
|
|
1701
|
+
logger.newline();
|
|
1702
|
+
logger.header("Erste Stage erstellen");
|
|
1703
|
+
const { data: servers } = await withSpinner(
|
|
1704
|
+
"Lade verf\xFCgbare Server...",
|
|
1705
|
+
() => apiClient.getDeploymentServers()
|
|
1706
|
+
);
|
|
1707
|
+
const stageTypes = [
|
|
1708
|
+
{ title: "Development", value: "development" },
|
|
1709
|
+
{ title: "Staging", value: "staging" },
|
|
1710
|
+
{ title: "Production", value: "production" }
|
|
1711
|
+
];
|
|
1712
|
+
const stageQuestions = [
|
|
1713
|
+
{
|
|
1714
|
+
choices: stageTypes,
|
|
1715
|
+
initial: 0,
|
|
1716
|
+
message: "Stage-Typ:",
|
|
1717
|
+
name: "type",
|
|
1718
|
+
type: "select"
|
|
1719
|
+
},
|
|
1720
|
+
{
|
|
1721
|
+
initial: (prev) => prev === "production" ? "Production" : prev === "staging" ? "Staging" : "Development",
|
|
1722
|
+
message: "Stage-Name:",
|
|
1723
|
+
name: "name",
|
|
1724
|
+
type: "text",
|
|
1725
|
+
validate: (value) => value.length > 0 || "Name ist erforderlich"
|
|
1726
|
+
},
|
|
1727
|
+
{
|
|
1728
|
+
initial: (_prev, values) => values.type || "",
|
|
1729
|
+
message: "Stage-Slug:",
|
|
1730
|
+
name: "slug",
|
|
1731
|
+
type: "text",
|
|
1732
|
+
validate: (value) => value.length > 0 || "Slug ist erforderlich"
|
|
1733
|
+
},
|
|
1734
|
+
{
|
|
1735
|
+
initial: (_prev, values) => `${values.slug || ""}.${projectSlug}.example.com`,
|
|
1736
|
+
message: "Domain:",
|
|
1737
|
+
name: "domain",
|
|
1738
|
+
type: "text"
|
|
1739
|
+
},
|
|
1740
|
+
{
|
|
1741
|
+
initial: (_prev, values) => values.type === "production" ? "main" : values.type === "staging" ? "staging" : "develop",
|
|
1742
|
+
message: "Branch:",
|
|
1743
|
+
name: "branch",
|
|
1744
|
+
type: "text"
|
|
1745
|
+
}
|
|
1746
|
+
];
|
|
1747
|
+
if (servers && servers.length > 0) {
|
|
1748
|
+
stageQuestions.push({
|
|
1749
|
+
choices: [
|
|
1750
|
+
{ title: "(Sp\xE4ter ausw\xE4hlen)", value: "" },
|
|
1751
|
+
...servers.map((s) => ({ title: `${s.name} (${s.host})`, value: s.id }))
|
|
1752
|
+
],
|
|
1753
|
+
message: "Server (optional):",
|
|
1754
|
+
name: "server",
|
|
1755
|
+
type: "select"
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
const stageDetails = await prompts(stageQuestions);
|
|
1759
|
+
if (!stageDetails.name || !stageDetails.slug) {
|
|
1760
|
+
logger.warning("Stage-Erstellung abgebrochen");
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
const { data: newStage, error: stageError } = await withSpinner(
|
|
1764
|
+
"Erstelle Stage...",
|
|
1765
|
+
() => apiClient.createStage({
|
|
1766
|
+
project: projectId,
|
|
1767
|
+
name: stageDetails.name,
|
|
1768
|
+
slug: stageDetails.slug,
|
|
1769
|
+
type: stageDetails.type,
|
|
1770
|
+
domain: stageDetails.domain || void 0,
|
|
1771
|
+
server: stageDetails.server || void 0,
|
|
1772
|
+
branch: stageDetails.branch || void 0
|
|
1773
|
+
})
|
|
1774
|
+
);
|
|
1775
|
+
if (stageError || !newStage) {
|
|
1776
|
+
logger.error(`Stage konnte nicht erstellt werden: ${stageError || "Unbekannter Fehler"}`);
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
logger.success(`Stage "${newStage.name}" wurde erstellt!`);
|
|
1780
|
+
}
|
|
1781
|
+
async function createPipeline(projectId) {
|
|
1782
|
+
logger.newline();
|
|
1783
|
+
logger.header("CI/CD Pipeline erstellen");
|
|
1784
|
+
const pipelineQuestions = [
|
|
1785
|
+
{
|
|
1786
|
+
choices: [
|
|
1787
|
+
{ title: "GitLab CI/CD", value: "gitlab" },
|
|
1788
|
+
{ title: "GitHub Actions", value: "github" }
|
|
1789
|
+
],
|
|
1790
|
+
initial: 0,
|
|
1791
|
+
message: "Pipeline-Typ:",
|
|
1792
|
+
name: "pipelineType",
|
|
1793
|
+
type: "select"
|
|
1794
|
+
}
|
|
1795
|
+
];
|
|
1796
|
+
const pipelineDetails = await prompts(pipelineQuestions);
|
|
1797
|
+
if (!pipelineDetails.pipelineType) {
|
|
1798
|
+
logger.warning("Pipeline-Erstellung abgebrochen");
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
const { data: pipeline, error } = await withSpinner(
|
|
1802
|
+
"Generiere Pipeline...",
|
|
1803
|
+
() => apiClient.generatePipeline(projectId, pipelineDetails.pipelineType)
|
|
1804
|
+
);
|
|
1805
|
+
if (error || !pipeline) {
|
|
1806
|
+
logger.error(`Pipeline konnte nicht generiert werden: ${error || "Unbekannter Fehler"}`);
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
const fs2 = await import("fs/promises");
|
|
1810
|
+
const path2 = await import("path");
|
|
1811
|
+
let filePath;
|
|
1812
|
+
if (pipelineDetails.pipelineType === "github") {
|
|
1813
|
+
const workflowsDir = path2.join(process.cwd(), ".github", "workflows");
|
|
1814
|
+
await fs2.mkdir(workflowsDir, { recursive: true });
|
|
1815
|
+
filePath = path2.join(workflowsDir, "deploy.yml");
|
|
1816
|
+
} else {
|
|
1817
|
+
filePath = path2.join(process.cwd(), ".gitlab-ci.yml");
|
|
1818
|
+
}
|
|
1819
|
+
try {
|
|
1820
|
+
await fs2.writeFile(filePath, pipeline.content, "utf-8");
|
|
1821
|
+
logger.success(`${pipeline.filename} wurde erstellt!`);
|
|
1822
|
+
logger.newline();
|
|
1823
|
+
const { data: secrets } = await apiClient.getPipelineSecrets(projectId, pipelineDetails.pipelineType);
|
|
1824
|
+
if (secrets && secrets.length > 0) {
|
|
1825
|
+
logger.header("Erforderliche CI/CD Secrets");
|
|
1826
|
+
for (const secret of secrets) {
|
|
1827
|
+
const value = secret.isSecret ? chalk6.dim("(geheim)") : chalk6.cyan(secret.value || "-");
|
|
1828
|
+
console.log(` ${chalk6.bold(secret.name)}: ${value}`);
|
|
1829
|
+
console.log(` ${chalk6.dim(secret.description)}`);
|
|
1830
|
+
}
|
|
1831
|
+
logger.newline();
|
|
1832
|
+
logger.info("F\xFCgen Sie diese Werte als CI/CD Secrets/Variables hinzu.");
|
|
1833
|
+
logger.info("Projekt-Token k\xF6nnen Sie in der TurboOps Web-UI erstellen.");
|
|
1834
|
+
}
|
|
1835
|
+
} catch (error2) {
|
|
1836
|
+
logger.error(`Fehler beim Schreiben der Pipeline-Datei: ${error2}`);
|
|
1237
1837
|
}
|
|
1238
|
-
|
|
1838
|
+
}
|
|
1839
|
+
async function showFinalSummary(project) {
|
|
1239
1840
|
logger.newline();
|
|
1240
|
-
logger.success("
|
|
1841
|
+
logger.success("Projekt initialisiert!");
|
|
1241
1842
|
logger.newline();
|
|
1242
|
-
logger.header("
|
|
1843
|
+
logger.header("Projektdetails");
|
|
1243
1844
|
logger.table({
|
|
1244
1845
|
Name: project.name,
|
|
1245
|
-
|
|
1246
|
-
|
|
1846
|
+
Repository: project.repositoryUrl || "-",
|
|
1847
|
+
Slug: project.slug
|
|
1247
1848
|
});
|
|
1248
1849
|
const { data: environments } = await apiClient.getEnvironments(project.id);
|
|
1249
1850
|
const envList = environments && environments.length > 0 ? environments.map((env) => ({
|
|
1250
|
-
slug: env.slug,
|
|
1251
1851
|
name: env.name,
|
|
1852
|
+
slug: env.slug,
|
|
1252
1853
|
type: env.type
|
|
1253
1854
|
})) : [];
|
|
1254
1855
|
addJsonData({
|
|
1856
|
+
environments: envList,
|
|
1255
1857
|
initialized: true,
|
|
1256
1858
|
project: {
|
|
1257
1859
|
name: project.name,
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
}
|
|
1261
|
-
environments: envList
|
|
1860
|
+
repositoryUrl: project.repositoryUrl || null,
|
|
1861
|
+
slug: project.slug
|
|
1862
|
+
}
|
|
1262
1863
|
});
|
|
1263
1864
|
if (environments && environments.length > 0) {
|
|
1264
1865
|
logger.newline();
|
|
1265
|
-
logger.header("
|
|
1866
|
+
logger.header("Verf\xFCgbare Stages");
|
|
1266
1867
|
for (const env of environments) {
|
|
1267
1868
|
console.log(` ${chalk6.bold(env.slug)} - ${env.name} (${env.type})`);
|
|
1268
1869
|
}
|
|
1269
1870
|
}
|
|
1270
1871
|
logger.newline();
|
|
1271
|
-
logger.header("
|
|
1872
|
+
logger.header("N\xE4chste Schritte");
|
|
1272
1873
|
logger.list([
|
|
1273
|
-
"
|
|
1274
|
-
"
|
|
1275
|
-
"
|
|
1874
|
+
"F\xFChren Sie `turbo status` aus, um alle Stages zu sehen",
|
|
1875
|
+
"F\xFChren Sie `turbo deploy <stage>` aus, um zu deployen",
|
|
1876
|
+
"F\xFChren Sie `turbo logs <stage>` aus, um Logs anzuzeigen"
|
|
1276
1877
|
]);
|
|
1277
|
-
}
|
|
1878
|
+
}
|
|
1278
1879
|
|
|
1279
1880
|
// src/commands/logs.ts
|
|
1280
1881
|
import { Command as Command6 } from "commander";
|
|
@@ -1439,7 +2040,7 @@ function getLevelColor(level) {
|
|
|
1439
2040
|
var VERSION = getCurrentVersion();
|
|
1440
2041
|
var shouldCheckUpdate = true;
|
|
1441
2042
|
var program = new Command7();
|
|
1442
|
-
program.name("turbo").description("TurboCLI - Command line interface for TurboOps deployments").version(VERSION, "-v, --version", "Show version number").option("--project <slug>", "Override project slug").option("--token <token>", "Override API token").option("--
|
|
2043
|
+
program.name("turbo").description("TurboCLI - Command line interface for TurboOps deployments").version(VERSION, "-v, --version", "Show version number").option("--project <slug>", "Override project slug").option("--token <token>", "Override API token").option("--json", "Output as JSON").option("--quiet", "Only show errors").option("--verbose", "Show debug output").option("--no-update-check", "Skip version check");
|
|
1443
2044
|
program.hook("preAction", (thisCommand) => {
|
|
1444
2045
|
const opts = thisCommand.opts();
|
|
1445
2046
|
clearJsonData();
|
|
@@ -1456,9 +2057,6 @@ program.hook("preAction", (thisCommand) => {
|
|
|
1456
2057
|
if (opts.token) {
|
|
1457
2058
|
configService.setToken(opts.token);
|
|
1458
2059
|
}
|
|
1459
|
-
if (opts.api) {
|
|
1460
|
-
configService.setApiUrl(opts.api);
|
|
1461
|
-
}
|
|
1462
2060
|
if (opts.verbose) {
|
|
1463
2061
|
process.env.DEBUG = "true";
|
|
1464
2062
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@turboops/cli",
|
|
3
|
-
"version": "1.0.0-dev.
|
|
3
|
+
"version": "1.0.0-dev.582",
|
|
4
4
|
"description": "TurboCLI - Command line interface for TurboOps deployments",
|
|
5
5
|
"author": "lenne.tech GmbH",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"chalk": "^5.3.0",
|
|
27
27
|
"commander": "^12.1.0",
|
|
28
28
|
"conf": "^13.0.1",
|
|
29
|
+
"open": "^10.1.0",
|
|
29
30
|
"ora": "^8.0.1",
|
|
30
31
|
"prompts": "^2.4.2",
|
|
31
32
|
"socket.io-client": "^4.7.0"
|