@homespunapps/cli 1.6.68 → 1.6.70
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/commands/apps.js +72 -5
- package/dist/help-catalog.js +22 -3
- package/dist/resolve-app.js +17 -4
- package/package.json +2 -2
package/dist/commands/apps.js
CHANGED
|
@@ -49,6 +49,12 @@ export async function runApps(args) {
|
|
|
49
49
|
return runShareLink(sub);
|
|
50
50
|
case "delete":
|
|
51
51
|
return runDelete(sub);
|
|
52
|
+
case "deleted":
|
|
53
|
+
return runDeleted(sub);
|
|
54
|
+
case "restore":
|
|
55
|
+
return runRestore(sub);
|
|
56
|
+
case "purge":
|
|
57
|
+
return runPurge(sub);
|
|
52
58
|
case "wake":
|
|
53
59
|
return runWake(sub);
|
|
54
60
|
case "watch":
|
|
@@ -56,7 +62,7 @@ export async function runApps(args) {
|
|
|
56
62
|
case "domain":
|
|
57
63
|
return runDomain(sub);
|
|
58
64
|
default:
|
|
59
|
-
fail(`unknown verb '${verb}': homespun apps <list|show|audit|update|share-link|delete|wake|watch|domain>`, "invalid_args");
|
|
65
|
+
fail(`unknown verb '${verb}': homespun apps <list|show|audit|update|share-link|delete|deleted|restore|purge|wake|watch|domain>`, "invalid_args");
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
68
|
// ---------------------------------------------------------------------------
|
|
@@ -66,8 +72,8 @@ async function runList(args) {
|
|
|
66
72
|
assertKnownFlags(args, ...specFor("apps", "list"));
|
|
67
73
|
const status = args.flags.get("status");
|
|
68
74
|
if (status !== undefined &&
|
|
69
|
-
!["active", "dormant", "archived", "suspended", "all"].includes(status)) {
|
|
70
|
-
fail("--status must be active|dormant|archived|suspended|all", "invalid_args");
|
|
75
|
+
!["active", "dormant", "archived", "suspended", "deleted", "all"].includes(status)) {
|
|
76
|
+
fail("--status must be active|dormant|archived|suspended|deleted|all", "invalid_args");
|
|
71
77
|
}
|
|
72
78
|
const limitRaw = args.flags.get("limit");
|
|
73
79
|
const limit = limitRaw !== undefined ? Number(limitRaw) : undefined;
|
|
@@ -225,13 +231,74 @@ async function runDelete(args) {
|
|
|
225
231
|
if (!appArg)
|
|
226
232
|
fail("usage: homespun apps delete <app> [--yes]", "invalid_args");
|
|
227
233
|
if (!args.bools.has("yes")) {
|
|
228
|
-
fail("'homespun apps delete'
|
|
234
|
+
fail("'homespun apps delete' takes the app offline immediately. Its data is kept and it can be brought back with 'homespun apps restore' until the retention window elapses ('homespun apps deleted' shows the deadline); 'homespun apps purge' destroys it for good. Pass --yes to confirm.", "invalid_args");
|
|
229
235
|
}
|
|
230
236
|
const client = makeClient(args);
|
|
231
237
|
const id = await resolveAppId(client, appArg);
|
|
232
238
|
try {
|
|
233
239
|
await client.deleteApp(id);
|
|
234
|
-
printJson({ deleted: true, app_id: id });
|
|
240
|
+
printJson({ deleted: true, app_id: id, restorable: true });
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
failFromError(e);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// deleted / restore / purge
|
|
248
|
+
//
|
|
249
|
+
// Named after the row-level `data deleted|restore|purge` verbs that already do
|
|
250
|
+
// exactly this one level down, so the same word means the same thing whichever
|
|
251
|
+
// noun it is typed against.
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
async function runDeleted(args) {
|
|
254
|
+
assertKnownFlags(args, ...specFor("apps", "deleted"));
|
|
255
|
+
const limitRaw = args.flags.get("limit");
|
|
256
|
+
const limit = limitRaw !== undefined ? Number(limitRaw) : undefined;
|
|
257
|
+
if (limit !== undefined && !Number.isInteger(limit)) {
|
|
258
|
+
fail("--limit must be an integer", "invalid_args");
|
|
259
|
+
}
|
|
260
|
+
const client = makeClient(args);
|
|
261
|
+
try {
|
|
262
|
+
printJson(await client.listApps({
|
|
263
|
+
status: "deleted",
|
|
264
|
+
limit,
|
|
265
|
+
cursor: args.flags.get("cursor"),
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
catch (e) {
|
|
269
|
+
failFromError(e);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async function runRestore(args) {
|
|
273
|
+
assertKnownFlags(args, ...specFor("apps", "restore"));
|
|
274
|
+
const appArg = args.positionals[0];
|
|
275
|
+
if (!appArg)
|
|
276
|
+
fail("usage: homespun apps restore <app>", "invalid_args");
|
|
277
|
+
const client = makeClient(args);
|
|
278
|
+
// includeDeleted, because the whole point is that this app is in trash: the
|
|
279
|
+
// default resolution path hides it and would fail with app_not_found.
|
|
280
|
+
const id = await resolveAppId(client, appArg, { includeDeleted: true });
|
|
281
|
+
try {
|
|
282
|
+
printJson(await client.restoreApp(id));
|
|
283
|
+
}
|
|
284
|
+
catch (e) {
|
|
285
|
+
failFromError(e);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function runPurge(args) {
|
|
289
|
+
assertKnownFlags(args, ...specFor("apps", "purge"));
|
|
290
|
+
const appArg = args.positionals[0];
|
|
291
|
+
if (!appArg) {
|
|
292
|
+
fail("usage: homespun apps purge <app> [--yes]", "invalid_args");
|
|
293
|
+
}
|
|
294
|
+
if (!args.bools.has("yes")) {
|
|
295
|
+
fail("'homespun apps purge' destroys the app and all its data immediately and forever — there is no restore afterwards. Pass --yes to confirm.", "invalid_args");
|
|
296
|
+
}
|
|
297
|
+
const client = makeClient(args);
|
|
298
|
+
const id = await resolveAppId(client, appArg, { includeDeleted: true });
|
|
299
|
+
try {
|
|
300
|
+
await client.purgeApp(id);
|
|
301
|
+
printJson({ purged: true, app_id: id });
|
|
235
302
|
}
|
|
236
303
|
catch (e) {
|
|
237
304
|
failFromError(e);
|
package/dist/help-catalog.js
CHANGED
|
@@ -31,7 +31,7 @@ const APPS = {
|
|
|
31
31
|
noun: "apps",
|
|
32
32
|
tagline: "app lifecycle management",
|
|
33
33
|
group: "app",
|
|
34
|
-
rootSummary: "App lifecycle: list, show, audit (security review of your apps' collection permissions), update, delete, wake, domain (custom domains), watch (stream the app's change feed as JSON-lines).",
|
|
34
|
+
rootSummary: "App lifecycle: list, show, audit (security review of your apps' collection permissions), update, delete, deleted (what is in the trash), restore, purge (destroy a deleted app for good), wake, domain (custom domains), watch (stream the app's change feed as JSON-lines).",
|
|
35
35
|
verbs: [
|
|
36
36
|
{
|
|
37
37
|
verb: "list",
|
|
@@ -39,7 +39,7 @@ const APPS = {
|
|
|
39
39
|
flags: [
|
|
40
40
|
{
|
|
41
41
|
name: "status",
|
|
42
|
-
value: "<active|dormant|archived|all>",
|
|
42
|
+
value: "<active|dormant|archived|deleted|all>",
|
|
43
43
|
description: "Filter by lifecycle status",
|
|
44
44
|
},
|
|
45
45
|
{ name: "limit", value: "<n>", description: "Page size" },
|
|
@@ -92,7 +92,26 @@ const APPS = {
|
|
|
92
92
|
{
|
|
93
93
|
verb: "delete",
|
|
94
94
|
positionals: "<app>",
|
|
95
|
-
summary: "
|
|
95
|
+
summary: "Takes an app offline. Its data is kept and `apps restore` brings it back until the retention window elapses.",
|
|
96
|
+
bools: [{ name: "yes", description: "Skip the confirmation prompt" }],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
verb: "deleted",
|
|
100
|
+
summary: "Lists your deleted apps, each with when it was deleted and when it is purged for good.",
|
|
101
|
+
flags: [
|
|
102
|
+
{ name: "limit", value: "<n>", description: "Page size" },
|
|
103
|
+
{ name: "cursor", value: "<cursor>", description: "Page cursor" },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
verb: "restore",
|
|
108
|
+
positionals: "<app>",
|
|
109
|
+
summary: "Brings a deleted app back, with all of its data. Fails if your account is at its app limit.",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
verb: "purge",
|
|
113
|
+
positionals: "<app>",
|
|
114
|
+
summary: "Destroys a deleted app and all its data now, instead of waiting out the retention window. Irreversible.",
|
|
96
115
|
bools: [{ name: "yes", description: "Skip the confirmation prompt" }],
|
|
97
116
|
},
|
|
98
117
|
{
|
package/dist/resolve-app.js
CHANGED
|
@@ -29,10 +29,17 @@ function looksLikeId(value) {
|
|
|
29
29
|
* `GET /v1/apps?slug=`. Values that don't look like a cuid skip straight to
|
|
30
30
|
* the slug lookup. Fails with `app_not_found` if no app matches either way.
|
|
31
31
|
*/
|
|
32
|
-
export async function resolveAppId(client, value) {
|
|
32
|
+
export async function resolveAppId(client, value, opts = {}) {
|
|
33
|
+
// Both lookups hide soft-deleted apps by default, which is right for every
|
|
34
|
+
// verb that operates on a live app and wrong for the three that operate on
|
|
35
|
+
// trash (`apps deleted|restore|purge`). Those pass includeDeleted, which
|
|
36
|
+
// swaps BOTH lookups for their trash-aware forms: `?include_deleted=true` on
|
|
37
|
+
// the detail read, and `?status=deleted` on the slug lookup, since
|
|
38
|
+
// `?status=all` means "every live status" and would never match.
|
|
39
|
+
const { includeDeleted = false } = opts;
|
|
33
40
|
if (looksLikeId(value)) {
|
|
34
41
|
try {
|
|
35
|
-
await client.getApp(value);
|
|
42
|
+
await client.getApp(value, { includeDeleted });
|
|
36
43
|
return value;
|
|
37
44
|
}
|
|
38
45
|
catch (e) {
|
|
@@ -42,10 +49,16 @@ export async function resolveAppId(client, value) {
|
|
|
42
49
|
// Fall through: treat it as a slug instead.
|
|
43
50
|
}
|
|
44
51
|
}
|
|
45
|
-
const page = await client.listApps({
|
|
52
|
+
const page = await client.listApps({
|
|
53
|
+
status: includeDeleted ? "deleted" : "all",
|
|
54
|
+
slug: value,
|
|
55
|
+
limit: 1,
|
|
56
|
+
});
|
|
46
57
|
const match = page.items[0];
|
|
47
58
|
if (!match) {
|
|
48
|
-
fail(
|
|
59
|
+
fail(includeDeleted
|
|
60
|
+
? `no deleted app found with slug '${value}'`
|
|
61
|
+
: `no app found with slug '${value}'`, "app_not_found");
|
|
49
62
|
}
|
|
50
63
|
return match.id;
|
|
51
64
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homespunapps/cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.70",
|
|
4
4
|
"description": "Command-line client for the Homespun relay: deploy a real multi-user web app from your agent, then keep reading and writing its data.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"test:unit": "vitest run"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@homespunapps/core": "^1.6.
|
|
39
|
+
"@homespunapps/core": "^1.6.70",
|
|
40
40
|
"qrcode-terminal": "^0.12.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|