@studiometa/forge-cli 0.3.0 → 0.4.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/README.md +18 -15
- package/dist/cli.js +421 -152
- package/dist/cli.js.map +1 -1
- package/dist/commands/backups/command.d.ts.map +1 -1
- package/dist/commands/backups/handlers.d.ts +1 -0
- package/dist/commands/backups/handlers.d.ts.map +1 -1
- package/dist/commands/backups/help.d.ts.map +1 -1
- package/dist/commands/certificates/command.d.ts.map +1 -1
- package/dist/commands/certificates/handlers.d.ts +0 -1
- package/dist/commands/certificates/handlers.d.ts.map +1 -1
- package/dist/commands/certificates/help.d.ts.map +1 -1
- package/dist/commands/commands/handlers.d.ts.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/daemons/command.d.ts.map +1 -1
- package/dist/commands/daemons/handlers.d.ts +1 -0
- package/dist/commands/daemons/handlers.d.ts.map +1 -1
- package/dist/commands/daemons/help.d.ts.map +1 -1
- package/dist/commands/database-users/command.d.ts.map +1 -1
- package/dist/commands/database-users/handlers.d.ts +1 -0
- package/dist/commands/database-users/handlers.d.ts.map +1 -1
- package/dist/commands/database-users/help.d.ts.map +1 -1
- package/dist/commands/deployments/handlers.d.ts.map +1 -1
- package/dist/commands/deployments/help.d.ts.map +1 -1
- package/dist/commands/monitors/handlers.d.ts.map +1 -1
- package/dist/commands/recipes/command.d.ts.map +1 -1
- package/dist/commands/recipes/handlers.d.ts +1 -0
- package/dist/commands/recipes/handlers.d.ts.map +1 -1
- package/dist/commands/recipes/help.d.ts.map +1 -1
- package/dist/commands/redirect-rules/handlers.d.ts.map +1 -1
- package/dist/commands/scheduled-jobs/handlers.d.ts.map +1 -1
- package/dist/commands/security-rules/command.d.ts.map +1 -1
- package/dist/commands/security-rules/handlers.d.ts +1 -0
- package/dist/commands/security-rules/handlers.d.ts.map +1 -1
- package/dist/commands/security-rules/help.d.ts.map +1 -1
- package/dist/commands/sites/command.d.ts.map +1 -1
- package/dist/commands/sites/handlers.d.ts +1 -0
- package/dist/commands/sites/handlers.d.ts.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/{error-handler-Byuujxh1.js → error-handler-ByMitiOr.js} +21 -10
- package/dist/error-handler-ByMitiOr.js.map +1 -0
- package/dist/error-handler.d.ts.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/args.d.ts +1 -0
- package/dist/utils/args.d.ts.map +1 -1
- package/dist/utils/command-router.d.ts.map +1 -1
- package/dist/utils/resolve.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/error-handler-Byuujxh1.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as createContext, c as colors, d as deleteToken, i as runCommand, l as setColorEnabled, m as
|
|
3
|
-
import { activateCertificate, createAuditLogger, createBackupConfig, createCommand, createDatabaseUser, createMonitor, createNginxTemplate, createRedirectRule, createScheduledJob, createSecurityRule, deleteBackupConfig, deleteDatabaseUser, deleteMonitor, deleteNginxTemplate, deleteRedirectRule, deleteScheduledJob, deleteSecurityRule, deploySiteAndWait, getBackupConfig, getCertificate, getCommand, getDaemon, getDatabase, getDatabaseUser, getEnv, getFirewallRule, getMonitor, getNginxConfig, getNginxTemplate, getRecipe, getRedirectRule, getScheduledJob, getSecurityRule, getServer, getSite, getSshKey, getUser, listBackupConfigs,
|
|
2
|
+
import { S as isOutputFormat, a as createContext, b as ValidationError, c as colors, d as deleteToken, g as setToken, h as setOrganizationSlug, i as runCommand, l as setColorEnabled, m as getToken, n as exitWithValidationError, p as getOrganizationSlug, s as OutputFormatter, u as createConfigStore } from "./error-handler-ByMitiOr.js";
|
|
3
|
+
import { activateCertificate, createAuditLogger, createBackupConfig, createCommand, createDatabaseUser, createMonitor, createNginxTemplate, createRedirectRule, createScheduledJob, createSecurityRule, deleteBackupConfig, deleteDatabaseUser, deleteMonitor, deleteNginxTemplate, deleteRedirectRule, deleteScheduledJob, deleteSecurityRule, deploySiteAndWait, getBackupConfig, getCertificate, getCommand, getDaemon, getDatabase, getDatabaseUser, getEnv, getFirewallRule, getMonitor, getNginxConfig, getNginxTemplate, getRecipe, getRedirectRule, getScheduledJob, getSecurityRule, getServer, getSite, getSshKey, getUser, listBackupConfigs, listCommands, listDaemons, listDatabaseUsers, listDatabases, listDeployments, listFirewallRules, listMonitors, listNginxTemplates, listRecipes, listRedirectRules, listScheduledJobs, listSecurityRules, listServers, listSites, listSshKeys, matchByName, rebootServer, restartDaemon, runRecipe, updateBackupConfig, updateDaemon, updateDatabaseUser, updateEnv, updateNginxConfig, updateNginxTemplate, updateRecipe, updateSecurityRule, updateSite } from "@studiometa/forge-core";
|
|
4
4
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
@@ -18,7 +18,8 @@ function getCliAuditLogger() {
|
|
|
18
18
|
*/
|
|
19
19
|
function createCommandRouter(config) {
|
|
20
20
|
return async function(subcommand, args, options) {
|
|
21
|
-
const
|
|
21
|
+
const rawFormat = options.format ?? options.f ?? "human";
|
|
22
|
+
const formatter = new OutputFormatter(isOutputFormat(rawFormat) ? rawFormat : "human", options["no-color"] === true);
|
|
22
23
|
const ctx = createContext(options);
|
|
23
24
|
const handler = config.handlers[subcommand];
|
|
24
25
|
if (!handler) {
|
|
@@ -114,10 +115,10 @@ async function backupsList(ctx) {
|
|
|
114
115
|
const result = await listBackupConfigs({ server_id: await resolveServerId(server, execCtx) }, execCtx);
|
|
115
116
|
ctx.formatter.outputList(result.data, [
|
|
116
117
|
"id",
|
|
117
|
-
"
|
|
118
|
-
"
|
|
118
|
+
"name",
|
|
119
|
+
"schedule",
|
|
119
120
|
"status"
|
|
120
|
-
], "No backup configurations found.", (b) => `${String(b.id).padEnd(8)} ${b.
|
|
121
|
+
], "No backup configurations found.", (b) => `${String(b.id).padEnd(8)} ${b.name.padEnd(20)} ${b.schedule.padEnd(12)} ${b.status}`);
|
|
121
122
|
}, ctx.formatter);
|
|
122
123
|
}
|
|
123
124
|
async function backupsGet(args, ctx) {
|
|
@@ -134,42 +135,70 @@ async function backupsGet(args, ctx) {
|
|
|
134
135
|
}, execCtx);
|
|
135
136
|
ctx.formatter.outputOne(result.data, [
|
|
136
137
|
"id",
|
|
138
|
+
"name",
|
|
137
139
|
"provider",
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
+
"schedule",
|
|
141
|
+
"displayable_schedule",
|
|
140
142
|
"retention",
|
|
141
143
|
"status",
|
|
142
|
-
"
|
|
144
|
+
"next_run_time"
|
|
143
145
|
]);
|
|
144
146
|
}, ctx.formatter);
|
|
145
147
|
}
|
|
146
148
|
async function backupsCreate(ctx) {
|
|
147
149
|
const server = String(ctx.options.server ?? "");
|
|
148
|
-
const
|
|
150
|
+
const storageProviderId = Number(ctx.options.provider ?? 0);
|
|
149
151
|
const frequency = String(ctx.options.frequency ?? "");
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
+
const databaseIdsRaw = ctx.options.databases;
|
|
153
|
+
const database_ids = Array.isArray(databaseIdsRaw) ? databaseIdsRaw.map(Number) : databaseIdsRaw ? [Number(databaseIdsRaw)] : [];
|
|
152
154
|
if (!server) exitWithValidationError("server_id", "forge backups create --server <server_id> --provider <provider> --frequency <frequency>", ctx.formatter);
|
|
153
|
-
if (!
|
|
155
|
+
if (!storageProviderId) exitWithValidationError("provider", "forge backups create --server <server_id> --provider <provider> --frequency <frequency>", ctx.formatter);
|
|
154
156
|
if (!frequency) exitWithValidationError("frequency", "forge backups create --server <server_id> --provider <provider> --frequency <frequency>", ctx.formatter);
|
|
155
157
|
await runCommand(async () => {
|
|
156
158
|
const token = ctx.getToken();
|
|
157
159
|
const execCtx = ctx.createExecutorContext(token);
|
|
158
|
-
|
|
160
|
+
await createBackupConfig({
|
|
159
161
|
server_id: await resolveServerId(server, execCtx),
|
|
160
|
-
|
|
162
|
+
storage_provider_id: storageProviderId,
|
|
161
163
|
frequency,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
retention: 5,
|
|
165
|
+
database_ids
|
|
164
166
|
}, execCtx);
|
|
165
|
-
ctx.formatter.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
167
|
+
ctx.formatter.success("Backup configuration created.");
|
|
168
|
+
}, ctx.formatter);
|
|
169
|
+
}
|
|
170
|
+
async function backupsUpdate(args, ctx) {
|
|
171
|
+
const [id] = args;
|
|
172
|
+
const server = String(ctx.options.server ?? "");
|
|
173
|
+
const storageProviderId = Number(ctx.options.provider ?? 0);
|
|
174
|
+
const frequency = String(ctx.options.frequency ?? "");
|
|
175
|
+
const retention = Number(ctx.options.retention ?? 0);
|
|
176
|
+
const databaseIdsRaw = ctx.options.databases;
|
|
177
|
+
const database_ids = Array.isArray(databaseIdsRaw) ? databaseIdsRaw.map(Number) : databaseIdsRaw ? [Number(databaseIdsRaw)] : [];
|
|
178
|
+
if (!id) exitWithValidationError("backup_id", "forge backups update <backup_id> --server <server_id> --provider <provider> --frequency <frequency> --retention <retention>", ctx.formatter);
|
|
179
|
+
if (!server) exitWithValidationError("server_id", "forge backups update <backup_id> --server <server_id> --provider <provider> --frequency <frequency> --retention <retention>", ctx.formatter);
|
|
180
|
+
if (!storageProviderId) exitWithValidationError("provider", "forge backups update <backup_id> --server <server_id> --provider <provider> --frequency <frequency> --retention <retention>", ctx.formatter);
|
|
181
|
+
if (!frequency) exitWithValidationError("frequency", "forge backups update <backup_id> --server <server_id> --provider <provider> --frequency <frequency> --retention <retention>", ctx.formatter);
|
|
182
|
+
if (!retention) exitWithValidationError("retention", "forge backups update <backup_id> --server <server_id> --provider <provider> --frequency <frequency> --retention <retention>", ctx.formatter);
|
|
183
|
+
await runCommand(async () => {
|
|
184
|
+
const token = ctx.getToken();
|
|
185
|
+
const execCtx = ctx.createExecutorContext(token);
|
|
186
|
+
await updateBackupConfig({
|
|
187
|
+
server_id: await resolveServerId(server, execCtx),
|
|
188
|
+
id,
|
|
189
|
+
storage_provider_id: storageProviderId,
|
|
190
|
+
frequency,
|
|
191
|
+
retention,
|
|
192
|
+
database_ids,
|
|
193
|
+
...ctx.options.name != null ? { name: String(ctx.options.name) } : {},
|
|
194
|
+
...ctx.options.bucket != null ? { bucket: String(ctx.options.bucket) } : {},
|
|
195
|
+
...ctx.options.directory != null ? { directory: String(ctx.options.directory) } : {},
|
|
196
|
+
...ctx.options.day != null ? { day: String(ctx.options.day) } : {},
|
|
197
|
+
...ctx.options.time != null ? { time: String(ctx.options.time) } : {},
|
|
198
|
+
...ctx.options.cron != null ? { cron: String(ctx.options.cron) } : {},
|
|
199
|
+
...ctx.options["notification-email"] != null ? { notification_email: String(ctx.options["notification-email"]) } : {}
|
|
200
|
+
}, execCtx);
|
|
201
|
+
ctx.formatter.success(`Backup configuration ${id} updated.`);
|
|
173
202
|
}, ctx.formatter);
|
|
174
203
|
}
|
|
175
204
|
async function backupsDelete(args, ctx) {
|
|
@@ -194,9 +223,14 @@ const handleBackupsCommand = createCommandRouter({
|
|
|
194
223
|
ls: backupsList,
|
|
195
224
|
get: [backupsGet, "args"],
|
|
196
225
|
create: backupsCreate,
|
|
226
|
+
update: [backupsUpdate, "args"],
|
|
197
227
|
delete: [backupsDelete, "args"]
|
|
198
228
|
},
|
|
199
|
-
writeSubcommands: [
|
|
229
|
+
writeSubcommands: [
|
|
230
|
+
"create",
|
|
231
|
+
"update",
|
|
232
|
+
"delete"
|
|
233
|
+
]
|
|
200
234
|
});
|
|
201
235
|
function showBackupsHelp(subcommand) {
|
|
202
236
|
if (subcommand === "list" || subcommand === "ls") console.log(`
|
|
@@ -227,6 +261,27 @@ ${colors.bold("OPTIONS:")}
|
|
|
227
261
|
--frequency <freq> Backup frequency (required)
|
|
228
262
|
--databases <ids> Comma-separated database IDs to back up (optional)
|
|
229
263
|
-f, --format <fmt> Output format: json, human, table
|
|
264
|
+
`);
|
|
265
|
+
else if (subcommand === "update") console.log(`
|
|
266
|
+
${colors.bold("forge backups update")} - Update a backup configuration
|
|
267
|
+
|
|
268
|
+
${colors.bold("USAGE:")}
|
|
269
|
+
forge backups update <backup_id> --server <server_id> --provider <provider> --frequency <frequency> --retention <retention> [options]
|
|
270
|
+
|
|
271
|
+
${colors.bold("OPTIONS:")}
|
|
272
|
+
--server <id> Server ID (required)
|
|
273
|
+
--provider <id> Storage provider ID (required)
|
|
274
|
+
--frequency <freq> Backup frequency (required)
|
|
275
|
+
--retention <n> Retention count (required)
|
|
276
|
+
--databases <ids> Comma-separated database IDs (optional)
|
|
277
|
+
--name <name> Backup name (optional)
|
|
278
|
+
--bucket <bucket> Bucket name (optional)
|
|
279
|
+
--directory <dir> Directory (optional)
|
|
280
|
+
--day <day> Day of week (optional)
|
|
281
|
+
--time <time> Time (optional)
|
|
282
|
+
--cron <cron> Cron expression (optional)
|
|
283
|
+
--notification-email <email> Notification email (optional)
|
|
284
|
+
-f, --format <fmt> Output format: json, human, table
|
|
230
285
|
`);
|
|
231
286
|
else if (subcommand === "delete") console.log(`
|
|
232
287
|
${colors.bold("forge backups delete")} - Delete a backup configuration
|
|
@@ -244,6 +299,7 @@ ${colors.bold("SUBCOMMANDS:")}
|
|
|
244
299
|
list, ls List backup configurations
|
|
245
300
|
get <id> Get backup configuration details
|
|
246
301
|
create Create a new backup configuration
|
|
302
|
+
update <id> Update a backup configuration
|
|
247
303
|
delete <id> Delete a backup configuration
|
|
248
304
|
|
|
249
305
|
${colors.bold("OPTIONS:")}
|
|
@@ -278,7 +334,7 @@ _forge_completions() {
|
|
|
278
334
|
local daemons_cmds="list ls get restart"
|
|
279
335
|
local env_cmds="get update"
|
|
280
336
|
local nginx_cmds="get update"
|
|
281
|
-
local certificates_cmds="
|
|
337
|
+
local certificates_cmds="get activate"
|
|
282
338
|
local firewall_rules_cmds="list ls get"
|
|
283
339
|
local ssh_keys_cmds="list ls get"
|
|
284
340
|
local backups_cmds="list ls get create delete"
|
|
@@ -533,9 +589,7 @@ _forge() {
|
|
|
533
589
|
certificates|certs)
|
|
534
590
|
local -a certificates_cmds
|
|
535
591
|
certificates_cmds=(
|
|
536
|
-
'
|
|
537
|
-
'ls:List certificates (alias)'
|
|
538
|
-
'get:Get certificate details'
|
|
592
|
+
'get:Get certificate for a domain'
|
|
539
593
|
'activate:Activate a certificate'
|
|
540
594
|
)
|
|
541
595
|
_describe 'certificates command' certificates_cmds
|
|
@@ -782,8 +836,7 @@ complete -c forge -f -n "__fish_seen_subcommand_from nginx" -a "get" -d "Get ngi
|
|
|
782
836
|
complete -c forge -f -n "__fish_seen_subcommand_from nginx" -a "update" -d "Update nginx config"
|
|
783
837
|
|
|
784
838
|
# certificates subcommands
|
|
785
|
-
complete -c forge -f -n "__fish_seen_subcommand_from certificates certs" -a "
|
|
786
|
-
complete -c forge -f -n "__fish_seen_subcommand_from certificates certs" -a "ls" -d "List certificates (alias)"
|
|
839
|
+
complete -c forge -f -n "__fish_seen_subcommand_from certificates certs" -a "get" -d "Get certificate for a domain"
|
|
787
840
|
complete -c forge -f -n "__fish_seen_subcommand_from certificates certs" -a "get" -d "Get certificate details"
|
|
788
841
|
complete -c forge -f -n "__fish_seen_subcommand_from certificates certs" -a "activate" -d "Activate a certificate"
|
|
789
842
|
|
|
@@ -980,7 +1033,7 @@ function handleCompletionCommand(shell, options = {}) {
|
|
|
980
1033
|
writeFileSync(installPath, script, "utf8");
|
|
981
1034
|
console.log(`${colors.green("✓")} Installed ${shell} completion to ${colors.cyan(installPath)}`);
|
|
982
1035
|
console.log();
|
|
983
|
-
console.log(
|
|
1036
|
+
console.log(colors.bold("Next steps:"));
|
|
984
1037
|
switch (shellLower) {
|
|
985
1038
|
case "bash":
|
|
986
1039
|
console.log(` 1. Restart your shell: ${colors.cyan("exec bash")}`);
|
|
@@ -1000,40 +1053,22 @@ function handleCompletionCommand(shell, options = {}) {
|
|
|
1000
1053
|
process.exit(1);
|
|
1001
1054
|
}
|
|
1002
1055
|
}
|
|
1003
|
-
function
|
|
1056
|
+
function requireServerSiteDomainRaw(ctx, usage) {
|
|
1004
1057
|
const server = String(ctx.options.server ?? "");
|
|
1005
1058
|
const site = String(ctx.options.site ?? "");
|
|
1059
|
+
const domain = String(ctx.options.domain ?? "");
|
|
1006
1060
|
if (!server) exitWithValidationError("server_id", usage, ctx.formatter);
|
|
1007
1061
|
if (!site) exitWithValidationError("site_id", usage, ctx.formatter);
|
|
1062
|
+
if (!domain) exitWithValidationError("domain_id", usage, ctx.formatter);
|
|
1008
1063
|
return {
|
|
1009
1064
|
server,
|
|
1010
|
-
site
|
|
1065
|
+
site,
|
|
1066
|
+
domain
|
|
1011
1067
|
};
|
|
1012
1068
|
}
|
|
1013
|
-
async function certificatesList(ctx) {
|
|
1014
|
-
const { server, site } = requireServerAndSiteRaw$3(ctx, "forge certificates list --server <server_id> --site <site_id>");
|
|
1015
|
-
await runCommand(async () => {
|
|
1016
|
-
const token = ctx.getToken();
|
|
1017
|
-
const execCtx = ctx.createExecutorContext(token);
|
|
1018
|
-
const server_id = await resolveServerId(server, execCtx);
|
|
1019
|
-
const result = await listCertificates({
|
|
1020
|
-
server_id,
|
|
1021
|
-
site_id: await resolveSiteId(site, server_id, execCtx)
|
|
1022
|
-
}, execCtx);
|
|
1023
|
-
ctx.formatter.outputList(result.data, [
|
|
1024
|
-
"id",
|
|
1025
|
-
"domain",
|
|
1026
|
-
"type",
|
|
1027
|
-
"status",
|
|
1028
|
-
"active"
|
|
1029
|
-
], "No certificates found.", (c) => `${String(c.id).padEnd(8)} ${c.domain.padEnd(40)} ${c.type.padEnd(10)} ${c.status.padEnd(12)} ${c.active ? "active" : "—"}`);
|
|
1030
|
-
}, ctx.formatter);
|
|
1031
|
-
}
|
|
1032
1069
|
async function certificatesGet(args, ctx) {
|
|
1033
|
-
const
|
|
1034
|
-
const
|
|
1035
|
-
if (!id) exitWithValidationError("certificate_id", usage, ctx.formatter);
|
|
1036
|
-
const { server, site } = requireServerAndSiteRaw$3(ctx, usage);
|
|
1070
|
+
const { server, site, domain } = requireServerSiteDomainRaw(ctx, "forge certificates get --server <server_id> --site <site_id> --domain <domain_id>");
|
|
1071
|
+
const domainId = args[0] || domain;
|
|
1037
1072
|
await runCommand(async () => {
|
|
1038
1073
|
const token = ctx.getToken();
|
|
1039
1074
|
const execCtx = ctx.createExecutorContext(token);
|
|
@@ -1041,25 +1076,22 @@ async function certificatesGet(args, ctx) {
|
|
|
1041
1076
|
const result = await getCertificate({
|
|
1042
1077
|
server_id,
|
|
1043
1078
|
site_id: await resolveSiteId(site, server_id, execCtx),
|
|
1044
|
-
|
|
1079
|
+
domain_id: domainId
|
|
1045
1080
|
}, execCtx);
|
|
1046
1081
|
ctx.formatter.outputOne(result.data, [
|
|
1047
1082
|
"id",
|
|
1048
|
-
"domain",
|
|
1049
1083
|
"type",
|
|
1084
|
+
"verification_method",
|
|
1085
|
+
"key_type",
|
|
1050
1086
|
"status",
|
|
1051
1087
|
"request_status",
|
|
1052
|
-
"active",
|
|
1053
|
-
"existing",
|
|
1054
1088
|
"created_at"
|
|
1055
1089
|
]);
|
|
1056
1090
|
}, ctx.formatter);
|
|
1057
1091
|
}
|
|
1058
1092
|
async function certificatesActivate(args, ctx) {
|
|
1059
|
-
const
|
|
1060
|
-
const
|
|
1061
|
-
if (!id) exitWithValidationError("certificate_id", usage, ctx.formatter);
|
|
1062
|
-
const { server, site } = requireServerAndSiteRaw$3(ctx, usage);
|
|
1093
|
+
const { server, site, domain } = requireServerSiteDomainRaw(ctx, "forge certificates activate --server <server_id> --site <site_id> --domain <domain_id>");
|
|
1094
|
+
const domainId = args[0] || domain;
|
|
1063
1095
|
await runCommand(async () => {
|
|
1064
1096
|
const token = ctx.getToken();
|
|
1065
1097
|
const execCtx = ctx.createExecutorContext(token);
|
|
@@ -1067,39 +1099,31 @@ async function certificatesActivate(args, ctx) {
|
|
|
1067
1099
|
await activateCertificate({
|
|
1068
1100
|
server_id,
|
|
1069
1101
|
site_id: await resolveSiteId(site, server_id, execCtx),
|
|
1070
|
-
|
|
1102
|
+
domain_id: domainId
|
|
1071
1103
|
}, execCtx);
|
|
1072
|
-
ctx.formatter.success(`Certificate ${
|
|
1104
|
+
ctx.formatter.success(`Certificate for domain ${domainId} activated.`);
|
|
1073
1105
|
}, ctx.formatter);
|
|
1074
1106
|
}
|
|
1075
1107
|
const handleCertificatesCommand = createCommandRouter({
|
|
1076
1108
|
resource: "certificates",
|
|
1077
1109
|
handlers: {
|
|
1078
|
-
list: certificatesList,
|
|
1079
|
-
ls: certificatesList,
|
|
1080
1110
|
get: [certificatesGet, "args"],
|
|
1081
1111
|
activate: [certificatesActivate, "args"]
|
|
1082
1112
|
},
|
|
1083
1113
|
writeSubcommands: ["activate"]
|
|
1084
1114
|
});
|
|
1085
1115
|
function showCertificatesHelp(subcommand) {
|
|
1086
|
-
if (subcommand === "
|
|
1087
|
-
${colors.bold("forge certificates
|
|
1088
|
-
|
|
1089
|
-
${colors.bold("USAGE:")}
|
|
1090
|
-
forge certificates list --server <server_id> --site <site_id> [options]
|
|
1091
|
-
`);
|
|
1092
|
-
else if (subcommand === "get") console.log(`
|
|
1093
|
-
${colors.bold("forge certificates get")} - Get certificate details
|
|
1116
|
+
if (subcommand === "get") console.log(`
|
|
1117
|
+
${colors.bold("forge certificates get")} - Get certificate for a domain
|
|
1094
1118
|
|
|
1095
1119
|
${colors.bold("USAGE:")}
|
|
1096
|
-
forge certificates get
|
|
1120
|
+
forge certificates get --server <server_id> --site <site_id> --domain <domain_id>
|
|
1097
1121
|
`);
|
|
1098
1122
|
else if (subcommand === "activate") console.log(`
|
|
1099
1123
|
${colors.bold("forge certificates activate")} - Activate a certificate
|
|
1100
1124
|
|
|
1101
1125
|
${colors.bold("USAGE:")}
|
|
1102
|
-
forge certificates activate
|
|
1126
|
+
forge certificates activate --server <server_id> --site <site_id> --domain <domain_id>
|
|
1103
1127
|
`);
|
|
1104
1128
|
else console.log(`
|
|
1105
1129
|
${colors.bold("forge certificates")} - Manage SSL certificates
|
|
@@ -1111,15 +1135,18 @@ ${colors.bold("ALIASES:")}
|
|
|
1111
1135
|
forge certs
|
|
1112
1136
|
|
|
1113
1137
|
${colors.bold("SUBCOMMANDS:")}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
activate <id> Activate a certificate
|
|
1138
|
+
get Get certificate for a domain
|
|
1139
|
+
activate Activate a certificate
|
|
1117
1140
|
|
|
1118
1141
|
${colors.bold("OPTIONS:")}
|
|
1119
1142
|
--server <id> Server ID (required)
|
|
1120
1143
|
--site <id> Site ID (required)
|
|
1144
|
+
--domain <id> Domain record ID (required)
|
|
1121
1145
|
-f, --format <fmt> Output format: json, human, table
|
|
1122
1146
|
|
|
1147
|
+
${colors.bold("NOTE:")}
|
|
1148
|
+
In Forge v2, certificates are per-domain. Use domains to manage certificates.
|
|
1149
|
+
|
|
1123
1150
|
Run ${colors.cyan("forge certificates <subcommand> --help")} for details.
|
|
1124
1151
|
`);
|
|
1125
1152
|
}
|
|
@@ -1169,7 +1196,8 @@ async function commandsGet(args, ctx) {
|
|
|
1169
1196
|
"id",
|
|
1170
1197
|
"command",
|
|
1171
1198
|
"status",
|
|
1172
|
-
"
|
|
1199
|
+
"user_id",
|
|
1200
|
+
"duration",
|
|
1173
1201
|
"created_at"
|
|
1174
1202
|
]);
|
|
1175
1203
|
}, ctx.formatter);
|
|
@@ -1183,17 +1211,12 @@ async function commandsCreate(ctx) {
|
|
|
1183
1211
|
const token = ctx.getToken();
|
|
1184
1212
|
const execCtx = ctx.createExecutorContext(token);
|
|
1185
1213
|
const server_id = await resolveServerId(server, execCtx);
|
|
1186
|
-
|
|
1214
|
+
await createCommand({
|
|
1187
1215
|
server_id,
|
|
1188
1216
|
site_id: await resolveSiteId(site, server_id, execCtx),
|
|
1189
1217
|
command
|
|
1190
1218
|
}, execCtx);
|
|
1191
|
-
ctx.formatter.
|
|
1192
|
-
"id",
|
|
1193
|
-
"command",
|
|
1194
|
-
"status",
|
|
1195
|
-
"created_at"
|
|
1196
|
-
]);
|
|
1219
|
+
ctx.formatter.success("Command queued for execution.");
|
|
1197
1220
|
}, ctx.formatter);
|
|
1198
1221
|
}
|
|
1199
1222
|
const handleCommandsCommand = createCommandRouter({
|
|
@@ -1263,56 +1286,82 @@ function showConfigHelp() {
|
|
|
1263
1286
|
${colors.bold("forge config")} - Manage CLI configuration
|
|
1264
1287
|
|
|
1265
1288
|
${colors.bold("USAGE:")}
|
|
1266
|
-
forge config <subcommand> [
|
|
1289
|
+
forge config <subcommand> [key] [value]
|
|
1267
1290
|
|
|
1268
1291
|
${colors.bold("SUBCOMMANDS:")}
|
|
1269
|
-
set <
|
|
1270
|
-
get Show current
|
|
1271
|
-
delete Delete stored
|
|
1292
|
+
set <key> <value> Save a config value
|
|
1293
|
+
get Show current configuration (masked)
|
|
1294
|
+
delete Delete all stored configuration
|
|
1295
|
+
|
|
1296
|
+
${colors.bold("CONFIG KEYS:")}
|
|
1297
|
+
apiToken Forge API token (scoped, from Forge dashboard)
|
|
1298
|
+
organizationSlug Default organization slug (e.g. 'studio-meta')
|
|
1272
1299
|
|
|
1273
1300
|
${colors.bold("EXAMPLES:")}
|
|
1274
|
-
forge config set YOUR_FORGE_TOKEN
|
|
1301
|
+
forge config set apiToken YOUR_FORGE_TOKEN
|
|
1302
|
+
forge config set organizationSlug studio-meta
|
|
1275
1303
|
forge config get
|
|
1276
1304
|
forge config delete
|
|
1277
1305
|
`);
|
|
1278
1306
|
}
|
|
1279
1307
|
function handleConfigCommand(subcommand, args, options) {
|
|
1280
|
-
const
|
|
1308
|
+
const rawFormat = options.format ?? options.f ?? "human";
|
|
1309
|
+
const format = isOutputFormat(rawFormat) ? rawFormat : "human";
|
|
1281
1310
|
const formatter = new OutputFormatter(format, options["no-color"] === true);
|
|
1282
1311
|
const store = createConfigStore();
|
|
1283
1312
|
switch (subcommand) {
|
|
1284
1313
|
case "set": {
|
|
1285
|
-
const [
|
|
1286
|
-
if (!
|
|
1287
|
-
formatter.error("
|
|
1314
|
+
const [key, value] = args;
|
|
1315
|
+
if (!key || !value) {
|
|
1316
|
+
formatter.error("Usage: forge config set <key> <value>");
|
|
1317
|
+
formatter.info("Keys: apiToken, organizationSlug");
|
|
1288
1318
|
process.exit(1);
|
|
1289
1319
|
return;
|
|
1290
1320
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1321
|
+
switch (key) {
|
|
1322
|
+
case "apiToken":
|
|
1323
|
+
setToken(value, store);
|
|
1324
|
+
formatter.success("API token saved.");
|
|
1325
|
+
break;
|
|
1326
|
+
case "organizationSlug":
|
|
1327
|
+
setOrganizationSlug(value, store);
|
|
1328
|
+
formatter.success(`Organization slug set to "${value}".`);
|
|
1329
|
+
break;
|
|
1330
|
+
default:
|
|
1331
|
+
formatter.error(`Unknown config key: ${key}`);
|
|
1332
|
+
formatter.info("Valid keys: apiToken, organizationSlug");
|
|
1333
|
+
process.exit(1);
|
|
1334
|
+
}
|
|
1293
1335
|
break;
|
|
1294
1336
|
}
|
|
1295
1337
|
case "get": {
|
|
1296
1338
|
const token = getToken(store);
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1339
|
+
const orgSlug = getOrganizationSlug(store);
|
|
1340
|
+
if (format === "json") formatter.output({
|
|
1341
|
+
apiToken: token ? maskToken(token) : null,
|
|
1342
|
+
organizationSlug: orgSlug ?? null
|
|
1343
|
+
});
|
|
1344
|
+
else {
|
|
1345
|
+
if (token) console.log(`apiToken: ${maskToken(token)}`);
|
|
1346
|
+
else formatter.warning("No API token configured.");
|
|
1347
|
+
if (orgSlug) console.log(`organizationSlug: ${orgSlug}`);
|
|
1348
|
+
else formatter.warning("No organization slug configured.");
|
|
1349
|
+
if (!token || !orgSlug) formatter.info("Run: forge config set <key> <value>");
|
|
1304
1350
|
}
|
|
1305
1351
|
break;
|
|
1306
1352
|
}
|
|
1307
1353
|
case "delete":
|
|
1308
1354
|
deleteToken(store);
|
|
1309
|
-
formatter.success("
|
|
1355
|
+
formatter.success("Configuration deleted.");
|
|
1310
1356
|
break;
|
|
1311
1357
|
default:
|
|
1312
1358
|
showConfigHelp();
|
|
1313
1359
|
break;
|
|
1314
1360
|
}
|
|
1315
1361
|
}
|
|
1362
|
+
function maskToken(token) {
|
|
1363
|
+
return token.length > 8 ? `${token.slice(0, 4)}${"*".repeat(token.length - 8)}${token.slice(-4)}` : "****";
|
|
1364
|
+
}
|
|
1316
1365
|
async function daemonsList(ctx) {
|
|
1317
1366
|
const server = String(ctx.options.server ?? "");
|
|
1318
1367
|
if (!server) exitWithValidationError("server_id", "forge daemons list --server <server_id>", ctx.formatter);
|
|
@@ -1351,6 +1400,34 @@ async function daemonsGet(args, ctx) {
|
|
|
1351
1400
|
]);
|
|
1352
1401
|
}, ctx.formatter);
|
|
1353
1402
|
}
|
|
1403
|
+
async function daemonsUpdate(args, ctx) {
|
|
1404
|
+
const [id] = args;
|
|
1405
|
+
const server = String(ctx.options.server ?? "");
|
|
1406
|
+
const name = String(ctx.options.name ?? "");
|
|
1407
|
+
if (!id) exitWithValidationError("daemon_id", "forge daemons update <daemon_id> --server <server_id> --name <name>", ctx.formatter);
|
|
1408
|
+
if (!server) exitWithValidationError("server_id", "forge daemons update <daemon_id> --server <server_id> --name <name>", ctx.formatter);
|
|
1409
|
+
if (!name) exitWithValidationError("name", "forge daemons update <daemon_id> --server <server_id> --name <name>", ctx.formatter);
|
|
1410
|
+
await runCommand(async () => {
|
|
1411
|
+
const token = ctx.getToken();
|
|
1412
|
+
const execCtx = ctx.createExecutorContext(token);
|
|
1413
|
+
const options = {
|
|
1414
|
+
server_id: await resolveServerId(server, execCtx),
|
|
1415
|
+
id,
|
|
1416
|
+
name
|
|
1417
|
+
};
|
|
1418
|
+
if (ctx.options.config) options.config = String(ctx.options.config);
|
|
1419
|
+
const result = await updateDaemon(options, execCtx);
|
|
1420
|
+
ctx.formatter.outputOne(result.data, [
|
|
1421
|
+
"id",
|
|
1422
|
+
"command",
|
|
1423
|
+
"user",
|
|
1424
|
+
"directory",
|
|
1425
|
+
"processes",
|
|
1426
|
+
"status",
|
|
1427
|
+
"created_at"
|
|
1428
|
+
]);
|
|
1429
|
+
}, ctx.formatter);
|
|
1430
|
+
}
|
|
1354
1431
|
async function daemonsRestart(args, ctx) {
|
|
1355
1432
|
const [id] = args;
|
|
1356
1433
|
const server = String(ctx.options.server ?? "");
|
|
@@ -1372,9 +1449,10 @@ const handleDaemonsCommand = createCommandRouter({
|
|
|
1372
1449
|
list: daemonsList,
|
|
1373
1450
|
ls: daemonsList,
|
|
1374
1451
|
get: [daemonsGet, "args"],
|
|
1375
|
-
restart: [daemonsRestart, "args"]
|
|
1452
|
+
restart: [daemonsRestart, "args"],
|
|
1453
|
+
update: [daemonsUpdate, "args"]
|
|
1376
1454
|
},
|
|
1377
|
-
writeSubcommands: ["restart"]
|
|
1455
|
+
writeSubcommands: ["restart", "update"]
|
|
1378
1456
|
});
|
|
1379
1457
|
function showDaemonsHelp(subcommand) {
|
|
1380
1458
|
if (subcommand === "list" || subcommand === "ls") console.log(`
|
|
@@ -1388,6 +1466,12 @@ ${colors.bold("forge daemons get")} - Get daemon details
|
|
|
1388
1466
|
|
|
1389
1467
|
${colors.bold("USAGE:")}
|
|
1390
1468
|
forge daemons get <daemon_id> --server <server_id>
|
|
1469
|
+
`);
|
|
1470
|
+
else if (subcommand === "update") console.log(`
|
|
1471
|
+
${colors.bold("forge daemons update")} - Update a daemon
|
|
1472
|
+
|
|
1473
|
+
${colors.bold("USAGE:")}
|
|
1474
|
+
forge daemons update <daemon_id> --server <server_id> --name <name> [--config <config>]
|
|
1391
1475
|
`);
|
|
1392
1476
|
else if (subcommand === "restart") console.log(`
|
|
1393
1477
|
${colors.bold("forge daemons restart")} - Restart a daemon
|
|
@@ -1404,6 +1488,7 @@ ${colors.bold("USAGE:")}
|
|
|
1404
1488
|
${colors.bold("SUBCOMMANDS:")}
|
|
1405
1489
|
list, ls List daemons
|
|
1406
1490
|
get <id> Get daemon details
|
|
1491
|
+
update <id> Update a daemon
|
|
1407
1492
|
restart <id> Restart a daemon
|
|
1408
1493
|
|
|
1409
1494
|
${colors.bold("OPTIONS:")}
|
|
@@ -1522,7 +1607,6 @@ async function databaseUsersGet(args, ctx) {
|
|
|
1522
1607
|
"id",
|
|
1523
1608
|
"name",
|
|
1524
1609
|
"status",
|
|
1525
|
-
"databases",
|
|
1526
1610
|
"created_at"
|
|
1527
1611
|
]);
|
|
1528
1612
|
}, ctx.formatter);
|
|
@@ -1549,7 +1633,31 @@ async function databaseUsersCreate(ctx) {
|
|
|
1549
1633
|
"id",
|
|
1550
1634
|
"name",
|
|
1551
1635
|
"status",
|
|
1552
|
-
"
|
|
1636
|
+
"created_at"
|
|
1637
|
+
]);
|
|
1638
|
+
}, ctx.formatter);
|
|
1639
|
+
}
|
|
1640
|
+
async function databaseUsersUpdate(args, ctx) {
|
|
1641
|
+
const [id] = args;
|
|
1642
|
+
const server = String(ctx.options.server ?? "");
|
|
1643
|
+
const password = ctx.options.password != null ? String(ctx.options.password) : void 0;
|
|
1644
|
+
const databaseIdsRaw = ctx.options["database-ids"] ?? ctx.options.database_ids;
|
|
1645
|
+
const database_ids = Array.isArray(databaseIdsRaw) ? databaseIdsRaw.map(Number) : databaseIdsRaw ? [Number(databaseIdsRaw)] : void 0;
|
|
1646
|
+
if (!id) exitWithValidationError("user_id", "forge database-users update <user_id> --server <server_id> [--password <password>] [--database-ids <ids>]", ctx.formatter);
|
|
1647
|
+
if (!server) exitWithValidationError("server_id", "forge database-users update <user_id> --server <server_id> [--password <password>] [--database-ids <ids>]", ctx.formatter);
|
|
1648
|
+
await runCommand(async () => {
|
|
1649
|
+
const token = ctx.getToken();
|
|
1650
|
+
const execCtx = ctx.createExecutorContext(token);
|
|
1651
|
+
const result = await updateDatabaseUser({
|
|
1652
|
+
server_id: await resolveServerId(server, execCtx),
|
|
1653
|
+
id,
|
|
1654
|
+
...password !== void 0 ? { password } : {},
|
|
1655
|
+
...database_ids ? { database_ids } : {}
|
|
1656
|
+
}, execCtx);
|
|
1657
|
+
ctx.formatter.outputOne(result.data, [
|
|
1658
|
+
"id",
|
|
1659
|
+
"name",
|
|
1660
|
+
"status",
|
|
1553
1661
|
"created_at"
|
|
1554
1662
|
]);
|
|
1555
1663
|
}, ctx.formatter);
|
|
@@ -1576,9 +1684,14 @@ const handleDatabaseUsersCommand = createCommandRouter({
|
|
|
1576
1684
|
ls: databaseUsersList,
|
|
1577
1685
|
get: [databaseUsersGet, "args"],
|
|
1578
1686
|
create: databaseUsersCreate,
|
|
1687
|
+
update: [databaseUsersUpdate, "args"],
|
|
1579
1688
|
delete: [databaseUsersDelete, "args"]
|
|
1580
1689
|
},
|
|
1581
|
-
writeSubcommands: [
|
|
1690
|
+
writeSubcommands: [
|
|
1691
|
+
"create",
|
|
1692
|
+
"update",
|
|
1693
|
+
"delete"
|
|
1694
|
+
]
|
|
1582
1695
|
});
|
|
1583
1696
|
function showDatabaseUsersHelp(subcommand) {
|
|
1584
1697
|
if (subcommand === "list" || subcommand === "ls") console.log(`
|
|
@@ -1609,6 +1722,18 @@ ${colors.bold("OPTIONS:")}
|
|
|
1609
1722
|
--password <pass> User password (required)
|
|
1610
1723
|
--databases <ids> Comma-separated database IDs to grant access to (optional)
|
|
1611
1724
|
-f, --format <fmt> Output format: json, human, table
|
|
1725
|
+
`);
|
|
1726
|
+
else if (subcommand === "update") console.log(`
|
|
1727
|
+
${colors.bold("forge database-users update")} - Update a database user
|
|
1728
|
+
|
|
1729
|
+
${colors.bold("USAGE:")}
|
|
1730
|
+
forge database-users update <user_id> --server <server_id> [options]
|
|
1731
|
+
|
|
1732
|
+
${colors.bold("OPTIONS:")}
|
|
1733
|
+
--server <id> Server ID (required)
|
|
1734
|
+
--password <pass> New password (optional)
|
|
1735
|
+
--database-ids <ids> Comma-separated database IDs (optional)
|
|
1736
|
+
-f, --format <fmt> Output format: json, human, table
|
|
1612
1737
|
`);
|
|
1613
1738
|
else if (subcommand === "delete") console.log(`
|
|
1614
1739
|
${colors.bold("forge database-users delete")} - Delete a database user
|
|
@@ -1626,6 +1751,7 @@ ${colors.bold("SUBCOMMANDS:")}
|
|
|
1626
1751
|
list, ls List database users
|
|
1627
1752
|
get <id> Get database user details
|
|
1628
1753
|
create Create a new database user
|
|
1754
|
+
update <id> Update a database user
|
|
1629
1755
|
delete <id> Delete a database user
|
|
1630
1756
|
|
|
1631
1757
|
${colors.bold("OPTIONS:")}
|
|
@@ -1651,14 +1777,15 @@ async function deploymentsList(ctx) {
|
|
|
1651
1777
|
ctx.formatter.outputList(result.data, [
|
|
1652
1778
|
"id",
|
|
1653
1779
|
"status",
|
|
1654
|
-
"
|
|
1780
|
+
"commit",
|
|
1655
1781
|
"started_at"
|
|
1656
|
-
], "No deployments found.", (d) => `${String(d.id).padEnd(8)} ${d.status.padEnd(12)} ${(d.
|
|
1782
|
+
], "No deployments found.", (d) => `${String(d.id).padEnd(8)} ${d.status.padEnd(12)} ${(d.commit.hash ?? "—").padEnd(10)} ${d.started_at}`);
|
|
1657
1783
|
}, ctx.formatter);
|
|
1658
1784
|
}
|
|
1659
1785
|
async function deploymentsDeploy(ctx) {
|
|
1660
1786
|
const server = String(ctx.options.server ?? "");
|
|
1661
1787
|
const site = String(ctx.options.site ?? "");
|
|
1788
|
+
const streamLogs = Boolean(ctx.options.stream);
|
|
1662
1789
|
if (!server) exitWithValidationError("server_id", "forge deployments deploy --server <server_id> --site <site_id>", ctx.formatter);
|
|
1663
1790
|
if (!site) exitWithValidationError("site_id", "forge deployments deploy --server <server_id> --site <site_id>", ctx.formatter);
|
|
1664
1791
|
await runCommand(async () => {
|
|
@@ -1669,15 +1796,18 @@ async function deploymentsDeploy(ctx) {
|
|
|
1669
1796
|
const result = await deploySiteAndWait({
|
|
1670
1797
|
server_id,
|
|
1671
1798
|
site_id,
|
|
1672
|
-
onProgress: ({ status, elapsed_ms }) => {
|
|
1799
|
+
onProgress: streamLogs ? void 0 : ({ status, elapsed_ms }) => {
|
|
1673
1800
|
process.stderr.write(`\rDeploying… ${status} (${(elapsed_ms / 1e3).toFixed(1)}s)`);
|
|
1674
|
-
}
|
|
1801
|
+
},
|
|
1802
|
+
onLog: streamLogs ? (chunk) => {
|
|
1803
|
+
process.stdout.write(chunk);
|
|
1804
|
+
} : void 0
|
|
1675
1805
|
}, execCtx);
|
|
1676
|
-
process.stderr.write("\n");
|
|
1806
|
+
if (!streamLogs) process.stderr.write("\n");
|
|
1677
1807
|
const elapsedSec = (result.data.elapsed_ms / 1e3).toFixed(1);
|
|
1678
1808
|
if (result.data.status === "success") ctx.formatter.success(`Deployment succeeded for site ${site_id} (${elapsedSec}s).`);
|
|
1679
|
-
else ctx.formatter.
|
|
1680
|
-
if (result.data.log) ctx.formatter.output(result.data.log);
|
|
1809
|
+
else ctx.formatter.error(`Deployment failed for site ${site_id} (${elapsedSec}s).`);
|
|
1810
|
+
if (!streamLogs && result.data.log) ctx.formatter.output(result.data.log);
|
|
1681
1811
|
}, ctx.formatter);
|
|
1682
1812
|
}
|
|
1683
1813
|
const handleDeploymentsCommand = createCommandRouter({
|
|
@@ -1705,7 +1835,13 @@ ${colors.bold("OPTIONS:")}
|
|
|
1705
1835
|
${colors.bold("forge deployments deploy")} - Trigger a deployment
|
|
1706
1836
|
|
|
1707
1837
|
${colors.bold("USAGE:")}
|
|
1708
|
-
forge deployments deploy --server <server_id> --site <site_id>
|
|
1838
|
+
forge deployments deploy --server <server_id> --site <site_id> [options]
|
|
1839
|
+
|
|
1840
|
+
${colors.bold("OPTIONS:")}
|
|
1841
|
+
--server <id> Server ID (required)
|
|
1842
|
+
--site <id> Site ID (required)
|
|
1843
|
+
--stream Stream deployment logs in real-time (default: show progress)
|
|
1844
|
+
-f, --format <fmt> Output format: json, human, table
|
|
1709
1845
|
`);
|
|
1710
1846
|
else console.log(`
|
|
1711
1847
|
${colors.bold("forge deployments")} - Manage deployments
|
|
@@ -1929,6 +2065,7 @@ async function monitorsCreate(ctx) {
|
|
|
1929
2065
|
const type = String(ctx.options.type ?? "");
|
|
1930
2066
|
const operator = String(ctx.options.operator ?? "");
|
|
1931
2067
|
const threshold = Number(ctx.options.threshold ?? 0);
|
|
2068
|
+
const notify = String(ctx.options.notify ?? "");
|
|
1932
2069
|
const minutes = Number(ctx.options.minutes ?? 0);
|
|
1933
2070
|
if (!server) exitWithValidationError("server_id", "forge monitors create --server <server_id> --type <type> --operator <operator> --threshold <threshold> --minutes <minutes>", ctx.formatter);
|
|
1934
2071
|
if (!type) exitWithValidationError("type", "forge monitors create --server <server_id> --type <type> --operator <operator> --threshold <threshold> --minutes <minutes>", ctx.formatter);
|
|
@@ -1941,6 +2078,7 @@ async function monitorsCreate(ctx) {
|
|
|
1941
2078
|
type,
|
|
1942
2079
|
operator,
|
|
1943
2080
|
threshold,
|
|
2081
|
+
notify,
|
|
1944
2082
|
minutes
|
|
1945
2083
|
}, execCtx);
|
|
1946
2084
|
ctx.formatter.outputOne(result.data, [
|
|
@@ -2313,6 +2451,30 @@ async function recipesGet(args, ctx) {
|
|
|
2313
2451
|
]);
|
|
2314
2452
|
}, ctx.formatter);
|
|
2315
2453
|
}
|
|
2454
|
+
async function recipesUpdate(args, ctx) {
|
|
2455
|
+
const [id] = args;
|
|
2456
|
+
if (!id) exitWithValidationError("id", "forge recipes update <recipe_id> [--name ...] [--user ...] [--script ...]", ctx.formatter);
|
|
2457
|
+
const name = ctx.options.name;
|
|
2458
|
+
const user = ctx.options.user;
|
|
2459
|
+
const script = ctx.options.script;
|
|
2460
|
+
await runCommand(async () => {
|
|
2461
|
+
const token = ctx.getToken();
|
|
2462
|
+
const execCtx = ctx.createExecutorContext(token);
|
|
2463
|
+
const result = await updateRecipe({
|
|
2464
|
+
id,
|
|
2465
|
+
name,
|
|
2466
|
+
user,
|
|
2467
|
+
script
|
|
2468
|
+
}, execCtx);
|
|
2469
|
+
ctx.formatter.outputOne(result.data, [
|
|
2470
|
+
"id",
|
|
2471
|
+
"name",
|
|
2472
|
+
"user",
|
|
2473
|
+
"created_at",
|
|
2474
|
+
"script"
|
|
2475
|
+
]);
|
|
2476
|
+
}, ctx.formatter);
|
|
2477
|
+
}
|
|
2316
2478
|
async function recipesRun(args, ctx) {
|
|
2317
2479
|
const [id] = args;
|
|
2318
2480
|
const serversRaw = String(ctx.options.servers ?? "");
|
|
@@ -2335,9 +2497,10 @@ const handleRecipesCommand = createCommandRouter({
|
|
|
2335
2497
|
list: recipesList,
|
|
2336
2498
|
ls: recipesList,
|
|
2337
2499
|
get: [recipesGet, "args"],
|
|
2500
|
+
update: [recipesUpdate, "args"],
|
|
2338
2501
|
run: [recipesRun, "args"]
|
|
2339
2502
|
},
|
|
2340
|
-
writeSubcommands: ["run"]
|
|
2503
|
+
writeSubcommands: ["update", "run"]
|
|
2341
2504
|
});
|
|
2342
2505
|
function showRecipesHelp(subcommand) {
|
|
2343
2506
|
if (subcommand === "list" || subcommand === "ls") console.log(`
|
|
@@ -2351,6 +2514,17 @@ ${colors.bold("forge recipes get")} - Get recipe details
|
|
|
2351
2514
|
|
|
2352
2515
|
${colors.bold("USAGE:")}
|
|
2353
2516
|
forge recipes get <recipe_id>
|
|
2517
|
+
`);
|
|
2518
|
+
else if (subcommand === "update") console.log(`
|
|
2519
|
+
${colors.bold("forge recipes update")} - Update a recipe
|
|
2520
|
+
|
|
2521
|
+
${colors.bold("USAGE:")}
|
|
2522
|
+
forge recipes update <recipe_id> [options]
|
|
2523
|
+
|
|
2524
|
+
${colors.bold("OPTIONS:")}
|
|
2525
|
+
--name <name> New recipe name
|
|
2526
|
+
--user <user> New recipe user
|
|
2527
|
+
--script <script> New recipe script
|
|
2354
2528
|
`);
|
|
2355
2529
|
else if (subcommand === "run") console.log(`
|
|
2356
2530
|
${colors.bold("forge recipes run")} - Run a recipe on servers
|
|
@@ -2370,6 +2544,7 @@ ${colors.bold("USAGE:")}
|
|
|
2370
2544
|
${colors.bold("SUBCOMMANDS:")}
|
|
2371
2545
|
list, ls List all recipes
|
|
2372
2546
|
get <id> Get recipe details
|
|
2547
|
+
update <id> Update a recipe
|
|
2373
2548
|
run <id> Run a recipe on servers
|
|
2374
2549
|
|
|
2375
2550
|
${colors.bold("OPTIONS:")}
|
|
@@ -2434,25 +2609,21 @@ async function redirectRulesCreate(ctx) {
|
|
|
2434
2609
|
const { server, site } = requireServerAndSiteRaw$1(ctx, usage);
|
|
2435
2610
|
const from = String(ctx.options.from ?? "");
|
|
2436
2611
|
const to = String(ctx.options.to ?? "");
|
|
2612
|
+
const type = String(ctx.options.type ?? "redirect");
|
|
2437
2613
|
if (!from) exitWithValidationError("from", usage, ctx.formatter);
|
|
2438
2614
|
if (!to) exitWithValidationError("to", usage, ctx.formatter);
|
|
2439
2615
|
await runCommand(async () => {
|
|
2440
2616
|
const token = ctx.getToken();
|
|
2441
2617
|
const execCtx = ctx.createExecutorContext(token);
|
|
2442
2618
|
const server_id = await resolveServerId(server, execCtx);
|
|
2443
|
-
|
|
2619
|
+
await createRedirectRule({
|
|
2444
2620
|
server_id,
|
|
2445
2621
|
site_id: await resolveSiteId(site, server_id, execCtx),
|
|
2446
2622
|
from,
|
|
2447
|
-
to
|
|
2623
|
+
to,
|
|
2624
|
+
type
|
|
2448
2625
|
}, execCtx);
|
|
2449
|
-
ctx.formatter.
|
|
2450
|
-
"id",
|
|
2451
|
-
"from",
|
|
2452
|
-
"to",
|
|
2453
|
-
"type",
|
|
2454
|
-
"created_at"
|
|
2455
|
-
]);
|
|
2626
|
+
ctx.formatter.success("Redirect rule created.");
|
|
2456
2627
|
}, ctx.formatter);
|
|
2457
2628
|
}
|
|
2458
2629
|
async function redirectRulesDelete(args, ctx) {
|
|
@@ -2542,11 +2713,16 @@ Run ${colors.cyan("forge redirect-rules <subcommand> --help")} for details.
|
|
|
2542
2713
|
}
|
|
2543
2714
|
async function scheduledJobsList(ctx) {
|
|
2544
2715
|
const server = String(ctx.options.server ?? "");
|
|
2716
|
+
const site = ctx.options.site ? String(ctx.options.site) : void 0;
|
|
2545
2717
|
if (!server) exitWithValidationError("server_id", "forge scheduled-jobs list --server <server_id>", ctx.formatter);
|
|
2546
2718
|
await runCommand(async () => {
|
|
2547
2719
|
const token = ctx.getToken();
|
|
2548
2720
|
const execCtx = ctx.createExecutorContext(token);
|
|
2549
|
-
const
|
|
2721
|
+
const server_id = await resolveServerId(server, execCtx);
|
|
2722
|
+
const result = await listScheduledJobs({
|
|
2723
|
+
server_id,
|
|
2724
|
+
site_id: site ? await resolveSiteId(site, server_id, execCtx) : void 0
|
|
2725
|
+
}, execCtx);
|
|
2550
2726
|
ctx.formatter.outputList(result.data, [
|
|
2551
2727
|
"id",
|
|
2552
2728
|
"command",
|
|
@@ -2558,14 +2734,17 @@ async function scheduledJobsList(ctx) {
|
|
|
2558
2734
|
async function scheduledJobsGet(args, ctx) {
|
|
2559
2735
|
const [id] = args;
|
|
2560
2736
|
const server = String(ctx.options.server ?? "");
|
|
2737
|
+
const site = ctx.options.site ? String(ctx.options.site) : void 0;
|
|
2561
2738
|
if (!id) exitWithValidationError("job_id", "forge scheduled-jobs get <job_id> --server <server_id>", ctx.formatter);
|
|
2562
2739
|
if (!server) exitWithValidationError("server_id", "forge scheduled-jobs get <job_id> --server <server_id>", ctx.formatter);
|
|
2563
2740
|
await runCommand(async () => {
|
|
2564
2741
|
const token = ctx.getToken();
|
|
2565
2742
|
const execCtx = ctx.createExecutorContext(token);
|
|
2743
|
+
const server_id = await resolveServerId(server, execCtx);
|
|
2566
2744
|
const result = await getScheduledJob({
|
|
2567
|
-
server_id
|
|
2568
|
-
id
|
|
2745
|
+
server_id,
|
|
2746
|
+
id,
|
|
2747
|
+
site_id: site ? await resolveSiteId(site, server_id, execCtx) : void 0
|
|
2569
2748
|
}, execCtx);
|
|
2570
2749
|
ctx.formatter.outputOne(result.data, [
|
|
2571
2750
|
"id",
|
|
@@ -2580,15 +2759,24 @@ async function scheduledJobsGet(args, ctx) {
|
|
|
2580
2759
|
}
|
|
2581
2760
|
async function scheduledJobsCreate(ctx) {
|
|
2582
2761
|
const server = String(ctx.options.server ?? "");
|
|
2762
|
+
const site = ctx.options.site ? String(ctx.options.site) : void 0;
|
|
2583
2763
|
const command = String(ctx.options.command ?? "");
|
|
2584
|
-
|
|
2585
|
-
|
|
2764
|
+
const user = String(ctx.options.user ?? "forge");
|
|
2765
|
+
const frequency = String(ctx.options.frequency ?? "custom");
|
|
2766
|
+
const cron = ctx.options.cron ? String(ctx.options.cron) : void 0;
|
|
2767
|
+
if (!server) exitWithValidationError("server_id", "forge scheduled-jobs create --server <server_id> --command <command> --user <user> --frequency <frequency>", ctx.formatter);
|
|
2768
|
+
if (!command) exitWithValidationError("command", "forge scheduled-jobs create --server <server_id> --command <command> --user <user> --frequency <frequency>", ctx.formatter);
|
|
2586
2769
|
await runCommand(async () => {
|
|
2587
2770
|
const token = ctx.getToken();
|
|
2588
2771
|
const execCtx = ctx.createExecutorContext(token);
|
|
2772
|
+
const server_id = await resolveServerId(server, execCtx);
|
|
2589
2773
|
const result = await createScheduledJob({
|
|
2590
|
-
server_id
|
|
2591
|
-
|
|
2774
|
+
server_id,
|
|
2775
|
+
site_id: site ? await resolveSiteId(site, server_id, execCtx) : void 0,
|
|
2776
|
+
command,
|
|
2777
|
+
user,
|
|
2778
|
+
frequency,
|
|
2779
|
+
cron
|
|
2592
2780
|
}, execCtx);
|
|
2593
2781
|
ctx.formatter.outputOne(result.data, [
|
|
2594
2782
|
"id",
|
|
@@ -2604,14 +2792,17 @@ async function scheduledJobsCreate(ctx) {
|
|
|
2604
2792
|
async function scheduledJobsDelete(args, ctx) {
|
|
2605
2793
|
const [id] = args;
|
|
2606
2794
|
const server = String(ctx.options.server ?? "");
|
|
2795
|
+
const site = ctx.options.site ? String(ctx.options.site) : void 0;
|
|
2607
2796
|
if (!id) exitWithValidationError("job_id", "forge scheduled-jobs delete <job_id> --server <server_id>", ctx.formatter);
|
|
2608
2797
|
if (!server) exitWithValidationError("server_id", "forge scheduled-jobs delete <job_id> --server <server_id>", ctx.formatter);
|
|
2609
2798
|
await runCommand(async () => {
|
|
2610
2799
|
const token = ctx.getToken();
|
|
2611
2800
|
const execCtx = ctx.createExecutorContext(token);
|
|
2801
|
+
const server_id = await resolveServerId(server, execCtx);
|
|
2612
2802
|
await deleteScheduledJob({
|
|
2613
|
-
server_id
|
|
2614
|
-
id
|
|
2803
|
+
server_id,
|
|
2804
|
+
id,
|
|
2805
|
+
site_id: site ? await resolveSiteId(site, server_id, execCtx) : void 0
|
|
2615
2806
|
}, execCtx);
|
|
2616
2807
|
ctx.formatter.success(`Scheduled job ${id} deleted.`);
|
|
2617
2808
|
}, ctx.formatter);
|
|
@@ -2725,7 +2916,6 @@ async function securityRulesGet(args, ctx) {
|
|
|
2725
2916
|
"id",
|
|
2726
2917
|
"name",
|
|
2727
2918
|
"path",
|
|
2728
|
-
"credentials",
|
|
2729
2919
|
"created_at"
|
|
2730
2920
|
]);
|
|
2731
2921
|
}, ctx.formatter);
|
|
@@ -2753,6 +2943,32 @@ async function securityRulesCreate(ctx) {
|
|
|
2753
2943
|
]);
|
|
2754
2944
|
}, ctx.formatter);
|
|
2755
2945
|
}
|
|
2946
|
+
async function securityRulesUpdate(args, ctx) {
|
|
2947
|
+
const [id] = args;
|
|
2948
|
+
const usage = "forge security-rules update <rule_id> --server <server_id> --site <site_id> --name <name>";
|
|
2949
|
+
if (!id) exitWithValidationError("rule_id", usage, ctx.formatter);
|
|
2950
|
+
const { server, site } = requireServerAndSiteRaw(ctx, usage);
|
|
2951
|
+
const name = String(ctx.options.name ?? "");
|
|
2952
|
+
if (!name) exitWithValidationError("name", usage, ctx.formatter);
|
|
2953
|
+
await runCommand(async () => {
|
|
2954
|
+
const token = ctx.getToken();
|
|
2955
|
+
const execCtx = ctx.createExecutorContext(token);
|
|
2956
|
+
const server_id = await resolveServerId(server, execCtx);
|
|
2957
|
+
const result = await updateSecurityRule({
|
|
2958
|
+
server_id,
|
|
2959
|
+
site_id: await resolveSiteId(site, server_id, execCtx),
|
|
2960
|
+
id,
|
|
2961
|
+
name,
|
|
2962
|
+
credentials: []
|
|
2963
|
+
}, execCtx);
|
|
2964
|
+
ctx.formatter.outputOne(result.data, [
|
|
2965
|
+
"id",
|
|
2966
|
+
"name",
|
|
2967
|
+
"path",
|
|
2968
|
+
"created_at"
|
|
2969
|
+
]);
|
|
2970
|
+
}, ctx.formatter);
|
|
2971
|
+
}
|
|
2756
2972
|
async function securityRulesDelete(args, ctx) {
|
|
2757
2973
|
const [id] = args;
|
|
2758
2974
|
const usage = "forge security-rules delete <rule_id> --server <server_id> --site <site_id>";
|
|
@@ -2777,9 +2993,14 @@ const handleSecurityRulesCommand = createCommandRouter({
|
|
|
2777
2993
|
ls: securityRulesList,
|
|
2778
2994
|
get: [securityRulesGet, "args"],
|
|
2779
2995
|
create: securityRulesCreate,
|
|
2996
|
+
update: [securityRulesUpdate, "args"],
|
|
2780
2997
|
delete: [securityRulesDelete, "args"]
|
|
2781
2998
|
},
|
|
2782
|
-
writeSubcommands: [
|
|
2999
|
+
writeSubcommands: [
|
|
3000
|
+
"create",
|
|
3001
|
+
"update",
|
|
3002
|
+
"delete"
|
|
3003
|
+
]
|
|
2783
3004
|
});
|
|
2784
3005
|
function showSecurityRulesHelp(subcommand) {
|
|
2785
3006
|
if (subcommand === "list" || subcommand === "ls") console.log(`
|
|
@@ -2805,6 +3026,18 @@ ${colors.bold("forge security-rules create")} - Create a new security rule
|
|
|
2805
3026
|
${colors.bold("USAGE:")}
|
|
2806
3027
|
forge security-rules create --server <server_id> --site <site_id> --name <name> [options]
|
|
2807
3028
|
|
|
3029
|
+
${colors.bold("OPTIONS:")}
|
|
3030
|
+
--server <id> Server ID (required)
|
|
3031
|
+
--site <id> Site ID (required)
|
|
3032
|
+
--name <name> Security rule name (required)
|
|
3033
|
+
-f, --format <fmt> Output format: json, human, table
|
|
3034
|
+
`);
|
|
3035
|
+
else if (subcommand === "update") console.log(`
|
|
3036
|
+
${colors.bold("forge security-rules update")} - Update a security rule
|
|
3037
|
+
|
|
3038
|
+
${colors.bold("USAGE:")}
|
|
3039
|
+
forge security-rules update <rule_id> --server <server_id> --site <site_id> --name <name> [options]
|
|
3040
|
+
|
|
2808
3041
|
${colors.bold("OPTIONS:")}
|
|
2809
3042
|
--server <id> Server ID (required)
|
|
2810
3043
|
--site <id> Site ID (required)
|
|
@@ -2827,6 +3060,7 @@ ${colors.bold("SUBCOMMANDS:")}
|
|
|
2827
3060
|
list, ls List security rules
|
|
2828
3061
|
get <id> Get security rule details
|
|
2829
3062
|
create Create a new security rule
|
|
3063
|
+
update <id> Update a security rule
|
|
2830
3064
|
delete <id> Delete a security rule
|
|
2831
3065
|
|
|
2832
3066
|
${colors.bold("OPTIONS:")}
|
|
@@ -2855,7 +3089,7 @@ async function serversList(ctx) {
|
|
|
2855
3089
|
ctx.formatter.info("No servers found.");
|
|
2856
3090
|
return;
|
|
2857
3091
|
}
|
|
2858
|
-
for (const server of result.data) console.log(`${String(server.id).padEnd(8)} ${server.name.padEnd(30)} ${server.ip_address.padEnd(16)} ${server.is_ready ? "ready" : "provisioning"}`);
|
|
3092
|
+
for (const server of result.data) console.log(`${String(server.id).padEnd(8)} ${server.name.padEnd(30)} ${(server.ip_address ?? "—").padEnd(16)} ${server.is_ready ? "ready" : "provisioning"}`);
|
|
2859
3093
|
}
|
|
2860
3094
|
}, ctx.formatter);
|
|
2861
3095
|
}
|
|
@@ -2998,12 +3232,43 @@ async function sitesGet(args, ctx) {
|
|
|
2998
3232
|
"name",
|
|
2999
3233
|
"status",
|
|
3000
3234
|
"php_version",
|
|
3001
|
-
"
|
|
3235
|
+
"app_type",
|
|
3236
|
+
"repository",
|
|
3237
|
+
"quick_deploy",
|
|
3238
|
+
"deployment_status",
|
|
3239
|
+
"https",
|
|
3240
|
+
"created_at"
|
|
3241
|
+
]);
|
|
3242
|
+
}, ctx.formatter);
|
|
3243
|
+
}
|
|
3244
|
+
async function sitesUpdate(args, ctx) {
|
|
3245
|
+
const [site_id] = args;
|
|
3246
|
+
const server = String(ctx.options.server ?? "");
|
|
3247
|
+
if (!site_id) exitWithValidationError("site_id", "forge sites update <site_id> --server <server_id>", ctx.formatter);
|
|
3248
|
+
if (!server) exitWithValidationError("server_id", "forge sites update <site_id> --server <server_id>", ctx.formatter);
|
|
3249
|
+
await runCommand(async () => {
|
|
3250
|
+
const token = ctx.getToken();
|
|
3251
|
+
const execCtx = ctx.createExecutorContext(token);
|
|
3252
|
+
const result = await updateSite({
|
|
3253
|
+
server_id: await resolveServerId(server, execCtx),
|
|
3254
|
+
site_id,
|
|
3255
|
+
root_path: ctx.options.root_path,
|
|
3256
|
+
directory: ctx.options.directory,
|
|
3257
|
+
type: ctx.options.type,
|
|
3258
|
+
php_version: ctx.options.php_version,
|
|
3259
|
+
push_to_deploy: ctx.options.push_to_deploy !== void 0 ? ctx.options.push_to_deploy === "true" || ctx.options.push_to_deploy === true : void 0,
|
|
3260
|
+
repository_branch: ctx.options.repository_branch
|
|
3261
|
+
}, execCtx);
|
|
3262
|
+
ctx.formatter.outputOne(result.data, [
|
|
3263
|
+
"id",
|
|
3264
|
+
"name",
|
|
3265
|
+
"status",
|
|
3266
|
+
"php_version",
|
|
3267
|
+
"app_type",
|
|
3002
3268
|
"repository",
|
|
3003
|
-
"repository_branch",
|
|
3004
3269
|
"quick_deploy",
|
|
3005
3270
|
"deployment_status",
|
|
3006
|
-
"
|
|
3271
|
+
"https",
|
|
3007
3272
|
"created_at"
|
|
3008
3273
|
]);
|
|
3009
3274
|
}, ctx.formatter);
|
|
@@ -3013,7 +3278,8 @@ const handleSitesCommand = createCommandRouter({
|
|
|
3013
3278
|
handlers: {
|
|
3014
3279
|
list: sitesList,
|
|
3015
3280
|
ls: sitesList,
|
|
3016
|
-
get: [sitesGet, "args"]
|
|
3281
|
+
get: [sitesGet, "args"],
|
|
3282
|
+
update: [sitesUpdate, "args"]
|
|
3017
3283
|
}
|
|
3018
3284
|
});
|
|
3019
3285
|
function showSitesHelp(subcommand) {
|
|
@@ -3210,7 +3476,7 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
3210
3476
|
positional
|
|
3211
3477
|
};
|
|
3212
3478
|
}
|
|
3213
|
-
var VERSION = "0.
|
|
3479
|
+
var VERSION = "0.4.0";
|
|
3214
3480
|
function showHelp() {
|
|
3215
3481
|
console.log(`
|
|
3216
3482
|
${colors.bold("forge")} v${VERSION}
|
|
@@ -3260,8 +3526,8 @@ ${colors.bold("COMMANDS:")}
|
|
|
3260
3526
|
get Get nginx config (requires --server --site)
|
|
3261
3527
|
update Update nginx config (requires --server --site --content)
|
|
3262
3528
|
|
|
3263
|
-
certificates, certs Manage SSL certificates
|
|
3264
|
-
|
|
3529
|
+
certificates, certs Manage SSL certificates (per-domain in v2)
|
|
3530
|
+
get Get certificate for a domain (requires --server --site --domain)
|
|
3265
3531
|
get <id> Get certificate details (requires --server --site)
|
|
3266
3532
|
activate <id> Activate a certificate (requires --server --site)
|
|
3267
3533
|
|
|
@@ -3330,8 +3596,10 @@ ${colors.bold("COMMANDS:")}
|
|
|
3330
3596
|
|
|
3331
3597
|
${colors.bold("OPTIONS:")}
|
|
3332
3598
|
--token <token> Forge API token (overrides config and env)
|
|
3599
|
+
--org <slug> Organization slug (overrides config and FORGE_ORG)
|
|
3333
3600
|
--server <id> Server ID (required for server-scoped commands)
|
|
3334
3601
|
--site <id> Site ID (required for site-scoped commands)
|
|
3602
|
+
--domain <id> Domain record ID (for certificate commands)
|
|
3335
3603
|
-f, --format <fmt> Output format: json, human, table (default: human)
|
|
3336
3604
|
--no-color Disable colored output
|
|
3337
3605
|
-h, --help Show help
|
|
@@ -3363,6 +3631,7 @@ ${colors.bold("CREDENTIAL PRIORITY:")}
|
|
|
3363
3631
|
|
|
3364
3632
|
${colors.bold("ENVIRONMENT VARIABLES:")}
|
|
3365
3633
|
FORGE_API_TOKEN API token
|
|
3634
|
+
FORGE_ORG Organization slug (e.g. studio-meta)
|
|
3366
3635
|
NO_COLOR Disable colors
|
|
3367
3636
|
XDG_CONFIG_HOME Config directory (respects XDG spec)
|
|
3368
3637
|
`);
|