@tolinax/ayoune-cli 2026.8.2 → 2026.10.0
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/lib/api/apiCallHandler.js +1 -1
- package/lib/api/apiClient.js +74 -62
- package/lib/commands/_registry.js +296 -0
- package/lib/commands/aggregate/_shared.js +21 -0
- package/lib/commands/aggregate/_stageBuilders.js +295 -0
- package/lib/commands/aggregate/exec.js +51 -0
- package/lib/commands/aggregate/index.js +38 -0
- package/lib/commands/aggregate/list.js +43 -0
- package/lib/commands/aggregate/models.js +43 -0
- package/lib/commands/aggregate/run.js +53 -0
- package/lib/commands/aggregate/save.js +53 -0
- package/lib/commands/aggregate/validate.js +47 -0
- package/lib/commands/aggregate/wizard.js +174 -0
- package/lib/commands/createAggregateCommand.js +5 -658
- package/lib/commands/createDeployCommand.js +5 -642
- package/lib/commands/createProgram.js +251 -161
- package/lib/commands/createSelfHostUpdateCommand.js +1 -20
- package/lib/commands/createServicesCommand.js +4 -5
- package/lib/commands/createSetupCommand.js +57 -5
- package/lib/commands/createWhoAmICommand.js +5 -5
- package/lib/commands/deploy/_token.js +8 -0
- package/lib/commands/deploy/alerts.js +43 -0
- package/lib/commands/deploy/clusters.js +62 -0
- package/lib/commands/deploy/dashboard.js +31 -0
- package/lib/commands/deploy/deployments.js +216 -0
- package/lib/commands/deploy/index.js +31 -0
- package/lib/commands/deploy/pipelines.js +82 -0
- package/lib/commands/deploy/plans.js +147 -0
- package/lib/commands/deploy/pods.js +70 -0
- package/lib/commands/deploy/repos.js +63 -0
- package/lib/commands/functions/_shared.js +38 -0
- package/lib/commands/functions/_validateSource.js +50 -0
- package/lib/commands/functions/create.js +109 -0
- package/lib/commands/functions/delete.js +40 -0
- package/lib/commands/functions/deploy.js +91 -0
- package/lib/commands/functions/get.js +31 -0
- package/lib/commands/functions/index.js +48 -0
- package/lib/commands/functions/invoke.js +75 -0
- package/lib/commands/functions/list.js +41 -0
- package/lib/commands/functions/logs.js +76 -0
- package/lib/commands/functions/rollback.js +44 -0
- package/lib/commands/functions/versions.js +32 -0
- package/lib/commands/local/_context.js +42 -0
- package/lib/commands/local/down.js +50 -0
- package/lib/commands/local/exec.js +45 -0
- package/lib/commands/local/index.js +40 -0
- package/lib/commands/local/logs.js +38 -0
- package/lib/commands/local/ps.js +41 -0
- package/lib/commands/local/pull.js +40 -0
- package/lib/commands/local/restart.js +31 -0
- package/lib/commands/local/up.js +80 -0
- package/lib/commands/provision/_detectTools.js +52 -0
- package/lib/commands/provision/_stateFile.js +36 -0
- package/lib/commands/provision/_wizard.js +60 -0
- package/lib/commands/provision/aws.js +107 -0
- package/lib/commands/provision/azure.js +113 -0
- package/lib/commands/provision/destroy.js +119 -0
- package/lib/commands/provision/digitalocean.js +82 -0
- package/lib/commands/provision/gcp.js +118 -0
- package/lib/commands/provision/hetzner.js +220 -0
- package/lib/commands/provision/index.js +44 -0
- package/lib/commands/provision/status.js +44 -0
- package/lib/helpers/dateFormat.js +119 -0
- package/lib/helpers/dockerCompose.js +143 -0
- package/lib/helpers/formatDocument.js +4 -5
- package/lib/helpers/logo.js +86 -13
- package/lib/helpers/saveFile.js +4 -9
- package/lib/models/getModelsInModules.js +6 -8
- package/lib/models/getModuleFromCollection.js +2 -2
- package/lib/operations/handleCollectionOperation.js +2 -3
- package/package.json +2 -12
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// `ay deploy alerts` — surface active deployment alerts (pod crashes,
|
|
2
|
+
// OOM kills, image pull errors, etc.). Read-only filter view; ack/resolve
|
|
3
|
+
// lives under `ay monitor` so we don't duplicate state mutation here.
|
|
4
|
+
import { apiCallHandler } from "../../api/apiCallHandler.js";
|
|
5
|
+
import { handleResponseFormatOptions } from "../../helpers/handleResponseFormatOptions.js";
|
|
6
|
+
import { saveFile } from "../../helpers/saveFile.js";
|
|
7
|
+
import { spinner } from "../../../index.js";
|
|
8
|
+
import { EXIT_GENERAL_ERROR } from "../../exitCodes.js";
|
|
9
|
+
import { cliError } from "../../helpers/cliError.js";
|
|
10
|
+
export function addAlertsSubcommands(deploy, rootProgram) {
|
|
11
|
+
deploy
|
|
12
|
+
.command("alerts")
|
|
13
|
+
.description("List active deployment alerts")
|
|
14
|
+
.option("--severity <level>", "Filter: critical, warning, info")
|
|
15
|
+
.option("--type <type>", "Filter: pod_crash, oom_killed, image_pull_error, deployment_failed, pipeline_failed, cluster_unreachable, high_restart_count, custom")
|
|
16
|
+
.option("-l, --limit <number>", "Limit", parseInt, 50)
|
|
17
|
+
.action(async (options) => {
|
|
18
|
+
var _a, _b, _c;
|
|
19
|
+
try {
|
|
20
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
21
|
+
spinner.start({ text: "Fetching alerts...", color: "magenta" });
|
|
22
|
+
const params = {
|
|
23
|
+
limit: opts.limit,
|
|
24
|
+
responseFormat: opts.responseFormat,
|
|
25
|
+
verbosity: opts.verbosity,
|
|
26
|
+
};
|
|
27
|
+
if (opts.severity)
|
|
28
|
+
params.severity = opts.severity;
|
|
29
|
+
if (opts.type)
|
|
30
|
+
params.type = opts.type;
|
|
31
|
+
const res = await apiCallHandler("devops", "alerts", "get", null, params);
|
|
32
|
+
handleResponseFormatOptions(opts, res);
|
|
33
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
34
|
+
spinner.success({ text: `Found ${total} alerts` });
|
|
35
|
+
spinner.stop();
|
|
36
|
+
if (opts.save)
|
|
37
|
+
await saveFile("deploy-alerts", opts, res);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
cliError(e.message || "Failed to fetch alerts", EXIT_GENERAL_ERROR);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// `ay deploy {clusters, cluster-sync}` — Kubernetes cluster discovery and
|
|
2
|
+
// manual reconcile trigger. The DevOps API maintains a registry of clusters
|
|
3
|
+
// the customer has connected; `clusters` lists them and `cluster-sync` kicks
|
|
4
|
+
// off a re-scan of resources for one cluster.
|
|
5
|
+
import { apiCallHandler } from "../../api/apiCallHandler.js";
|
|
6
|
+
import { handleResponseFormatOptions } from "../../helpers/handleResponseFormatOptions.js";
|
|
7
|
+
import { saveFile } from "../../helpers/saveFile.js";
|
|
8
|
+
import { spinner } from "../../../index.js";
|
|
9
|
+
import { EXIT_GENERAL_ERROR } from "../../exitCodes.js";
|
|
10
|
+
import { cliError } from "../../helpers/cliError.js";
|
|
11
|
+
export function addClustersSubcommands(deploy, rootProgram) {
|
|
12
|
+
// ay deploy clusters
|
|
13
|
+
deploy
|
|
14
|
+
.command("clusters")
|
|
15
|
+
.alias("cl")
|
|
16
|
+
.description("List Kubernetes clusters and their health status")
|
|
17
|
+
.addHelpText("after", `
|
|
18
|
+
Examples:
|
|
19
|
+
ay deploy clusters List all clusters
|
|
20
|
+
ay deploy clusters -r table Show clusters in table format`)
|
|
21
|
+
.option("-l, --limit <number>", "Limit", parseInt, 50)
|
|
22
|
+
.action(async (options) => {
|
|
23
|
+
var _a, _b, _c;
|
|
24
|
+
try {
|
|
25
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
26
|
+
spinner.start({ text: "Fetching clusters...", color: "magenta" });
|
|
27
|
+
const res = await apiCallHandler("devops", "clusters", "get", null, {
|
|
28
|
+
limit: opts.limit,
|
|
29
|
+
responseFormat: opts.responseFormat,
|
|
30
|
+
verbosity: opts.verbosity,
|
|
31
|
+
});
|
|
32
|
+
handleResponseFormatOptions(opts, res);
|
|
33
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
34
|
+
spinner.success({ text: `Found ${total} clusters` });
|
|
35
|
+
spinner.stop();
|
|
36
|
+
if (opts.save)
|
|
37
|
+
await saveFile("deploy-clusters", opts, res);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
cliError(e.message || "Failed to list clusters", EXIT_GENERAL_ERROR);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
// ay deploy cluster-sync <id>
|
|
44
|
+
deploy
|
|
45
|
+
.command("cluster-sync <id>")
|
|
46
|
+
.description("Trigger sync for a Kubernetes cluster")
|
|
47
|
+
.action(async (id, options) => {
|
|
48
|
+
try {
|
|
49
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
50
|
+
spinner.start({ text: `Syncing cluster ${id}...`, color: "magenta" });
|
|
51
|
+
const res = await apiCallHandler("devops", `clusters/${id}/sync`, "post", null, {
|
|
52
|
+
responseFormat: opts.responseFormat,
|
|
53
|
+
});
|
|
54
|
+
handleResponseFormatOptions(opts, res);
|
|
55
|
+
spinner.success({ text: `Cluster ${id} sync initiated` });
|
|
56
|
+
spinner.stop();
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
cliError(e.message || "Failed to sync cluster", EXIT_GENERAL_ERROR);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// `ay deploy dashboard` — single-call overview that returns the same
|
|
2
|
+
// summary the web devops dashboard renders (cluster health, deployment
|
|
3
|
+
// counts, active alerts, recent pipelines). Useful as a one-shot health
|
|
4
|
+
// check during incident response.
|
|
5
|
+
import { apiCallHandler } from "../../api/apiCallHandler.js";
|
|
6
|
+
import { handleResponseFormatOptions } from "../../helpers/handleResponseFormatOptions.js";
|
|
7
|
+
import { spinner } from "../../../index.js";
|
|
8
|
+
import { EXIT_GENERAL_ERROR } from "../../exitCodes.js";
|
|
9
|
+
import { cliError } from "../../helpers/cliError.js";
|
|
10
|
+
export function addDashboardSubcommands(deploy, rootProgram) {
|
|
11
|
+
deploy
|
|
12
|
+
.command("dashboard")
|
|
13
|
+
.alias("dash")
|
|
14
|
+
.description("Show deployment overview dashboard (clusters, deployments, alerts, pipelines)")
|
|
15
|
+
.action(async (options) => {
|
|
16
|
+
try {
|
|
17
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
18
|
+
spinner.start({ text: "Fetching dashboard...", color: "magenta" });
|
|
19
|
+
const res = await apiCallHandler("devops", "dashboard", "get", null, {
|
|
20
|
+
responseFormat: opts.responseFormat,
|
|
21
|
+
verbosity: opts.verbosity,
|
|
22
|
+
});
|
|
23
|
+
handleResponseFormatOptions(opts, res);
|
|
24
|
+
spinner.success({ text: "Dashboard loaded" });
|
|
25
|
+
spinner.stop();
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
cliError(e.message || "Failed to load dashboard", EXIT_GENERAL_ERROR);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// `ay deploy {list,get,logs,scale,restart}` — the core deployment-management
|
|
2
|
+
// subcommands. All hit the DevOps API except `logs --follow`, which streams
|
|
3
|
+
// Server-Sent Events directly from `${devops}/deployments/{id}/logs` so we
|
|
4
|
+
// can render live log lines without buffering through axios.
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { getModuleBaseUrl } from "../../api/apiClient.js";
|
|
7
|
+
import { apiCallHandler } from "../../api/apiCallHandler.js";
|
|
8
|
+
import { handleResponseFormatOptions } from "../../helpers/handleResponseFormatOptions.js";
|
|
9
|
+
import { saveFile } from "../../helpers/saveFile.js";
|
|
10
|
+
import { spinner } from "../../../index.js";
|
|
11
|
+
import { EXIT_GENERAL_ERROR } from "../../exitCodes.js";
|
|
12
|
+
import { cliError } from "../../helpers/cliError.js";
|
|
13
|
+
import { getDeployToken } from "./_token.js";
|
|
14
|
+
export function addDeploymentsSubcommands(deploy, rootProgram) {
|
|
15
|
+
// ay deploy list
|
|
16
|
+
deploy
|
|
17
|
+
.command("list")
|
|
18
|
+
.alias("ls")
|
|
19
|
+
.description("List deployments across clusters")
|
|
20
|
+
.option("--cluster <name>", "Filter by cluster name")
|
|
21
|
+
.option("--namespace <ns>", "Filter by namespace")
|
|
22
|
+
.option("--health <status>", "Filter: healthy, degraded, crashed, pending")
|
|
23
|
+
.option("-q, --search <term>", "Search by name")
|
|
24
|
+
.option("-l, --limit <number>", "Limit", parseInt, 50)
|
|
25
|
+
.option("-p, --page <number>", "Page", parseInt, 1)
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
var _a, _b;
|
|
28
|
+
try {
|
|
29
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
30
|
+
spinner.start({ text: "Fetching deployments...", color: "magenta" });
|
|
31
|
+
const params = {
|
|
32
|
+
page: opts.page,
|
|
33
|
+
limit: opts.limit,
|
|
34
|
+
responseFormat: opts.responseFormat,
|
|
35
|
+
verbosity: opts.verbosity,
|
|
36
|
+
hideMeta: opts.hideMeta,
|
|
37
|
+
};
|
|
38
|
+
if (opts.cluster)
|
|
39
|
+
params.cluster = opts.cluster;
|
|
40
|
+
if (opts.namespace)
|
|
41
|
+
params.namespace = opts.namespace;
|
|
42
|
+
if (opts.health)
|
|
43
|
+
params.health = opts.health;
|
|
44
|
+
if (opts.search)
|
|
45
|
+
params.q = opts.search;
|
|
46
|
+
const res = await apiCallHandler("devops", "deployments", "get", null, params);
|
|
47
|
+
const { result: fmtResult, meta: fmtMeta } = handleResponseFormatOptions(opts, res);
|
|
48
|
+
const total = (_b = (_a = fmtMeta === null || fmtMeta === void 0 ? void 0 : fmtMeta.pageInfo) === null || _a === void 0 ? void 0 : _a.totalEntries) !== null && _b !== void 0 ? _b : (Array.isArray(fmtResult) ? fmtResult.length : 0);
|
|
49
|
+
spinner.success({ text: `Found ${total} deployments` });
|
|
50
|
+
spinner.stop();
|
|
51
|
+
if (opts.save)
|
|
52
|
+
await saveFile("deploy-list", opts, res);
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
cliError(e.message || "Failed to list deployments", EXIT_GENERAL_ERROR);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// ay deploy get <id>
|
|
59
|
+
deploy
|
|
60
|
+
.command("get <id>")
|
|
61
|
+
.description("Get deployment details by ID")
|
|
62
|
+
.action(async (id, options) => {
|
|
63
|
+
try {
|
|
64
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
65
|
+
spinner.start({ text: `Fetching deployment ${id}...`, color: "magenta" });
|
|
66
|
+
const res = await apiCallHandler("devops", `deployments/${id}`, "get", null, {
|
|
67
|
+
responseFormat: opts.responseFormat,
|
|
68
|
+
verbosity: opts.verbosity,
|
|
69
|
+
});
|
|
70
|
+
handleResponseFormatOptions(opts, res);
|
|
71
|
+
spinner.success({ text: `Deployment ${id} loaded` });
|
|
72
|
+
spinner.stop();
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
cliError(e.message || "Failed to get deployment", EXIT_GENERAL_ERROR);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
// ay deploy logs <id>
|
|
79
|
+
deploy
|
|
80
|
+
.command("logs <id>")
|
|
81
|
+
.description("Get deployment logs (supports live streaming)")
|
|
82
|
+
.addHelpText("after", `
|
|
83
|
+
Examples:
|
|
84
|
+
ay deploy logs 64a1b2c3d4e5 Get recent logs
|
|
85
|
+
ay deploy logs 64a1b2c3d4e5 --follow Stream logs in real-time (SSE)
|
|
86
|
+
ay deploy logs 64a1b2c3d4e5 --tail 100 Last 100 log lines`)
|
|
87
|
+
.option("-f, --follow", "Stream logs in real-time (Server-Sent Events)")
|
|
88
|
+
.option("--tail <lines>", "Number of recent log lines", parseInt, 50)
|
|
89
|
+
.action(async (id, options) => {
|
|
90
|
+
try {
|
|
91
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
92
|
+
if (opts.follow) {
|
|
93
|
+
// SSE streaming mode — bypass apiCallHandler so we can read the
|
|
94
|
+
// stream chunk-by-chunk and emit each event as it arrives.
|
|
95
|
+
spinner.start({
|
|
96
|
+
text: `Streaming logs for deployment ${id}...`,
|
|
97
|
+
color: "magenta",
|
|
98
|
+
});
|
|
99
|
+
spinner.stop();
|
|
100
|
+
console.error(chalk.dim(` Streaming logs for ${id} (Ctrl+C to stop)\n`));
|
|
101
|
+
const baseUrl = getModuleBaseUrl("devops");
|
|
102
|
+
const url = `${baseUrl}/deployments/${id}/logs?follow=true&tail=${opts.tail}`;
|
|
103
|
+
const https = await import("https");
|
|
104
|
+
const { URL } = await import("url");
|
|
105
|
+
const parsed = new URL(url);
|
|
106
|
+
const req = https.get({
|
|
107
|
+
hostname: parsed.hostname,
|
|
108
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
109
|
+
headers: {
|
|
110
|
+
Authorization: `Bearer ${getDeployToken()}`,
|
|
111
|
+
Accept: "text/event-stream",
|
|
112
|
+
},
|
|
113
|
+
}, (res) => {
|
|
114
|
+
if (res.statusCode !== 200) {
|
|
115
|
+
cliError(`Error: HTTP ${res.statusCode}`, EXIT_GENERAL_ERROR);
|
|
116
|
+
}
|
|
117
|
+
res.setEncoding("utf-8");
|
|
118
|
+
res.on("data", (chunk) => {
|
|
119
|
+
// Parse SSE: lines starting with "data: "
|
|
120
|
+
const lines = chunk.split("\n");
|
|
121
|
+
for (const line of lines) {
|
|
122
|
+
if (line.startsWith("data: ")) {
|
|
123
|
+
const data = line.slice(6);
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(data);
|
|
126
|
+
if (opts.responseFormat === "json") {
|
|
127
|
+
console.log(JSON.stringify(parsed));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const ts = parsed.timestamp || "";
|
|
131
|
+
const level = parsed.level || "info";
|
|
132
|
+
const msg = parsed.message || data;
|
|
133
|
+
console.log(`${chalk.dim(ts)} ${chalk.cyan(level)} ${msg}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (_a) {
|
|
137
|
+
console.log(data);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
res.on("end", () => {
|
|
143
|
+
console.error(chalk.dim("\n Stream ended."));
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
req.on("error", (e) => {
|
|
147
|
+
cliError(`Stream error: ${e.message}`, EXIT_GENERAL_ERROR);
|
|
148
|
+
});
|
|
149
|
+
// Keep alive until Ctrl+C
|
|
150
|
+
process.on("SIGINT", () => {
|
|
151
|
+
req.destroy();
|
|
152
|
+
process.exit(0);
|
|
153
|
+
});
|
|
154
|
+
// Prevent Node from exiting
|
|
155
|
+
await new Promise(() => { });
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// One-shot log fetch
|
|
159
|
+
spinner.start({ text: `Fetching logs for deployment ${id}...`, color: "magenta" });
|
|
160
|
+
const res = await apiCallHandler("devops", `deployments/${id}/logs`, "get", null, {
|
|
161
|
+
tail: opts.tail,
|
|
162
|
+
responseFormat: opts.responseFormat,
|
|
163
|
+
verbosity: opts.verbosity,
|
|
164
|
+
});
|
|
165
|
+
handleResponseFormatOptions(opts, res);
|
|
166
|
+
spinner.success({ text: `Logs loaded for deployment ${id}` });
|
|
167
|
+
spinner.stop();
|
|
168
|
+
if (opts.save)
|
|
169
|
+
await saveFile("deploy-logs", opts, res);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
cliError(e.message || "Failed to fetch logs", EXIT_GENERAL_ERROR);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
// ay deploy scale <deployment> <replicas>
|
|
177
|
+
deploy
|
|
178
|
+
.command("scale <deployment> <replicas>")
|
|
179
|
+
.description("Scale a deployment to N replicas")
|
|
180
|
+
.action(async (deployment, replicas, options) => {
|
|
181
|
+
try {
|
|
182
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
183
|
+
spinner.start({
|
|
184
|
+
text: `Scaling ${deployment} to ${replicas} replicas...`,
|
|
185
|
+
color: "magenta",
|
|
186
|
+
});
|
|
187
|
+
const res = await apiCallHandler("devops", "deployments/scale", "post", {
|
|
188
|
+
deployment,
|
|
189
|
+
replicas: parseInt(replicas, 10),
|
|
190
|
+
}, { responseFormat: opts.responseFormat });
|
|
191
|
+
handleResponseFormatOptions(opts, res);
|
|
192
|
+
spinner.success({ text: `Scaled ${deployment} to ${replicas} replicas` });
|
|
193
|
+
spinner.stop();
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
cliError(e.message || "Failed to scale deployment", EXIT_GENERAL_ERROR);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
// ay deploy restart <deployment>
|
|
200
|
+
deploy
|
|
201
|
+
.command("restart <deployment>")
|
|
202
|
+
.description("Restart a deployment (rolling restart)")
|
|
203
|
+
.action(async (deployment, options) => {
|
|
204
|
+
try {
|
|
205
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
206
|
+
spinner.start({ text: `Restarting ${deployment}...`, color: "magenta" });
|
|
207
|
+
const res = await apiCallHandler("devops", "deployments/restart", "post", { deployment }, { responseFormat: opts.responseFormat });
|
|
208
|
+
handleResponseFormatOptions(opts, res);
|
|
209
|
+
spinner.success({ text: `Restart initiated for ${deployment}` });
|
|
210
|
+
spinner.stop();
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
cliError(e.message || "Failed to restart deployment", EXIT_GENERAL_ERROR);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// `ay deploy` parent command — DevOps API surface (deployments, pods,
|
|
2
|
+
// clusters, alerts, pipelines, plans, repos, dashboard).
|
|
3
|
+
//
|
|
4
|
+
// The original `createDeployCommand.ts` was 695 lines with 22 subcommands
|
|
5
|
+
// inline; the split groups subcommands by topic into 8 files. This file just
|
|
6
|
+
// instantiates the parent and wires the children. The exported function name
|
|
7
|
+
// (`createDeployCommand`) is preserved so `_registry.ts` and the existing
|
|
8
|
+
// test (`createDeployCommand.test.ts`) keep working through the back-compat
|
|
9
|
+
// shim at `../createDeployCommand.ts`.
|
|
10
|
+
import { addDeploymentsSubcommands } from "./deployments.js";
|
|
11
|
+
import { addPodsSubcommands } from "./pods.js";
|
|
12
|
+
import { addClustersSubcommands } from "./clusters.js";
|
|
13
|
+
import { addAlertsSubcommands } from "./alerts.js";
|
|
14
|
+
import { addPipelinesSubcommands } from "./pipelines.js";
|
|
15
|
+
import { addPlansSubcommands } from "./plans.js";
|
|
16
|
+
import { addReposSubcommands } from "./repos.js";
|
|
17
|
+
import { addDashboardSubcommands } from "./dashboard.js";
|
|
18
|
+
export function createDeployCommand(program) {
|
|
19
|
+
const deploy = program
|
|
20
|
+
.command("deploy")
|
|
21
|
+
.alias("dp")
|
|
22
|
+
.description("Manage deployments, pods, clusters, pipelines, and plans via DevOps API");
|
|
23
|
+
addDeploymentsSubcommands(deploy, program);
|
|
24
|
+
addPodsSubcommands(deploy, program);
|
|
25
|
+
addClustersSubcommands(deploy, program);
|
|
26
|
+
addAlertsSubcommands(deploy, program);
|
|
27
|
+
addPipelinesSubcommands(deploy, program);
|
|
28
|
+
addPlansSubcommands(deploy, program);
|
|
29
|
+
addReposSubcommands(deploy, program);
|
|
30
|
+
addDashboardSubcommands(deploy, program);
|
|
31
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// `ay deploy {pipelines, trigger}` — CI/CD pipeline visibility + manual
|
|
2
|
+
// trigger. `pipelines` is read-only listing; `trigger` POSTs to
|
|
3
|
+
// `pipelines/trigger` with a repo slug + branch + optional target name to
|
|
4
|
+
// kick off a build. Default branch is "master" because that's what the
|
|
5
|
+
// platform deploys from (see CLAUDE.md: master deploys to develop namespace
|
|
6
|
+
// which IS prod).
|
|
7
|
+
import { apiCallHandler } from "../../api/apiCallHandler.js";
|
|
8
|
+
import { handleResponseFormatOptions } from "../../helpers/handleResponseFormatOptions.js";
|
|
9
|
+
import { saveFile } from "../../helpers/saveFile.js";
|
|
10
|
+
import { spinner } from "../../../index.js";
|
|
11
|
+
import { EXIT_GENERAL_ERROR } from "../../exitCodes.js";
|
|
12
|
+
import { cliError } from "../../helpers/cliError.js";
|
|
13
|
+
export function addPipelinesSubcommands(deploy, rootProgram) {
|
|
14
|
+
// ay deploy pipelines
|
|
15
|
+
deploy
|
|
16
|
+
.command("pipelines")
|
|
17
|
+
.alias("pipe")
|
|
18
|
+
.description("List recent CI/CD pipelines")
|
|
19
|
+
.option("--repo <slug>", "Filter by repository slug")
|
|
20
|
+
.option("--result <result>", "Filter: SUCCESSFUL, FAILED, STOPPED, EXPIRED")
|
|
21
|
+
.option("-l, --limit <number>", "Limit", parseInt, 20)
|
|
22
|
+
.action(async (options) => {
|
|
23
|
+
var _a, _b, _c;
|
|
24
|
+
try {
|
|
25
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
26
|
+
spinner.start({ text: "Fetching pipelines...", color: "magenta" });
|
|
27
|
+
const params = {
|
|
28
|
+
limit: opts.limit,
|
|
29
|
+
responseFormat: opts.responseFormat,
|
|
30
|
+
verbosity: opts.verbosity,
|
|
31
|
+
};
|
|
32
|
+
if (opts.repo)
|
|
33
|
+
params.repository = opts.repo;
|
|
34
|
+
if (opts.result)
|
|
35
|
+
params.result = opts.result;
|
|
36
|
+
const res = await apiCallHandler("devops", "pipelines", "get", null, params);
|
|
37
|
+
handleResponseFormatOptions(opts, res);
|
|
38
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
39
|
+
spinner.success({ text: `Found ${total} pipelines` });
|
|
40
|
+
spinner.stop();
|
|
41
|
+
if (opts.save)
|
|
42
|
+
await saveFile("deploy-pipelines", opts, res);
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
cliError(e.message || "Failed to fetch pipelines", EXIT_GENERAL_ERROR);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
// ay deploy trigger <repo>
|
|
49
|
+
deploy
|
|
50
|
+
.command("trigger <repo>")
|
|
51
|
+
.description("Trigger a CI/CD pipeline for a repository")
|
|
52
|
+
.addHelpText("after", `
|
|
53
|
+
Examples:
|
|
54
|
+
ay deploy trigger ayoune-cli Trigger pipeline for ayoune-cli
|
|
55
|
+
ay deploy trigger ayoune-cli --branch feature/new Use specific branch
|
|
56
|
+
ay deploy trigger ayoune-cli --target staging Custom pipeline target`)
|
|
57
|
+
.option("--branch <branch>", "Branch to trigger pipeline on", "master")
|
|
58
|
+
.option("--target <target>", "Pipeline target name")
|
|
59
|
+
.action(async (repo, options) => {
|
|
60
|
+
try {
|
|
61
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
62
|
+
spinner.start({ text: `Triggering pipeline for ${repo}...`, color: "magenta" });
|
|
63
|
+
const body = {
|
|
64
|
+
repositorySlug: repo,
|
|
65
|
+
branch: opts.branch,
|
|
66
|
+
};
|
|
67
|
+
if (opts.target)
|
|
68
|
+
body.target = opts.target;
|
|
69
|
+
const res = await apiCallHandler("devops", "pipelines/trigger", "post", body, {
|
|
70
|
+
responseFormat: opts.responseFormat,
|
|
71
|
+
});
|
|
72
|
+
handleResponseFormatOptions(opts, res);
|
|
73
|
+
spinner.success({
|
|
74
|
+
text: `Pipeline triggered for ${repo} (branch: ${opts.branch})`,
|
|
75
|
+
});
|
|
76
|
+
spinner.stop();
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
cliError(e.message || "Failed to trigger pipeline", EXIT_GENERAL_ERROR);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// `ay deploy plans {list,get,create,execute,delete}` — multi-step deployment
|
|
2
|
+
// plans (e.g. "rolling deploy of 5 services with health-check gates between
|
|
3
|
+
// each step"). The plans tree forms its own intermediate command so the
|
|
4
|
+
// `ay deploy plans <action>` invocation surface stays grouped.
|
|
5
|
+
import { apiCallHandler } from "../../api/apiCallHandler.js";
|
|
6
|
+
import { handleResponseFormatOptions } from "../../helpers/handleResponseFormatOptions.js";
|
|
7
|
+
import { saveFile } from "../../helpers/saveFile.js";
|
|
8
|
+
import { spinner } from "../../../index.js";
|
|
9
|
+
import { EXIT_GENERAL_ERROR } from "../../exitCodes.js";
|
|
10
|
+
import { cliError } from "../../helpers/cliError.js";
|
|
11
|
+
export function addPlansSubcommands(deploy, rootProgram) {
|
|
12
|
+
const plans = deploy.command("plans").description("Manage multi-step deployment plans");
|
|
13
|
+
// ay deploy plans list
|
|
14
|
+
plans
|
|
15
|
+
.command("list")
|
|
16
|
+
.alias("ls")
|
|
17
|
+
.description("List deployment plans")
|
|
18
|
+
.option("-l, --limit <number>", "Limit", parseInt, 50)
|
|
19
|
+
.option("-p, --page <number>", "Page", parseInt, 1)
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
var _a, _b, _c;
|
|
22
|
+
try {
|
|
23
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
24
|
+
spinner.start({ text: "Fetching deployment plans...", color: "magenta" });
|
|
25
|
+
const res = await apiCallHandler("devops", "deployment-plans", "get", null, {
|
|
26
|
+
page: opts.page,
|
|
27
|
+
limit: opts.limit,
|
|
28
|
+
responseFormat: opts.responseFormat,
|
|
29
|
+
verbosity: opts.verbosity,
|
|
30
|
+
});
|
|
31
|
+
handleResponseFormatOptions(opts, res);
|
|
32
|
+
const total = (_c = (_b = (_a = res.meta) === null || _a === void 0 ? void 0 : _a.pageInfo) === null || _b === void 0 ? void 0 : _b.totalEntries) !== null && _c !== void 0 ? _c : 0;
|
|
33
|
+
spinner.success({ text: `Found ${total} deployment plans` });
|
|
34
|
+
spinner.stop();
|
|
35
|
+
if (opts.save)
|
|
36
|
+
await saveFile("deploy-plans", opts, res);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
cliError(e.message || "Failed to list deployment plans", EXIT_GENERAL_ERROR);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
// ay deploy plans get <id>
|
|
43
|
+
plans
|
|
44
|
+
.command("get <id>")
|
|
45
|
+
.description("Get deployment plan details")
|
|
46
|
+
.action(async (id, options) => {
|
|
47
|
+
try {
|
|
48
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
49
|
+
spinner.start({ text: `Fetching plan ${id}...`, color: "magenta" });
|
|
50
|
+
const res = await apiCallHandler("devops", `deployment-plans/${id}`, "get", null, {
|
|
51
|
+
responseFormat: opts.responseFormat,
|
|
52
|
+
verbosity: opts.verbosity,
|
|
53
|
+
});
|
|
54
|
+
handleResponseFormatOptions(opts, res);
|
|
55
|
+
spinner.success({ text: `Plan ${id} loaded` });
|
|
56
|
+
spinner.stop();
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
cliError(e.message || "Failed to get plan", EXIT_GENERAL_ERROR);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
// ay deploy plans create --body '{...}'
|
|
63
|
+
plans
|
|
64
|
+
.command("create")
|
|
65
|
+
.description("Create a new deployment plan")
|
|
66
|
+
.option("--body <json>", "Plan definition as JSON")
|
|
67
|
+
.option("--body-file <path>", "Read plan definition from file")
|
|
68
|
+
.action(async (options) => {
|
|
69
|
+
var _a;
|
|
70
|
+
try {
|
|
71
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
72
|
+
let body = null;
|
|
73
|
+
if (opts.body) {
|
|
74
|
+
try {
|
|
75
|
+
body = JSON.parse(opts.body);
|
|
76
|
+
}
|
|
77
|
+
catch (_b) {
|
|
78
|
+
cliError("Invalid JSON in --body", EXIT_GENERAL_ERROR);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (opts.bodyFile) {
|
|
82
|
+
const fs = await import("fs");
|
|
83
|
+
const content = fs.readFileSync(opts.bodyFile, "utf-8");
|
|
84
|
+
try {
|
|
85
|
+
body = JSON.parse(content);
|
|
86
|
+
}
|
|
87
|
+
catch (_c) {
|
|
88
|
+
cliError(`Invalid JSON in file: ${opts.bodyFile}`, EXIT_GENERAL_ERROR);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!body) {
|
|
92
|
+
cliError("Provide plan definition via --body or --body-file", EXIT_GENERAL_ERROR);
|
|
93
|
+
}
|
|
94
|
+
spinner.start({ text: "Creating deployment plan...", color: "magenta" });
|
|
95
|
+
const res = await apiCallHandler("devops", "deployment-plans", "post", body, {
|
|
96
|
+
responseFormat: opts.responseFormat,
|
|
97
|
+
});
|
|
98
|
+
handleResponseFormatOptions(opts, res);
|
|
99
|
+
const planId = ((_a = res.payload) === null || _a === void 0 ? void 0 : _a._id) || "unknown";
|
|
100
|
+
spinner.success({ text: `Deployment plan ${planId} created` });
|
|
101
|
+
spinner.stop();
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
cliError(e.message || "Failed to create plan", EXIT_GENERAL_ERROR);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// ay deploy plans execute <id>
|
|
108
|
+
plans
|
|
109
|
+
.command("execute <id>")
|
|
110
|
+
.alias("exec")
|
|
111
|
+
.description("Execute a deployment plan")
|
|
112
|
+
.action(async (id, options) => {
|
|
113
|
+
try {
|
|
114
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
115
|
+
spinner.start({ text: `Executing deployment plan ${id}...`, color: "magenta" });
|
|
116
|
+
const res = await apiCallHandler("devops", `deployment-plans/${id}/execute`, "post", null, {
|
|
117
|
+
responseFormat: opts.responseFormat,
|
|
118
|
+
});
|
|
119
|
+
handleResponseFormatOptions(opts, res);
|
|
120
|
+
spinner.success({ text: `Plan ${id} execution started` });
|
|
121
|
+
spinner.stop();
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
cliError(e.message || "Failed to execute plan", EXIT_GENERAL_ERROR);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
// ay deploy plans delete <id>
|
|
128
|
+
plans
|
|
129
|
+
.command("delete <id>")
|
|
130
|
+
.alias("rm")
|
|
131
|
+
.description("Delete a deployment plan")
|
|
132
|
+
.action(async (id, options) => {
|
|
133
|
+
try {
|
|
134
|
+
const opts = { ...rootProgram.opts(), ...options };
|
|
135
|
+
spinner.start({ text: `Deleting plan ${id}...`, color: "magenta" });
|
|
136
|
+
const res = await apiCallHandler("devops", `deployment-plans/${id}`, "delete", null, {
|
|
137
|
+
responseFormat: opts.responseFormat,
|
|
138
|
+
});
|
|
139
|
+
handleResponseFormatOptions(opts, res);
|
|
140
|
+
spinner.success({ text: `Plan ${id} deleted` });
|
|
141
|
+
spinner.stop();
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
cliError(e.message || "Failed to delete plan", EXIT_GENERAL_ERROR);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|