@get-dx/cli 0.3.3 → 0.3.5
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.
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
████████████████
|
|
6
|
+
█████████████████
|
|
7
|
+
████████████████
|
|
8
|
+
██████ ████████
|
|
9
|
+
██████ ███████
|
|
10
|
+
██████ ███████
|
|
11
|
+
██████ ███████
|
|
12
|
+
██████ ███████
|
|
13
|
+
██████ ███████
|
|
14
|
+
██████ ███████
|
|
15
|
+
██████ ████████
|
|
16
|
+
██████████████████████
|
|
17
|
+
████████████████████
|
|
18
|
+
█████████████████
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
package/assets/logo.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
████████████████ ███████ ████████
|
|
6
|
+
█████████████████ ███████ ███████
|
|
7
|
+
████████████████ ███████ ███████
|
|
8
|
+
██████ ████████ ████ ████████
|
|
9
|
+
██████ ███████ ██ ███████
|
|
10
|
+
██████ ███████ ███████
|
|
11
|
+
██████ ███████ ████████
|
|
12
|
+
██████ ███████ ███████
|
|
13
|
+
██████ ███████ ███████
|
|
14
|
+
██████ ███████ ████████ █
|
|
15
|
+
██████ ████████ ████████ █████
|
|
16
|
+
██████████████████████ ███████ ██████
|
|
17
|
+
████████████████████ ████████ ███████
|
|
18
|
+
█████████████████ ████████ ████████
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { input, select, confirm, search } from "@inquirer/prompts";
|
|
2
|
+
import { listTeams } from "../teams.js";
|
|
3
|
+
import { CliError, EXIT_CODES } from "../../errors.js";
|
|
4
|
+
import { renderRichText } from "../../renderers.js";
|
|
5
|
+
import * as ui from "../../ui.js";
|
|
6
|
+
export async function promptForParameterValue(runtime, param) {
|
|
7
|
+
const message = param.description
|
|
8
|
+
? `${param.name}: ${param.description}`
|
|
9
|
+
: param.name;
|
|
10
|
+
switch (param.type) {
|
|
11
|
+
case "STRING": {
|
|
12
|
+
const rawValue = await input({
|
|
13
|
+
message,
|
|
14
|
+
default: param.default_value
|
|
15
|
+
? param.default_value
|
|
16
|
+
: undefined,
|
|
17
|
+
validate: (value) => {
|
|
18
|
+
if (value === "" && !param.is_required) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
else if (value === "") {
|
|
22
|
+
return "Value is required";
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
return rawValue === "" ? undefined : rawValue;
|
|
28
|
+
}
|
|
29
|
+
case "INTEGER": {
|
|
30
|
+
const rawValue = await input({
|
|
31
|
+
message,
|
|
32
|
+
default: param.default_value
|
|
33
|
+
? param.default_value
|
|
34
|
+
: undefined,
|
|
35
|
+
validate: (value) => {
|
|
36
|
+
if (value === "" && !param.is_required) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
else if (value === "") {
|
|
40
|
+
return "Value is required";
|
|
41
|
+
}
|
|
42
|
+
const num = Number(value);
|
|
43
|
+
if (isNaN(num)) {
|
|
44
|
+
return "Value must be a number";
|
|
45
|
+
}
|
|
46
|
+
else if (num % 1 !== 0) {
|
|
47
|
+
return "Value must be an integer";
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
return rawValue === "" ? undefined : Number(rawValue);
|
|
55
|
+
}
|
|
56
|
+
case "FLOAT": {
|
|
57
|
+
const rawValue = await input({
|
|
58
|
+
message,
|
|
59
|
+
default: param.default_value
|
|
60
|
+
? param.default_value
|
|
61
|
+
: undefined,
|
|
62
|
+
validate: (value) => {
|
|
63
|
+
if (value === "" && !param.is_required) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
else if (value === "") {
|
|
67
|
+
return "Value is required";
|
|
68
|
+
}
|
|
69
|
+
const num = Number(value);
|
|
70
|
+
if (isNaN(num)) {
|
|
71
|
+
return "Value must be a number";
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
return rawValue === "" ? undefined : Number(rawValue);
|
|
79
|
+
}
|
|
80
|
+
case "BOOLEAN":
|
|
81
|
+
return await confirm({
|
|
82
|
+
message,
|
|
83
|
+
default: false,
|
|
84
|
+
});
|
|
85
|
+
case "SELECT": {
|
|
86
|
+
return await select({
|
|
87
|
+
message,
|
|
88
|
+
default: param.default_value
|
|
89
|
+
? param.default_value
|
|
90
|
+
: undefined,
|
|
91
|
+
choices: param.definition.options.map((option) => ({
|
|
92
|
+
name: option,
|
|
93
|
+
value: option,
|
|
94
|
+
})),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
case "USER":
|
|
98
|
+
throw new CliError("User-based parameters are not yet supported in the CLI", EXIT_CODES.ARGUMENT_ERROR);
|
|
99
|
+
case "TEAM": {
|
|
100
|
+
renderRichText([ui.p("Fetching teams...")]);
|
|
101
|
+
const teamsResponse = await listTeams(runtime);
|
|
102
|
+
const teams = teamsResponse.teams;
|
|
103
|
+
const teamId = await search({
|
|
104
|
+
message,
|
|
105
|
+
source: (term) => teams
|
|
106
|
+
.filter((team) => term === undefined ||
|
|
107
|
+
team.name.toLowerCase().includes(term.toLowerCase()))
|
|
108
|
+
.map((team) => ({
|
|
109
|
+
name: team.name,
|
|
110
|
+
value: team.id,
|
|
111
|
+
})),
|
|
112
|
+
});
|
|
113
|
+
// TODO: change the trigger endpoint to accept encoded IDs too
|
|
114
|
+
const decodedTeamId = decodeBase64(teamId);
|
|
115
|
+
return decodedTeamId;
|
|
116
|
+
}
|
|
117
|
+
case "EMAIL": {
|
|
118
|
+
const rawValue = await input({
|
|
119
|
+
message,
|
|
120
|
+
default: param.default_value
|
|
121
|
+
? param.default_value
|
|
122
|
+
: undefined,
|
|
123
|
+
validate: (value) => {
|
|
124
|
+
if (value === "" && !param.is_required) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
else if (value === "") {
|
|
128
|
+
return "Value is required";
|
|
129
|
+
}
|
|
130
|
+
else if (!isValidEmail(value)) {
|
|
131
|
+
return "Value must be a valid email address";
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
return rawValue === "" ? undefined : rawValue;
|
|
137
|
+
}
|
|
138
|
+
default:
|
|
139
|
+
throw new CliError(`Unknown parameter type: ${param.type}`, EXIT_CODES.ARGUMENT_ERROR);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export function renderParameterData(parameters, data) {
|
|
143
|
+
const items = [];
|
|
144
|
+
for (const parameter of parameters) {
|
|
145
|
+
items.push(ui.p(parameter.description
|
|
146
|
+
? `${ui.bold(parameter.name)} ${ui.dim(parameter.description)}`
|
|
147
|
+
: ui.bold(parameter.name), false));
|
|
148
|
+
if (data[parameter.identifier] !== undefined) {
|
|
149
|
+
items.push(ui.p(` ${ui.code(String(data[parameter.identifier]))}`));
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
items.push(ui.p(` ${ui.dim("(none)")}`));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
items.push(ui.blankLine());
|
|
156
|
+
renderRichText(items, { useStderr: true });
|
|
157
|
+
}
|
|
158
|
+
function decodeBase64(value) {
|
|
159
|
+
return Buffer.from(value, "base64").toString("utf-8");
|
|
160
|
+
}
|
|
161
|
+
function isValidEmail(value) {
|
|
162
|
+
return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value);
|
|
163
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { confirm } from "@inquirer/prompts";
|
|
3
|
+
import { createExampleText, getContext, wrapAction, } from "../../commandHelpers.js";
|
|
4
|
+
import { CliError, EXIT_CODES, HttpError } from "../../errors.js";
|
|
5
|
+
import { request } from "../../http.js";
|
|
6
|
+
import { renderJson, renderRichText } from "../../renderers.js";
|
|
7
|
+
import { buildRuntime } from "../../runtime.js";
|
|
8
|
+
import * as ui from "../../ui.js";
|
|
9
|
+
import { listWorkflows } from "../workflows.js";
|
|
10
|
+
import { promptForParameterValue, renderParameterData } from "./parameters.js";
|
|
11
|
+
import { getWorkflowRun, renderWorkflowRunSummary, } from "./info.js";
|
|
12
|
+
import { workflowRunEventContent } from "./events.js";
|
|
13
|
+
const POLL_INTERVAL_MS = 1000;
|
|
14
|
+
const PENDING_WORKFLOW_RUN_STATUSES = new Set([
|
|
15
|
+
"PENDING_RUN",
|
|
16
|
+
"IN_PROGRESS",
|
|
17
|
+
"WAITING_FOR_APPROVAL",
|
|
18
|
+
"WAITING_FOR_EVENTS",
|
|
19
|
+
]);
|
|
20
|
+
const TERMINAL_SUCCESS = new Set(["SUCCEEDED"]);
|
|
21
|
+
const TERMINAL_FAILURE = new Set([
|
|
22
|
+
"FAILED",
|
|
23
|
+
"REJECTED",
|
|
24
|
+
"TIMEOUT",
|
|
25
|
+
"CANCELLED",
|
|
26
|
+
]);
|
|
27
|
+
export function triggerCommand() {
|
|
28
|
+
return new Command()
|
|
29
|
+
.name("trigger")
|
|
30
|
+
.description("Trigger a Self-service workflow run and wait until it succeeds or fails. If the workflow requires approval, this command will submit a request and optionally notify approvers.")
|
|
31
|
+
.argument("<workflow-identifier>", "Workflow identifier (from dx workflows list)")
|
|
32
|
+
.option("--entity <identifier>", "Catalog entity identifier (required for entity-scoped workflows)")
|
|
33
|
+
.option("--param <key=value>", "Workflow parameter value (repeatable). Value after the first '=' is kept verbatim", (val, prev) => [...prev, val], [])
|
|
34
|
+
.option("--notify-approvers <boolean>", "For workflows that require approval, send a message to each approver requesting review")
|
|
35
|
+
.addHelpText("afterAll", createExampleText([
|
|
36
|
+
{
|
|
37
|
+
label: "Trigger a global workflow and wait for completion",
|
|
38
|
+
command: "dx workflowRuns trigger my-global-workflow",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
label: "Trigger with an entity and parameters",
|
|
42
|
+
command: 'dx workflowRuns trigger provision-db --entity acme-api --param size=small --param "greeting=hello world"',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: "Trigger with machine-readable output",
|
|
46
|
+
command: "dx workflowRuns trigger my-workflow --json --entity svc-1 --param count=3",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
label: "Request approval to trigger a workflow but do not notify approvers",
|
|
50
|
+
command: "dx workflowRuns trigger workflow-requiring-approval --notify-approvers false",
|
|
51
|
+
},
|
|
52
|
+
]))
|
|
53
|
+
.action(wrapAction(async (workflowIdentifier, options, command) => {
|
|
54
|
+
const context = getContext(command);
|
|
55
|
+
const runtime = buildRuntime(context);
|
|
56
|
+
// Fetch list of all workflows, find the one that applies
|
|
57
|
+
const workflowsResponse = await listWorkflows(runtime, {});
|
|
58
|
+
const workflow = workflowsResponse.workflows.find((w) => w.identifier === workflowIdentifier);
|
|
59
|
+
if (!workflow) {
|
|
60
|
+
throw new CliError(`Workflow \`${workflowIdentifier}\` not found`, EXIT_CODES.ARGUMENT_ERROR);
|
|
61
|
+
}
|
|
62
|
+
const requiresApproval = workflow.trigger_type === "APPROVAL";
|
|
63
|
+
const entityIdentifier = parseOptionalTrimmed(options.entity);
|
|
64
|
+
if (entityIdentifier === undefined && workflow.scope === "ENTITY") {
|
|
65
|
+
throw new CliError("--entity is required for entity-scoped workflows", EXIT_CODES.ARGUMENT_ERROR);
|
|
66
|
+
}
|
|
67
|
+
// Collect parameter data
|
|
68
|
+
const parameterData = parseParameterData(options.param);
|
|
69
|
+
if (isInteractive()) {
|
|
70
|
+
for (const param of workflow.parameters) {
|
|
71
|
+
if (parameterData[param.identifier] !== undefined) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
parameterData[param.identifier] = await promptForParameterValue(runtime, param);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
let notifyApprovers = true;
|
|
78
|
+
if (requiresApproval && options.notifyApprovers !== undefined) {
|
|
79
|
+
notifyApprovers = options.notifyApprovers === "true";
|
|
80
|
+
}
|
|
81
|
+
else if (requiresApproval && isInteractive()) {
|
|
82
|
+
notifyApprovers = await confirm({
|
|
83
|
+
message: "Notify approvers?",
|
|
84
|
+
default: true,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (isInteractive()) {
|
|
88
|
+
// Confirm everything looks right
|
|
89
|
+
renderRichText([ui.blankLine(), ui.h3("Confirm"), ui.blankLine()], {
|
|
90
|
+
useStderr: true,
|
|
91
|
+
});
|
|
92
|
+
if (entityIdentifier !== undefined) {
|
|
93
|
+
renderRichText([
|
|
94
|
+
ui.p(`${ui.bold("Entity")}`, false),
|
|
95
|
+
ui.p(` ${ui.code(entityIdentifier)}`),
|
|
96
|
+
ui.blankLine(),
|
|
97
|
+
], {
|
|
98
|
+
useStderr: true,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (workflow.parameters.length > 0) {
|
|
102
|
+
renderParameterData(workflow.parameters, parameterData);
|
|
103
|
+
}
|
|
104
|
+
const confirmationMessage = requiresApproval
|
|
105
|
+
? "Request approval to trigger this workflow run with these settings?"
|
|
106
|
+
: "Trigger this workflow run with these settings?";
|
|
107
|
+
const isConfirmed = await confirm({
|
|
108
|
+
message: confirmationMessage,
|
|
109
|
+
default: true,
|
|
110
|
+
});
|
|
111
|
+
if (!isConfirmed) {
|
|
112
|
+
throw new CliError("Workflow run not confirmed", EXIT_CODES.RETRY_RECOMMENDED);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Create the request body
|
|
116
|
+
const triggerRequestBody = {
|
|
117
|
+
workflow_identifier: workflowIdentifier.trim(),
|
|
118
|
+
};
|
|
119
|
+
if (entityIdentifier !== undefined) {
|
|
120
|
+
triggerRequestBody.entity_identifier = entityIdentifier;
|
|
121
|
+
}
|
|
122
|
+
if (Object.keys(parameterData).length > 0) {
|
|
123
|
+
triggerRequestBody.data = parameterData;
|
|
124
|
+
}
|
|
125
|
+
if (requiresApproval) {
|
|
126
|
+
triggerRequestBody.notify_approvers = notifyApprovers;
|
|
127
|
+
}
|
|
128
|
+
// Trigger the workflow
|
|
129
|
+
let runId;
|
|
130
|
+
try {
|
|
131
|
+
const triggerResponse = await triggerWorkflowRun(runtime, triggerRequestBody);
|
|
132
|
+
runId = triggerResponse.body.workflow_run.id;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
renderRichText([
|
|
136
|
+
ui.p(`${ui.error(ui.GLYPHS.ERROR)} Failed to trigger workflow run.`),
|
|
137
|
+
], { useStderr: true });
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
renderRichText([
|
|
141
|
+
ui.blankLine(),
|
|
142
|
+
ui.p(`Workflow run ${ui.code(runId)} triggered.`, false),
|
|
143
|
+
ui.p(`Web link: ${ui.link(ui.webLink(`/self-service/workflow-runs/${runId}`, runtime))}`),
|
|
144
|
+
ui.blankLine(),
|
|
145
|
+
], { useStderr: true });
|
|
146
|
+
// Poll for updates
|
|
147
|
+
try {
|
|
148
|
+
await sleep(POLL_INTERVAL_MS);
|
|
149
|
+
renderRichText([ui.h3("Events"), ui.blankLine()], {
|
|
150
|
+
useStderr: true,
|
|
151
|
+
});
|
|
152
|
+
const finalDetail = await pollForWorkflowRunInfo(runtime, runId);
|
|
153
|
+
if (runtime.context.json) {
|
|
154
|
+
renderJson({ ok: true, workflow_run: finalDetail });
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
renderRichText([
|
|
158
|
+
ui.blankLine(),
|
|
159
|
+
ui.p(`${ui.success(ui.GLYPHS.CHECK)} Workflow run ${ui.code(finalDetail.id)} completed with status ${ui.bold(finalDetail.status)}.`),
|
|
160
|
+
ui.blankLine(),
|
|
161
|
+
]);
|
|
162
|
+
renderWorkflowRunSummary(finalDetail, runtime);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
renderRichText([
|
|
167
|
+
ui.blankLine(),
|
|
168
|
+
ui.p(`${ui.error(ui.GLYPHS.ERROR)} Workflow run failed.`),
|
|
169
|
+
], { useStderr: true });
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
// TODO: move somewhere central
|
|
175
|
+
function isInteractive() {
|
|
176
|
+
return process.stdin.isTTY && process.stdout.isTTY && process.stderr.isTTY;
|
|
177
|
+
}
|
|
178
|
+
function parseOptionalTrimmed(value) {
|
|
179
|
+
if (typeof value !== "string") {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
const t = value.trim();
|
|
183
|
+
return t.length > 0 ? t : undefined;
|
|
184
|
+
}
|
|
185
|
+
function parseParameterData(pairs) {
|
|
186
|
+
if (pairs.length === 0) {
|
|
187
|
+
return {};
|
|
188
|
+
}
|
|
189
|
+
const result = {};
|
|
190
|
+
for (const pair of pairs) {
|
|
191
|
+
const eqIdx = pair.indexOf("=");
|
|
192
|
+
if (eqIdx === -1) {
|
|
193
|
+
throw new CliError(`Invalid --param format: "${pair}". Expected key=value.`, EXIT_CODES.ARGUMENT_ERROR);
|
|
194
|
+
}
|
|
195
|
+
const key = pair.slice(0, eqIdx).trim();
|
|
196
|
+
if (!key) {
|
|
197
|
+
throw new CliError(`Invalid --param format: "${pair}". Parameter name cannot be empty.`, EXIT_CODES.ARGUMENT_ERROR);
|
|
198
|
+
}
|
|
199
|
+
const raw = pair.slice(eqIdx + 1);
|
|
200
|
+
result[key] = coerceParamValue(raw);
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
function coerceParamValue(raw) {
|
|
205
|
+
const trimmed = raw.trim();
|
|
206
|
+
if (trimmed === "true") {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
if (trimmed === "false") {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
if (/^-?\d+$/.test(trimmed)) {
|
|
213
|
+
return Number(trimmed);
|
|
214
|
+
}
|
|
215
|
+
if (/^-?\d+\.\d+$/.test(trimmed)) {
|
|
216
|
+
return Number(trimmed);
|
|
217
|
+
}
|
|
218
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
219
|
+
try {
|
|
220
|
+
return JSON.parse(trimmed);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
throw new CliError(`Invalid JSON in --param value: ${trimmed.slice(0, 80)}${trimmed.length > 80 ? "…" : ""}`, EXIT_CODES.ARGUMENT_ERROR);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return raw;
|
|
227
|
+
}
|
|
228
|
+
async function triggerWorkflowRun(runtime, body) {
|
|
229
|
+
return request(runtime, "/workflowRuns.trigger", {
|
|
230
|
+
method: "POST",
|
|
231
|
+
body,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
async function pollForWorkflowRunInfo(runtime, workflowRunId) {
|
|
235
|
+
const seenEventIds = new Set();
|
|
236
|
+
while (true) {
|
|
237
|
+
try {
|
|
238
|
+
const infoResponse = await getWorkflowRun(runtime, workflowRunId);
|
|
239
|
+
const workflowRun = infoResponse.body.workflow_run;
|
|
240
|
+
if (workflowRun.events) {
|
|
241
|
+
for (const event of workflowRun.events) {
|
|
242
|
+
if (!seenEventIds.has(event.id)) {
|
|
243
|
+
renderRichText([workflowRunEventContent(event), ui.blankLine()], {
|
|
244
|
+
useStderr: true,
|
|
245
|
+
});
|
|
246
|
+
seenEventIds.add(event.id);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (TERMINAL_SUCCESS.has(workflowRun.status)) {
|
|
251
|
+
return workflowRun;
|
|
252
|
+
}
|
|
253
|
+
if (TERMINAL_FAILURE.has(workflowRun.status)) {
|
|
254
|
+
throw buildTerminalStatusError(workflowRun);
|
|
255
|
+
}
|
|
256
|
+
if (!PENDING_WORKFLOW_RUN_STATUSES.has(workflowRun.status)) {
|
|
257
|
+
throw new CliError(`Unexpected workflow run status: ${workflowRun.status}`, 1);
|
|
258
|
+
}
|
|
259
|
+
// progress.update(
|
|
260
|
+
// `${ui.bold("Workflow running")} ${ui.dim(`(${workflowRunId})`)} — ${workflowRun.status}`,
|
|
261
|
+
// );
|
|
262
|
+
await sleep(POLL_INTERVAL_MS);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
if (error instanceof HttpError && error.status === 429) {
|
|
266
|
+
// progress.update(
|
|
267
|
+
// `${ui.warning(ui.GLYPHS.WARNING)} Rate limited while polling ${ui.dim(`(${workflowRunId})`)}; retrying`,
|
|
268
|
+
// );
|
|
269
|
+
await sleep(POLL_INTERVAL_MS * 5);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function buildTerminalStatusError(run) {
|
|
277
|
+
if (run.status === "FAILED") {
|
|
278
|
+
return new CliError(`Workflow run ${run.id} failed.`, 1);
|
|
279
|
+
}
|
|
280
|
+
return new CliError(`Workflow run ${run.id} ended with status ${run.status}.`, 1);
|
|
281
|
+
}
|
|
282
|
+
async function sleep(ms) {
|
|
283
|
+
await new Promise((resolve) => {
|
|
284
|
+
setTimeout(resolve, ms);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
@@ -3,6 +3,7 @@ import { addLinkCommand } from "./workflowRuns/addLink.js";
|
|
|
3
3
|
import { changeStatusCommand } from "./workflowRuns/changeStatus.js";
|
|
4
4
|
import { infoCommand } from "./workflowRuns/info.js";
|
|
5
5
|
import { postMessageCommand } from "./workflowRuns/postMessage.js";
|
|
6
|
+
import { triggerCommand } from "./workflowRuns/trigger.js";
|
|
6
7
|
export function workflowRunsCommand() {
|
|
7
8
|
const workflowRuns = new Command()
|
|
8
9
|
.name("workflowRuns")
|
|
@@ -11,5 +12,6 @@ export function workflowRunsCommand() {
|
|
|
11
12
|
workflowRuns.addCommand(changeStatusCommand());
|
|
12
13
|
workflowRuns.addCommand(infoCommand());
|
|
13
14
|
workflowRuns.addCommand(postMessageCommand());
|
|
15
|
+
workflowRuns.addCommand(triggerCommand());
|
|
14
16
|
return workflowRuns;
|
|
15
17
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@get-dx/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "DX CLI",
|
|
5
5
|
"author": "DX <developers@getdx.com>",
|
|
6
6
|
"bugs": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"dx": "dist/index.js"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
|
+
"assets",
|
|
14
15
|
"dist"
|
|
15
16
|
],
|
|
16
17
|
"homepage": "https://github.com/get-dx/cli#readme",
|