@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.
- package/README.md +84 -66
- package/dist/index.js +305 -390
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TurboCLI
|
|
2
2
|
|
|
3
|
-
Command line interface for TurboOps
|
|
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
|
-
|
|
42
|
+
# Set configuration value
|
|
43
|
+
turbo config set project <slug>
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
turbo deploy production
|
|
48
|
-
turbo deploy staging
|
|
49
|
-
turbo deploy dev
|
|
45
|
+
# Get configuration value
|
|
46
|
+
turbo config get project
|
|
50
47
|
|
|
51
|
-
#
|
|
52
|
-
turbo
|
|
48
|
+
# Clear configuration
|
|
49
|
+
turbo config clear --all
|
|
50
|
+
turbo config clear --token
|
|
51
|
+
turbo config clear --project
|
|
52
|
+
```
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
turbo deploy production --no-wait
|
|
54
|
+
### Status
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
```bash
|
|
57
|
+
# Show status of all environments
|
|
58
|
+
turbo status
|
|
59
59
|
|
|
60
|
-
#
|
|
61
|
-
turbo
|
|
60
|
+
# Show status of specific environment
|
|
61
|
+
turbo status production
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
###
|
|
64
|
+
### Logs
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
#
|
|
68
|
-
turbo
|
|
67
|
+
# View logs
|
|
68
|
+
turbo logs production
|
|
69
69
|
|
|
70
|
-
#
|
|
71
|
-
turbo
|
|
70
|
+
# Follow logs (stream)
|
|
71
|
+
turbo logs production --follow
|
|
72
72
|
|
|
73
|
-
#
|
|
74
|
-
turbo
|
|
73
|
+
# Limit lines
|
|
74
|
+
turbo logs production --lines 50
|
|
75
75
|
|
|
76
|
-
#
|
|
77
|
-
turbo
|
|
78
|
-
turbo status production
|
|
76
|
+
# Filter by service
|
|
77
|
+
turbo logs production --service api
|
|
79
78
|
```
|
|
80
79
|
|
|
81
|
-
###
|
|
80
|
+
### Deployment (CI/CD)
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
# List environment variables
|
|
85
|
-
turbo env production
|
|
82
|
+
The deploy command is designed for CI/CD pipelines using Project Tokens.
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
```bash
|
|
85
|
+
# Trigger deployment and wait for completion (default)
|
|
86
|
+
turbo deploy production
|
|
89
87
|
|
|
90
|
-
#
|
|
91
|
-
turbo
|
|
88
|
+
# Deploy specific image tag
|
|
89
|
+
turbo deploy production --image my-app:v1.2.3
|
|
92
90
|
|
|
93
|
-
#
|
|
94
|
-
turbo
|
|
91
|
+
# Deploy without waiting (fire-and-forget)
|
|
92
|
+
turbo deploy staging --no-wait
|
|
95
93
|
|
|
96
|
-
#
|
|
97
|
-
turbo
|
|
94
|
+
# Custom timeout (default: 10 minutes)
|
|
95
|
+
turbo deploy production --timeout 900000
|
|
98
96
|
```
|
|
99
97
|
|
|
100
|
-
|
|
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
|
-
#
|
|
104
|
-
turbo
|
|
114
|
+
# Generate pipeline interactively
|
|
115
|
+
turbo pipeline generate
|
|
105
116
|
|
|
106
|
-
#
|
|
107
|
-
turbo
|
|
117
|
+
# Generate GitLab CI pipeline
|
|
118
|
+
turbo pipeline generate --type gitlab
|
|
108
119
|
|
|
109
|
-
#
|
|
110
|
-
turbo
|
|
111
|
-
```
|
|
120
|
+
# Generate GitHub Actions pipeline
|
|
121
|
+
turbo pipeline generate --type github
|
|
112
122
|
|
|
113
|
-
|
|
123
|
+
# Force overwrite existing pipeline
|
|
124
|
+
turbo pipeline generate --type gitlab --force
|
|
114
125
|
|
|
115
|
-
|
|
126
|
+
# Custom output path
|
|
127
|
+
turbo pipeline generate --type gitlab --output ./ci/pipeline.yml
|
|
116
128
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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 |
|
|
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
|
|
139
|
-
if (existsSync(
|
|
140
|
-
return require2(
|
|
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,
|
|
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}${
|
|
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(
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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 =
|
|
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 (
|
|
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 (
|
|
754
|
-
|
|
677
|
+
if (onProgress) {
|
|
678
|
+
onProgress(data);
|
|
755
679
|
}
|
|
756
|
-
|
|
680
|
+
const terminalStatuses = ["running", "failed", "stopped", "rolled_back"];
|
|
681
|
+
if (terminalStatuses.includes(data.status)) {
|
|
757
682
|
return data;
|
|
758
683
|
}
|
|
759
|
-
|
|
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((
|
|
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/
|
|
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
|
-
|
|
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(
|
|
1316
|
-
console.log(
|
|
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:
|
|
1098
|
+
Domain: chalk3.cyan(env.domain)
|
|
1322
1099
|
});
|
|
1323
1100
|
}
|
|
1324
1101
|
});
|
|
1325
|
-
var configCommand = new
|
|
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
|
|
1177
|
+
return chalk3.green;
|
|
1401
1178
|
case "deploying":
|
|
1402
|
-
return
|
|
1179
|
+
return chalk3.yellow;
|
|
1403
1180
|
case "failed":
|
|
1404
1181
|
case "stopped":
|
|
1405
|
-
return
|
|
1182
|
+
return chalk3.red;
|
|
1406
1183
|
default:
|
|
1407
|
-
return
|
|
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
|
|
1189
|
+
import { Command as Command3 } from "commander";
|
|
1508
1190
|
import prompts from "prompts";
|
|
1509
|
-
import
|
|
1510
|
-
var initCommand = new
|
|
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
|
|
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
|
|
1590
|
-
const
|
|
1591
|
-
const gitlabCiPath =
|
|
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
|
|
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
|
|
1810
|
-
const
|
|
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 =
|
|
1814
|
-
await
|
|
1815
|
-
filePath =
|
|
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 =
|
|
1499
|
+
filePath = path3.join(process.cwd(), ".gitlab-ci.yml");
|
|
1818
1500
|
}
|
|
1819
1501
|
try {
|
|
1820
|
-
await
|
|
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 ?
|
|
1828
|
-
console.log(` ${
|
|
1829
|
-
console.log(` ${
|
|
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(` ${
|
|
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
|
|
1882
|
-
import
|
|
1562
|
+
import { Command as Command4 } from "commander";
|
|
1563
|
+
import chalk5 from "chalk";
|
|
1883
1564
|
import { io } from "socket.io-client";
|
|
1884
|
-
var logsCommand = new
|
|
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 =
|
|
1686
|
+
let output = chalk5.gray(`[${timestamp}]`) + levelColor(` ${levelStr}`);
|
|
2006
1687
|
if (log.step) {
|
|
2007
|
-
output +=
|
|
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
|
|
1705
|
+
return chalk5.red;
|
|
2025
1706
|
case "warn":
|
|
2026
1707
|
case "warning":
|
|
2027
|
-
return
|
|
1708
|
+
return chalk5.yellow;
|
|
2028
1709
|
case "info":
|
|
2029
|
-
return
|
|
1710
|
+
return chalk5.blue;
|
|
2030
1711
|
case "debug":
|
|
2031
|
-
return
|
|
1712
|
+
return chalk5.gray;
|
|
2032
1713
|
case "trace":
|
|
2033
|
-
return
|
|
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
|
|
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 {
|