@turboops/cli 1.0.0-dev.580 → 1.0.0-dev.583

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +84 -66
  2. package/dist/index.js +800 -436
  3. package/package.json +1 -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 path of paths) {
137
- if (existsSync(path)) {
138
- return require2(path);
138
+ for (const path3 of paths) {
139
+ if (existsSync(path3)) {
140
+ return require2(path3);
139
141
  }
140
142
  }
141
143
  return { version: "0.0.0", name: "@turboops/cli" };
@@ -200,41 +202,76 @@ var APP_URLS = {
200
202
  production: "https://turbo-ops.de",
201
203
  dev: "https://dev.turbo-ops.de"
202
204
  };
203
- function getDefaultApiUrl() {
204
- const version = getCurrentVersion();
205
- const isDevVersion = version.includes("-dev");
206
- return isDevVersion ? API_URLS.dev : API_URLS.production;
207
- }
208
- var defaults = {
209
- apiUrl: API_URLS.production,
205
+ var LOCAL_CONFIG_FILE = ".turboops.json";
206
+ var globalDefaults = {
210
207
  token: null,
211
- project: null,
212
208
  userId: null
213
209
  };
214
- var config = new Conf({
210
+ var globalConfig = new Conf({
215
211
  projectName: "turboops-cli",
216
- defaults
212
+ defaults: globalDefaults
217
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
+ }
218
266
  var configService = {
219
267
  /**
220
- * Get API URL
221
- * Priority: ENV variable > stored config > version-based default
268
+ * Get API URL (fixed based on CLI version)
222
269
  */
223
270
  getApiUrl() {
224
271
  if (process.env.TURBOOPS_API_URL) {
225
272
  return process.env.TURBOOPS_API_URL;
226
273
  }
227
- const storedUrl = config.get("apiUrl");
228
- if (storedUrl === "https://api.turboops.io" || storedUrl === API_URLS.production) {
229
- return getDefaultApiUrl();
230
- }
231
- return storedUrl;
232
- },
233
- /**
234
- * Set API URL
235
- */
236
- setApiUrl(url) {
237
- config.set("apiUrl", url);
274
+ return getApiUrl();
238
275
  },
239
276
  /**
240
277
  * Get App URL (frontend)
@@ -246,53 +283,65 @@ var configService = {
246
283
  return isDevVersion ? APP_URLS.dev : APP_URLS.production;
247
284
  },
248
285
  /**
249
- * Get authentication token
286
+ * Get authentication token (global - user session)
250
287
  */
251
288
  getToken() {
252
- return process.env.TURBOOPS_TOKEN || config.get("token");
289
+ return process.env.TURBOOPS_TOKEN || globalConfig.get("token");
253
290
  },
254
291
  /**
255
- * Set authentication token
292
+ * Set authentication token (global - user session)
256
293
  */
257
294
  setToken(token) {
258
- config.set("token", token);
295
+ globalConfig.set("token", token);
259
296
  },
260
297
  /**
261
298
  * Clear authentication token
262
299
  */
263
300
  clearToken() {
264
- config.set("token", null);
265
- config.set("userId", null);
301
+ globalConfig.set("token", null);
302
+ globalConfig.set("userId", null);
266
303
  },
267
304
  /**
268
- * Get current project
305
+ * Get current project (local - project specific)
269
306
  */
270
307
  getProject() {
271
- return process.env.TURBOOPS_PROJECT || config.get("project");
308
+ if (process.env.TURBOOPS_PROJECT) {
309
+ return process.env.TURBOOPS_PROJECT;
310
+ }
311
+ const localConfig = readLocalConfig();
312
+ return localConfig?.project || null;
272
313
  },
273
314
  /**
274
- * Set current project
315
+ * Set current project (local - writes to .turboops.json)
275
316
  */
276
317
  setProject(project) {
277
- config.set("project", project);
318
+ const localConfig = readLocalConfig() || { project: "" };
319
+ localConfig.project = project;
320
+ writeLocalConfig(localConfig);
278
321
  },
279
322
  /**
280
323
  * Clear current project
281
324
  */
282
325
  clearProject() {
283
- config.set("project", null);
326
+ const configPath = getLocalConfigPath();
327
+ if (configPath && fs.existsSync(configPath)) {
328
+ try {
329
+ fs.unlinkSync(configPath);
330
+ } catch {
331
+ }
332
+ }
284
333
  },
285
334
  /**
286
- * Get user ID
335
+ * Get user ID (global - user session)
287
336
  */
288
337
  getUserId() {
289
- return config.get("userId");
338
+ return globalConfig.get("userId");
290
339
  },
291
340
  /**
292
- * Set user ID
341
+ * Set user ID (global - user session)
293
342
  */
294
343
  setUserId(userId) {
295
- config.set("userId", userId);
344
+ globalConfig.set("userId", userId);
296
345
  },
297
346
  /**
298
347
  * Check if user is authenticated
@@ -306,6 +355,19 @@ var configService = {
306
355
  hasProject() {
307
356
  return !!this.getProject();
308
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
+ },
309
371
  /**
310
372
  * Get all configuration
311
373
  */
@@ -318,17 +380,25 @@ var configService = {
318
380
  };
319
381
  },
320
382
  /**
321
- * Clear all configuration
383
+ * Clear all configuration (both global and local)
322
384
  */
323
385
  clearAll() {
324
- config.clear();
386
+ globalConfig.clear();
387
+ this.clearProject();
325
388
  },
326
389
  /**
327
- * Check if using a project token (turbo_xxx format)
390
+ * Check if using a project token (turbo_xxx format, but not turbo_cli_xxx)
328
391
  */
329
392
  isProjectToken() {
330
393
  const token = this.getToken();
331
- 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_");
332
402
  },
333
403
  /**
334
404
  * Show current configuration
@@ -336,12 +406,15 @@ var configService = {
336
406
  show() {
337
407
  const data = this.getAll();
338
408
  const isProjectToken2 = this.isProjectToken();
409
+ const isCliToken = this.isCliSessionToken();
410
+ const localConfigPath = this.getLocalConfigPath();
339
411
  logger.header("Configuration");
340
412
  logger.table({
341
413
  "API URL": data.apiUrl,
342
414
  Token: data.token || "Not set",
343
- "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",
344
416
  Project: data.project || (isProjectToken2 ? "(from token)" : "Not set"),
417
+ "Project Config": localConfigPath || "(not found)",
345
418
  "User ID": data.userId || (isProjectToken2 ? "(N/A for project token)" : "Not set")
346
419
  });
347
420
  }
@@ -352,14 +425,14 @@ import { Command } from "commander";
352
425
 
353
426
  // src/services/api.ts
354
427
  function isProjectToken(token) {
355
- return token.startsWith("turbo_");
428
+ return token.startsWith("turbo_") && !token.startsWith("turbo_cli_");
356
429
  }
357
430
  var cachedProjectInfo = null;
358
431
  var apiClient = {
359
432
  /**
360
433
  * Make an API request
361
434
  */
362
- async request(method, path, body) {
435
+ async request(method, path3, body) {
363
436
  const apiUrl = configService.getApiUrl();
364
437
  const token = configService.getToken();
365
438
  if (!token) {
@@ -368,7 +441,7 @@ var apiClient = {
368
441
  status: 401
369
442
  };
370
443
  }
371
- const url = `${apiUrl}${path}`;
444
+ const url = `${apiUrl}${path3}`;
372
445
  const headers = {
373
446
  "Content-Type": "application/json",
374
447
  Authorization: `Bearer ${token}`
@@ -513,136 +586,106 @@ var apiClient = {
513
586
  return this.request("GET", `/deployment/projects/by-slug/${slug}`);
514
587
  },
515
588
  /**
516
- * Get environments for project
517
- */
518
- async getEnvironments(projectId) {
519
- return this.request(
520
- "GET",
521
- `/deployment/environments?projectId=${projectId}`
522
- );
523
- },
524
- /**
525
- * Get environment by slug
526
- */
527
- async getEnvironmentBySlug(projectId, slug) {
528
- return this.request(
529
- "GET",
530
- `/deployment/environments/by-slug/${projectId}/${slug}`
531
- );
532
- },
533
- /**
534
- * Trigger deployment
589
+ * Create a new project
535
590
  */
536
- async deploy(environmentId, imageTag) {
537
- return this.request("POST", `/deployment/deployments`, {
538
- environmentId,
539
- imageTag
540
- });
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);
541
600
  },
542
601
  /**
543
- * Get deployment status
602
+ * Get all customers
544
603
  */
545
- async getDeploymentStatus(deploymentId) {
546
- return this.request("GET", `/deployment/deployments/${deploymentId}`);
604
+ async getCustomers() {
605
+ return this.request("GET", "/customer");
547
606
  },
548
607
  /**
549
- * Rollback deployment
608
+ * Get environments (stages) for project
550
609
  */
551
- async rollback(environmentId, targetDeploymentId) {
552
- return this.request("POST", `/deployment/deployments/rollback`, {
553
- environmentId,
554
- targetDeploymentId
555
- });
610
+ async getEnvironments(projectId) {
611
+ return this.request(
612
+ "GET",
613
+ `/deployment/stages?projectId=${projectId}`
614
+ );
556
615
  },
557
616
  /**
558
- * Restart containers
617
+ * Get environment (stage) by slug
559
618
  */
560
- async restart(environmentId) {
619
+ async getEnvironmentBySlug(projectId, slug) {
561
620
  return this.request(
562
- "POST",
563
- `/deployment/environments/${environmentId}/restart`
621
+ "GET",
622
+ `/deployment/stages/by-slug/${projectId}/${slug}`
564
623
  );
565
624
  },
566
625
  /**
567
- * Stop containers
626
+ * Create a new stage
568
627
  */
569
- async stop(environmentId) {
570
- return this.request(
571
- "POST",
572
- `/deployment/environments/${environmentId}/stop`
573
- );
628
+ async createStage(data) {
629
+ return this.request("POST", "/deployment/stages/simple", data);
574
630
  },
575
631
  /**
576
- * Wake containers
632
+ * Get deployment-ready servers
577
633
  */
578
- async wake(environmentId) {
579
- return this.request(
580
- "POST",
581
- `/deployment/environments/${environmentId}/wake`
582
- );
634
+ async getDeploymentServers() {
635
+ return this.request("GET", "/server?deploymentReady=true");
583
636
  },
584
637
  /**
585
- * Get deployment logs
638
+ * Generate CI/CD pipeline configuration
586
639
  */
587
- async getLogs(deploymentId) {
588
- return this.request("GET", `/deployment/deployments/${deploymentId}/logs`);
640
+ async generatePipeline(projectId, type) {
641
+ return this.request("GET", `/deployment/projects/${projectId}/pipeline/${type}`);
589
642
  },
590
643
  /**
591
- * Get environment variables
644
+ * Get required secrets for pipeline
592
645
  */
593
- async getEnvVars(environmentId) {
594
- return this.request(
595
- "GET",
596
- `/deployment/environments/${environmentId}/env-vars`
597
- );
646
+ async getPipelineSecrets(projectId, type) {
647
+ return this.request("GET", `/deployment/projects/${projectId}/pipeline/${type}/secrets`);
598
648
  },
599
649
  /**
600
- * Set environment variable
650
+ * Trigger a deployment
601
651
  */
602
- async setEnvVar(environmentId, key, value, secret) {
603
- return this.request(
604
- "PUT",
605
- `/deployment/environments/${environmentId}/env-vars`,
606
- {
607
- key,
608
- value,
609
- secret
610
- }
611
- );
652
+ async deploy(stageId, imageTag) {
653
+ const body = { stage: stageId };
654
+ if (imageTag) body.imageTag = imageTag;
655
+ return this.request("POST", "/deployment/deployments", body);
612
656
  },
613
657
  /**
614
- * Delete environment variable
658
+ * Get deployment status
615
659
  */
616
- async deleteEnvVar(environmentId, key) {
617
- return this.request(
618
- "DELETE",
619
- `/deployment/environments/${environmentId}/env-vars/${key}`
620
- );
660
+ async getDeploymentStatus(deploymentId) {
661
+ return this.request("GET", `/deployment/deployments/${deploymentId}`);
621
662
  },
622
663
  /**
623
664
  * Wait for deployment to complete
624
665
  */
625
- async waitForDeployment(deploymentId, options) {
626
- const timeout = options?.timeout || 6e5;
627
- const pollInterval = options?.pollInterval || 3e3;
666
+ async waitForDeployment(deploymentId, options = {}) {
667
+ const { timeout = 6e5, pollInterval = 3e3, onProgress } = options;
628
668
  const startTime = Date.now();
629
- while (Date.now() - startTime < timeout) {
669
+ while (true) {
630
670
  const { data, error } = await this.getDeploymentStatus(deploymentId);
631
671
  if (error) {
632
- throw new Error(error);
672
+ throw new Error(`Failed to get deployment status: ${error}`);
633
673
  }
634
674
  if (!data) {
635
675
  throw new Error("No deployment data received");
636
676
  }
637
- if (options?.onProgress) {
638
- options.onProgress(data);
677
+ if (onProgress) {
678
+ onProgress(data);
639
679
  }
640
- if (["running", "failed", "stopped", "rolled_back"].includes(data.status)) {
680
+ const terminalStatuses = ["running", "failed", "stopped", "rolled_back"];
681
+ if (terminalStatuses.includes(data.status)) {
641
682
  return data;
642
683
  }
643
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
684
+ if (Date.now() - startTime > timeout) {
685
+ throw new Error(`Deployment timeout after ${timeout / 1e3} seconds`);
686
+ }
687
+ await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
644
688
  }
645
- throw new Error(`Deployment timeout after ${timeout / 1e3} seconds`);
646
689
  }
647
690
  };
648
691
 
@@ -758,7 +801,7 @@ var authService = {
758
801
  if (result.status === "expired" || result.status === "consumed") {
759
802
  return { error: "Autorisierungscode abgelaufen oder bereits verwendet", success: false };
760
803
  }
761
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
804
+ await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
762
805
  }
763
806
  return { error: "Zeit\xFCberschreitung bei der Anmeldung", success: false };
764
807
  },
@@ -923,8 +966,9 @@ var whoamiCommand = new Command("whoami").description("Show current authenticate
923
966
  });
924
967
  });
925
968
 
926
- // src/commands/deploy.ts
969
+ // src/commands/status.ts
927
970
  import { Command as Command2 } from "commander";
971
+ import chalk3 from "chalk";
928
972
 
929
973
  // src/utils/guards.ts
930
974
  async function requireAuth() {
@@ -1000,159 +1044,8 @@ async function getCommandContextWithEnvironment(environmentSlug, showSpinner = t
1000
1044
  return { projectSlug, project, environment };
1001
1045
  }
1002
1046
 
1003
- // src/commands/deploy.ts
1004
- import chalk3 from "chalk";
1005
- var deployCommand = new Command2("deploy").description("Deploy to an environment").argument(
1006
- "<environment>",
1007
- "Environment slug (e.g., production, staging, dev)"
1008
- ).option("-i, --image <tag>", "Specific image tag to deploy").option("-w, --wait", "Wait for deployment to complete (default: true)", true).option("--no-wait", "Do not wait for deployment to complete").option("--timeout <ms>", "Timeout in milliseconds for --wait", "600000").action(async (environment, options) => {
1009
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1010
- logger.header(`Deploying to ${environment}`);
1011
- const { data: deployment, error: deployError } = await withSpinner(
1012
- `Starting deployment...`,
1013
- () => apiClient.deploy(env.id, options.image)
1014
- );
1015
- if (deployError || !deployment) {
1016
- logger.error(
1017
- `Failed to start deployment: ${deployError || "Unknown error"}`
1018
- );
1019
- process.exit(13 /* API_ERROR */);
1020
- }
1021
- logger.success(`Deployment started: ${deployment.id}`);
1022
- addJsonData({ deploymentId: deployment.id });
1023
- if (!options.wait) {
1024
- logger.info(`View deployment status: turbo status ${environment}`);
1025
- return;
1026
- }
1027
- logger.newline();
1028
- const spinner = createSpinner("Deploying...");
1029
- spinner.start();
1030
- try {
1031
- const finalStatus = await apiClient.waitForDeployment(deployment.id, {
1032
- timeout: parseInt(options.timeout),
1033
- onProgress: (status) => {
1034
- spinner.text = getStatusText(status);
1035
- }
1036
- });
1037
- if (finalStatus.status === "running") {
1038
- spinner.succeed(`Deployment successful!`);
1039
- const resultData = {
1040
- environment: env.name,
1041
- environmentSlug: env.slug,
1042
- imageTag: finalStatus.imageTag,
1043
- status: finalStatus.status,
1044
- healthyContainers: finalStatus.healthStatus?.healthy || 0,
1045
- totalContainers: finalStatus.healthStatus?.total || 0,
1046
- domain: env.domain
1047
- };
1048
- addJsonData(resultData);
1049
- logger.newline();
1050
- logger.table({
1051
- Environment: env.name,
1052
- Image: finalStatus.imageTag,
1053
- Status: chalk3.green(finalStatus.status),
1054
- Health: `${finalStatus.healthStatus?.healthy || 0}/${finalStatus.healthStatus?.total || 0} healthy`
1055
- });
1056
- logger.newline();
1057
- logger.info(`View at: https://${env.domain}`);
1058
- } else if (finalStatus.status === "failed") {
1059
- spinner.fail("Deployment failed!");
1060
- addJsonData({ status: "failed", error: finalStatus.errorMessage });
1061
- logger.error(finalStatus.errorMessage || "Unknown error");
1062
- process.exit(3 /* HEALTH_CHECK_FAILED */);
1063
- } else {
1064
- spinner.warn(`Deployment ended with status: ${finalStatus.status}`);
1065
- addJsonData({ status: finalStatus.status });
1066
- process.exit(1 /* ERROR */);
1067
- }
1068
- } catch (error) {
1069
- spinner.fail("Deployment failed!");
1070
- const message = error instanceof Error ? error.message : "Unknown error";
1071
- addJsonData({ error: message });
1072
- logger.error(message);
1073
- if (message.includes("timeout")) {
1074
- process.exit(2 /* TIMEOUT */);
1075
- }
1076
- process.exit(1 /* ERROR */);
1077
- }
1078
- });
1079
- var rollbackCommand = new Command2("rollback").description("Rollback to a previous deployment").argument("<environment>", "Environment slug").option("--to <id>", "Specific deployment ID to rollback to").action(async (environment, options) => {
1080
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1081
- logger.header(`Rolling back ${environment}`);
1082
- const { data, error } = await withSpinner(
1083
- "Starting rollback...",
1084
- () => apiClient.rollback(env.id, options.to)
1085
- );
1086
- if (error || !data) {
1087
- logger.error(`Rollback failed: ${error || "Unknown error"}`);
1088
- process.exit(13 /* API_ERROR */);
1089
- }
1090
- addJsonData({
1091
- status: "rollback_initiated",
1092
- imageTag: data.imageTag,
1093
- deploymentId: data.id
1094
- });
1095
- logger.success(`Rollback initiated to ${data.imageTag}`);
1096
- logger.info("Run `turbo status` to monitor the rollback.");
1097
- });
1098
- var restartCommand = new Command2("restart").description("Restart containers in an environment").argument("<environment>", "Environment slug").action(async (environment) => {
1099
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1100
- const { error } = await withSpinner(
1101
- "Restarting containers...",
1102
- () => apiClient.restart(env.id)
1103
- );
1104
- if (error) {
1105
- logger.error(`Restart failed: ${error}`);
1106
- process.exit(13 /* API_ERROR */);
1107
- }
1108
- addJsonData({ status: "restarting", environment: env.slug });
1109
- logger.success("Containers are being restarted");
1110
- });
1111
- var stopCommand = new Command2("stop").description("Stop containers in an environment").argument("<environment>", "Environment slug").action(async (environment) => {
1112
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1113
- const { error } = await withSpinner(
1114
- "Stopping containers...",
1115
- () => apiClient.stop(env.id)
1116
- );
1117
- if (error) {
1118
- logger.error(`Stop failed: ${error}`);
1119
- process.exit(13 /* API_ERROR */);
1120
- }
1121
- addJsonData({ status: "stopped", environment: env.slug });
1122
- logger.success("Containers have been stopped");
1123
- });
1124
- var wakeCommand = new Command2("wake").description("Wake (start) stopped containers in an environment").argument("<environment>", "Environment slug").action(async (environment) => {
1125
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1126
- const { error } = await withSpinner(
1127
- "Starting containers...",
1128
- () => apiClient.wake(env.id)
1129
- );
1130
- if (error) {
1131
- logger.error(`Wake failed: ${error}`);
1132
- process.exit(13 /* API_ERROR */);
1133
- }
1134
- addJsonData({ status: "starting", environment: env.slug });
1135
- logger.success("Containers are starting");
1136
- });
1137
- function getStatusText(status) {
1138
- switch (status.status) {
1139
- case "pending":
1140
- return "Waiting for deployment to start...";
1141
- case "deploying":
1142
- return `Deploying ${status.imageTag}...`;
1143
- case "running":
1144
- return "Deployment complete!";
1145
- case "failed":
1146
- return "Deployment failed";
1147
- default:
1148
- return `Status: ${status.status}`;
1149
- }
1150
- }
1151
-
1152
1047
  // src/commands/status.ts
1153
- import { Command as Command3 } from "commander";
1154
- import chalk4 from "chalk";
1155
- var statusCommand = new Command3("status").description("Show deployment status").argument(
1048
+ var statusCommand = new Command2("status").description("Show deployment status").argument(
1156
1049
  "[environment]",
1157
1050
  "Environment slug (optional, shows all if not specified)"
1158
1051
  ).action(async (environment) => {
@@ -1196,136 +1089,107 @@ var statusCommand = new Command3("status").description("Show deployment status")
1196
1089
  });
1197
1090
  for (const env of envsToShow) {
1198
1091
  logger.newline();
1199
- console.log(chalk4.bold(env.name) + ` (${env.slug})`);
1200
- console.log(chalk4.gray("\u2500".repeat(40)));
1092
+ console.log(chalk3.bold(env.name) + ` (${env.slug})`);
1093
+ console.log(chalk3.gray("\u2500".repeat(40)));
1201
1094
  const statusColor = getStatusColor(env.status);
1202
1095
  logger.table({
1203
1096
  Status: statusColor(env.status),
1204
1097
  Type: env.type,
1205
- Domain: chalk4.cyan(env.domain)
1098
+ Domain: chalk3.cyan(env.domain)
1206
1099
  });
1207
1100
  }
1208
1101
  });
1209
- var configCommand = new Command3("config").description("Show current configuration").action(() => {
1210
- const config2 = configService.getAll();
1211
- addJsonData({ config: config2 });
1102
+ var configCommand = new Command2("config").description("Manage configuration");
1103
+ configCommand.command("show", { isDefault: true }).description("Show current configuration").action(() => {
1104
+ const config = configService.getAll();
1105
+ addJsonData({ config });
1212
1106
  configService.show();
1213
1107
  });
1108
+ configCommand.command("set").description("Set a configuration value").argument("<key>", "Configuration key (token, project)").argument("<value>", "Value to set").action((key, value) => {
1109
+ switch (key.toLowerCase()) {
1110
+ case "token":
1111
+ configService.setToken(value);
1112
+ logger.success("Token saved (global)");
1113
+ addJsonData({ key: "token", saved: true, scope: "global" });
1114
+ break;
1115
+ case "project":
1116
+ configService.setProject(value);
1117
+ logger.success(`Project saved to .turboops.json`);
1118
+ addJsonData({ key: "project", value, saved: true, scope: "local" });
1119
+ break;
1120
+ default:
1121
+ logger.error(`Unknown config key: ${key}`);
1122
+ logger.info("Available keys: token, project");
1123
+ addJsonData({ error: `Unknown config key: ${key}` });
1124
+ process.exit(14 /* VALIDATION_ERROR */);
1125
+ }
1126
+ });
1127
+ configCommand.command("get").description("Get a configuration value").argument("<key>", "Configuration key (token, project, api-url)").action((key) => {
1128
+ let value = null;
1129
+ switch (key.toLowerCase()) {
1130
+ case "token":
1131
+ value = configService.getToken();
1132
+ if (value) {
1133
+ console.log("***");
1134
+ addJsonData({ key: "token", set: true });
1135
+ } else {
1136
+ console.log("(not set)");
1137
+ addJsonData({ key: "token", set: false });
1138
+ }
1139
+ return;
1140
+ case "project":
1141
+ value = configService.getProject();
1142
+ break;
1143
+ case "api-url":
1144
+ value = configService.getApiUrl();
1145
+ break;
1146
+ default:
1147
+ logger.error(`Unknown config key: ${key}`);
1148
+ logger.info("Available keys: token, project, api-url");
1149
+ addJsonData({ error: `Unknown config key: ${key}` });
1150
+ process.exit(14 /* VALIDATION_ERROR */);
1151
+ }
1152
+ console.log(value || "(not set)");
1153
+ addJsonData({ key, value });
1154
+ });
1155
+ configCommand.command("clear").description("Clear configuration").option("--all", "Clear all configuration").option("--token", "Clear token only").option("--project", "Clear project only").action((opts) => {
1156
+ if (opts.all) {
1157
+ configService.clearAll();
1158
+ logger.success("All configuration cleared");
1159
+ addJsonData({ cleared: "all" });
1160
+ } else if (opts.token) {
1161
+ configService.clearToken();
1162
+ logger.success("Token cleared");
1163
+ addJsonData({ cleared: "token" });
1164
+ } else if (opts.project) {
1165
+ configService.clearProject();
1166
+ logger.success("Project configuration cleared");
1167
+ addJsonData({ cleared: "project" });
1168
+ } else {
1169
+ logger.error("Specify what to clear: --all, --token, or --project");
1170
+ process.exit(14 /* VALIDATION_ERROR */);
1171
+ }
1172
+ });
1214
1173
  function getStatusColor(status) {
1215
1174
  switch (status) {
1216
1175
  case "healthy":
1217
1176
  case "running":
1218
- return chalk4.green;
1177
+ return chalk3.green;
1219
1178
  case "deploying":
1220
- return chalk4.yellow;
1179
+ return chalk3.yellow;
1221
1180
  case "failed":
1222
1181
  case "stopped":
1223
- return chalk4.red;
1182
+ return chalk3.red;
1224
1183
  default:
1225
- return chalk4.gray;
1184
+ return chalk3.gray;
1226
1185
  }
1227
1186
  }
1228
1187
 
1229
- // src/commands/env.ts
1230
- import { Command as Command4 } from "commander";
1231
- import chalk5 from "chalk";
1232
- var envCommand = new Command4("env").description("Manage environment variables").argument("<environment>", "Environment slug").option("-r, --reveal", "Show secret values (normally masked)").action(async (environment, options) => {
1233
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1234
- const { data: envVars, error } = await withSpinner(
1235
- "Fetching environment variables...",
1236
- () => apiClient.getEnvVars(env.id)
1237
- );
1238
- if (error || !envVars) {
1239
- logger.error(
1240
- `Failed to fetch environment variables: ${error || "Unknown error"}`
1241
- );
1242
- process.exit(13 /* API_ERROR */);
1243
- }
1244
- addJsonData({
1245
- environment: env.name,
1246
- environmentSlug: env.slug,
1247
- variables: envVars.map((v) => ({
1248
- key: v.key,
1249
- secret: v.secret
1250
- }))
1251
- });
1252
- logger.header(`Environment Variables: ${env.name}`);
1253
- if (envVars.length === 0) {
1254
- logger.warning("No environment variables configured.");
1255
- return;
1256
- }
1257
- for (const v of envVars) {
1258
- const icon = v.secret ? chalk5.yellow("\u{1F512}") : chalk5.gray(" ");
1259
- const value = v.secret && !options.reveal ? chalk5.gray("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022") : chalk5.green("(set)");
1260
- console.log(`${icon} ${chalk5.bold(v.key)} = ${value}`);
1261
- }
1262
- logger.newline();
1263
- logger.info(
1264
- "Use `turbo env <environment> set KEY=value` to add/update variables."
1265
- );
1266
- logger.info("Use `turbo env <environment> unset KEY` to remove variables.");
1267
- });
1268
- var envSetCommand = new Command4("set").description("Set an environment variable").argument("<key=value>", "Variable to set (e.g., API_KEY=secret123)").option("-s, --secret", "Mark as secret (value will be encrypted)").action(async (keyValue, options, cmd) => {
1269
- const environment = cmd.parent?.args[0];
1270
- if (!environment) {
1271
- logger.error("Environment not specified");
1272
- process.exit(14 /* VALIDATION_ERROR */);
1273
- }
1274
- const [key, ...valueParts] = keyValue.split("=");
1275
- const value = valueParts.join("=");
1276
- if (!key || !value) {
1277
- logger.error("Invalid format. Use: KEY=value");
1278
- process.exit(14 /* VALIDATION_ERROR */);
1279
- }
1280
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1281
- const { error } = await withSpinner(
1282
- `Setting ${key}...`,
1283
- () => apiClient.setEnvVar(env.id, key, value, options.secret || false)
1284
- );
1285
- if (error) {
1286
- logger.error(`Failed to set variable: ${error}`);
1287
- process.exit(13 /* API_ERROR */);
1288
- }
1289
- addJsonData({
1290
- action: "set",
1291
- key,
1292
- secret: options.secret || false,
1293
- environment: env.slug
1294
- });
1295
- logger.success(`Set ${key}${options.secret ? " (secret)" : ""}`);
1296
- logger.info("Changes will take effect on the next deployment.");
1297
- });
1298
- var envUnsetCommand = new Command4("unset").description("Remove an environment variable").argument("<key>", "Variable key to remove").action(async (key, _options, cmd) => {
1299
- const environment = cmd.parent?.args[0];
1300
- if (!environment) {
1301
- logger.error("Environment not specified");
1302
- process.exit(14 /* VALIDATION_ERROR */);
1303
- }
1304
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1305
- const { error } = await withSpinner(
1306
- `Removing ${key}...`,
1307
- () => apiClient.deleteEnvVar(env.id, key)
1308
- );
1309
- if (error) {
1310
- logger.error(`Failed to remove variable: ${error}`);
1311
- process.exit(13 /* API_ERROR */);
1312
- }
1313
- addJsonData({
1314
- action: "unset",
1315
- key,
1316
- environment: env.slug
1317
- });
1318
- logger.success(`Removed ${key}`);
1319
- logger.info("Changes will take effect on the next deployment.");
1320
- });
1321
- envCommand.addCommand(envSetCommand);
1322
- envCommand.addCommand(envUnsetCommand);
1323
-
1324
1188
  // src/commands/init.ts
1325
- import { Command as Command5 } from "commander";
1189
+ import { Command as Command3 } from "commander";
1326
1190
  import prompts from "prompts";
1327
- import chalk6 from "chalk";
1328
- var initCommand = new Command5("init").description("Initialize TurboOps project in current directory").action(async () => {
1191
+ import chalk4 from "chalk";
1192
+ var initCommand = new Command3("init").description("Initialize TurboOps project in current directory").action(async () => {
1329
1193
  logger.header("TurboOps Project Initialization");
1330
1194
  if (!configService.isAuthenticated()) {
1331
1195
  logger.warning("Nicht authentifiziert. Bitte melden Sie sich zuerst an.");
@@ -1366,25 +1230,295 @@ var initCommand = new Command5("init").description("Initialize TurboOps project
1366
1230
  addJsonData({ initialized: false, reason: "cancelled" });
1367
1231
  return;
1368
1232
  }
1369
- const { data: project, error: projectError } = await withSpinner(
1370
- "Verifiziere Projekt...",
1233
+ const { data: project } = await withSpinner(
1234
+ "Suche Projekt...",
1371
1235
  () => apiClient.getProject(projectSlug)
1372
1236
  );
1373
- if (projectError || !project) {
1374
- logger.error(
1375
- `Projekt "${projectSlug}" nicht gefunden oder Sie haben keinen Zugriff.`
1376
- );
1377
- logger.info(
1378
- "Stellen Sie sicher, dass das Projekt in TurboOps existiert und Sie die Berechtigung haben, darauf zuzugreifen."
1379
- );
1237
+ if (project) {
1238
+ await setupProject(project);
1239
+ return;
1240
+ }
1241
+ logger.newline();
1242
+ logger.warning(`Projekt "${projectSlug}" nicht gefunden.`);
1243
+ const { shouldCreate } = await prompts({
1244
+ initial: true,
1245
+ message: "M\xF6chten Sie ein neues Projekt anlegen?",
1246
+ name: "shouldCreate",
1247
+ type: "confirm"
1248
+ });
1249
+ if (!shouldCreate) {
1250
+ logger.info("Initialisierung abgebrochen.");
1251
+ addJsonData({ initialized: false, reason: "project_not_found" });
1252
+ return;
1253
+ }
1254
+ await createNewProject(projectSlug);
1255
+ });
1256
+ async function setupProject(project) {
1257
+ configService.setProject(project.slug);
1258
+ const { data: environments } = await apiClient.getEnvironments(project.id);
1259
+ if (!environments || environments.length === 0) {
1260
+ logger.newline();
1261
+ const { shouldCreateStage } = await prompts({
1262
+ initial: true,
1263
+ message: "Das Projekt hat noch keine Stages. M\xF6chten Sie jetzt eine erstellen?",
1264
+ name: "shouldCreateStage",
1265
+ type: "confirm"
1266
+ });
1267
+ if (shouldCreateStage) {
1268
+ await createFirstStage(project.id, project.slug);
1269
+ }
1270
+ }
1271
+ const fs3 = await import("fs/promises");
1272
+ const path3 = await import("path");
1273
+ const gitlabCiPath = path3.join(process.cwd(), ".gitlab-ci.yml");
1274
+ let hasGitLabPipeline = false;
1275
+ try {
1276
+ await fs3.access(gitlabCiPath);
1277
+ hasGitLabPipeline = true;
1278
+ } catch {
1279
+ }
1280
+ if (!hasGitLabPipeline) {
1281
+ logger.newline();
1282
+ const { shouldCreatePipeline } = await prompts({
1283
+ initial: false,
1284
+ message: "M\xF6chten Sie eine CI/CD Pipeline anlegen?",
1285
+ name: "shouldCreatePipeline",
1286
+ type: "confirm"
1287
+ });
1288
+ if (shouldCreatePipeline) {
1289
+ await createPipeline(project.id);
1290
+ }
1291
+ }
1292
+ await showFinalSummary(project);
1293
+ }
1294
+ async function createNewProject(slug) {
1295
+ logger.newline();
1296
+ logger.header("Neues Projekt erstellen");
1297
+ const { data: customers } = await withSpinner(
1298
+ "Lade verf\xFCgbare Kunden...",
1299
+ () => apiClient.getCustomers()
1300
+ );
1301
+ const promptQuestions = [
1302
+ {
1303
+ initial: slug.charAt(0).toUpperCase() + slug.slice(1).replace(/-/g, " "),
1304
+ message: "Projektname:",
1305
+ name: "name",
1306
+ type: "text",
1307
+ validate: (value) => value.length > 0 || "Projektname ist erforderlich"
1308
+ }
1309
+ ];
1310
+ if (customers && customers.length > 0) {
1311
+ promptQuestions.push({
1312
+ choices: [
1313
+ { title: "(Kein Kunde)", value: "" },
1314
+ ...customers.map((c) => ({ title: c.name, value: c.id }))
1315
+ ],
1316
+ message: "Kunde (optional):",
1317
+ name: "customer",
1318
+ type: "select"
1319
+ });
1320
+ }
1321
+ promptQuestions.push(
1322
+ {
1323
+ message: "Beschreibung (optional):",
1324
+ name: "description",
1325
+ type: "text"
1326
+ },
1327
+ {
1328
+ message: "Repository URL (optional):",
1329
+ name: "repositoryUrl",
1330
+ type: "text"
1331
+ }
1332
+ );
1333
+ const projectDetails = await prompts(promptQuestions);
1334
+ if (!projectDetails.name) {
1335
+ logger.warning("Projekterstellung abgebrochen");
1336
+ addJsonData({ initialized: false, reason: "cancelled" });
1337
+ return;
1338
+ }
1339
+ const { data: newProject, error: createError } = await withSpinner(
1340
+ "Erstelle Projekt...",
1341
+ () => apiClient.createProject({
1342
+ customer: projectDetails.customer || void 0,
1343
+ description: projectDetails.description || void 0,
1344
+ name: projectDetails.name,
1345
+ repositoryUrl: projectDetails.repositoryUrl || void 0,
1346
+ slug
1347
+ })
1348
+ );
1349
+ if (createError || !newProject) {
1350
+ logger.error(`Projekt konnte nicht erstellt werden: ${createError || "Unbekannter Fehler"}`);
1380
1351
  addJsonData({
1352
+ error: createError || "Unknown error",
1381
1353
  initialized: false,
1382
- projectSlug,
1383
- reason: "project_not_found"
1354
+ reason: "create_failed"
1384
1355
  });
1385
- process.exit(12 /* NOT_FOUND */);
1356
+ process.exit(13 /* API_ERROR */);
1357
+ }
1358
+ logger.success(`Projekt "${newProject.name}" wurde erstellt!`);
1359
+ configService.setProject(newProject.slug);
1360
+ logger.newline();
1361
+ const { shouldCreateStage } = await prompts({
1362
+ initial: true,
1363
+ message: "M\xF6chten Sie jetzt die erste Stage anlegen?",
1364
+ name: "shouldCreateStage",
1365
+ type: "confirm"
1366
+ });
1367
+ if (shouldCreateStage) {
1368
+ await createFirstStage(newProject.id, newProject.slug);
1386
1369
  }
1387
- configService.setProject(projectSlug);
1370
+ logger.newline();
1371
+ const { shouldCreatePipeline } = await prompts({
1372
+ initial: true,
1373
+ message: "M\xF6chten Sie eine CI/CD Pipeline anlegen?",
1374
+ name: "shouldCreatePipeline",
1375
+ type: "confirm"
1376
+ });
1377
+ if (shouldCreatePipeline) {
1378
+ await createPipeline(newProject.id);
1379
+ }
1380
+ await showFinalSummary(newProject);
1381
+ }
1382
+ async function createFirstStage(projectId, projectSlug) {
1383
+ logger.newline();
1384
+ logger.header("Erste Stage erstellen");
1385
+ const { data: servers } = await withSpinner(
1386
+ "Lade verf\xFCgbare Server...",
1387
+ () => apiClient.getDeploymentServers()
1388
+ );
1389
+ const stageTypes = [
1390
+ { title: "Development", value: "development" },
1391
+ { title: "Staging", value: "staging" },
1392
+ { title: "Production", value: "production" }
1393
+ ];
1394
+ const stageQuestions = [
1395
+ {
1396
+ choices: stageTypes,
1397
+ initial: 0,
1398
+ message: "Stage-Typ:",
1399
+ name: "type",
1400
+ type: "select"
1401
+ },
1402
+ {
1403
+ initial: (prev) => prev === "production" ? "Production" : prev === "staging" ? "Staging" : "Development",
1404
+ message: "Stage-Name:",
1405
+ name: "name",
1406
+ type: "text",
1407
+ validate: (value) => value.length > 0 || "Name ist erforderlich"
1408
+ },
1409
+ {
1410
+ initial: (_prev, values) => values.type || "",
1411
+ message: "Stage-Slug:",
1412
+ name: "slug",
1413
+ type: "text",
1414
+ validate: (value) => value.length > 0 || "Slug ist erforderlich"
1415
+ },
1416
+ {
1417
+ initial: (_prev, values) => `${values.slug || ""}.${projectSlug}.example.com`,
1418
+ message: "Domain:",
1419
+ name: "domain",
1420
+ type: "text"
1421
+ },
1422
+ {
1423
+ initial: (_prev, values) => values.type === "production" ? "main" : values.type === "staging" ? "staging" : "develop",
1424
+ message: "Branch:",
1425
+ name: "branch",
1426
+ type: "text"
1427
+ }
1428
+ ];
1429
+ if (servers && servers.length > 0) {
1430
+ stageQuestions.push({
1431
+ choices: [
1432
+ { title: "(Sp\xE4ter ausw\xE4hlen)", value: "" },
1433
+ ...servers.map((s) => ({ title: `${s.name} (${s.host})`, value: s.id }))
1434
+ ],
1435
+ message: "Server (optional):",
1436
+ name: "server",
1437
+ type: "select"
1438
+ });
1439
+ }
1440
+ const stageDetails = await prompts(stageQuestions);
1441
+ if (!stageDetails.name || !stageDetails.slug) {
1442
+ logger.warning("Stage-Erstellung abgebrochen");
1443
+ return;
1444
+ }
1445
+ const { data: newStage, error: stageError } = await withSpinner(
1446
+ "Erstelle Stage...",
1447
+ () => apiClient.createStage({
1448
+ project: projectId,
1449
+ name: stageDetails.name,
1450
+ slug: stageDetails.slug,
1451
+ type: stageDetails.type,
1452
+ domain: stageDetails.domain || void 0,
1453
+ server: stageDetails.server || void 0,
1454
+ branch: stageDetails.branch || void 0
1455
+ })
1456
+ );
1457
+ if (stageError || !newStage) {
1458
+ logger.error(`Stage konnte nicht erstellt werden: ${stageError || "Unbekannter Fehler"}`);
1459
+ return;
1460
+ }
1461
+ logger.success(`Stage "${newStage.name}" wurde erstellt!`);
1462
+ }
1463
+ async function createPipeline(projectId) {
1464
+ logger.newline();
1465
+ logger.header("CI/CD Pipeline erstellen");
1466
+ const pipelineQuestions = [
1467
+ {
1468
+ choices: [
1469
+ { title: "GitLab CI/CD", value: "gitlab" },
1470
+ { title: "GitHub Actions", value: "github" }
1471
+ ],
1472
+ initial: 0,
1473
+ message: "Pipeline-Typ:",
1474
+ name: "pipelineType",
1475
+ type: "select"
1476
+ }
1477
+ ];
1478
+ const pipelineDetails = await prompts(pipelineQuestions);
1479
+ if (!pipelineDetails.pipelineType) {
1480
+ logger.warning("Pipeline-Erstellung abgebrochen");
1481
+ return;
1482
+ }
1483
+ const { data: pipeline, error } = await withSpinner(
1484
+ "Generiere Pipeline...",
1485
+ () => apiClient.generatePipeline(projectId, pipelineDetails.pipelineType)
1486
+ );
1487
+ if (error || !pipeline) {
1488
+ logger.error(`Pipeline konnte nicht generiert werden: ${error || "Unbekannter Fehler"}`);
1489
+ return;
1490
+ }
1491
+ const fs3 = await import("fs/promises");
1492
+ const path3 = await import("path");
1493
+ let filePath;
1494
+ if (pipelineDetails.pipelineType === "github") {
1495
+ const workflowsDir = path3.join(process.cwd(), ".github", "workflows");
1496
+ await fs3.mkdir(workflowsDir, { recursive: true });
1497
+ filePath = path3.join(workflowsDir, "deploy.yml");
1498
+ } else {
1499
+ filePath = path3.join(process.cwd(), ".gitlab-ci.yml");
1500
+ }
1501
+ try {
1502
+ await fs3.writeFile(filePath, pipeline.content, "utf-8");
1503
+ logger.success(`${pipeline.filename} wurde erstellt!`);
1504
+ logger.newline();
1505
+ const { data: secrets } = await apiClient.getPipelineSecrets(projectId, pipelineDetails.pipelineType);
1506
+ if (secrets && secrets.length > 0) {
1507
+ logger.header("Erforderliche CI/CD Secrets");
1508
+ for (const secret of secrets) {
1509
+ const value = secret.isSecret ? chalk4.dim("(geheim)") : chalk4.cyan(secret.value || "-");
1510
+ console.log(` ${chalk4.bold(secret.name)}: ${value}`);
1511
+ console.log(` ${chalk4.dim(secret.description)}`);
1512
+ }
1513
+ logger.newline();
1514
+ logger.info("F\xFCgen Sie diese Werte als CI/CD Secrets/Variables hinzu.");
1515
+ logger.info("Projekt-Token k\xF6nnen Sie in der TurboOps Web-UI erstellen.");
1516
+ }
1517
+ } catch (error2) {
1518
+ logger.error(`Fehler beim Schreiben der Pipeline-Datei: ${error2}`);
1519
+ }
1520
+ }
1521
+ async function showFinalSummary(project) {
1388
1522
  logger.newline();
1389
1523
  logger.success("Projekt initialisiert!");
1390
1524
  logger.newline();
@@ -1411,25 +1545,24 @@ var initCommand = new Command5("init").description("Initialize TurboOps project
1411
1545
  });
1412
1546
  if (environments && environments.length > 0) {
1413
1547
  logger.newline();
1414
- logger.header("Verf\xFCgbare Umgebungen");
1548
+ logger.header("Verf\xFCgbare Stages");
1415
1549
  for (const env of environments) {
1416
- console.log(` ${chalk6.bold(env.slug)} - ${env.name} (${env.type})`);
1550
+ console.log(` ${chalk4.bold(env.slug)} - ${env.name} (${env.type})`);
1417
1551
  }
1418
1552
  }
1419
1553
  logger.newline();
1420
1554
  logger.header("N\xE4chste Schritte");
1421
1555
  logger.list([
1422
- "F\xFChren Sie `turbo status` aus, um alle Umgebungen zu sehen",
1423
- "F\xFChren Sie `turbo deploy <umgebung>` aus, um zu deployen",
1424
- "F\xFChren Sie `turbo logs <umgebung>` aus, um Logs anzuzeigen"
1556
+ "F\xFChren Sie `turbo status` aus, um alle Stages zu sehen",
1557
+ "F\xFChren Sie `turbo logs <stage>` aus, um Logs anzuzeigen"
1425
1558
  ]);
1426
- });
1559
+ }
1427
1560
 
1428
1561
  // src/commands/logs.ts
1429
- import { Command as Command6 } from "commander";
1430
- import chalk7 from "chalk";
1562
+ import { Command as Command4 } from "commander";
1563
+ import chalk5 from "chalk";
1431
1564
  import { io } from "socket.io-client";
1432
- var logsCommand = new Command6("logs").description("View deployment logs").argument("<environment>", "Environment slug").option("-n, --lines <number>", "Number of lines to show", "100").option("-f, --follow", "Follow log output (stream)").option("--service <name>", "Filter logs by service name").action(async (environment, options) => {
1565
+ var logsCommand = new Command4("logs").description("View deployment logs").argument("<environment>", "Environment slug").option("-n, --lines <number>", "Number of lines to show", "100").option("-f, --follow", "Follow log output (stream)").option("--service <name>", "Filter logs by service name").action(async (environment, options) => {
1433
1566
  const { environment: env } = await getCommandContextWithEnvironment(environment);
1434
1567
  logger.header(`Logs: ${env.name}`);
1435
1568
  if (options.follow) {
@@ -1550,9 +1683,9 @@ function printLogEntry(log) {
1550
1683
  const timestamp = formatTimestamp(log.timestamp);
1551
1684
  const levelColor = getLevelColor(log.level);
1552
1685
  const levelStr = log.level.toUpperCase().padEnd(5);
1553
- let output = chalk7.gray(`[${timestamp}]`) + levelColor(` ${levelStr}`);
1686
+ let output = chalk5.gray(`[${timestamp}]`) + levelColor(` ${levelStr}`);
1554
1687
  if (log.step) {
1555
- output += chalk7.cyan(` [${log.step}]`);
1688
+ output += chalk5.cyan(` [${log.step}]`);
1556
1689
  }
1557
1690
  output += ` ${log.message}`;
1558
1691
  logger.raw(output);
@@ -1569,26 +1702,264 @@ function getLevelColor(level) {
1569
1702
  switch (level.toLowerCase()) {
1570
1703
  case "error":
1571
1704
  case "fatal":
1572
- return chalk7.red;
1705
+ return chalk5.red;
1573
1706
  case "warn":
1574
1707
  case "warning":
1575
- return chalk7.yellow;
1708
+ return chalk5.yellow;
1576
1709
  case "info":
1577
- return chalk7.blue;
1710
+ return chalk5.blue;
1578
1711
  case "debug":
1579
- return chalk7.gray;
1712
+ return chalk5.gray;
1580
1713
  case "trace":
1581
- return chalk7.magenta;
1714
+ return chalk5.magenta;
1582
1715
  default:
1583
- return chalk7.white;
1716
+ return chalk5.white;
1584
1717
  }
1585
1718
  }
1586
1719
 
1720
+ // src/commands/deploy.ts
1721
+ import { Command as Command5 } from "commander";
1722
+ import chalk6 from "chalk";
1723
+ var deployCommand = new Command5("deploy").description("Trigger a deployment (for CI/CD pipelines)").argument("<environment>", "Environment slug (e.g., production, staging)").option("-i, --image <tag>", "Docker image tag to deploy").option("-w, --wait", "Wait for deployment to complete", true).option("--no-wait", "Do not wait for deployment to complete").option(
1724
+ "--timeout <ms>",
1725
+ "Timeout in milliseconds when waiting",
1726
+ "600000"
1727
+ ).action(async (environment, options) => {
1728
+ const { project, environment: env } = await getCommandContextWithEnvironment(environment);
1729
+ logger.header(`Deploying: ${project.name} \u2192 ${env.name}`);
1730
+ logger.info("Triggering deployment...");
1731
+ const { data: deployment, error } = await apiClient.deploy(
1732
+ env.id,
1733
+ options.image
1734
+ );
1735
+ if (error) {
1736
+ logger.error(`Failed to trigger deployment: ${error}`);
1737
+ process.exit(13 /* API_ERROR */);
1738
+ }
1739
+ if (!deployment) {
1740
+ logger.error("No deployment data received");
1741
+ process.exit(13 /* API_ERROR */);
1742
+ }
1743
+ logger.success(`Deployment triggered: ${deployment.id}`);
1744
+ addJsonData({
1745
+ deploymentId: deployment.id,
1746
+ status: deployment.status,
1747
+ environment: env.slug,
1748
+ project: project.slug,
1749
+ imageTag: options.image || "latest"
1750
+ });
1751
+ if (!options.wait) {
1752
+ logger.info("Deployment started. Use --wait to wait for completion.");
1753
+ return;
1754
+ }
1755
+ logger.newline();
1756
+ logger.info("Waiting for deployment to complete...");
1757
+ const timeout = parseInt(options.timeout);
1758
+ let lastStatus = "";
1759
+ try {
1760
+ const finalStatus = await apiClient.waitForDeployment(deployment.id, {
1761
+ timeout,
1762
+ pollInterval: 3e3,
1763
+ onProgress: (status) => {
1764
+ if (status.status !== lastStatus) {
1765
+ lastStatus = status.status;
1766
+ const statusColor = getStatusColor2(status.status);
1767
+ if (!isJsonMode()) {
1768
+ logger.info(`Status: ${statusColor(status.status)}`);
1769
+ }
1770
+ }
1771
+ }
1772
+ });
1773
+ addJsonData({
1774
+ finalStatus: finalStatus.status,
1775
+ completedAt: finalStatus.completedAt,
1776
+ healthStatus: finalStatus.healthStatus
1777
+ });
1778
+ if (finalStatus.status === "running") {
1779
+ logger.newline();
1780
+ logger.success("Deployment completed successfully!");
1781
+ if (finalStatus.healthStatus) {
1782
+ logger.info(
1783
+ `Health: ${finalStatus.healthStatus.healthy}/${finalStatus.healthStatus.total} containers healthy`
1784
+ );
1785
+ }
1786
+ } else if (finalStatus.status === "failed") {
1787
+ logger.newline();
1788
+ logger.error("Deployment failed!");
1789
+ if (finalStatus.errorMessage) {
1790
+ logger.error(`Error: ${finalStatus.errorMessage}`);
1791
+ }
1792
+ process.exit(1 /* ERROR */);
1793
+ } else {
1794
+ logger.newline();
1795
+ logger.warning(`Deployment ended with status: ${finalStatus.status}`);
1796
+ process.exit(1 /* ERROR */);
1797
+ }
1798
+ } catch (err) {
1799
+ const message = err instanceof Error ? err.message : "Unknown error";
1800
+ if (message.includes("timeout")) {
1801
+ logger.error(`Deployment timed out after ${timeout / 1e3} seconds`);
1802
+ addJsonData({ error: "timeout" });
1803
+ process.exit(2 /* TIMEOUT */);
1804
+ }
1805
+ logger.error(`Error waiting for deployment: ${message}`);
1806
+ process.exit(1 /* ERROR */);
1807
+ }
1808
+ });
1809
+ function getStatusColor2(status) {
1810
+ switch (status) {
1811
+ case "running":
1812
+ return chalk6.green;
1813
+ case "failed":
1814
+ case "rolled_back":
1815
+ return chalk6.red;
1816
+ case "deploying":
1817
+ case "pending":
1818
+ return chalk6.yellow;
1819
+ case "stopped":
1820
+ return chalk6.gray;
1821
+ default:
1822
+ return chalk6.white;
1823
+ }
1824
+ }
1825
+
1826
+ // src/commands/pipeline.ts
1827
+ import { Command as Command6 } from "commander";
1828
+ import prompts2 from "prompts";
1829
+ import chalk7 from "chalk";
1830
+ import * as fs2 from "fs/promises";
1831
+ import * as path2 from "path";
1832
+ var pipelineCommand = new Command6("pipeline").description("Manage CI/CD pipeline configuration");
1833
+ pipelineCommand.command("generate").description("Generate CI/CD pipeline configuration").option("-t, --type <type>", "Pipeline type (gitlab, github)").option("-f, --force", "Overwrite existing pipeline file").option("-o, --output <path>", "Custom output path").action(async (options) => {
1834
+ const { project } = await getCommandContext();
1835
+ logger.header("CI/CD Pipeline generieren");
1836
+ let pipelineType = options.type;
1837
+ if (!pipelineType) {
1838
+ const { selectedType } = await prompts2({
1839
+ choices: [
1840
+ { title: "GitLab CI/CD", value: "gitlab" },
1841
+ { title: "GitHub Actions", value: "github" }
1842
+ ],
1843
+ initial: 0,
1844
+ message: "Pipeline-Typ:",
1845
+ name: "selectedType",
1846
+ type: "select"
1847
+ });
1848
+ if (!selectedType) {
1849
+ logger.warning("Pipeline-Generierung abgebrochen");
1850
+ addJsonData({ generated: false, reason: "cancelled" });
1851
+ return;
1852
+ }
1853
+ pipelineType = selectedType;
1854
+ }
1855
+ if (!["gitlab", "github"].includes(pipelineType)) {
1856
+ logger.error(`Ung\xFCltiger Pipeline-Typ: ${pipelineType}. Erlaubt: gitlab, github`);
1857
+ process.exit(14 /* VALIDATION_ERROR */);
1858
+ }
1859
+ let outputPath;
1860
+ if (options.output) {
1861
+ outputPath = path2.resolve(options.output);
1862
+ } else if (pipelineType === "github") {
1863
+ outputPath = path2.join(process.cwd(), ".github", "workflows", "deploy.yml");
1864
+ } else {
1865
+ outputPath = path2.join(process.cwd(), ".gitlab-ci.yml");
1866
+ }
1867
+ if (!options.force) {
1868
+ try {
1869
+ await fs2.access(outputPath);
1870
+ const { shouldOverwrite } = await prompts2({
1871
+ initial: false,
1872
+ message: `${path2.basename(outputPath)} existiert bereits. \xDCberschreiben?`,
1873
+ name: "shouldOverwrite",
1874
+ type: "confirm"
1875
+ });
1876
+ if (!shouldOverwrite) {
1877
+ logger.info("Pipeline-Generierung abgebrochen");
1878
+ addJsonData({ generated: false, reason: "file_exists" });
1879
+ return;
1880
+ }
1881
+ } catch {
1882
+ }
1883
+ }
1884
+ const { data: pipeline, error } = await withSpinner(
1885
+ "Generiere Pipeline...",
1886
+ () => apiClient.generatePipeline(project.id, pipelineType)
1887
+ );
1888
+ if (error || !pipeline) {
1889
+ logger.error(`Pipeline konnte nicht generiert werden: ${error || "Unbekannter Fehler"}`);
1890
+ addJsonData({ error: error || "Unknown error", generated: false });
1891
+ process.exit(13 /* API_ERROR */);
1892
+ }
1893
+ const outputDir = path2.dirname(outputPath);
1894
+ await fs2.mkdir(outputDir, { recursive: true });
1895
+ try {
1896
+ await fs2.writeFile(outputPath, pipeline.content, "utf-8");
1897
+ logger.success(`${path2.relative(process.cwd(), outputPath)} wurde erstellt!`);
1898
+ addJsonData({
1899
+ filename: path2.relative(process.cwd(), outputPath),
1900
+ generated: true,
1901
+ project: project.slug,
1902
+ type: pipelineType
1903
+ });
1904
+ } catch (err) {
1905
+ const message = err instanceof Error ? err.message : "Unknown error";
1906
+ logger.error(`Fehler beim Schreiben der Datei: ${message}`);
1907
+ addJsonData({ error: message, generated: false });
1908
+ process.exit(1 /* ERROR */);
1909
+ }
1910
+ logger.newline();
1911
+ await showSecrets(project.id, pipelineType);
1912
+ });
1913
+ pipelineCommand.command("secrets").description("Show required CI/CD secrets").option("-t, --type <type>", "Pipeline type (gitlab, github)", "gitlab").action(async (options) => {
1914
+ const { project } = await getCommandContext();
1915
+ const pipelineType = options.type;
1916
+ if (!["gitlab", "github"].includes(pipelineType)) {
1917
+ logger.error(`Ung\xFCltiger Pipeline-Typ: ${pipelineType}. Erlaubt: gitlab, github`);
1918
+ process.exit(14 /* VALIDATION_ERROR */);
1919
+ }
1920
+ logger.header(`CI/CD Secrets f\xFCr ${pipelineType === "gitlab" ? "GitLab" : "GitHub"}`);
1921
+ await showSecrets(project.id, pipelineType);
1922
+ });
1923
+ async function showSecrets(projectId, pipelineType) {
1924
+ const { data: secrets, error } = await apiClient.getPipelineSecrets(projectId, pipelineType);
1925
+ if (error) {
1926
+ logger.error(`Fehler beim Laden der Secrets: ${error}`);
1927
+ return;
1928
+ }
1929
+ if (!secrets || secrets.length === 0) {
1930
+ logger.info("Keine Secrets erforderlich.");
1931
+ addJsonData({ secrets: [] });
1932
+ return;
1933
+ }
1934
+ const secretsList = [];
1935
+ for (const secret of secrets) {
1936
+ const value = secret.isSecret ? chalk7.dim("(geheim)") : chalk7.cyan(secret.value || "-");
1937
+ console.log(` ${chalk7.bold(secret.name)}: ${value}`);
1938
+ console.log(` ${chalk7.dim(secret.description)}`);
1939
+ console.log();
1940
+ secretsList.push({
1941
+ description: secret.description,
1942
+ name: secret.name,
1943
+ value: secret.isSecret ? void 0 : secret.value
1944
+ });
1945
+ }
1946
+ addJsonData({ secrets: secretsList });
1947
+ logger.info("F\xFCgen Sie diese Werte als CI/CD Secrets/Variables hinzu.");
1948
+ if (pipelineType === "gitlab") {
1949
+ logger.info("GitLab: Settings \u2192 CI/CD \u2192 Variables");
1950
+ } else {
1951
+ logger.info("GitHub: Settings \u2192 Secrets and variables \u2192 Actions");
1952
+ }
1953
+ logger.newline();
1954
+ logger.info("Projekt-Token k\xF6nnen Sie in der TurboOps Web-UI erstellen:");
1955
+ logger.info("Projekt \u2192 Settings \u2192 Tokens \u2192 Neuen Token erstellen");
1956
+ }
1957
+
1587
1958
  // src/index.ts
1588
1959
  var VERSION = getCurrentVersion();
1589
1960
  var shouldCheckUpdate = true;
1590
1961
  var program = new Command7();
1591
- 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("--api <url>", "Override API URL").option("--json", "Output as JSON").option("--quiet", "Only show errors").option("--verbose", "Show debug output").option("--no-update-check", "Skip version check");
1962
+ 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");
1592
1963
  program.hook("preAction", (thisCommand) => {
1593
1964
  const opts = thisCommand.opts();
1594
1965
  clearJsonData();
@@ -1605,9 +1976,6 @@ program.hook("preAction", (thisCommand) => {
1605
1976
  if (opts.token) {
1606
1977
  configService.setToken(opts.token);
1607
1978
  }
1608
- if (opts.api) {
1609
- configService.setApiUrl(opts.api);
1610
- }
1611
1979
  if (opts.verbose) {
1612
1980
  process.env.DEBUG = "true";
1613
1981
  }
@@ -1626,13 +1994,9 @@ program.addCommand(whoamiCommand);
1626
1994
  program.addCommand(initCommand);
1627
1995
  program.addCommand(statusCommand);
1628
1996
  program.addCommand(configCommand);
1629
- program.addCommand(deployCommand);
1630
- program.addCommand(rollbackCommand);
1631
- program.addCommand(restartCommand);
1632
- program.addCommand(stopCommand);
1633
- program.addCommand(wakeCommand);
1634
- program.addCommand(envCommand);
1635
1997
  program.addCommand(logsCommand);
1998
+ program.addCommand(deployCommand);
1999
+ program.addCommand(pipelineCommand);
1636
2000
  program.command("self-update").description("Update TurboCLI to the latest version").action(async () => {
1637
2001
  logger.info("Checking for updates...");
1638
2002
  try {