deploro 0.2.1 → 0.3.1
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/index.js +254 -24
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
|
|
6
7
|
// src/commands/login.ts
|
|
@@ -148,6 +149,11 @@ async function logoutCommand() {
|
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
// src/lib/api.ts
|
|
152
|
+
function errorMessage(status, body, statusText) {
|
|
153
|
+
const parts = [body.error ?? statusText];
|
|
154
|
+
if (body.detail && body.detail !== body.error) parts.push(body.detail);
|
|
155
|
+
return `${status} ${parts.join(" \u2014 ")}`;
|
|
156
|
+
}
|
|
151
157
|
async function apiRequest(path, options, configOverride) {
|
|
152
158
|
const config = configOverride ?? requireConfig();
|
|
153
159
|
const headers = {
|
|
@@ -159,7 +165,7 @@ async function apiRequest(path, options, configOverride) {
|
|
|
159
165
|
const res = await fetch(`${config.baseUrl}${path}`, { ...options, headers });
|
|
160
166
|
if (!res.ok) {
|
|
161
167
|
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
162
|
-
throw new Error(
|
|
168
|
+
throw new Error(errorMessage(res.status, body, res.statusText));
|
|
163
169
|
}
|
|
164
170
|
return res.json();
|
|
165
171
|
}
|
|
@@ -175,7 +181,7 @@ async function apiRequestNoProject(path, options) {
|
|
|
175
181
|
});
|
|
176
182
|
if (!res.ok) {
|
|
177
183
|
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
178
|
-
throw new Error(
|
|
184
|
+
throw new Error(errorMessage(res.status, body, res.statusText));
|
|
179
185
|
}
|
|
180
186
|
return res.json();
|
|
181
187
|
}
|
|
@@ -226,6 +232,44 @@ function json(data) {
|
|
|
226
232
|
}
|
|
227
233
|
|
|
228
234
|
// src/commands/db.ts
|
|
235
|
+
async function dbCreateCommand() {
|
|
236
|
+
const config = requireProject();
|
|
237
|
+
const data = await apiRequestNoProject(
|
|
238
|
+
`/api/projects/${config.projectId}/database`,
|
|
239
|
+
{ method: "POST" }
|
|
240
|
+
);
|
|
241
|
+
console.log(`\u2713 Database ready: ${data.database_name}`);
|
|
242
|
+
}
|
|
243
|
+
async function dbInfoCommand() {
|
|
244
|
+
const config = requireProject();
|
|
245
|
+
const { projects } = await apiRequestNoProject("/api/projects");
|
|
246
|
+
const project = projects.find((p) => p.id === config.projectId);
|
|
247
|
+
if (!project) {
|
|
248
|
+
console.error("Active project not found or not visible to this token. Run: deploro projects");
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
console.log("\n\u2500\u2500 DATABASE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
252
|
+
console.log(` Project : ${project.slug}`);
|
|
253
|
+
console.log(` Database name : ${project.database_name ?? "(not provisioned \u2014 run: deploro db create)"}`);
|
|
254
|
+
console.log("");
|
|
255
|
+
if (project.database_name) {
|
|
256
|
+
console.log(
|
|
257
|
+
"Direct PostgreSQL access (host/port/user/password) is not available.\nThe database lives on an internal network reachable only by Deploro's own\nSQL proxy \u2014 and Cloudflare Workers can't open raw TCP sockets regardless of\nnetwork reachability, so a deployed Worker can never use a connection string\neven if one were issued. There is no DATABASE_URL / HYPERDRIVE binding to\nconfigure on your deployed app; none is provisioned automatically.\n"
|
|
258
|
+
);
|
|
259
|
+
console.log(
|
|
260
|
+
"For a deployed Worker to read/write this project's data at runtime, call\nthe per-project REST API instead (every table gets one automatically):\n"
|
|
261
|
+
);
|
|
262
|
+
console.log(` Base URL : ${config.baseUrl}/api/projects/${config.projectId}/studio`);
|
|
263
|
+
console.log(` Spec : ${config.baseUrl}/api/projects/${config.projectId}/studio/spec (OpenAPI 3.1)`);
|
|
264
|
+
console.log(` Auth : Authorization: Bearer <project-scoped PAT>`);
|
|
265
|
+
console.log(` create one with: deploro token create <name> --project ${project.slug}`);
|
|
266
|
+
console.log("");
|
|
267
|
+
console.log("To hand that to your deployed Worker as secrets:");
|
|
268
|
+
console.log(` deploro hosting set-env DEPLORO_API_URL "${config.baseUrl}/api/projects/${config.projectId}/studio"`);
|
|
269
|
+
console.log(` deploro hosting set-env DEPLORO_API_TOKEN "<pat>"`);
|
|
270
|
+
console.log("");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
229
273
|
async function dbTablesCommand() {
|
|
230
274
|
requireProject();
|
|
231
275
|
const data = await apiRequest("/api/db/tables");
|
|
@@ -319,6 +363,9 @@ async function dbDropTableCommand(tableName, opts) {
|
|
|
319
363
|
}
|
|
320
364
|
|
|
321
365
|
// src/commands/auth.ts
|
|
366
|
+
function isPatToken(accessToken) {
|
|
367
|
+
return accessToken.startsWith("gallium_pat_") || accessToken.startsWith("deploro_pat_");
|
|
368
|
+
}
|
|
322
369
|
async function authUsersCommand() {
|
|
323
370
|
requireConfig();
|
|
324
371
|
const data = await apiRequest("/api/auth/users");
|
|
@@ -345,30 +392,188 @@ async function authInviteCommand(email, opts) {
|
|
|
345
392
|
}
|
|
346
393
|
}
|
|
347
394
|
async function authWhoamiCommand() {
|
|
395
|
+
requireConfig();
|
|
396
|
+
const data = await apiRequestNoProject("/api/auth/whoami");
|
|
397
|
+
if (data.authMethod === "pat") {
|
|
398
|
+
console.log("Authenticated via personal access token.");
|
|
399
|
+
console.log(` User id : ${data.userId}`);
|
|
400
|
+
console.log(` Role : ${data.role}`);
|
|
401
|
+
console.log(` Scopes : ${(data.scopes ?? []).join(", ") || "(none)"}`);
|
|
402
|
+
console.log(` Scoped to ${data.projects?.length ?? 0} project(s):`);
|
|
403
|
+
if (data.projects && data.projects.length > 0) {
|
|
404
|
+
table(data.projects.map((p) => ({ id: p.id, slug: p.slug, name: p.name })));
|
|
405
|
+
} else {
|
|
406
|
+
console.log(" (none \u2014 this token cannot access any project)");
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
console.log("Authenticated via browser session.");
|
|
410
|
+
console.log(` User id : ${data.userId}`);
|
|
411
|
+
console.log(` Role : ${data.role}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function requireSessionAuth(config) {
|
|
415
|
+
if (isPatToken(config.accessToken)) {
|
|
416
|
+
console.error(
|
|
417
|
+
"Managing tokens requires a browser-session login, not a personal access token.\nRun 'deploro login' without --token (email + password) and try again."
|
|
418
|
+
);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
async function tokenListCommand() {
|
|
348
423
|
const config = requireConfig();
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
424
|
+
requireSessionAuth(config);
|
|
425
|
+
const data = await apiRequestNoProject("/api/auth/tokens");
|
|
426
|
+
table(
|
|
427
|
+
data.tokens.map((t) => ({
|
|
428
|
+
id: t.id,
|
|
429
|
+
name: t.name,
|
|
430
|
+
prefix: `${t.token_prefix}\u2026`,
|
|
431
|
+
scopes: t.scopes.join(","),
|
|
432
|
+
projects: t.project_ids.length,
|
|
433
|
+
expires: new Date(t.expires_at).toLocaleDateString(),
|
|
434
|
+
status: t.revoked_at ? "revoked" : "active"
|
|
435
|
+
}))
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
async function tokenCreateCommand(name, opts) {
|
|
439
|
+
const config = requireConfig();
|
|
440
|
+
requireSessionAuth(config);
|
|
441
|
+
const scopes = !opts.read && !opts.write ? ["read"] : [opts.read && "read", opts.write && "write"].filter(Boolean);
|
|
442
|
+
let projectIds;
|
|
443
|
+
if (opts.project) {
|
|
444
|
+
const list = await apiRequestNoProject(
|
|
445
|
+
"/api/projects"
|
|
446
|
+
);
|
|
447
|
+
const wanted = opts.project.split(",").map((s) => s.trim().toLowerCase());
|
|
448
|
+
projectIds = list.projects.filter((p) => wanted.includes(p.id) || wanted.includes(p.slug.toLowerCase()) || wanted.includes(p.name.toLowerCase())).map((p) => p.id);
|
|
449
|
+
if (projectIds.length === 0) {
|
|
450
|
+
console.error(`No project matches "${opts.project}". Run: deploro projects`);
|
|
451
|
+
process.exit(1);
|
|
364
452
|
}
|
|
365
|
-
|
|
453
|
+
} else if (config.projectId) {
|
|
454
|
+
projectIds = [config.projectId];
|
|
455
|
+
} else {
|
|
456
|
+
console.error("No project specified. Pass --project <slug> or set an active project with 'deploro use'.");
|
|
366
457
|
process.exit(1);
|
|
367
458
|
}
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
459
|
+
const days = Number(opts.days ?? 90);
|
|
460
|
+
const data = await apiRequestNoProject(
|
|
461
|
+
"/api/auth/tokens",
|
|
462
|
+
{ method: "POST", body: JSON.stringify({ name, scopes, projectIds, expiresInDays: days }) }
|
|
463
|
+
);
|
|
464
|
+
console.log("\u2713 Token created \u2014 copy it now, it will not be shown again:");
|
|
465
|
+
console.log(`
|
|
466
|
+
${data.token}
|
|
467
|
+
`);
|
|
468
|
+
console.log(` Scopes : ${scopes.join(", ")}`);
|
|
469
|
+
console.log(` Projects: ${projectIds.length}`);
|
|
470
|
+
console.log(` Expires : in ${data.expiresInDays} days`);
|
|
471
|
+
console.log(`
|
|
472
|
+
Use it with: deploro login --token ${data.token}`);
|
|
473
|
+
}
|
|
474
|
+
async function tokenRevokeCommand(id, opts) {
|
|
475
|
+
const config = requireConfig();
|
|
476
|
+
requireSessionAuth(config);
|
|
477
|
+
await confirmOrDie(`Revoke token ${id}?`, opts.yes);
|
|
478
|
+
await apiRequestNoProject(`/api/auth/tokens/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
479
|
+
console.log(`\u2713 Token ${id} revoked`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/commands/migrate.ts
|
|
483
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
484
|
+
function readSqlOpt(inline, file, label) {
|
|
485
|
+
if (file) {
|
|
486
|
+
try {
|
|
487
|
+
return readFileSync2(file, "utf-8");
|
|
488
|
+
} catch (err) {
|
|
489
|
+
console.error(`Could not read --${label}-file "${file}": ${err instanceof Error ? err.message : String(err)}`);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return inline;
|
|
494
|
+
}
|
|
495
|
+
async function migrateCreateCommand(name, opts) {
|
|
496
|
+
requireProject();
|
|
497
|
+
const sqlUp = readSqlOpt(opts.up, opts.upFile, "up");
|
|
498
|
+
if (!sqlUp) {
|
|
499
|
+
console.error('Migration SQL is required: pass --up "<sql>" or --up-file <path>');
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
const sqlDown = readSqlOpt(opts.down, opts.downFile, "down");
|
|
503
|
+
const config = requireProject();
|
|
504
|
+
const data = await apiRequest(
|
|
505
|
+
`/api/projects/${config.projectId}/migrations`,
|
|
506
|
+
{ method: "POST", body: JSON.stringify({ name, sql_up: sqlUp, sql_down: sqlDown }) }
|
|
507
|
+
);
|
|
508
|
+
console.log(`\u2713 Migration created: ${data.migration.name} (${data.migration.id})`);
|
|
509
|
+
console.log(` Apply it with: deploro migrate apply ${data.migration.id}`);
|
|
510
|
+
}
|
|
511
|
+
async function migrateListCommand() {
|
|
512
|
+
const config = requireProject();
|
|
513
|
+
const data = await apiRequest(`/api/projects/${config.projectId}/migrations`);
|
|
514
|
+
table(
|
|
515
|
+
data.migrations.map((m) => ({
|
|
516
|
+
id: m.id,
|
|
517
|
+
name: m.name,
|
|
518
|
+
status: m.applied_at ? "applied" : "pending",
|
|
519
|
+
applied_at: m.applied_at ? new Date(m.applied_at).toLocaleString() : "\u2014",
|
|
520
|
+
created_at: new Date(m.created_at).toLocaleDateString()
|
|
521
|
+
}))
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
async function resolveMigration(idOrName) {
|
|
525
|
+
const config = requireProject();
|
|
526
|
+
const data = await apiRequest(`/api/projects/${config.projectId}/migrations`);
|
|
527
|
+
const matches = data.migrations.filter((m) => m.id === idOrName || m.name === idOrName);
|
|
528
|
+
if (matches.length === 0) {
|
|
529
|
+
console.error(`No migration matches "${idOrName}". Run: deploro migrate list`);
|
|
530
|
+
process.exit(1);
|
|
531
|
+
}
|
|
532
|
+
if (matches.length > 1) {
|
|
533
|
+
console.error(`"${idOrName}" is ambiguous (matches ${matches.length} migrations by name) \u2014 use the id.`);
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
return { config, migration: matches[0] };
|
|
537
|
+
}
|
|
538
|
+
async function migrateApplyCommand(idOrName) {
|
|
539
|
+
const { config, migration } = await resolveMigration(idOrName);
|
|
540
|
+
if (migration.applied_at) {
|
|
541
|
+
console.error(`Migration "${migration.name}" is already applied.`);
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}
|
|
544
|
+
const data = await apiRequest(
|
|
545
|
+
`/api/projects/${config.projectId}/migrations/${migration.id}/apply`,
|
|
546
|
+
{ method: "POST" }
|
|
547
|
+
);
|
|
548
|
+
console.log(`\u2713 Applied: ${data.migration.name}`);
|
|
549
|
+
console.log(` Applied at: ${data.migration.applied_at ? new Date(data.migration.applied_at).toLocaleString() : "(unknown)"}`);
|
|
550
|
+
}
|
|
551
|
+
async function migrateRollbackCommand(idOrName, opts) {
|
|
552
|
+
const { config, migration } = await resolveMigration(idOrName);
|
|
553
|
+
if (!migration.applied_at) {
|
|
554
|
+
console.error(`Migration "${migration.name}" has not been applied.`);
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
if (!migration.sql_down) {
|
|
558
|
+
console.error(`Migration "${migration.name}" has no rollback SQL defined.`);
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
await confirmOrDie(`Roll back "${migration.name}"? This runs its sql_down against the project database.`, opts.yes);
|
|
562
|
+
const data = await apiRequest(
|
|
563
|
+
`/api/projects/${config.projectId}/migrations/${migration.id}/rollback`,
|
|
564
|
+
{ method: "POST" }
|
|
565
|
+
);
|
|
566
|
+
console.log(`\u2713 Rolled back: ${data.migration.name}`);
|
|
567
|
+
}
|
|
568
|
+
async function migrateDeleteCommand(idOrName, opts) {
|
|
569
|
+
const { config, migration } = await resolveMigration(idOrName);
|
|
570
|
+
if (migration.applied_at) {
|
|
571
|
+
console.error(`"${migration.name}" is applied \u2014 roll it back first: deploro migrate rollback ${migration.id}`);
|
|
572
|
+
process.exit(1);
|
|
573
|
+
}
|
|
574
|
+
await confirmOrDie(`Delete unapplied migration "${migration.name}"?`, opts.yes);
|
|
575
|
+
await apiRequest(`/api/projects/${config.projectId}/migrations/${migration.id}`, { method: "DELETE" });
|
|
576
|
+
console.log(`\u2713 Migration "${migration.name}" deleted`);
|
|
372
577
|
}
|
|
373
578
|
|
|
374
579
|
// src/commands/hosting.ts
|
|
@@ -416,6 +621,17 @@ async function hostingSetEnvCommand(key, value) {
|
|
|
416
621
|
});
|
|
417
622
|
console.log(`\u2713 Secret '${key}' updated`);
|
|
418
623
|
}
|
|
624
|
+
async function hostingEnvCommand() {
|
|
625
|
+
requireConfig();
|
|
626
|
+
const data = await apiRequest("/api/hosting/env");
|
|
627
|
+
if (data.secrets.length === 0) {
|
|
628
|
+
console.log("No secrets set on the active project's Worker.");
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
console.log("\nSecrets set on the active project's Worker (values are never returned by the API):\n");
|
|
632
|
+
for (const s of data.secrets) console.log(` ${s.name}`);
|
|
633
|
+
console.log("");
|
|
634
|
+
}
|
|
419
635
|
var BARS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
420
636
|
function spark(values) {
|
|
421
637
|
if (values.length === 0) return "(no data)";
|
|
@@ -589,8 +805,9 @@ async function deployLogsCommand(deploymentId) {
|
|
|
589
805
|
}
|
|
590
806
|
|
|
591
807
|
// src/index.ts
|
|
808
|
+
var pkg = createRequire(import.meta.url)("../package.json");
|
|
592
809
|
var program = new Command();
|
|
593
|
-
program.name("deploro").description("Deploro developer platform CLI").version(
|
|
810
|
+
program.name("deploro").description("Deploro developer platform CLI").version(pkg.version);
|
|
594
811
|
program.command("login").description("Log in to Deploro").option("--url <url>", "Worker base URL").option("--token <token>", "Personal access token from the Deploro dashboard").action(loginCommand);
|
|
595
812
|
program.command("logout").description("Log out and clear local credentials").action(logoutCommand);
|
|
596
813
|
program.command("projects").description("List projects").action(projectsListCommand);
|
|
@@ -598,6 +815,8 @@ program.command("create <name>").description("Create a new project (and set it a
|
|
|
598
815
|
program.command("use <project>").description("Set the active project (accepts id, slug, or name)").action(useProjectCommand);
|
|
599
816
|
program.command("delete <project>").description("Delete a project and its database (accepts id, slug, or name)").option("-y, --yes", "Skip confirmation prompt").action(projectsDeleteCommand);
|
|
600
817
|
var db = program.command("db").description("Database commands");
|
|
818
|
+
db.command("create").description("Provision the active project's database (idempotent)").action(dbCreateCommand);
|
|
819
|
+
db.command("info").description("Show database status and how to reach it from a deployed app").action(dbInfoCommand);
|
|
601
820
|
db.command("tables").description("List tables in the active project").action(dbTablesCommand);
|
|
602
821
|
db.command("rows <table>").description("List rows in a table").option("--limit <n>", "Rows per page (max 200)", "20").option("--page <n>", "Page number", "1").option("--json", "Output raw JSON").action(dbRowsCommand);
|
|
603
822
|
db.command("query <sql>").description("Run a SQL query (project admin)").option("--json", "Output raw JSON").action(dbQueryCommand);
|
|
@@ -606,13 +825,24 @@ db.command("insert <table>").description("Insert a row").requiredOption("--data
|
|
|
606
825
|
db.command("update <table> <id>").description("Update a row by id").requiredOption("--data <json>", "Fields to update as a JSON object").action(dbUpdateCommand);
|
|
607
826
|
db.command("delete-row <table> <id>").description("Delete a row by id").option("--yes", "Skip confirmation prompt").action(dbDeleteRowCommand);
|
|
608
827
|
db.command("drop <table>").description("Drop a table and all its data (project admin)").option("--yes", "Skip confirmation prompt").action(dbDropTableCommand);
|
|
828
|
+
var migrate = program.command("migrate").description("Database migration commands");
|
|
829
|
+
migrate.command("create <name>").description("Register a new migration (admin)").option("--up <sql>", "Migration SQL (inline)").option("--up-file <path>", "Read migration SQL from a file").option("--down <sql>", "Rollback SQL (inline)").option("--down-file <path>", "Read rollback SQL from a file").action(migrateCreateCommand);
|
|
830
|
+
migrate.command("list").description("List migrations for the active project").action(migrateListCommand);
|
|
831
|
+
migrate.command("apply <id-or-name>").description("Apply a pending migration (admin)").action(migrateApplyCommand);
|
|
832
|
+
migrate.command("rollback <id-or-name>").description("Roll back an applied migration (admin)").option("--yes", "Skip confirmation prompt").action(migrateRollbackCommand);
|
|
833
|
+
migrate.command("delete <id-or-name>").description("Delete an unapplied migration (admin)").option("--yes", "Skip confirmation prompt").action(migrateDeleteCommand);
|
|
609
834
|
var auth = program.command("auth").description("Auth management commands");
|
|
610
|
-
auth.command("whoami").description("Show the current identity").action(authWhoamiCommand);
|
|
835
|
+
auth.command("whoami").description("Show the current identity, and \u2014 for a token login \u2014 exactly what it's scoped to").action(authWhoamiCommand);
|
|
611
836
|
auth.command("users").description("List platform users").action(authUsersCommand);
|
|
612
837
|
auth.command("invite <email>").description("Invite a user by email").option("--admin", "Grant admin role").action(authInviteCommand);
|
|
838
|
+
var token = program.command("token").description("Personal access token commands");
|
|
839
|
+
token.command("list").description("List your personal access tokens").action(tokenListCommand);
|
|
840
|
+
token.command("create <name>").description("Create a new project-scoped personal access token").option("--read", "Grant read scope").option("--write", "Grant write scope").option("--project <slugs>", "Comma-separated project slugs/ids/names (defaults to the active project)").option("--days <n>", "Expiry in days (1-365)", "90").action(tokenCreateCommand);
|
|
841
|
+
token.command("revoke <id>").description("Revoke a personal access token").option("--yes", "Skip confirmation prompt").action(tokenRevokeCommand);
|
|
613
842
|
var hosting = program.command("hosting").description("Hosting commands");
|
|
614
843
|
hosting.command("status").description("Show Worker and VPS health").action(hostingStatusCommand);
|
|
615
844
|
hosting.command("metrics").description("Show recent VPS CPU/memory/disk sparklines").action(hostingMetricsCommand);
|
|
845
|
+
hosting.command("env").description("List secret names set on the active project's Worker (values are never returned)").action(hostingEnvCommand);
|
|
616
846
|
hosting.command("set-env <key> <value>").description("Set a Worker secret").action(hostingSetEnvCommand);
|
|
617
847
|
var deploy = program.command("deploy").description("Deployment commands");
|
|
618
848
|
deploy.command("trigger").description("Trigger a manual deployment").action(deployCommand);
|
package/package.json
CHANGED