driggsby 0.3.2 → 0.3.3
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 +3 -1
- package/dist/args-delete.js +38 -0
- package/dist/args.js +5 -1
- package/dist/deploy/delete-command.js +125 -0
- package/dist/deploy/versions-command.js +10 -5
- package/dist/help.js +14 -0
- package/dist/main.js +3 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -86,7 +86,9 @@ Every deploy after that uploads only the files that changed. Deploy with
|
|
|
86
86
|
`--preview` to upload a version without
|
|
87
87
|
changing what visitors see, `npx driggsby@latest versions` to list every
|
|
88
88
|
kept version, and `npx driggsby@latest rollback` to switch which one is
|
|
89
|
-
live — switching re-uploads nothing. `npx driggsby@latest
|
|
89
|
+
live — switching re-uploads nothing. `npx driggsby@latest delete` removes
|
|
90
|
+
a deployed app forever, after asking you to type its address name back
|
|
91
|
+
(`--yes` for scripts). `npx driggsby@latest query <tool>`
|
|
90
92
|
runs one read-only data tool and prints its result as JSON, the same
|
|
91
93
|
shape a `driggsby.watch` callback receives, so you can see the data
|
|
92
94
|
before writing any render code.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Argv parsing for driggsby delete: one optional positional (the app's
|
|
2
|
+
// address name), --yes for scripts and agents, help, and the shared
|
|
3
|
+
// usage-error shape — usage errors exit 2, echoed argv is sanitized by the
|
|
4
|
+
// shared builders.
|
|
5
|
+
import { helpCommand, unexpectedArgument, unexpectedFlagValue, } from "./args-shared.js";
|
|
6
|
+
import { DELETE_HELP } from "./help.js";
|
|
7
|
+
const DELETE_USAGE = "Usage: npx driggsby@latest delete [APP-ADDRESS-NAME] [--yes]";
|
|
8
|
+
export function parseDelete(argv) {
|
|
9
|
+
let slug = null;
|
|
10
|
+
let yes = false;
|
|
11
|
+
let optionsEnded = false;
|
|
12
|
+
for (const token of argv) {
|
|
13
|
+
if (!optionsEnded && token === "--") {
|
|
14
|
+
optionsEnded = true;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (!optionsEnded && (token === "-h" || token === "--help")) {
|
|
18
|
+
return helpCommand(DELETE_HELP);
|
|
19
|
+
}
|
|
20
|
+
if (!optionsEnded && token === "--yes") {
|
|
21
|
+
yes = true;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (!optionsEnded && token.startsWith("--yes=")) {
|
|
25
|
+
throw unexpectedFlagValue("--yes", token.slice("--yes=".length), DELETE_USAGE);
|
|
26
|
+
}
|
|
27
|
+
// Any other flag-looking token is a mistake, not a name; after "--"
|
|
28
|
+
// everything is positional and the server refuses unknown names.
|
|
29
|
+
if (!optionsEnded && token.startsWith("-") && token !== "-") {
|
|
30
|
+
throw unexpectedArgument(token, DELETE_USAGE);
|
|
31
|
+
}
|
|
32
|
+
if (slug !== null) {
|
|
33
|
+
throw unexpectedArgument(token, DELETE_USAGE);
|
|
34
|
+
}
|
|
35
|
+
slug = token;
|
|
36
|
+
}
|
|
37
|
+
return { kind: "delete", slug, yes };
|
|
38
|
+
}
|
package/dist/args.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// clap's byte-level quirks.
|
|
8
8
|
import { parseDeploy, parseRollback, parseVersions } from "./args-deploy.js";
|
|
9
9
|
import { parseDev } from "./args-dev.js";
|
|
10
|
+
import { parseDelete } from "./args-delete.js";
|
|
10
11
|
import { parseInit } from "./args-init.js";
|
|
11
12
|
import { parseMcpSetup } from "./args-mcp-setup.js";
|
|
12
13
|
import { parseQuery } from "./args-query.js";
|
|
@@ -21,7 +22,7 @@ const MCP_USAGE = "Usage: npx driggsby@latest mcp <COMMAND>";
|
|
|
21
22
|
const ROOT_LEVEL = {
|
|
22
23
|
helpText: ROOT_HELP,
|
|
23
24
|
usage: ROOT_USAGE,
|
|
24
|
-
subcommands: ["mcp", "login", "logout", "init", "dev", "deploy", "rollback", "versions", "query"],
|
|
25
|
+
subcommands: ["mcp", "login", "logout", "init", "dev", "deploy", "rollback", "versions", "delete", "query"],
|
|
25
26
|
longFlags: ["help", "version"],
|
|
26
27
|
subcommandFlags: [
|
|
27
28
|
{ name: "mcp", longFlags: ["help"] },
|
|
@@ -32,6 +33,7 @@ const ROOT_LEVEL = {
|
|
|
32
33
|
{ name: "deploy", longFlags: ["preview", "help"] },
|
|
33
34
|
{ name: "rollback", longFlags: ["to", "help"] },
|
|
34
35
|
{ name: "versions", longFlags: ["help"] },
|
|
36
|
+
{ name: "delete", longFlags: ["yes", "help"] },
|
|
35
37
|
{ name: "query", longFlags: ["sql", "params", "help"] },
|
|
36
38
|
],
|
|
37
39
|
usagePrefix: "npx driggsby@latest",
|
|
@@ -96,6 +98,8 @@ function dispatchSubcommand(name, rest) {
|
|
|
96
98
|
return parseDeploy(rest);
|
|
97
99
|
case "rollback":
|
|
98
100
|
return parseRollback(rest);
|
|
101
|
+
case "delete":
|
|
102
|
+
return parseDelete(rest);
|
|
99
103
|
case "versions":
|
|
100
104
|
return parseVersions(rest);
|
|
101
105
|
case "query":
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// driggsby delete: permanently remove a deployed app — the app itself and
|
|
2
|
+
// every version Driggsby kept for it. There is no undo, so this command
|
|
3
|
+
// owns the "are you sure": interactively the person types the app's
|
|
4
|
+
// address name back; in a script or agent run, --yes is required.
|
|
5
|
+
import { access } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { deleteApp, readProjectConfig, slugProblem } from "@driggsby/deploy";
|
|
8
|
+
import { apiBaseUrl } from "../api/base-url.js";
|
|
9
|
+
import { CliError } from "../cli-error.js";
|
|
10
|
+
import { defaultCredentialEnvironment, } from "../credentials/store.js";
|
|
11
|
+
import { capForTerminal, quotedForTerminal, wrapProse } from "../terminal-text.js";
|
|
12
|
+
import { MAX_SERVER_TEXT_CHARS, MAX_SERVER_URL_CHARS, deployFailure, requireDeploySession, } from "./api-session.js";
|
|
13
|
+
import { fetchVersionList } from "./versions-command.js";
|
|
14
|
+
const DELETE_RETRY_COMMAND = "npx driggsby@latest delete";
|
|
15
|
+
// The success line is `✓ Deleted ` (12 columns) plus the quoted name;
|
|
16
|
+
// 12 + 2 quotes + 64 = 78 keeps it inside 80 whatever the server sends.
|
|
17
|
+
const DELETED_LINE_NAME_CHARS = 64;
|
|
18
|
+
// Once the name is known (and validated), the retry names it too, so a
|
|
19
|
+
// retry from the wrong folder can't quietly target a different app.
|
|
20
|
+
function retryCommandFor(slug) {
|
|
21
|
+
return slug === null ? DELETE_RETRY_COMMAND : `${DELETE_RETRY_COMMAND} ${slug}`;
|
|
22
|
+
}
|
|
23
|
+
function defaultDeleteIo() {
|
|
24
|
+
return {
|
|
25
|
+
out: (text) => {
|
|
26
|
+
process.stdout.write(text);
|
|
27
|
+
},
|
|
28
|
+
ask: async (question) => {
|
|
29
|
+
const { createInterface } = await import("node:readline/promises");
|
|
30
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
31
|
+
try {
|
|
32
|
+
return await readline.question(question);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
readline.close();
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
interactive: process.stdin.isTTY && process.stdout.isTTY,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export async function runDelete(options, environment = defaultCredentialEnvironment(), io = defaultDeleteIo()) {
|
|
42
|
+
const projectDirectory = options.projectDirectory ?? process.cwd();
|
|
43
|
+
const baseUrl = apiBaseUrl(environment.env);
|
|
44
|
+
let resolvedSlug = null;
|
|
45
|
+
try {
|
|
46
|
+
const slug = options.slug === null
|
|
47
|
+
? await slugFromProjectFolder(projectDirectory)
|
|
48
|
+
: validatedArgvSlug(options.slug);
|
|
49
|
+
resolvedSlug = slug;
|
|
50
|
+
const session = await requireDeploySession(environment);
|
|
51
|
+
const api = { baseUrl: session.baseUrl, token: session.token };
|
|
52
|
+
// The look-before-you-leap read: it shows exactly what is about to
|
|
53
|
+
// go, and an unknown name stops here — with delete's own not-found,
|
|
54
|
+
// never the version list's "deploy it first" suggestion — before the
|
|
55
|
+
// DELETE is ever sent.
|
|
56
|
+
const list = await fetchVersionList(api, slug, appAlreadyGone);
|
|
57
|
+
const versionCount = list.versions.length;
|
|
58
|
+
const versionsLine = versionCount === 1 ? "1 deployed version" : `${versionCount} deployed versions`;
|
|
59
|
+
// The slug passed the strict pattern above (argv) or came from the
|
|
60
|
+
// validated project config, so it prints bare here.
|
|
61
|
+
io.out(`${wrapProse(`Deleting ${slug} removes it forever: ${versionsLine}, its page in Driggsby, and its address:`)}\n` +
|
|
62
|
+
` ${capForTerminal(list.url, MAX_SERVER_URL_CHARS)}\n\n`);
|
|
63
|
+
if (!options.yes) {
|
|
64
|
+
await confirmOrStop(slug, io);
|
|
65
|
+
}
|
|
66
|
+
const deleted = await deleteApp(api, slug);
|
|
67
|
+
// The name is server text: quoted so it reads as a name, never as the
|
|
68
|
+
// CLI's own sentence, and capped tighter than usual (64) so the line
|
|
69
|
+
// stays inside 80 columns whatever the server sends. The preflight
|
|
70
|
+
// above already named the address, so the slug isn't repeated here.
|
|
71
|
+
io.out(`✓ Deleted ${quotedForTerminal(deleted.appName, DELETED_LINE_NAME_CHARS)}\n\n`);
|
|
72
|
+
io.out(`${wrapProse("The app and its versions are gone, and its address now shows nothing. Deploying the app's folder again would create a new app at a new address.")}\n` +
|
|
73
|
+
"\nNext:\n" +
|
|
74
|
+
" Start a new app with npx driggsby@latest init\n");
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
throw deployFailure(error, baseUrl, retryCommandFor(resolvedSlug));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// A name typed on the command line is shape-gated before it goes anywhere:
|
|
82
|
+
// a typo gets the naming rules instead of a network round trip, nothing
|
|
83
|
+
// unvalidated is ever echoed, and the empty string can't slip through to
|
|
84
|
+
// match an empty confirmation answer.
|
|
85
|
+
function validatedArgvSlug(slug) {
|
|
86
|
+
const problem = slugProblem(slug);
|
|
87
|
+
if (problem === null)
|
|
88
|
+
return slug;
|
|
89
|
+
const flatProblem = problem.replaceAll("\n", " ");
|
|
90
|
+
throw new CliError(wrapProse(`${quotedForTerminal(slug, MAX_SERVER_TEXT_CHARS)} doesn't look like an app's address name: ${flatProblem}. Copy the name from the app's driggsby.json ("slug"), or from npx driggsby@latest versions run in its folder.`), 2);
|
|
91
|
+
}
|
|
92
|
+
// The app already isn't there — for a delete that is closer to done than
|
|
93
|
+
// to broken, and the one wrong suggestion would be "deploy it first".
|
|
94
|
+
function appAlreadyGone(slug) {
|
|
95
|
+
return new CliError(wrapProse(`There's no app named ${quotedForTerminal(slug, MAX_SERVER_TEXT_CHARS)} on your Driggsby account — maybe it was already deleted. Nothing was changed.`), 1);
|
|
96
|
+
}
|
|
97
|
+
// With no name given, the app is the one this folder deploys. Only a
|
|
98
|
+
// genuinely missing driggsby.json gets the "name it directly" guidance;
|
|
99
|
+
// a present-but-broken config surfaces its own precise error, the same
|
|
100
|
+
// one deploy and versions would show.
|
|
101
|
+
async function slugFromProjectFolder(projectDirectory) {
|
|
102
|
+
try {
|
|
103
|
+
await access(join(projectDirectory, "driggsby.json"));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
throw new CliError(`${wrapProse("This folder doesn't have a driggsby.json, so there's no app here to delete. Name the app directly — the \"slug\" in its driggsby.json:")}\n` +
|
|
107
|
+
" npx driggsby@latest delete <APP-ADDRESS-NAME>", 1);
|
|
108
|
+
}
|
|
109
|
+
const config = await readProjectConfig(projectDirectory);
|
|
110
|
+
return config.slug;
|
|
111
|
+
}
|
|
112
|
+
// The person types the address name back. Anything else — including an
|
|
113
|
+
// empty answer — deletes nothing and says so. Scripts and agents have no
|
|
114
|
+
// one to ask, so they must say --yes explicitly.
|
|
115
|
+
async function confirmOrStop(slug, io) {
|
|
116
|
+
if (!io.interactive) {
|
|
117
|
+
// Already shape-validated, so echoing it into a command line is safe.
|
|
118
|
+
throw new CliError(`${wrapProse("Deleting can't be undone, and this terminal can't ask you to confirm. Say it explicitly:")}\n` +
|
|
119
|
+
` npx driggsby@latest delete ${slug} --yes`, 2);
|
|
120
|
+
}
|
|
121
|
+
const answer = (await io.ask("Type the app's address name to confirm: ")).trim();
|
|
122
|
+
if (answer !== slug) {
|
|
123
|
+
throw new CliError("That didn't match, so nothing was deleted.", 1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -42,20 +42,25 @@ export async function runVersions(options, environment = defaultCredentialEnviro
|
|
|
42
42
|
throw deployFailure(error, baseUrl, "npx driggsby@latest versions");
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
// A 404 here means the app
|
|
46
|
-
// the fix is a deploy
|
|
47
|
-
|
|
45
|
+
// A 404 here means the app isn't on this account. For versions and
|
|
46
|
+
// rollback the fix is a deploy; a caller whose right next step differs
|
|
47
|
+
// (delete) passes its own notFound shape. The slug is quoted and capped
|
|
48
|
+
// either way — delete hands this an argv string, not only config values.
|
|
49
|
+
export async function fetchVersionList(api, slug, notFound = firstDeployCreatesIt) {
|
|
48
50
|
try {
|
|
49
51
|
return await listVersions(api, slug);
|
|
50
52
|
}
|
|
51
53
|
catch (error) {
|
|
52
54
|
if (error instanceof DeployApiError && error.status === 404) {
|
|
53
|
-
throw
|
|
54
|
-
" npx driggsby@latest deploy", 1);
|
|
55
|
+
throw notFound(slug);
|
|
55
56
|
}
|
|
56
57
|
throw error;
|
|
57
58
|
}
|
|
58
59
|
}
|
|
60
|
+
function firstDeployCreatesIt(slug) {
|
|
61
|
+
return new CliError(`${wrapProse(`There's no app named ${quotedForTerminal(slug, MAX_SERVER_TEXT_CHARS)} on your Driggsby account yet. Its first deploy creates it:`)}\n` +
|
|
62
|
+
" npx driggsby@latest deploy", 1);
|
|
63
|
+
}
|
|
59
64
|
export function renderVersionRows(list) {
|
|
60
65
|
const rows = list.versions.map((version) => ({
|
|
61
66
|
label: `v${version.number}`,
|
package/dist/help.js
CHANGED
|
@@ -19,6 +19,7 @@ Commands:
|
|
|
19
19
|
deploy Deploy the app in this directory to Driggsby.
|
|
20
20
|
rollback Switch which deployed version of your app is live.
|
|
21
21
|
versions List your deployed app's versions.
|
|
22
|
+
delete Permanently remove a deployed app.
|
|
22
23
|
query Run one Driggsby data tool and print its result as JSON.
|
|
23
24
|
|
|
24
25
|
Options:
|
|
@@ -128,6 +129,19 @@ Options:
|
|
|
128
129
|
npx driggsby@latest versions.
|
|
129
130
|
-h, --help Print help
|
|
130
131
|
`;
|
|
132
|
+
export const DELETE_HELP = `Permanently remove a deployed app.
|
|
133
|
+
|
|
134
|
+
This deletes the app and every version Driggsby kept for it. There is no
|
|
135
|
+
undo: the app's address shows nothing from that moment on. With no name,
|
|
136
|
+
the app in this folder's driggsby.json is the one deleted. You'll be asked
|
|
137
|
+
to type the name back to confirm; scripts and agents pass --yes instead.
|
|
138
|
+
|
|
139
|
+
Usage: npx driggsby@latest delete [APP-ADDRESS-NAME] [--yes]
|
|
140
|
+
|
|
141
|
+
Options:
|
|
142
|
+
--yes Skip the confirmation question. For scripts and agents.
|
|
143
|
+
-h, --help Print help
|
|
144
|
+
`;
|
|
131
145
|
export const VERSIONS_HELP = `List your deployed app's versions.
|
|
132
146
|
|
|
133
147
|
Shows every version Driggsby has kept for the app in this directory, which
|
package/dist/main.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { parseArgv } from "./args.js";
|
|
5
5
|
import { CliError } from "./cli-error.js";
|
|
6
|
+
import { runDelete } from "./deploy/delete-command.js";
|
|
6
7
|
import { runDeploy } from "./deploy/deploy-command.js";
|
|
7
8
|
import { runDev } from "./dev/dev-command.js";
|
|
8
9
|
import { runDevStop } from "./dev/dev-stop.js";
|
|
@@ -39,6 +40,8 @@ async function run(argv) {
|
|
|
39
40
|
return command.stop ? await runDevStop(homedir()) : await runDev();
|
|
40
41
|
case "deploy":
|
|
41
42
|
return await runDeploy({ preview: command.preview });
|
|
43
|
+
case "delete":
|
|
44
|
+
return await runDelete({ slug: command.slug, yes: command.yes });
|
|
42
45
|
case "rollback":
|
|
43
46
|
return await runRollback({ toVersion: command.toVersion });
|
|
44
47
|
case "versions":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "driggsby",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Set up and build on Driggsby, the MCP server for personal finance.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"node": ">=18"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@driggsby/deploy": "0.3.
|
|
32
|
-
"@driggsby/sdk": "0.3.
|
|
31
|
+
"@driggsby/deploy": "0.3.3",
|
|
32
|
+
"@driggsby/sdk": "0.3.3"
|
|
33
33
|
}
|
|
34
34
|
}
|