bdy 1.23.13 → 1.23.14-dev
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/distTs/package.json +3 -1
- package/distTs/src/api/client.js +259 -24
- package/distTs/src/cliIndex.js +4 -0
- package/distTs/src/command/api/request.js +5 -4
- package/distTs/src/command/artifact/list.js +2 -6
- package/distTs/src/command/distro/list.js +1 -0
- package/distTs/src/command/distro/route/update.js +1 -1
- package/distTs/src/command/distro/update.js +9 -9
- package/distTs/src/command/environment/create.js +70 -0
- package/distTs/src/command/environment/delete.js +30 -0
- package/distTs/src/command/environment/get.js +48 -0
- package/distTs/src/command/environment/list.js +45 -0
- package/distTs/src/command/environment/resolve.js +39 -0
- package/distTs/src/command/environment/update.js +60 -0
- package/distTs/src/command/environment.js +20 -0
- package/distTs/src/command/project/git/credential.js +6 -4
- package/distTs/src/command/sandbox/exec/command.js +1 -1
- package/distTs/src/command/sandbox/exec/logs.js +1 -1
- package/distTs/src/command/target/create.js +33 -0
- package/distTs/src/command/target/delete.js +31 -0
- package/distTs/src/command/target/exec/command.js +101 -0
- package/distTs/src/command/target/exec/kill.js +31 -0
- package/distTs/src/command/target/exec/list.js +53 -0
- package/distTs/src/command/target/exec/logs.js +27 -0
- package/distTs/src/command/target/exec/status.js +44 -0
- package/distTs/src/command/target/exec.js +24 -0
- package/distTs/src/command/target/get.js +63 -0
- package/distTs/src/command/target/list.js +49 -0
- package/distTs/src/command/target/scope.js +138 -0
- package/distTs/src/command/target/update.js +32 -0
- package/distTs/src/command/target.js +22 -0
- package/distTs/src/index.js +14 -0
- package/distTs/src/input.js +65 -0
- package/distTs/src/output/pipeline.js +5 -3
- package/distTs/src/output.js +30 -2
- package/distTs/src/texts.js +251 -27
- package/distTs/src/utils.js +18 -2
- package/package.json +3 -1
package/distTs/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bdy",
|
|
3
3
|
"preferGlobal": false,
|
|
4
|
-
"version": "1.23.
|
|
4
|
+
"version": "1.23.14-dev",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://buddy.works/docs/cli",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"e2e:tunnel": "vitest run --config e2e/vitest.config.ts e2e/modules/tunnel/",
|
|
28
28
|
"e2e:agent": "vitest run --config e2e/vitest.config.ts e2e/modules/agent/",
|
|
29
29
|
"e2e:distro": "vitest run --config e2e/vitest.config.ts e2e/modules/distro/",
|
|
30
|
+
"e2e:target": "vitest run --config e2e/vitest.config.ts e2e/modules/target/",
|
|
31
|
+
"e2e:environment": "vitest run --config e2e/vitest.config.ts e2e/modules/environment/",
|
|
30
32
|
"unit": "vitest run --config vitest.config.ts"
|
|
31
33
|
},
|
|
32
34
|
"files": [
|
package/distTs/src/api/client.js
CHANGED
|
@@ -16,6 +16,25 @@ const cfg_1 = __importDefault(require("../tunnel/cfg"));
|
|
|
16
16
|
* echo back the escaped form - callers comparing a response against the names
|
|
17
17
|
* they passed in must apply the same mapping.
|
|
18
18
|
*/
|
|
19
|
+
const RETRIABLE_NETWORK_ERROR = /ECONNREFUSED|ECONNRESET|ETIMEDOUT/;
|
|
20
|
+
// undici headers can be string | string[]; callers want the first value.
|
|
21
|
+
const headerValue = (h) => Array.isArray(h) ? h[0] : h;
|
|
22
|
+
// How long to wait before retrying a transient status on an idempotent
|
|
23
|
+
// request, or null when a retry makes no sense (non-retriable status, or a
|
|
24
|
+
// rate limit window that resets too far in the future to wait for).
|
|
25
|
+
const retryDelay = (status, attempt, rateLimitReset) => {
|
|
26
|
+
if (status === 429) {
|
|
27
|
+
if (!rateLimitReset)
|
|
28
|
+
return null;
|
|
29
|
+
const ms = parseInt(rateLimitReset, 10) * 1000 - Date.now();
|
|
30
|
+
if (ms <= 0)
|
|
31
|
+
return 1000;
|
|
32
|
+
return ms > 15000 ? null : ms + 500;
|
|
33
|
+
}
|
|
34
|
+
return [502, 503, 504].includes(status) ? attempt * 1000 : null;
|
|
35
|
+
};
|
|
36
|
+
const RATE_LIMIT_WARN_THRESHOLD = 25;
|
|
37
|
+
let rateLimitWarned = false;
|
|
19
38
|
const formDataFileName = (name) => name.replace(/["\r\n]/g, (c) => ({ '"': '%22', '\r': '%0D', '\n': '%0A' })[c]);
|
|
20
39
|
exports.formDataFileName = formDataFileName;
|
|
21
40
|
class ApiClient {
|
|
@@ -67,9 +86,8 @@ class ApiClient {
|
|
|
67
86
|
}
|
|
68
87
|
return false;
|
|
69
88
|
}
|
|
70
|
-
async request(
|
|
71
|
-
|
|
72
|
-
headers = {};
|
|
89
|
+
async request(args) {
|
|
90
|
+
const { method = 'GET', path, query, body = null, headers = {}, parseResponseBody = false, rawResponseBody = false, rawResponse = false, httpUrlEncoded = false, tryRefreshingToken = true, } = args;
|
|
73
91
|
if (this.token && !headers.authorization) {
|
|
74
92
|
headers.authorization = `Bearer ${this.token}`;
|
|
75
93
|
}
|
|
@@ -112,20 +130,41 @@ class ApiClient {
|
|
|
112
130
|
logger_1.default.debug(headers);
|
|
113
131
|
logger_1.default.debug('BODY');
|
|
114
132
|
logger_1.default.debug(bodyParsed);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
133
|
+
// Transient failures (rate limit with a nearby reset, 5xx, connection
|
|
134
|
+
// resets) are retried with a backoff, but only for idempotent requests -
|
|
135
|
+
// retrying a POST could duplicate the operation.
|
|
136
|
+
const maxAttempts = method === 'GET' ? 3 : 1;
|
|
137
|
+
for (let attempt = 1;; attempt += 1) {
|
|
138
|
+
try {
|
|
139
|
+
response = await this.client.request(opts);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
logger_1.default.debug('API CLIENT RESPONSE ERROR');
|
|
143
|
+
logger_1.default.debug(err);
|
|
144
|
+
const retriable = RETRIABLE_NETWORK_ERROR.test(err.message);
|
|
145
|
+
if (retriable && attempt < maxAttempts) {
|
|
146
|
+
await (0, utils_1.sleep)(attempt * 1000);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
throw new Error(retriable ? texts_1.ERR_REST_API_CONNECT_ERROR : texts_1.ERR_REST_API_GENERAL_ERROR, { cause: err });
|
|
150
|
+
}
|
|
151
|
+
rateLimitReset = headerValue(response.headers['x-rate-limit-reset']);
|
|
118
152
|
status = response.statusCode;
|
|
119
153
|
responseBody = response.body;
|
|
120
154
|
logger_1.default.debug(`API CLIENT RESPONSE STATUS: ${status}`);
|
|
155
|
+
const delay = attempt < maxAttempts
|
|
156
|
+
? retryDelay(status, attempt, rateLimitReset)
|
|
157
|
+
: null;
|
|
158
|
+
if (delay === null)
|
|
159
|
+
break;
|
|
160
|
+
await responseBody.dump();
|
|
161
|
+
logger_1.default.debug(`API CLIENT RETRY IN ${delay}ms`);
|
|
162
|
+
await (0, utils_1.sleep)(delay);
|
|
121
163
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
throw new Error(texts_1.ERR_REST_API_CONNECT_ERROR, { cause: err });
|
|
127
|
-
}
|
|
128
|
-
throw new Error(texts_1.ERR_REST_API_GENERAL_ERROR, { cause: err });
|
|
164
|
+
this.warnRateLimitLow(headerValue(response.headers['x-rate-limit-remaining']), rateLimitReset);
|
|
165
|
+
if (status === 429) {
|
|
166
|
+
await responseBody.dump();
|
|
167
|
+
throw new Error((0, texts_1.ERR_REST_API_RATE_LIMIT)(rateLimitReset));
|
|
129
168
|
}
|
|
130
169
|
if (status === 401) {
|
|
131
170
|
await responseBody.dump();
|
|
@@ -136,15 +175,12 @@ class ApiClient {
|
|
|
136
175
|
const success = await this.tryRefreshToken();
|
|
137
176
|
if (success) {
|
|
138
177
|
delete headers.authorization;
|
|
178
|
+
// Retry with the complete original args (an explicit field list
|
|
179
|
+
// once silently dropped `query` here); only the mutated headers
|
|
180
|
+
// and the refresh guard are overridden.
|
|
139
181
|
return await this.request({
|
|
140
|
-
|
|
141
|
-
path,
|
|
142
|
-
body,
|
|
182
|
+
...args,
|
|
143
183
|
headers,
|
|
144
|
-
parseResponseBody,
|
|
145
|
-
rawResponseBody,
|
|
146
|
-
rawResponse,
|
|
147
|
-
httpUrlEncoded,
|
|
148
184
|
tryRefreshingToken: false,
|
|
149
185
|
});
|
|
150
186
|
}
|
|
@@ -185,7 +221,7 @@ class ApiClient {
|
|
|
185
221
|
if (status === 404)
|
|
186
222
|
throw new Error(texts_1.ERR_REST_API_RESOURCE_NOT_FOUND);
|
|
187
223
|
else
|
|
188
|
-
throw new Error(texts_1.
|
|
224
|
+
throw new Error((0, texts_1.ERR_REST_API_HTTP_ERROR)(status));
|
|
189
225
|
}
|
|
190
226
|
if ([200, 201].includes(status)) {
|
|
191
227
|
if (parseResponseBody) {
|
|
@@ -213,6 +249,12 @@ class ApiClient {
|
|
|
213
249
|
}
|
|
214
250
|
}
|
|
215
251
|
else if (status === 204) {
|
|
252
|
+
// A no-content success still honors the raw-output contracts: callers
|
|
253
|
+
// stream the (empty) body instead of tripping over a null.
|
|
254
|
+
if (rawResponse)
|
|
255
|
+
return response;
|
|
256
|
+
if (rawResponseBody)
|
|
257
|
+
return responseBody;
|
|
216
258
|
await responseBody.dump();
|
|
217
259
|
return null;
|
|
218
260
|
}
|
|
@@ -220,10 +262,38 @@ class ApiClient {
|
|
|
220
262
|
throw new Error(texts_1.ERR_REST_API_NOT_RESPONDING);
|
|
221
263
|
}
|
|
222
264
|
else {
|
|
223
|
-
|
|
224
|
-
|
|
265
|
+
// Any other error status (409, 422, 5xx...): surface the API's own
|
|
266
|
+
// message when the body carries one (e.g. "Concurrency limit reached")
|
|
267
|
+
// instead of reducing it to a bare HTTP code.
|
|
268
|
+
let json;
|
|
269
|
+
try {
|
|
270
|
+
json = await responseBody.json();
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
throw new Error((0, texts_1.ERR_REST_API_HTTP_ERROR)(status));
|
|
274
|
+
}
|
|
275
|
+
logger_1.default.debug('API CLIENT PARSED RESPONSE:');
|
|
276
|
+
logger_1.default.debug(json);
|
|
277
|
+
if (json.errors && json.errors[0] && json.errors[0].message) {
|
|
278
|
+
throw new Error((0, texts_1.ERR_API_MESSAGE_REPLACER)(json.errors[0].message, path, this.baseUrl, rateLimitReset));
|
|
279
|
+
}
|
|
280
|
+
throw new Error((0, texts_1.ERR_REST_API_HTTP_ERROR)(status));
|
|
225
281
|
}
|
|
226
282
|
}
|
|
283
|
+
// Set by the CLI bootstrap (Input.restApiTokenClient); the transport layer
|
|
284
|
+
// only detects the condition and leaves rendering to the caller, so
|
|
285
|
+
// non-interactive consumers (MCP proxy) stay silent.
|
|
286
|
+
static onRateLimitLow;
|
|
287
|
+
// One-time notification when the rate limit window is nearly exhausted.
|
|
288
|
+
warnRateLimitLow(remaining, reset) {
|
|
289
|
+
if (rateLimitWarned || !remaining || !ApiClient.onRateLimitLow)
|
|
290
|
+
return;
|
|
291
|
+
const left = parseInt(remaining, 10);
|
|
292
|
+
if (Number.isNaN(left) || left > RATE_LIMIT_WARN_THRESHOLD)
|
|
293
|
+
return;
|
|
294
|
+
rateLimitWarned = true;
|
|
295
|
+
ApiClient.onRateLimitLow(remaining, reset);
|
|
296
|
+
}
|
|
227
297
|
async getPipelineRunActionLogs(workspace, project, pipelineId, executionId, actionExecutionId, offset, limit) {
|
|
228
298
|
const query = {
|
|
229
299
|
offset: String(offset),
|
|
@@ -563,6 +633,162 @@ class ApiClient {
|
|
|
563
633
|
q.project = project;
|
|
564
634
|
return await this.getResourceByIdentifier(workspace, q);
|
|
565
635
|
}
|
|
636
|
+
// Target methods
|
|
637
|
+
targetScopeQuery(scope) {
|
|
638
|
+
const query = {};
|
|
639
|
+
if (scope.project)
|
|
640
|
+
query.project_name = scope.project;
|
|
641
|
+
if (scope.pipelineId)
|
|
642
|
+
query.pipeline_id = scope.pipelineId.toString();
|
|
643
|
+
if (scope.environmentId)
|
|
644
|
+
query.environment_id = scope.environmentId;
|
|
645
|
+
return query;
|
|
646
|
+
}
|
|
647
|
+
async getTargets(workspace, filters = {}) {
|
|
648
|
+
return await this.request({
|
|
649
|
+
method: 'GET',
|
|
650
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/targets`,
|
|
651
|
+
query: this.targetScopeQuery(filters),
|
|
652
|
+
parseResponseBody: true,
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
async getTarget(workspace, targetId) {
|
|
656
|
+
return await this.request({
|
|
657
|
+
method: 'GET',
|
|
658
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/targets/${encodeURIComponent(targetId)}`,
|
|
659
|
+
parseResponseBody: true,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
async createTargetByYaml(workspace, scope, body) {
|
|
663
|
+
return await this.request({
|
|
664
|
+
method: 'POST',
|
|
665
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/targets/yaml`,
|
|
666
|
+
query: this.targetScopeQuery(scope),
|
|
667
|
+
body,
|
|
668
|
+
parseResponseBody: true,
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
async updateTargetByYaml(workspace, targetId, body) {
|
|
672
|
+
return await this.request({
|
|
673
|
+
method: 'PATCH',
|
|
674
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/targets/${encodeURIComponent(targetId)}/yaml`,
|
|
675
|
+
body,
|
|
676
|
+
parseResponseBody: true,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
async deleteTarget(workspace, targetId) {
|
|
680
|
+
return await this.request({
|
|
681
|
+
method: 'DELETE',
|
|
682
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/targets/${encodeURIComponent(targetId)}`,
|
|
683
|
+
parseResponseBody: false,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
async executeTargetCommand(workspace, targetId, body) {
|
|
687
|
+
return await this.request({
|
|
688
|
+
method: 'POST',
|
|
689
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/targets/${encodeURIComponent(targetId)}/execs`,
|
|
690
|
+
body,
|
|
691
|
+
parseResponseBody: true,
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
async getTargetExecs(workspace, targetId, limit) {
|
|
695
|
+
const query = {};
|
|
696
|
+
if (limit)
|
|
697
|
+
query.limit = limit.toString();
|
|
698
|
+
return await this.request({
|
|
699
|
+
method: 'GET',
|
|
700
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/targets/${encodeURIComponent(targetId)}/execs`,
|
|
701
|
+
query,
|
|
702
|
+
parseResponseBody: true,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
async getTargetExec(workspace, execId, wait) {
|
|
706
|
+
const query = {};
|
|
707
|
+
if (wait)
|
|
708
|
+
query.wait = wait.toString();
|
|
709
|
+
return await this.request({
|
|
710
|
+
method: 'GET',
|
|
711
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/targets/execs/${encodeURIComponent(execId)}`,
|
|
712
|
+
query,
|
|
713
|
+
parseResponseBody: true,
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
async cancelTargetExec(workspace, execId) {
|
|
717
|
+
return await this.request({
|
|
718
|
+
method: 'DELETE',
|
|
719
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/targets/execs/${encodeURIComponent(execId)}`,
|
|
720
|
+
parseResponseBody: false,
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
async getTargetExecLog(workspace, execId, offset, limit) {
|
|
724
|
+
const query = {};
|
|
725
|
+
if (offset)
|
|
726
|
+
query.offset = offset.toString();
|
|
727
|
+
if (limit)
|
|
728
|
+
query.limit = limit.toString();
|
|
729
|
+
return await this.request({
|
|
730
|
+
method: 'GET',
|
|
731
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/targets/execs/${encodeURIComponent(execId)}/log`,
|
|
732
|
+
query,
|
|
733
|
+
parseResponseBody: true,
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
async getTargetByIdentifier(workspace, identifier, line = {}) {
|
|
737
|
+
return await this.getResourceByIdentifier(workspace, {
|
|
738
|
+
target: identifier,
|
|
739
|
+
...line,
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
// Environment methods
|
|
743
|
+
async getEnvironments(workspace, project) {
|
|
744
|
+
const query = {};
|
|
745
|
+
if (project)
|
|
746
|
+
query.project_name = project;
|
|
747
|
+
return await this.request({
|
|
748
|
+
method: 'GET',
|
|
749
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/environments`,
|
|
750
|
+
query,
|
|
751
|
+
parseResponseBody: true,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
async getEnvironment(workspace, environmentId) {
|
|
755
|
+
return await this.request({
|
|
756
|
+
method: 'GET',
|
|
757
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/environments/${encodeURIComponent(environmentId)}`,
|
|
758
|
+
parseResponseBody: true,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
async createEnvironment(workspace, body) {
|
|
762
|
+
return await this.request({
|
|
763
|
+
method: 'POST',
|
|
764
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/environments`,
|
|
765
|
+
body,
|
|
766
|
+
parseResponseBody: true,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
async updateEnvironment(workspace, environmentId, body) {
|
|
770
|
+
return await this.request({
|
|
771
|
+
method: 'PATCH',
|
|
772
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/environments/${encodeURIComponent(environmentId)}`,
|
|
773
|
+
body,
|
|
774
|
+
parseResponseBody: true,
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
async deleteEnvironment(workspace, environmentId) {
|
|
778
|
+
return await this.request({
|
|
779
|
+
method: 'DELETE',
|
|
780
|
+
path: `/workspaces/${encodeURIComponent(workspace)}/environments/${encodeURIComponent(environmentId)}`,
|
|
781
|
+
parseResponseBody: false,
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
async getEnvironmentByIdentifier(workspace, identifier, project) {
|
|
785
|
+
const q = {
|
|
786
|
+
environment: identifier,
|
|
787
|
+
};
|
|
788
|
+
if (project)
|
|
789
|
+
q.project = project;
|
|
790
|
+
return await this.getResourceByIdentifier(workspace, q);
|
|
791
|
+
}
|
|
566
792
|
// Sandbox methods
|
|
567
793
|
async createSandbox(workspace, project, body) {
|
|
568
794
|
return await this.request({
|
|
@@ -1027,11 +1253,20 @@ class ApiClient {
|
|
|
1027
1253
|
query += '&';
|
|
1028
1254
|
query += encodeURIComponent(key) + '=' + encodeURIComponent(value);
|
|
1029
1255
|
});
|
|
1030
|
-
|
|
1256
|
+
const resolved = await this.request({
|
|
1031
1257
|
method: 'GET',
|
|
1032
1258
|
path: `/workspaces/${encodeURIComponent(workspace)}/identifiers${query}`,
|
|
1033
1259
|
parseResponseBody: true,
|
|
1034
1260
|
});
|
|
1261
|
+
// A queried project must itself resolve: for some resources
|
|
1262
|
+
// (environments, artifacts, distributions) the endpoint otherwise falls
|
|
1263
|
+
// back to the workspace resource of the same identifier, silently
|
|
1264
|
+
// answering for a different scope than asked (TARGET_API_REQUIREMENTS.md
|
|
1265
|
+
// p6). project_identifier is present whenever the project exists.
|
|
1266
|
+
if (params.project && !resolved.project_identifier) {
|
|
1267
|
+
throw new Error(texts_1.ERR_PROJECT_NOT_FOUND);
|
|
1268
|
+
}
|
|
1269
|
+
return resolved;
|
|
1035
1270
|
}
|
|
1036
1271
|
async resolveIdentifiers(workspace, params) {
|
|
1037
1272
|
let query = '';
|
package/distTs/src/cliIndex.js
CHANGED
|
@@ -24,6 +24,8 @@ const artifact_1 = __importDefault(require("./command/artifact"));
|
|
|
24
24
|
const api_1 = __importDefault(require("./command/api"));
|
|
25
25
|
const domain_1 = __importDefault(require("./command/domain"));
|
|
26
26
|
const distro_1 = __importDefault(require("./command/distro"));
|
|
27
|
+
const target_1 = __importDefault(require("./command/target"));
|
|
28
|
+
const environment_1 = __importDefault(require("./command/environment"));
|
|
27
29
|
const register_1 = __importDefault(require("./command/register"));
|
|
28
30
|
const mcp_1 = __importDefault(require("./command/mcp"));
|
|
29
31
|
function cliIndex() {
|
|
@@ -47,6 +49,8 @@ function cliIndex() {
|
|
|
47
49
|
program.addCommand(sandbox_1.default);
|
|
48
50
|
program.addCommand(domain_1.default);
|
|
49
51
|
program.addCommand(distro_1.default);
|
|
52
|
+
program.addCommand(target_1.default);
|
|
53
|
+
program.addCommand(environment_1.default);
|
|
50
54
|
program.addCommand(login_1.default);
|
|
51
55
|
program.addCommand(register_1.default);
|
|
52
56
|
program.addCommand(whoami_1.default);
|
|
@@ -159,14 +159,15 @@ const request = async (method, url, options) => {
|
|
|
159
159
|
output_1.default.exitNormal('');
|
|
160
160
|
}
|
|
161
161
|
else {
|
|
162
|
-
// setEncoding keeps multibyte characters intact across chunk boundaries
|
|
163
|
-
//
|
|
162
|
+
// setEncoding keeps multibyte characters intact across chunk boundaries;
|
|
163
|
+
// the response is a 1:1 payload, so it bypasses terminal-kit (whose
|
|
164
|
+
// formatting corrupts % and ^ sequences, common in URL-encoded data)
|
|
164
165
|
body.setEncoding('utf8');
|
|
165
166
|
body.on('data', (data) => {
|
|
166
|
-
output_1.default.
|
|
167
|
+
output_1.default.data(data, false);
|
|
167
168
|
});
|
|
168
169
|
body.on('close', () => {
|
|
169
|
-
output_1.default.
|
|
170
|
+
output_1.default.data('');
|
|
170
171
|
output_1.default.exitNormal('');
|
|
171
172
|
});
|
|
172
173
|
}
|
|
@@ -18,14 +18,12 @@ commandArtifactList.action(async (options) => {
|
|
|
18
18
|
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
19
19
|
const project = input_1.default.restApiProject(options.project, true);
|
|
20
20
|
const scope = input_1.default.scope(project, options.scope);
|
|
21
|
+
input_1.default.projectScopeNote(options, project, scope, 'artifacts');
|
|
21
22
|
const client = input_1.default.restApiTokenClient();
|
|
22
23
|
const response = await client.getArtifacts(workspace, scope === distro_1.SCOPE.PROJECT ? project : null);
|
|
23
24
|
const artifacts = response.artifacts || [];
|
|
24
25
|
if (options.format === 'json') {
|
|
25
|
-
|
|
26
|
-
? artifacts.filter((a) => a.scope !== distro_1.SCOPE.WORKSPACE)
|
|
27
|
-
: artifacts;
|
|
28
|
-
output_1.default.json(filtered);
|
|
26
|
+
output_1.default.json(artifacts);
|
|
29
27
|
}
|
|
30
28
|
else {
|
|
31
29
|
if (artifacts.length === 0) {
|
|
@@ -33,8 +31,6 @@ commandArtifactList.action(async (options) => {
|
|
|
33
31
|
}
|
|
34
32
|
const data = [['NAME', 'IDENTIFIER', 'TYPE', 'URL']];
|
|
35
33
|
for (const artifact of artifacts) {
|
|
36
|
-
if (scope === distro_1.SCOPE.PROJECT && artifact.scope === distro_1.SCOPE.WORKSPACE)
|
|
37
|
-
continue;
|
|
38
34
|
data.push([
|
|
39
35
|
artifact.name,
|
|
40
36
|
artifact.identifier,
|
|
@@ -19,6 +19,7 @@ commandDistroList.action(async (options) => {
|
|
|
19
19
|
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
20
20
|
const project = input_1.default.restApiProject(options.project, true);
|
|
21
21
|
const scope = input_1.default.scope(project, options.scope);
|
|
22
|
+
input_1.default.projectScopeNote(options, project, scope, 'distributions');
|
|
22
23
|
const client = input_1.default.restApiTokenClient();
|
|
23
24
|
const r = await client.getDistributions(workspace, scope === distro_1.SCOPE.PROJECT ? project : null);
|
|
24
25
|
const distributions = r.distributions || [];
|
|
@@ -77,7 +77,7 @@ commandDistroRouteUpdate.action(async (identifier, routeId, options) => {
|
|
|
77
77
|
changed = true;
|
|
78
78
|
}
|
|
79
79
|
if (!changed) {
|
|
80
|
-
output_1.default.exitError(texts_1.
|
|
80
|
+
output_1.default.exitError(texts_1.ERR_COMMAND_NO_UPDATE);
|
|
81
81
|
}
|
|
82
82
|
const result = await client.updateRoute(workspace, scope === distro_1.SCOPE.PROJECT ? project : null, data.distribution_id, routeId, body);
|
|
83
83
|
output_1.default.id(result.id);
|
|
@@ -27,14 +27,6 @@ commandDistroUpdate.action(async (identifier, options) => {
|
|
|
27
27
|
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
28
28
|
const project = input_1.default.restApiProject(options.project, true);
|
|
29
29
|
const scope = input_1.default.scope(project, options.scope);
|
|
30
|
-
const client = input_1.default.restApiTokenClient();
|
|
31
|
-
const data = await client.getDistributionByIdentifier(workspace, scope === distro_1.SCOPE.PROJECT ? project : null, identifier);
|
|
32
|
-
if (!data || !data.domain) {
|
|
33
|
-
output_1.default.exitError(texts_1.ERR_WORKSPACE_NOT_FOUND);
|
|
34
|
-
}
|
|
35
|
-
if (!data || !data.distribution_id) {
|
|
36
|
-
output_1.default.exitError(texts_1.ERR_COMMAND_DISTRO_NOT_FOUND);
|
|
37
|
-
}
|
|
38
30
|
const body = {};
|
|
39
31
|
let changed = false;
|
|
40
32
|
if (options.name) {
|
|
@@ -54,7 +46,15 @@ commandDistroUpdate.action(async (identifier, options) => {
|
|
|
54
46
|
changed = true;
|
|
55
47
|
}
|
|
56
48
|
if (!changed) {
|
|
57
|
-
output_1.default.exitError(texts_1.
|
|
49
|
+
output_1.default.exitError(texts_1.ERR_COMMAND_NO_UPDATE);
|
|
50
|
+
}
|
|
51
|
+
const client = input_1.default.restApiTokenClient();
|
|
52
|
+
const data = await client.getDistributionByIdentifier(workspace, scope === distro_1.SCOPE.PROJECT ? project : null, identifier);
|
|
53
|
+
if (!data || !data.domain) {
|
|
54
|
+
output_1.default.exitError(texts_1.ERR_WORKSPACE_NOT_FOUND);
|
|
55
|
+
}
|
|
56
|
+
if (!data || !data.distribution_id) {
|
|
57
|
+
output_1.default.exitError(texts_1.ERR_COMMAND_DISTRO_NOT_FOUND);
|
|
58
58
|
}
|
|
59
59
|
const result = await client.updateDistribution(workspace, scope === distro_1.SCOPE.PROJECT ? project : null, data.distribution_id, body);
|
|
60
60
|
output_1.default.identifier(result.identifier);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const utils_1 = require("../../utils");
|
|
7
|
+
const texts_1 = require("../../texts");
|
|
8
|
+
const input_1 = __importDefault(require("../../input"));
|
|
9
|
+
const output_1 = __importDefault(require("../../output"));
|
|
10
|
+
const distro_1 = require("../../types/distro");
|
|
11
|
+
const commandEnvironmentCreate = (0, utils_1.newCommand)('create', texts_1.DESC_COMMAND_ENVIRONMENT_CREATE);
|
|
12
|
+
commandEnvironmentCreate.alias('add');
|
|
13
|
+
commandEnvironmentCreate.option('-i, --identifier <identifier>', texts_1.OPTION_ENVIRONMENT_CREATE_IDENTIFIER);
|
|
14
|
+
commandEnvironmentCreate.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
|
|
15
|
+
commandEnvironmentCreate.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
|
|
16
|
+
commandEnvironmentCreate.option('--scope <scope>', texts_1.OPT_COMMAND_SCOPE);
|
|
17
|
+
commandEnvironmentCreate.option('--tags <tags>', texts_1.OPTION_ENVIRONMENT_TAGS);
|
|
18
|
+
commandEnvironmentCreate.option('--public-url <url>', texts_1.OPTION_ENVIRONMENT_PUBLIC_URL);
|
|
19
|
+
commandEnvironmentCreate.option('--icon <icon>', texts_1.OPTION_ENVIRONMENT_ICON);
|
|
20
|
+
commandEnvironmentCreate.option('--note <note>', texts_1.OPT_COMMAND_NOTE);
|
|
21
|
+
commandEnvironmentCreate.option('--agent-note <note>', texts_1.OPT_COMMAND_AGENT_NOTE);
|
|
22
|
+
commandEnvironmentCreate.argument('[name]', texts_1.OPTION_ENVIRONMENT_NAME);
|
|
23
|
+
commandEnvironmentCreate.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_ENVIRONMENT_CREATE}`);
|
|
24
|
+
commandEnvironmentCreate.action(async (name, options) => {
|
|
25
|
+
const humanId = require('human-id').default;
|
|
26
|
+
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
27
|
+
const project = input_1.default.restApiProject(options.project, true);
|
|
28
|
+
const scope = input_1.default.scope(project, options.scope);
|
|
29
|
+
if (!name) {
|
|
30
|
+
if (options.identifier)
|
|
31
|
+
name = options.identifier;
|
|
32
|
+
else
|
|
33
|
+
name = humanId({ separator: '-', capitalize: false });
|
|
34
|
+
}
|
|
35
|
+
// identifier is required by the API; derive a slug from the name when
|
|
36
|
+
// -i is not given (hyphens cannot appear at the start or end)
|
|
37
|
+
const slug = name
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
40
|
+
.replace(/^-+|-+$/g, '');
|
|
41
|
+
const data = {
|
|
42
|
+
name,
|
|
43
|
+
identifier: options.identifier || slug,
|
|
44
|
+
scope,
|
|
45
|
+
};
|
|
46
|
+
if (project && scope === distro_1.SCOPE.PROJECT) {
|
|
47
|
+
data.project = {
|
|
48
|
+
name: project,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (options.tags) {
|
|
52
|
+
data.tags = input_1.default.tags(options.tags);
|
|
53
|
+
}
|
|
54
|
+
if (options.publicUrl) {
|
|
55
|
+
data.public_url = options.publicUrl;
|
|
56
|
+
}
|
|
57
|
+
if (options.icon) {
|
|
58
|
+
data.icon = options.icon;
|
|
59
|
+
}
|
|
60
|
+
if (options.note) {
|
|
61
|
+
data.note = options.note;
|
|
62
|
+
}
|
|
63
|
+
if (options.agentNote) {
|
|
64
|
+
data.agent_note = options.agentNote;
|
|
65
|
+
}
|
|
66
|
+
const client = input_1.default.restApiTokenClient();
|
|
67
|
+
const result = await client.createEnvironment(workspace, data);
|
|
68
|
+
output_1.default.exitSuccess((0, texts_1.TXT_ENVIRONMENT_CREATED)(result.identifier, result.html_url));
|
|
69
|
+
});
|
|
70
|
+
exports.default = commandEnvironmentCreate;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const utils_1 = require("../../utils");
|
|
7
|
+
const texts_1 = require("../../texts");
|
|
8
|
+
const output_1 = __importDefault(require("../../output"));
|
|
9
|
+
const input_1 = __importDefault(require("../../input"));
|
|
10
|
+
const resolve_1 = require("./resolve");
|
|
11
|
+
const commandEnvironmentDelete = (0, utils_1.newCommand)('delete', texts_1.DESC_COMMAND_ENVIRONMENT_DELETE);
|
|
12
|
+
commandEnvironmentDelete.alias('rm');
|
|
13
|
+
commandEnvironmentDelete.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
|
|
14
|
+
commandEnvironmentDelete.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
|
|
15
|
+
commandEnvironmentDelete.option('-f, --force', texts_1.OPTION_CONFIRM_FORCE);
|
|
16
|
+
commandEnvironmentDelete.argument('<identifier>', texts_1.OPTION_ENVIRONMENT_IDENTIFIER);
|
|
17
|
+
commandEnvironmentDelete.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_ENVIRONMENT_DELETE}`);
|
|
18
|
+
commandEnvironmentDelete.action(async (identifier, options) => {
|
|
19
|
+
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
20
|
+
const client = input_1.default.restApiTokenClient();
|
|
21
|
+
const environment_id = await (0, resolve_1.resolveEnvironmentId)(client, workspace, identifier, options,
|
|
22
|
+
/* strictProject */ true);
|
|
23
|
+
const confirmed = options.force ||
|
|
24
|
+
(await output_1.default.confirm((0, texts_1.TXT_ENVIRONMENT_DELETE_CONFIRM)(identifier)));
|
|
25
|
+
if (!confirmed)
|
|
26
|
+
output_1.default.exitNormal();
|
|
27
|
+
await client.deleteEnvironment(workspace, environment_id);
|
|
28
|
+
output_1.default.exitSuccess((0, texts_1.TXT_ENVIRONMENT_DELETED)(identifier));
|
|
29
|
+
});
|
|
30
|
+
exports.default = commandEnvironmentDelete;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const utils_1 = require("../../utils");
|
|
7
|
+
const texts_1 = require("../../texts");
|
|
8
|
+
const output_1 = __importDefault(require("../../output"));
|
|
9
|
+
const input_1 = __importDefault(require("../../input"));
|
|
10
|
+
const resolve_1 = require("./resolve");
|
|
11
|
+
const commandEnvironmentGet = (0, utils_1.newCommand)('get', texts_1.DESC_COMMAND_ENVIRONMENT_GET);
|
|
12
|
+
commandEnvironmentGet.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
|
|
13
|
+
commandEnvironmentGet.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
|
|
14
|
+
commandEnvironmentGet.option('--format <text|json>', texts_1.OPTION_FORMAT);
|
|
15
|
+
commandEnvironmentGet.argument('<identifier>', texts_1.OPTION_ENVIRONMENT_IDENTIFIER);
|
|
16
|
+
commandEnvironmentGet.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_ENVIRONMENT_GET}`);
|
|
17
|
+
commandEnvironmentGet.action(async (identifier, options) => {
|
|
18
|
+
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
19
|
+
const client = input_1.default.restApiTokenClient();
|
|
20
|
+
const environment_id = await (0, resolve_1.resolveEnvironmentId)(client, workspace, identifier, options);
|
|
21
|
+
const environment = await client.getEnvironment(workspace, environment_id);
|
|
22
|
+
if (options.format === 'json') {
|
|
23
|
+
output_1.default.json(environment);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const data = [
|
|
27
|
+
['Field', 'Value'],
|
|
28
|
+
['ID', environment.id || '-'],
|
|
29
|
+
['Identifier', environment.identifier || '-'],
|
|
30
|
+
['Name', environment.name || '-'],
|
|
31
|
+
['Scope', environment.scope || '-'],
|
|
32
|
+
['Tags', (environment.tags || []).join(', ') || '-'],
|
|
33
|
+
['Public URL', environment.public_url || '-'],
|
|
34
|
+
['Icon', environment.icon || '-'],
|
|
35
|
+
['Note', environment.note || '-'],
|
|
36
|
+
['Agent note', environment.agent_note || '-'],
|
|
37
|
+
];
|
|
38
|
+
if (environment.project) {
|
|
39
|
+
data.push(['Project', environment.project.name || '-']);
|
|
40
|
+
}
|
|
41
|
+
if (environment.html_url) {
|
|
42
|
+
data.push(['URL', environment.html_url]);
|
|
43
|
+
}
|
|
44
|
+
output_1.default.table(data);
|
|
45
|
+
}
|
|
46
|
+
output_1.default.exitNormal();
|
|
47
|
+
});
|
|
48
|
+
exports.default = commandEnvironmentGet;
|