@turboops/cli 1.0.0-dev.582 → 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 +305 -390
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # TurboCLI
2
2
 
3
- Command line interface for TurboOps deployments.
3
+ Command line interface for TurboOps.
4
4
 
5
5
  ## Installation
6
6
 
@@ -17,7 +17,7 @@ npx @turboops/cli
17
17
  ### Authentication
18
18
 
19
19
  ```bash
20
- # Login interactively
20
+ # Login interactively (opens browser)
21
21
  turbo login
22
22
 
23
23
  # Login with token (for CI/CD)
@@ -37,100 +37,115 @@ turbo logout
37
37
  turbo init
38
38
 
39
39
  # Show current configuration
40
- turbo config
41
- ```
40
+ turbo config show
42
41
 
43
- ### Deployment
42
+ # Set configuration value
43
+ turbo config set project <slug>
44
44
 
45
- ```bash
46
- # Deploy to an environment
47
- turbo deploy production
48
- turbo deploy staging
49
- turbo deploy dev
45
+ # Get configuration value
46
+ turbo config get project
50
47
 
51
- # Deploy specific image
52
- turbo deploy production --image v1.2.3
48
+ # Clear configuration
49
+ turbo config clear --all
50
+ turbo config clear --token
51
+ turbo config clear --project
52
+ ```
53
53
 
54
- # Deploy without waiting
55
- turbo deploy production --no-wait
54
+ ### Status
56
55
 
57
- # Rollback to previous deployment
58
- turbo rollback production
56
+ ```bash
57
+ # Show status of all environments
58
+ turbo status
59
59
 
60
- # Rollback to specific deployment
61
- turbo rollback production --to <deployment-id>
60
+ # Show status of specific environment
61
+ turbo status production
62
62
  ```
63
63
 
64
- ### Container Management
64
+ ### Logs
65
65
 
66
66
  ```bash
67
- # Restart containers
68
- turbo restart production
67
+ # View logs
68
+ turbo logs production
69
69
 
70
- # Stop containers
71
- turbo stop production
70
+ # Follow logs (stream)
71
+ turbo logs production --follow
72
72
 
73
- # Start stopped containers
74
- turbo wake production
73
+ # Limit lines
74
+ turbo logs production --lines 50
75
75
 
76
- # Show status
77
- turbo status
78
- turbo status production
76
+ # Filter by service
77
+ turbo logs production --service api
79
78
  ```
80
79
 
81
- ### Environment Variables
80
+ ### Deployment (CI/CD)
82
81
 
83
- ```bash
84
- # List environment variables
85
- turbo env production
82
+ The deploy command is designed for CI/CD pipelines using Project Tokens.
86
83
 
87
- # Show secret values
88
- turbo env production --reveal
84
+ ```bash
85
+ # Trigger deployment and wait for completion (default)
86
+ turbo deploy production
89
87
 
90
- # Set variable
91
- turbo env production set API_KEY=secret123
88
+ # Deploy specific image tag
89
+ turbo deploy production --image my-app:v1.2.3
92
90
 
93
- # Set as secret
94
- turbo env production set API_KEY=secret123 --secret
91
+ # Deploy without waiting (fire-and-forget)
92
+ turbo deploy staging --no-wait
95
93
 
96
- # Remove variable
97
- turbo env production unset API_KEY
94
+ # Custom timeout (default: 10 minutes)
95
+ turbo deploy production --timeout 900000
98
96
  ```
99
97
 
100
- ### Logs
98
+ **Options:**
99
+
100
+ | Option | Description |
101
+ |--------|-------------|
102
+ | `-i, --image <tag>` | Docker image tag to deploy |
103
+ | `-w, --wait` | Wait for deployment to complete (default) |
104
+ | `--no-wait` | Do not wait for deployment to complete |
105
+ | `--timeout <ms>` | Timeout in milliseconds when waiting (default: 600000) |
106
+
107
+ **Note:** This command requires a Project Token with `deploy` permission. Set the token via `TURBOOPS_TOKEN` environment variable or `--token` flag.
108
+
109
+ ### Pipeline Management
110
+
111
+ Generate and manage CI/CD pipeline configurations.
101
112
 
102
113
  ```bash
103
- # View logs
104
- turbo logs production
114
+ # Generate pipeline interactively
115
+ turbo pipeline generate
105
116
 
106
- # Follow logs (stream)
107
- turbo logs production --follow
117
+ # Generate GitLab CI pipeline
118
+ turbo pipeline generate --type gitlab
108
119
 
109
- # Limit lines
110
- turbo logs production --lines 50
111
- ```
120
+ # Generate GitHub Actions pipeline
121
+ turbo pipeline generate --type github
112
122
 
113
- ## CI/CD Usage
123
+ # Force overwrite existing pipeline
124
+ turbo pipeline generate --type gitlab --force
114
125
 
115
- ### GitLab CI
126
+ # Custom output path
127
+ turbo pipeline generate --type gitlab --output ./ci/pipeline.yml
116
128
 
117
- ```yaml
118
- deploy:
119
- stage: deploy
120
- script:
121
- - npx @turboops/cli deploy production --wait
122
- environment:
123
- name: production
129
+ # Show required secrets
130
+ turbo pipeline secrets
131
+
132
+ # Show secrets for specific pipeline type
133
+ turbo pipeline secrets --type github
124
134
  ```
125
135
 
126
- ### GitHub Actions
136
+ **Options for `pipeline generate`:**
137
+
138
+ | Option | Description |
139
+ |--------|-------------|
140
+ | `-t, --type <type>` | Pipeline type: `gitlab` or `github` |
141
+ | `-f, --force` | Overwrite existing pipeline file |
142
+ | `-o, --output <path>` | Custom output path |
127
143
 
128
- ```yaml
129
- - name: Deploy
130
- run: npx @turboops/cli deploy production --wait
131
- env:
132
- TURBOOPS_TOKEN: ${{ secrets.TURBOOPS_TOKEN }}
133
- TURBOOPS_PROJECT: ${{ vars.TURBOOPS_PROJECT }}
144
+ ### Self-Update
145
+
146
+ ```bash
147
+ # Check for updates and show upgrade instructions
148
+ turbo self-update
134
149
  ```
135
150
 
136
151
  ## Environment Variables
@@ -146,7 +161,6 @@ deploy:
146
161
  ```bash
147
162
  --project <slug> Override project slug
148
163
  --token <token> Override API token
149
- --api <url> Override API URL
150
164
  --json Output as JSON
151
165
  --quiet Only show errors
152
166
  --verbose Show debug output
@@ -160,7 +174,11 @@ deploy:
160
174
  | 0 | Success |
161
175
  | 1 | General error |
162
176
  | 2 | Timeout |
163
- | 3 | Health check failed |
177
+ | 3 | Network error |
178
+ | 10 | Authentication error |
179
+ | 11 | Not found |
180
+ | 12 | API error |
181
+ | 20 | Validation error |
164
182
 
165
183
  ## License
166
184
 
package/dist/index.js CHANGED
@@ -135,9 +135,9 @@ function loadPackageJson() {
135
135
  join(__dirname, "../package.json")
136
136
  // From dist/
137
137
  ];
138
- for (const path2 of paths) {
139
- if (existsSync(path2)) {
140
- return require2(path2);
138
+ for (const path3 of paths) {
139
+ if (existsSync(path3)) {
140
+ return require2(path3);
141
141
  }
142
142
  }
143
143
  return { version: "0.0.0", name: "@turboops/cli" };
@@ -425,14 +425,14 @@ import { Command } from "commander";
425
425
 
426
426
  // src/services/api.ts
427
427
  function isProjectToken(token) {
428
- return token.startsWith("turbo_");
428
+ return token.startsWith("turbo_") && !token.startsWith("turbo_cli_");
429
429
  }
430
430
  var cachedProjectInfo = null;
431
431
  var apiClient = {
432
432
  /**
433
433
  * Make an API request
434
434
  */
435
- async request(method, path2, body) {
435
+ async request(method, path3, body) {
436
436
  const apiUrl = configService.getApiUrl();
437
437
  const token = configService.getToken();
438
438
  if (!token) {
@@ -441,7 +441,7 @@ var apiClient = {
441
441
  status: 401
442
442
  };
443
443
  }
444
- const url = `${apiUrl}${path2}`;
444
+ const url = `${apiUrl}${path3}`;
445
445
  const headers = {
446
446
  "Content-Type": "application/json",
447
447
  Authorization: `Bearer ${token}`
@@ -647,13 +647,12 @@ var apiClient = {
647
647
  return this.request("GET", `/deployment/projects/${projectId}/pipeline/${type}/secrets`);
648
648
  },
649
649
  /**
650
- * Trigger deployment
650
+ * Trigger a deployment
651
651
  */
652
- async deploy(environmentId, imageTag) {
653
- return this.request("POST", `/deployment/deployments`, {
654
- environmentId,
655
- imageTag
656
- });
652
+ async deploy(stageId, imageTag) {
653
+ const body = { stage: stageId };
654
+ if (imageTag) body.imageTag = imageTag;
655
+ return this.request("POST", "/deployment/deployments", body);
657
656
  },
658
657
  /**
659
658
  * Get deployment status
@@ -661,104 +660,32 @@ var apiClient = {
661
660
  async getDeploymentStatus(deploymentId) {
662
661
  return this.request("GET", `/deployment/deployments/${deploymentId}`);
663
662
  },
664
- /**
665
- * Rollback deployment
666
- */
667
- async rollback(environmentId, targetDeploymentId) {
668
- return this.request("POST", `/deployment/deployments/rollback`, {
669
- environmentId,
670
- targetDeploymentId
671
- });
672
- },
673
- /**
674
- * Restart containers
675
- */
676
- async restart(environmentId) {
677
- return this.request(
678
- "POST",
679
- `/deployment/stages/${environmentId}/restart`
680
- );
681
- },
682
- /**
683
- * Stop containers
684
- */
685
- async stop(environmentId) {
686
- return this.request(
687
- "POST",
688
- `/deployment/stages/${environmentId}/stop`
689
- );
690
- },
691
- /**
692
- * Wake containers
693
- */
694
- async wake(environmentId) {
695
- return this.request(
696
- "POST",
697
- `/deployment/stages/${environmentId}/wake`
698
- );
699
- },
700
- /**
701
- * Get deployment logs
702
- */
703
- async getLogs(deploymentId) {
704
- return this.request("GET", `/deployment/deployments/${deploymentId}/logs`);
705
- },
706
- /**
707
- * Get environment variables
708
- */
709
- async getEnvVars(environmentId) {
710
- return this.request(
711
- "GET",
712
- `/deployment/stages/${environmentId}/env-vars`
713
- );
714
- },
715
- /**
716
- * Set environment variable
717
- */
718
- async setEnvVar(environmentId, key, value, secret) {
719
- return this.request(
720
- "PUT",
721
- `/deployment/stages/${environmentId}/env-vars`,
722
- {
723
- key,
724
- value,
725
- secret
726
- }
727
- );
728
- },
729
- /**
730
- * Delete environment variable
731
- */
732
- async deleteEnvVar(environmentId, key) {
733
- return this.request(
734
- "DELETE",
735
- `/deployment/stages/${environmentId}/env-vars/${key}`
736
- );
737
- },
738
663
  /**
739
664
  * Wait for deployment to complete
740
665
  */
741
- async waitForDeployment(deploymentId, options) {
742
- const timeout = options?.timeout || 6e5;
743
- const pollInterval = options?.pollInterval || 3e3;
666
+ async waitForDeployment(deploymentId, options = {}) {
667
+ const { timeout = 6e5, pollInterval = 3e3, onProgress } = options;
744
668
  const startTime = Date.now();
745
- while (Date.now() - startTime < timeout) {
669
+ while (true) {
746
670
  const { data, error } = await this.getDeploymentStatus(deploymentId);
747
671
  if (error) {
748
- throw new Error(error);
672
+ throw new Error(`Failed to get deployment status: ${error}`);
749
673
  }
750
674
  if (!data) {
751
675
  throw new Error("No deployment data received");
752
676
  }
753
- if (options?.onProgress) {
754
- options.onProgress(data);
677
+ if (onProgress) {
678
+ onProgress(data);
755
679
  }
756
- if (["running", "failed", "stopped", "rolled_back"].includes(data.status)) {
680
+ const terminalStatuses = ["running", "failed", "stopped", "rolled_back"];
681
+ if (terminalStatuses.includes(data.status)) {
757
682
  return data;
758
683
  }
759
- 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));
760
688
  }
761
- throw new Error(`Deployment timeout after ${timeout / 1e3} seconds`);
762
689
  }
763
690
  };
764
691
 
@@ -874,7 +801,7 @@ var authService = {
874
801
  if (result.status === "expired" || result.status === "consumed") {
875
802
  return { error: "Autorisierungscode abgelaufen oder bereits verwendet", success: false };
876
803
  }
877
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
804
+ await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
878
805
  }
879
806
  return { error: "Zeit\xFCberschreitung bei der Anmeldung", success: false };
880
807
  },
@@ -1039,8 +966,9 @@ var whoamiCommand = new Command("whoami").description("Show current authenticate
1039
966
  });
1040
967
  });
1041
968
 
1042
- // src/commands/deploy.ts
969
+ // src/commands/status.ts
1043
970
  import { Command as Command2 } from "commander";
971
+ import chalk3 from "chalk";
1044
972
 
1045
973
  // src/utils/guards.ts
1046
974
  async function requireAuth() {
@@ -1116,159 +1044,8 @@ async function getCommandContextWithEnvironment(environmentSlug, showSpinner = t
1116
1044
  return { projectSlug, project, environment };
1117
1045
  }
1118
1046
 
1119
- // src/commands/deploy.ts
1120
- import chalk3 from "chalk";
1121
- var deployCommand = new Command2("deploy").description("Deploy to an environment").argument(
1122
- "<environment>",
1123
- "Environment slug (e.g., production, staging, dev)"
1124
- ).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) => {
1125
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1126
- logger.header(`Deploying to ${environment}`);
1127
- const { data: deployment, error: deployError } = await withSpinner(
1128
- `Starting deployment...`,
1129
- () => apiClient.deploy(env.id, options.image)
1130
- );
1131
- if (deployError || !deployment) {
1132
- logger.error(
1133
- `Failed to start deployment: ${deployError || "Unknown error"}`
1134
- );
1135
- process.exit(13 /* API_ERROR */);
1136
- }
1137
- logger.success(`Deployment started: ${deployment.id}`);
1138
- addJsonData({ deploymentId: deployment.id });
1139
- if (!options.wait) {
1140
- logger.info(`View deployment status: turbo status ${environment}`);
1141
- return;
1142
- }
1143
- logger.newline();
1144
- const spinner = createSpinner("Deploying...");
1145
- spinner.start();
1146
- try {
1147
- const finalStatus = await apiClient.waitForDeployment(deployment.id, {
1148
- timeout: parseInt(options.timeout),
1149
- onProgress: (status) => {
1150
- spinner.text = getStatusText(status);
1151
- }
1152
- });
1153
- if (finalStatus.status === "running") {
1154
- spinner.succeed(`Deployment successful!`);
1155
- const resultData = {
1156
- environment: env.name,
1157
- environmentSlug: env.slug,
1158
- imageTag: finalStatus.imageTag,
1159
- status: finalStatus.status,
1160
- healthyContainers: finalStatus.healthStatus?.healthy || 0,
1161
- totalContainers: finalStatus.healthStatus?.total || 0,
1162
- domain: env.domain
1163
- };
1164
- addJsonData(resultData);
1165
- logger.newline();
1166
- logger.table({
1167
- Environment: env.name,
1168
- Image: finalStatus.imageTag,
1169
- Status: chalk3.green(finalStatus.status),
1170
- Health: `${finalStatus.healthStatus?.healthy || 0}/${finalStatus.healthStatus?.total || 0} healthy`
1171
- });
1172
- logger.newline();
1173
- logger.info(`View at: https://${env.domain}`);
1174
- } else if (finalStatus.status === "failed") {
1175
- spinner.fail("Deployment failed!");
1176
- addJsonData({ status: "failed", error: finalStatus.errorMessage });
1177
- logger.error(finalStatus.errorMessage || "Unknown error");
1178
- process.exit(3 /* HEALTH_CHECK_FAILED */);
1179
- } else {
1180
- spinner.warn(`Deployment ended with status: ${finalStatus.status}`);
1181
- addJsonData({ status: finalStatus.status });
1182
- process.exit(1 /* ERROR */);
1183
- }
1184
- } catch (error) {
1185
- spinner.fail("Deployment failed!");
1186
- const message = error instanceof Error ? error.message : "Unknown error";
1187
- addJsonData({ error: message });
1188
- logger.error(message);
1189
- if (message.includes("timeout")) {
1190
- process.exit(2 /* TIMEOUT */);
1191
- }
1192
- process.exit(1 /* ERROR */);
1193
- }
1194
- });
1195
- 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) => {
1196
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1197
- logger.header(`Rolling back ${environment}`);
1198
- const { data, error } = await withSpinner(
1199
- "Starting rollback...",
1200
- () => apiClient.rollback(env.id, options.to)
1201
- );
1202
- if (error || !data) {
1203
- logger.error(`Rollback failed: ${error || "Unknown error"}`);
1204
- process.exit(13 /* API_ERROR */);
1205
- }
1206
- addJsonData({
1207
- status: "rollback_initiated",
1208
- imageTag: data.imageTag,
1209
- deploymentId: data.id
1210
- });
1211
- logger.success(`Rollback initiated to ${data.imageTag}`);
1212
- logger.info("Run `turbo status` to monitor the rollback.");
1213
- });
1214
- var restartCommand = new Command2("restart").description("Restart containers in an environment").argument("<environment>", "Environment slug").action(async (environment) => {
1215
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1216
- const { error } = await withSpinner(
1217
- "Restarting containers...",
1218
- () => apiClient.restart(env.id)
1219
- );
1220
- if (error) {
1221
- logger.error(`Restart failed: ${error}`);
1222
- process.exit(13 /* API_ERROR */);
1223
- }
1224
- addJsonData({ status: "restarting", environment: env.slug });
1225
- logger.success("Containers are being restarted");
1226
- });
1227
- var stopCommand = new Command2("stop").description("Stop containers in an environment").argument("<environment>", "Environment slug").action(async (environment) => {
1228
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1229
- const { error } = await withSpinner(
1230
- "Stopping containers...",
1231
- () => apiClient.stop(env.id)
1232
- );
1233
- if (error) {
1234
- logger.error(`Stop failed: ${error}`);
1235
- process.exit(13 /* API_ERROR */);
1236
- }
1237
- addJsonData({ status: "stopped", environment: env.slug });
1238
- logger.success("Containers have been stopped");
1239
- });
1240
- var wakeCommand = new Command2("wake").description("Wake (start) stopped containers in an environment").argument("<environment>", "Environment slug").action(async (environment) => {
1241
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1242
- const { error } = await withSpinner(
1243
- "Starting containers...",
1244
- () => apiClient.wake(env.id)
1245
- );
1246
- if (error) {
1247
- logger.error(`Wake failed: ${error}`);
1248
- process.exit(13 /* API_ERROR */);
1249
- }
1250
- addJsonData({ status: "starting", environment: env.slug });
1251
- logger.success("Containers are starting");
1252
- });
1253
- function getStatusText(status) {
1254
- switch (status.status) {
1255
- case "pending":
1256
- return "Waiting for deployment to start...";
1257
- case "deploying":
1258
- return `Deploying ${status.imageTag}...`;
1259
- case "running":
1260
- return "Deployment complete!";
1261
- case "failed":
1262
- return "Deployment failed";
1263
- default:
1264
- return `Status: ${status.status}`;
1265
- }
1266
- }
1267
-
1268
1047
  // src/commands/status.ts
1269
- import { Command as Command3 } from "commander";
1270
- import chalk4 from "chalk";
1271
- var statusCommand = new Command3("status").description("Show deployment status").argument(
1048
+ var statusCommand = new Command2("status").description("Show deployment status").argument(
1272
1049
  "[environment]",
1273
1050
  "Environment slug (optional, shows all if not specified)"
1274
1051
  ).action(async (environment) => {
@@ -1312,17 +1089,17 @@ var statusCommand = new Command3("status").description("Show deployment status")
1312
1089
  });
1313
1090
  for (const env of envsToShow) {
1314
1091
  logger.newline();
1315
- console.log(chalk4.bold(env.name) + ` (${env.slug})`);
1316
- console.log(chalk4.gray("\u2500".repeat(40)));
1092
+ console.log(chalk3.bold(env.name) + ` (${env.slug})`);
1093
+ console.log(chalk3.gray("\u2500".repeat(40)));
1317
1094
  const statusColor = getStatusColor(env.status);
1318
1095
  logger.table({
1319
1096
  Status: statusColor(env.status),
1320
1097
  Type: env.type,
1321
- Domain: chalk4.cyan(env.domain)
1098
+ Domain: chalk3.cyan(env.domain)
1322
1099
  });
1323
1100
  }
1324
1101
  });
1325
- var configCommand = new Command3("config").description("Manage configuration");
1102
+ var configCommand = new Command2("config").description("Manage configuration");
1326
1103
  configCommand.command("show", { isDefault: true }).description("Show current configuration").action(() => {
1327
1104
  const config = configService.getAll();
1328
1105
  addJsonData({ config });
@@ -1397,117 +1174,22 @@ function getStatusColor(status) {
1397
1174
  switch (status) {
1398
1175
  case "healthy":
1399
1176
  case "running":
1400
- return chalk4.green;
1177
+ return chalk3.green;
1401
1178
  case "deploying":
1402
- return chalk4.yellow;
1179
+ return chalk3.yellow;
1403
1180
  case "failed":
1404
1181
  case "stopped":
1405
- return chalk4.red;
1182
+ return chalk3.red;
1406
1183
  default:
1407
- return chalk4.gray;
1184
+ return chalk3.gray;
1408
1185
  }
1409
1186
  }
1410
1187
 
1411
- // src/commands/env.ts
1412
- import { Command as Command4 } from "commander";
1413
- import chalk5 from "chalk";
1414
- 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) => {
1415
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1416
- const { data: envVars, error } = await withSpinner(
1417
- "Fetching environment variables...",
1418
- () => apiClient.getEnvVars(env.id)
1419
- );
1420
- if (error || !envVars) {
1421
- logger.error(
1422
- `Failed to fetch environment variables: ${error || "Unknown error"}`
1423
- );
1424
- process.exit(13 /* API_ERROR */);
1425
- }
1426
- addJsonData({
1427
- environment: env.name,
1428
- environmentSlug: env.slug,
1429
- variables: envVars.map((v) => ({
1430
- key: v.key,
1431
- secret: v.secret
1432
- }))
1433
- });
1434
- logger.header(`Environment Variables: ${env.name}`);
1435
- if (envVars.length === 0) {
1436
- logger.warning("No environment variables configured.");
1437
- return;
1438
- }
1439
- for (const v of envVars) {
1440
- const icon = v.secret ? chalk5.yellow("\u{1F512}") : chalk5.gray(" ");
1441
- const value = v.secret && !options.reveal ? chalk5.gray("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022") : chalk5.green("(set)");
1442
- console.log(`${icon} ${chalk5.bold(v.key)} = ${value}`);
1443
- }
1444
- logger.newline();
1445
- logger.info(
1446
- "Use `turbo env <environment> set KEY=value` to add/update variables."
1447
- );
1448
- logger.info("Use `turbo env <environment> unset KEY` to remove variables.");
1449
- });
1450
- 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) => {
1451
- const environment = cmd.parent?.args[0];
1452
- if (!environment) {
1453
- logger.error("Environment not specified");
1454
- process.exit(14 /* VALIDATION_ERROR */);
1455
- }
1456
- const [key, ...valueParts] = keyValue.split("=");
1457
- const value = valueParts.join("=");
1458
- if (!key || !value) {
1459
- logger.error("Invalid format. Use: KEY=value");
1460
- process.exit(14 /* VALIDATION_ERROR */);
1461
- }
1462
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1463
- const { error } = await withSpinner(
1464
- `Setting ${key}...`,
1465
- () => apiClient.setEnvVar(env.id, key, value, options.secret || false)
1466
- );
1467
- if (error) {
1468
- logger.error(`Failed to set variable: ${error}`);
1469
- process.exit(13 /* API_ERROR */);
1470
- }
1471
- addJsonData({
1472
- action: "set",
1473
- key,
1474
- secret: options.secret || false,
1475
- environment: env.slug
1476
- });
1477
- logger.success(`Set ${key}${options.secret ? " (secret)" : ""}`);
1478
- logger.info("Changes will take effect on the next deployment.");
1479
- });
1480
- var envUnsetCommand = new Command4("unset").description("Remove an environment variable").argument("<key>", "Variable key to remove").action(async (key, _options, cmd) => {
1481
- const environment = cmd.parent?.args[0];
1482
- if (!environment) {
1483
- logger.error("Environment not specified");
1484
- process.exit(14 /* VALIDATION_ERROR */);
1485
- }
1486
- const { environment: env } = await getCommandContextWithEnvironment(environment);
1487
- const { error } = await withSpinner(
1488
- `Removing ${key}...`,
1489
- () => apiClient.deleteEnvVar(env.id, key)
1490
- );
1491
- if (error) {
1492
- logger.error(`Failed to remove variable: ${error}`);
1493
- process.exit(13 /* API_ERROR */);
1494
- }
1495
- addJsonData({
1496
- action: "unset",
1497
- key,
1498
- environment: env.slug
1499
- });
1500
- logger.success(`Removed ${key}`);
1501
- logger.info("Changes will take effect on the next deployment.");
1502
- });
1503
- envCommand.addCommand(envSetCommand);
1504
- envCommand.addCommand(envUnsetCommand);
1505
-
1506
1188
  // src/commands/init.ts
1507
- import { Command as Command5 } from "commander";
1189
+ import { Command as Command3 } from "commander";
1508
1190
  import prompts from "prompts";
1509
- import chalk6 from "chalk";
1510
- 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 () => {
1511
1193
  logger.header("TurboOps Project Initialization");
1512
1194
  if (!configService.isAuthenticated()) {
1513
1195
  logger.warning("Nicht authentifiziert. Bitte melden Sie sich zuerst an.");
@@ -1548,7 +1230,7 @@ var initCommand = new Command5("init").description("Initialize TurboOps project
1548
1230
  addJsonData({ initialized: false, reason: "cancelled" });
1549
1231
  return;
1550
1232
  }
1551
- const { data: project, error: projectError } = await withSpinner(
1233
+ const { data: project } = await withSpinner(
1552
1234
  "Suche Projekt...",
1553
1235
  () => apiClient.getProject(projectSlug)
1554
1236
  );
@@ -1586,12 +1268,12 @@ async function setupProject(project) {
1586
1268
  await createFirstStage(project.id, project.slug);
1587
1269
  }
1588
1270
  }
1589
- const fs2 = await import("fs/promises");
1590
- const path2 = await import("path");
1591
- const gitlabCiPath = path2.join(process.cwd(), ".gitlab-ci.yml");
1271
+ const fs3 = await import("fs/promises");
1272
+ const path3 = await import("path");
1273
+ const gitlabCiPath = path3.join(process.cwd(), ".gitlab-ci.yml");
1592
1274
  let hasGitLabPipeline = false;
1593
1275
  try {
1594
- await fs2.access(gitlabCiPath);
1276
+ await fs3.access(gitlabCiPath);
1595
1277
  hasGitLabPipeline = true;
1596
1278
  } catch {
1597
1279
  }
@@ -1806,27 +1488,27 @@ async function createPipeline(projectId) {
1806
1488
  logger.error(`Pipeline konnte nicht generiert werden: ${error || "Unbekannter Fehler"}`);
1807
1489
  return;
1808
1490
  }
1809
- const fs2 = await import("fs/promises");
1810
- const path2 = await import("path");
1491
+ const fs3 = await import("fs/promises");
1492
+ const path3 = await import("path");
1811
1493
  let filePath;
1812
1494
  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");
1495
+ const workflowsDir = path3.join(process.cwd(), ".github", "workflows");
1496
+ await fs3.mkdir(workflowsDir, { recursive: true });
1497
+ filePath = path3.join(workflowsDir, "deploy.yml");
1816
1498
  } else {
1817
- filePath = path2.join(process.cwd(), ".gitlab-ci.yml");
1499
+ filePath = path3.join(process.cwd(), ".gitlab-ci.yml");
1818
1500
  }
1819
1501
  try {
1820
- await fs2.writeFile(filePath, pipeline.content, "utf-8");
1502
+ await fs3.writeFile(filePath, pipeline.content, "utf-8");
1821
1503
  logger.success(`${pipeline.filename} wurde erstellt!`);
1822
1504
  logger.newline();
1823
1505
  const { data: secrets } = await apiClient.getPipelineSecrets(projectId, pipelineDetails.pipelineType);
1824
1506
  if (secrets && secrets.length > 0) {
1825
1507
  logger.header("Erforderliche CI/CD Secrets");
1826
1508
  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)}`);
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)}`);
1830
1512
  }
1831
1513
  logger.newline();
1832
1514
  logger.info("F\xFCgen Sie diese Werte als CI/CD Secrets/Variables hinzu.");
@@ -1865,23 +1547,22 @@ async function showFinalSummary(project) {
1865
1547
  logger.newline();
1866
1548
  logger.header("Verf\xFCgbare Stages");
1867
1549
  for (const env of environments) {
1868
- console.log(` ${chalk6.bold(env.slug)} - ${env.name} (${env.type})`);
1550
+ console.log(` ${chalk4.bold(env.slug)} - ${env.name} (${env.type})`);
1869
1551
  }
1870
1552
  }
1871
1553
  logger.newline();
1872
1554
  logger.header("N\xE4chste Schritte");
1873
1555
  logger.list([
1874
1556
  "F\xFChren Sie `turbo status` aus, um alle Stages zu sehen",
1875
- "F\xFChren Sie `turbo deploy <stage>` aus, um zu deployen",
1876
1557
  "F\xFChren Sie `turbo logs <stage>` aus, um Logs anzuzeigen"
1877
1558
  ]);
1878
1559
  }
1879
1560
 
1880
1561
  // src/commands/logs.ts
1881
- import { Command as Command6 } from "commander";
1882
- import chalk7 from "chalk";
1562
+ import { Command as Command4 } from "commander";
1563
+ import chalk5 from "chalk";
1883
1564
  import { io } from "socket.io-client";
1884
- 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) => {
1885
1566
  const { environment: env } = await getCommandContextWithEnvironment(environment);
1886
1567
  logger.header(`Logs: ${env.name}`);
1887
1568
  if (options.follow) {
@@ -2002,9 +1683,9 @@ function printLogEntry(log) {
2002
1683
  const timestamp = formatTimestamp(log.timestamp);
2003
1684
  const levelColor = getLevelColor(log.level);
2004
1685
  const levelStr = log.level.toUpperCase().padEnd(5);
2005
- let output = chalk7.gray(`[${timestamp}]`) + levelColor(` ${levelStr}`);
1686
+ let output = chalk5.gray(`[${timestamp}]`) + levelColor(` ${levelStr}`);
2006
1687
  if (log.step) {
2007
- output += chalk7.cyan(` [${log.step}]`);
1688
+ output += chalk5.cyan(` [${log.step}]`);
2008
1689
  }
2009
1690
  output += ` ${log.message}`;
2010
1691
  logger.raw(output);
@@ -2021,19 +1702,257 @@ function getLevelColor(level) {
2021
1702
  switch (level.toLowerCase()) {
2022
1703
  case "error":
2023
1704
  case "fatal":
2024
- return chalk7.red;
1705
+ return chalk5.red;
2025
1706
  case "warn":
2026
1707
  case "warning":
2027
- return chalk7.yellow;
1708
+ return chalk5.yellow;
2028
1709
  case "info":
2029
- return chalk7.blue;
1710
+ return chalk5.blue;
2030
1711
  case "debug":
2031
- return chalk7.gray;
1712
+ return chalk5.gray;
2032
1713
  case "trace":
2033
- return chalk7.magenta;
1714
+ return chalk5.magenta;
1715
+ default:
1716
+ return chalk5.white;
1717
+ }
1718
+ }
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;
2034
1821
  default:
2035
- return chalk7.white;
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 */);
2036
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");
2037
1956
  }
2038
1957
 
2039
1958
  // src/index.ts
@@ -2075,13 +1994,9 @@ program.addCommand(whoamiCommand);
2075
1994
  program.addCommand(initCommand);
2076
1995
  program.addCommand(statusCommand);
2077
1996
  program.addCommand(configCommand);
2078
- program.addCommand(deployCommand);
2079
- program.addCommand(rollbackCommand);
2080
- program.addCommand(restartCommand);
2081
- program.addCommand(stopCommand);
2082
- program.addCommand(wakeCommand);
2083
- program.addCommand(envCommand);
2084
1997
  program.addCommand(logsCommand);
1998
+ program.addCommand(deployCommand);
1999
+ program.addCommand(pipelineCommand);
2085
2000
  program.command("self-update").description("Update TurboCLI to the latest version").action(async () => {
2086
2001
  logger.info("Checking for updates...");
2087
2002
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turboops/cli",
3
- "version": "1.0.0-dev.582",
3
+ "version": "1.0.0-dev.583",
4
4
  "description": "TurboCLI - Command line interface for TurboOps deployments",
5
5
  "author": "lenne.tech GmbH",
6
6
  "license": "MIT",