@trayai/tray-sync-cli 1.0.11 → 1.0.13
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/dist/api/resources/project.js +11 -0
- package/dist/api/resources/workflow.js +38 -0
- package/dist/commands/api/api.js +2 -0
- package/dist/commands/api/project.js +37 -2
- package/dist/commands/api/workflow.js +166 -0
- package/dist/views/api/project.v2.js +18 -0
- package/dist/views/api/workflow.v2.js +21 -0
- package/package.json +1 -1
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
export async function rollbackProject(client, projectId, versionNumber) {
|
|
2
2
|
await client.request("POST", `/v1/projects/${projectId}/rollbacks/versions/${versionNumber}`);
|
|
3
3
|
}
|
|
4
|
+
export async function listProjectVersions(client, projectId, options = {}) {
|
|
5
|
+
const params = new URLSearchParams();
|
|
6
|
+
if (options.last !== undefined)
|
|
7
|
+
params.set("last", String(options.last));
|
|
8
|
+
const query = params.size > 0 ? `?${params}` : "";
|
|
9
|
+
const result = (await client.request("GET", `/v1/projects/${projectId}/versions${query}`));
|
|
10
|
+
return {
|
|
11
|
+
versions: result.elements ?? [],
|
|
12
|
+
nextCursor: result.pageInfo?.endCursor ?? undefined,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
4
15
|
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export async function getWorkflow(client, workflowId) {
|
|
2
|
+
return (await client.request("GET", `/v2/workflows/${workflowId}`));
|
|
3
|
+
}
|
|
4
|
+
export async function setWorkflowEnabled(client, workflowId, enabled) {
|
|
5
|
+
await client.request("PUT", `/v2/workflows/${workflowId}/unversioned`, { enabled });
|
|
6
|
+
}
|
|
7
|
+
export async function deleteWorkflow(client, workflowId) {
|
|
8
|
+
await client.request("DELETE", `/v2/workflows/${workflowId}`);
|
|
9
|
+
}
|
|
10
|
+
const EDITABLE_FIELDS = new Set([
|
|
11
|
+
"name",
|
|
12
|
+
"description",
|
|
13
|
+
"tags",
|
|
14
|
+
"alerting_workflow",
|
|
15
|
+
"legacy_error_handling",
|
|
16
|
+
]);
|
|
17
|
+
export function assertValidWorkflowEditPayload(payload) {
|
|
18
|
+
const unknownKeys = Object.keys(payload).filter((key) => !EDITABLE_FIELDS.has(key));
|
|
19
|
+
if (unknownKeys.length > 0) {
|
|
20
|
+
throw new Error(`Unknown field(s) in edit payload: ${unknownKeys.join(", ")}. ` +
|
|
21
|
+
`Editable fields are: ${[...EDITABLE_FIELDS].join(", ")}.`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function updateWorkflowUnversioned(client, workflowId, payload) {
|
|
25
|
+
const body = {};
|
|
26
|
+
if (payload.name !== undefined)
|
|
27
|
+
body.name = payload.name;
|
|
28
|
+
if (payload.description !== undefined)
|
|
29
|
+
body.description = { value: payload.description };
|
|
30
|
+
if (payload.tags !== undefined)
|
|
31
|
+
body.tags = payload.tags;
|
|
32
|
+
if (payload.alerting_workflow !== undefined)
|
|
33
|
+
body.alerting_workflow = { value: payload.alerting_workflow };
|
|
34
|
+
if (payload.legacy_error_handling !== undefined)
|
|
35
|
+
body.legacy_error_handling = payload.legacy_error_handling;
|
|
36
|
+
await client.request("PUT", `/v2/workflows/${workflowId}/unversioned`, body);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=workflow.js.map
|
package/dist/commands/api/api.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { projectCommand } from "./project.js";
|
|
3
|
+
import { workflowCommand } from "./workflow.js";
|
|
3
4
|
export const apiCommand = new Command("api").description("Direct, authenticated access to the Tray API");
|
|
4
5
|
apiCommand.addCommand(projectCommand);
|
|
6
|
+
apiCommand.addCommand(workflowCommand);
|
|
5
7
|
//# sourceMappingURL=api.js.map
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { Command, InvalidArgumentError } from "commander";
|
|
2
|
-
import { rollbackProject } from "../../api/resources/project.js";
|
|
2
|
+
import { listProjectVersions, rollbackProject } from "../../api/resources/project.js";
|
|
3
3
|
import { isUuid } from "../../lib/uuid.js";
|
|
4
4
|
import { parseVersionNumber } from "../../lib/versionNumber.js";
|
|
5
5
|
import { OutputFormats } from "../../lib/outputFormat.js";
|
|
6
|
-
import { printProjectRollbackResult } from "../../views/api/project.v2.js";
|
|
6
|
+
import { printProjectRollbackResult, printProjectVersionListTable } from "../../views/api/project.v2.js";
|
|
7
7
|
import { printConfirmationJson } from "../../views/api/shared.js";
|
|
8
8
|
import { addCommonApiOptions, apiClientFor, realDeps, runApiAction, } from "./shared.js";
|
|
9
|
+
const DEFAULT_VERSION_LIST_LIMIT = 5;
|
|
10
|
+
function parseLimit(value) {
|
|
11
|
+
const limit = Number(value);
|
|
12
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
13
|
+
throw new InvalidArgumentError("--limit must be a positive integer");
|
|
14
|
+
}
|
|
15
|
+
return limit;
|
|
16
|
+
}
|
|
9
17
|
function parseProjectId(value) {
|
|
10
18
|
if (!isUuid(value)) {
|
|
11
19
|
throw new InvalidArgumentError("project id must be a UUID");
|
|
@@ -27,6 +35,19 @@ export async function runProjectRollback(deps, options) {
|
|
|
27
35
|
}
|
|
28
36
|
return 0;
|
|
29
37
|
}
|
|
38
|
+
export async function runProjectVersionList(deps, options) {
|
|
39
|
+
const client = apiClientFor(deps, options);
|
|
40
|
+
const result = await listProjectVersions(client, options.projectId, {
|
|
41
|
+
last: options.limit ?? DEFAULT_VERSION_LIST_LIMIT,
|
|
42
|
+
});
|
|
43
|
+
if (options.format === OutputFormats.JSON) {
|
|
44
|
+
deps.stdout(JSON.stringify(result, null, 2));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
printProjectVersionListTable(deps.stdout, result.versions);
|
|
48
|
+
}
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
30
51
|
export const projectCommand = new Command("project").description("Manage projects");
|
|
31
52
|
const versionCommand = new Command("version").description("Manage project versions");
|
|
32
53
|
projectCommand.addCommand(versionCommand);
|
|
@@ -44,4 +65,18 @@ addCommonApiOptions(versionCommand
|
|
|
44
65
|
format: opts.format,
|
|
45
66
|
}));
|
|
46
67
|
});
|
|
68
|
+
addCommonApiOptions(versionCommand
|
|
69
|
+
.command("list")
|
|
70
|
+
.description("List a project's most recent versions")
|
|
71
|
+
.argument("<project-id>", "Tray project UUID", parseProjectId)
|
|
72
|
+
.option("--limit <n>", `most recent versions to show (default ${DEFAULT_VERSION_LIST_LIMIT})`, parseLimit)).action(async (projectId, opts) => {
|
|
73
|
+
const deps = realDeps();
|
|
74
|
+
await runApiAction(deps, () => runProjectVersionList(deps, {
|
|
75
|
+
projectId,
|
|
76
|
+
limit: opts.limit,
|
|
77
|
+
region: opts.region,
|
|
78
|
+
token: opts.token,
|
|
79
|
+
format: opts.format,
|
|
80
|
+
}));
|
|
81
|
+
});
|
|
47
82
|
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Command, InvalidArgumentError } from "commander";
|
|
2
|
+
import ansis from "ansis";
|
|
3
|
+
import { assertValidWorkflowEditPayload, deleteWorkflow, getWorkflow, setWorkflowEnabled, updateWorkflowUnversioned, } from "../../api/resources/workflow.js";
|
|
4
|
+
import { isUuid } from "../../lib/uuid.js";
|
|
5
|
+
import { OutputFormats } from "../../lib/outputFormat.js";
|
|
6
|
+
import { printWorkflowDeletedResult, printWorkflowEditedResult, printWorkflowEnabledResult, printWorkflowShowResult, } from "../../views/api/workflow.v2.js";
|
|
7
|
+
import { printConfirmationJson } from "../../views/api/shared.js";
|
|
8
|
+
import { FAILURE_ICON } from "../../views/icons.js";
|
|
9
|
+
import { COMMAND_COLOR } from "../../views/help.js";
|
|
10
|
+
import { addCommonApiOptions, apiClientFor, realDeps, runApiAction, } from "./shared.js";
|
|
11
|
+
async function runWorkflowSetEnabled(deps, options, enabled) {
|
|
12
|
+
const client = apiClientFor(deps, options);
|
|
13
|
+
await setWorkflowEnabled(client, options.workflowId, enabled);
|
|
14
|
+
if (options.format === OutputFormats.JSON) {
|
|
15
|
+
printConfirmationJson(deps.stdout, { workflowId: options.workflowId, enabled });
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
printWorkflowEnabledResult(deps.stdout, options.workflowId, enabled);
|
|
19
|
+
}
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
export async function runWorkflowEnable(deps, options) {
|
|
23
|
+
return runWorkflowSetEnabled(deps, options, true);
|
|
24
|
+
}
|
|
25
|
+
export async function runWorkflowDisable(deps, options) {
|
|
26
|
+
return runWorkflowSetEnabled(deps, options, false);
|
|
27
|
+
}
|
|
28
|
+
export async function runWorkflowShow(deps, options) {
|
|
29
|
+
const client = apiClientFor(deps, options);
|
|
30
|
+
const workflow = await getWorkflow(client, options.workflowId);
|
|
31
|
+
if (options.format === OutputFormats.JSON) {
|
|
32
|
+
deps.stdout(JSON.stringify(workflow, null, 2));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
printWorkflowShowResult(deps.stdout, options.workflowId, workflow);
|
|
36
|
+
}
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
export async function runWorkflowDelete(deps, options) {
|
|
40
|
+
const client = apiClientFor(deps, options);
|
|
41
|
+
await deleteWorkflow(client, options.workflowId);
|
|
42
|
+
if (options.format === OutputFormats.JSON) {
|
|
43
|
+
printConfirmationJson(deps.stdout, { workflowId: options.workflowId, deleted: true });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
printWorkflowDeletedResult(deps.stdout, options.workflowId);
|
|
47
|
+
}
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
export async function runWorkflowEdit(deps, options) {
|
|
51
|
+
if (Object.keys(options.payload).length === 0) {
|
|
52
|
+
deps.stderr(`${FAILURE_ICON} Nothing to edit — payload has no fields.`);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
const client = apiClientFor(deps, options);
|
|
56
|
+
await updateWorkflowUnversioned(client, options.workflowId, options.payload);
|
|
57
|
+
if (options.format === OutputFormats.JSON) {
|
|
58
|
+
printConfirmationJson(deps.stdout, { workflowId: options.workflowId, edited: true });
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
printWorkflowEditedResult(deps.stdout, options.workflowId);
|
|
62
|
+
}
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
function parseWorkflowId(value) {
|
|
66
|
+
if (!isUuid(value)) {
|
|
67
|
+
throw new InvalidArgumentError("workflow id must be a UUID");
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
function parseEditPayload(value) {
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = JSON.parse(value);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
throw new InvalidArgumentError(`payload is not valid JSON: ${err.message}`);
|
|
78
|
+
}
|
|
79
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
80
|
+
throw new InvalidArgumentError("payload must be a JSON object");
|
|
81
|
+
}
|
|
82
|
+
const record = parsed;
|
|
83
|
+
try {
|
|
84
|
+
assertValidWorkflowEditPayload(record);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
throw new InvalidArgumentError(err.message);
|
|
88
|
+
}
|
|
89
|
+
return record;
|
|
90
|
+
}
|
|
91
|
+
export const workflowCommand = new Command("workflow").description("Manage workflows");
|
|
92
|
+
addCommonApiOptions(workflowCommand
|
|
93
|
+
.command("enable")
|
|
94
|
+
.description("Enable a workflow")
|
|
95
|
+
.argument("<workflow-id>", "Tray workflow UUID", parseWorkflowId)).action(async (workflowId, opts) => {
|
|
96
|
+
const deps = realDeps();
|
|
97
|
+
await runApiAction(deps, () => runWorkflowEnable(deps, {
|
|
98
|
+
workflowId,
|
|
99
|
+
region: opts.region,
|
|
100
|
+
token: opts.token,
|
|
101
|
+
format: opts.format,
|
|
102
|
+
}));
|
|
103
|
+
});
|
|
104
|
+
addCommonApiOptions(workflowCommand
|
|
105
|
+
.command("disable")
|
|
106
|
+
.description("Disable a workflow")
|
|
107
|
+
.argument("<workflow-id>", "Tray workflow UUID", parseWorkflowId)).action(async (workflowId, opts) => {
|
|
108
|
+
const deps = realDeps();
|
|
109
|
+
await runApiAction(deps, () => runWorkflowDisable(deps, {
|
|
110
|
+
workflowId,
|
|
111
|
+
region: opts.region,
|
|
112
|
+
token: opts.token,
|
|
113
|
+
format: opts.format,
|
|
114
|
+
}));
|
|
115
|
+
});
|
|
116
|
+
addCommonApiOptions(workflowCommand
|
|
117
|
+
.command("show")
|
|
118
|
+
.description("Show a workflow")
|
|
119
|
+
.argument("<workflow-id>", "Tray workflow UUID", parseWorkflowId)).action(async (workflowId, opts) => {
|
|
120
|
+
const deps = realDeps();
|
|
121
|
+
await runApiAction(deps, () => runWorkflowShow(deps, {
|
|
122
|
+
workflowId,
|
|
123
|
+
region: opts.region,
|
|
124
|
+
token: opts.token,
|
|
125
|
+
format: opts.format,
|
|
126
|
+
}));
|
|
127
|
+
});
|
|
128
|
+
addCommonApiOptions(workflowCommand
|
|
129
|
+
.command("delete")
|
|
130
|
+
.description("Delete a workflow")
|
|
131
|
+
.argument("<workflow-id>", "Tray workflow UUID", parseWorkflowId)).action(async (workflowId, opts) => {
|
|
132
|
+
const deps = realDeps();
|
|
133
|
+
await runApiAction(deps, () => runWorkflowDelete(deps, {
|
|
134
|
+
workflowId,
|
|
135
|
+
region: opts.region,
|
|
136
|
+
token: opts.token,
|
|
137
|
+
format: opts.format,
|
|
138
|
+
}));
|
|
139
|
+
});
|
|
140
|
+
const teal = ansis.hex(COMMAND_COLOR);
|
|
141
|
+
const EDIT_PAYLOAD_EXAMPLE = `${ansis.bold("Example:")}
|
|
142
|
+
$ ${teal(`tray api workflow edit <workflow-id> '{"name": "New name", "tags": ["prod", "reviewed"]}'`)}
|
|
143
|
+
|
|
144
|
+
Editable fields (all optional, only the ones you include are changed):
|
|
145
|
+
${teal("name")} string
|
|
146
|
+
${teal("description")} string, or null to clear
|
|
147
|
+
${teal("tags")} string[] — replaces the FULL tag list, does not add/remove single tags
|
|
148
|
+
${teal("alerting_workflow")} string (a workflow id), or null to clear
|
|
149
|
+
${teal("legacy_error_handling")} boolean
|
|
150
|
+
`;
|
|
151
|
+
addCommonApiOptions(workflowCommand
|
|
152
|
+
.command("edit")
|
|
153
|
+
.description("Edit a workflow's name, description, tags, alerting workflow, or legacy error handling")
|
|
154
|
+
.argument("<workflow-id>", "Tray workflow UUID", parseWorkflowId)
|
|
155
|
+
.argument("<payload>", "JSON object of fields to change", parseEditPayload)
|
|
156
|
+
.addHelpText("after", `\n${EDIT_PAYLOAD_EXAMPLE}`)).action(async (workflowId, payload, opts) => {
|
|
157
|
+
const deps = realDeps();
|
|
158
|
+
await runApiAction(deps, () => runWorkflowEdit(deps, {
|
|
159
|
+
workflowId,
|
|
160
|
+
payload,
|
|
161
|
+
region: opts.region,
|
|
162
|
+
token: opts.token,
|
|
163
|
+
format: opts.format,
|
|
164
|
+
}));
|
|
165
|
+
});
|
|
166
|
+
//# sourceMappingURL=workflow.js.map
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import ansis from "ansis";
|
|
2
|
+
import Table from "cli-table3";
|
|
2
3
|
import { SUCCESS_ICON } from "../icons.js";
|
|
3
4
|
export function printProjectRollbackResult(stdout, projectId, versionNumber) {
|
|
4
5
|
stdout(`${SUCCESS_ICON} Project ${ansis.bold(projectId)} rolled back to version ${ansis.bold(versionNumber)}.`);
|
|
5
6
|
}
|
|
7
|
+
function formatCreated(created) {
|
|
8
|
+
return created.replace("T", " ").slice(0, 19);
|
|
9
|
+
}
|
|
10
|
+
export function printProjectVersionListTable(stdout, versions) {
|
|
11
|
+
if (versions.length === 0) {
|
|
12
|
+
stdout("No versions found.");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const table = new Table({
|
|
16
|
+
head: ["Version", "Title", "Created", "Creator"].map((h) => ansis.bold(h)),
|
|
17
|
+
style: { head: [] },
|
|
18
|
+
});
|
|
19
|
+
for (const version of versions) {
|
|
20
|
+
table.push([ansis.bold(version.versionNumber), version.title, formatCreated(version.created), version.creator]);
|
|
21
|
+
}
|
|
22
|
+
stdout(table.toString());
|
|
23
|
+
}
|
|
6
24
|
//# sourceMappingURL=project.v2.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import ansis from "ansis";
|
|
2
|
+
export function printWorkflowEnabledResult(stdout, workflowId, enabled) {
|
|
3
|
+
const verb = enabled ? "Enabled" : "Disabled";
|
|
4
|
+
stdout(`${ansis.bold(verb)} workflow ${ansis.bold(workflowId)}.`);
|
|
5
|
+
}
|
|
6
|
+
export function printWorkflowShowResult(stdout, workflowId, workflow) {
|
|
7
|
+
const data = workflow.unversioned_data;
|
|
8
|
+
stdout(`${ansis.bold(data.name)} (${workflowId})`);
|
|
9
|
+
stdout(` Enabled: ${data.enabled ? "yes" : "no"}`);
|
|
10
|
+
if (data.description)
|
|
11
|
+
stdout(` Description: ${data.description}`);
|
|
12
|
+
if (data.tags.length > 0)
|
|
13
|
+
stdout(` Tags: ${data.tags.join(", ")}`);
|
|
14
|
+
}
|
|
15
|
+
export function printWorkflowDeletedResult(stdout, workflowId) {
|
|
16
|
+
stdout(`${ansis.bold("Deleted")} workflow ${ansis.bold(workflowId)}.`);
|
|
17
|
+
}
|
|
18
|
+
export function printWorkflowEditedResult(stdout, workflowId) {
|
|
19
|
+
stdout(`${ansis.bold("Edited")} workflow ${ansis.bold(workflowId)}.`);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=workflow.v2.js.map
|
package/package.json
CHANGED