@kryd/cli 0.5.0 → 0.6.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 +3 -3
- package/dist/index.js +268 -46
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -34,10 +34,10 @@ Once you've run `kryd init` in a directory, the commands below work **arg-less**
|
|
|
34
34
|
| `kryd deploy [project]` | Re-deploy the production-branch HEAD already on the forge — no new commit. |
|
|
35
35
|
| `kryd logs [target]` | Follow a deploy's build/deploy log **in full** — this is where the build output lives (`--runtime` tails the live container instead). |
|
|
36
36
|
| `kryd rollback [project] [deployment]` | Roll back to a previous successful deploy — no rebuild. |
|
|
37
|
-
| `kryd db
|
|
38
|
-
| `kryd storage
|
|
37
|
+
| `kryd db add \| remove \| status [project]` | Attach, tear down or inspect managed Postgres (shared or bring-your-own). `remove` destroys the data and asks first. |
|
|
38
|
+
| `kryd storage add \| remove \| status [project]` | Attach, tear down or inspect S3-compatible object storage. `remove` destroys every object and asks first. |
|
|
39
39
|
| `kryd env list \| set \| rm [project]` | Manage your own environment variables. `set KEY --stdin` (or a prompt) keeps a secret out of `ps` and your shell history; values are never printed back. Add `--build` for build-time variables (`VITE_*`, `NEXT_PUBLIC_*`) — these are compiled into your public bundle, so they must never be secrets, and they take effect at your next build rather than your next deploy. |
|
|
40
|
-
| `kryd ai
|
|
40
|
+
| `kryd ai add \| remove \| status [project]` | Give the project an authenticated EU AI-gateway endpoint (injected on the next deploy), take it away again, or show what it has. |
|
|
41
41
|
|
|
42
42
|
Run `kryd <command> --help` for options. Every project-scoped command accepts an explicit `<project>` id, or resolves it from the `.kryd` link (walking up from the current directory, git-style).
|
|
43
43
|
|
package/dist/index.js
CHANGED
|
@@ -14894,8 +14894,9 @@ function isTerminalResourceStatus(status) {
|
|
|
14894
14894
|
}
|
|
14895
14895
|
|
|
14896
14896
|
// src/index.ts
|
|
14897
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
14897
|
+
import { existsSync as existsSync3, realpathSync } from "node:fs";
|
|
14898
14898
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
14899
|
+
import { pathToFileURL } from "node:url";
|
|
14899
14900
|
|
|
14900
14901
|
// src/config.ts
|
|
14901
14902
|
import {
|
|
@@ -15064,6 +15065,7 @@ function browserLogin(dashboardUrl, opts = {}) {
|
|
|
15064
15065
|
fn();
|
|
15065
15066
|
};
|
|
15066
15067
|
const server = createServer((req, res) => {
|
|
15068
|
+
res.setHeader("connection", "close");
|
|
15067
15069
|
const reqUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
15068
15070
|
if (reqUrl.pathname !== "/callback") {
|
|
15069
15071
|
res.writeHead(404);
|
|
@@ -15075,20 +15077,26 @@ function browserLogin(dashboardUrl, opts = {}) {
|
|
|
15075
15077
|
const gotState = reqUrl.searchParams.get("state");
|
|
15076
15078
|
if (!token || gotState !== state) {
|
|
15077
15079
|
res.writeHead(400, { "content-type": "text/html" });
|
|
15078
|
-
res.end(FAIL_HTML)
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
|
|
15080
|
+
res.end(FAIL_HTML, () => {
|
|
15081
|
+
settle(() => {
|
|
15082
|
+
shutdown();
|
|
15083
|
+
reject(new Error("Login failed \u2014 the approval didn't match this request. Try `kryd login` again."));
|
|
15084
|
+
});
|
|
15082
15085
|
});
|
|
15083
15086
|
return;
|
|
15084
15087
|
}
|
|
15085
15088
|
res.writeHead(200, { "content-type": "text/html" });
|
|
15086
|
-
res.end(SUCCESS_HTML)
|
|
15087
|
-
|
|
15088
|
-
|
|
15089
|
-
|
|
15089
|
+
res.end(SUCCESS_HTML, () => {
|
|
15090
|
+
settle(() => {
|
|
15091
|
+
shutdown();
|
|
15092
|
+
resolve2({ token, ...apiUrl ? { apiUrl } : {} });
|
|
15093
|
+
});
|
|
15090
15094
|
});
|
|
15091
15095
|
});
|
|
15096
|
+
const shutdown = () => {
|
|
15097
|
+
server.closeAllConnections();
|
|
15098
|
+
server.close();
|
|
15099
|
+
};
|
|
15092
15100
|
server.on("error", (err) => settle(() => reject(err)));
|
|
15093
15101
|
server.listen(0, "127.0.0.1", () => {
|
|
15094
15102
|
const port = server.address().port;
|
|
@@ -15105,7 +15113,7 @@ Waiting for approval\u2026
|
|
|
15105
15113
|
});
|
|
15106
15114
|
setTimeout(
|
|
15107
15115
|
() => settle(() => {
|
|
15108
|
-
|
|
15116
|
+
shutdown();
|
|
15109
15117
|
reject(new Error("Login timed out after 2 minutes."));
|
|
15110
15118
|
}),
|
|
15111
15119
|
timeoutMs
|
|
@@ -15258,6 +15266,36 @@ async function enableAi(apiUrl, token, input) {
|
|
|
15258
15266
|
}
|
|
15259
15267
|
return { enabled: true, url: parsed.url };
|
|
15260
15268
|
}
|
|
15269
|
+
async function getAiStatus(apiUrl, token, projectId) {
|
|
15270
|
+
const res = await fetch(`${apiUrl}/ai/${encodeURIComponent(projectId)}`, {
|
|
15271
|
+
headers: { authorization: `Bearer ${token}` }
|
|
15272
|
+
});
|
|
15273
|
+
if (!res.ok) {
|
|
15274
|
+
throw new ApiError(
|
|
15275
|
+
`AI status check failed (${res.status})`,
|
|
15276
|
+
await parseEnvelope(res),
|
|
15277
|
+
res.status
|
|
15278
|
+
);
|
|
15279
|
+
}
|
|
15280
|
+
const parsed = await res.json().catch(() => void 0);
|
|
15281
|
+
if (!parsed || typeof parsed.enabled !== "boolean" || typeof parsed.url !== "string" && parsed.url !== null) {
|
|
15282
|
+
throw new ApiError("The API returned an unexpected AI-status response.");
|
|
15283
|
+
}
|
|
15284
|
+
return { enabled: parsed.enabled, url: parsed.url };
|
|
15285
|
+
}
|
|
15286
|
+
async function removeAi(apiUrl, token, projectId) {
|
|
15287
|
+
const res = await fetch(`${apiUrl}/ai/${encodeURIComponent(projectId)}`, {
|
|
15288
|
+
method: "DELETE",
|
|
15289
|
+
headers: { authorization: `Bearer ${token}` }
|
|
15290
|
+
});
|
|
15291
|
+
if (!res.ok) {
|
|
15292
|
+
throw new ApiError(
|
|
15293
|
+
`AI removal failed (${res.status})`,
|
|
15294
|
+
await parseEnvelope(res),
|
|
15295
|
+
res.status
|
|
15296
|
+
);
|
|
15297
|
+
}
|
|
15298
|
+
}
|
|
15261
15299
|
async function fetchResourceStatus(apiUrl, token, path) {
|
|
15262
15300
|
const res = await fetch(`${apiUrl}${path}`, {
|
|
15263
15301
|
headers: { authorization: `Bearer ${token}` }
|
|
@@ -16851,7 +16889,7 @@ ${note}
|
|
|
16851
16889
|
reportError(err);
|
|
16852
16890
|
}
|
|
16853
16891
|
}
|
|
16854
|
-
async function
|
|
16892
|
+
async function runDbAdd(opts) {
|
|
16855
16893
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
16856
16894
|
const token = loadConfig().token;
|
|
16857
16895
|
if (!token) {
|
|
@@ -16861,7 +16899,7 @@ async function runDbCreate(opts) {
|
|
|
16861
16899
|
}
|
|
16862
16900
|
const project2 = resolveProjectId(opts.project, opts.cwd);
|
|
16863
16901
|
if (!project2) {
|
|
16864
|
-
reportNotLinked("kryd db
|
|
16902
|
+
reportNotLinked("kryd db add <projectId>");
|
|
16865
16903
|
return;
|
|
16866
16904
|
}
|
|
16867
16905
|
if (opts.shared && opts.connectionString !== void 0) {
|
|
@@ -16910,7 +16948,7 @@ The connection string will be injected as DATABASE_URL on your next deploy.
|
|
|
16910
16948
|
The connection string will be injected as DATABASE_URL on your next deploy.
|
|
16911
16949
|
`,
|
|
16912
16950
|
failedLabel: "Database provisioning",
|
|
16913
|
-
retryCmd: `kryd db
|
|
16951
|
+
retryCmd: `kryd db status ${project2}`
|
|
16914
16952
|
});
|
|
16915
16953
|
} catch (err) {
|
|
16916
16954
|
reportError(err);
|
|
@@ -16932,7 +16970,7 @@ function reportSettleResult(result, opts) {
|
|
|
16932
16970
|
);
|
|
16933
16971
|
}
|
|
16934
16972
|
}
|
|
16935
|
-
async function
|
|
16973
|
+
async function runStorageAdd(opts) {
|
|
16936
16974
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
16937
16975
|
const token = loadConfig().token;
|
|
16938
16976
|
if (!token) {
|
|
@@ -16942,7 +16980,7 @@ async function runStorageCreate(opts) {
|
|
|
16942
16980
|
}
|
|
16943
16981
|
const project2 = resolveProjectId(opts.project, opts.cwd);
|
|
16944
16982
|
if (!project2) {
|
|
16945
|
-
reportNotLinked("kryd storage
|
|
16983
|
+
reportNotLinked("kryd storage add <projectId>");
|
|
16946
16984
|
return;
|
|
16947
16985
|
}
|
|
16948
16986
|
try {
|
|
@@ -16957,7 +16995,7 @@ async function runStorageCreate(opts) {
|
|
|
16957
16995
|
Its S3 credentials will be injected on your next deploy.
|
|
16958
16996
|
`,
|
|
16959
16997
|
failedLabel: "Storage provisioning",
|
|
16960
|
-
retryCmd: `kryd storage
|
|
16998
|
+
retryCmd: `kryd storage status ${project2}`
|
|
16961
16999
|
});
|
|
16962
17000
|
} catch (err) {
|
|
16963
17001
|
reportError(err);
|
|
@@ -16974,12 +17012,27 @@ function reportDetachResult(result, opts) {
|
|
|
16974
17012
|
process.exitCode = 1;
|
|
16975
17013
|
} else {
|
|
16976
17014
|
process.stderr.write(
|
|
16977
|
-
`Could not confirm
|
|
17015
|
+
`Could not confirm removal \u2014 still tearing down (or the worker stalled). Re-run \`${opts.retryCmd}\` to confirm the resource is gone.
|
|
16978
17016
|
`
|
|
16979
17017
|
);
|
|
16980
17018
|
process.exitCode = 1;
|
|
16981
17019
|
}
|
|
16982
17020
|
}
|
|
17021
|
+
async function confirmResourceRemoval(opts) {
|
|
17022
|
+
if (opts.yes) return true;
|
|
17023
|
+
const io = opts.io ?? defaultPromptIO();
|
|
17024
|
+
if (!isInteractive(io)) {
|
|
17025
|
+
process.stderr.write(
|
|
17026
|
+
`Refusing to remove ${opts.subject} without confirmation. Re-run with --yes to confirm non-interactively.
|
|
17027
|
+
`
|
|
17028
|
+
);
|
|
17029
|
+
process.exitCode = 1;
|
|
17030
|
+
return false;
|
|
17031
|
+
}
|
|
17032
|
+
if (await promptConfirm(opts.question, io)) return true;
|
|
17033
|
+
process.stdout.write(opts.declined);
|
|
17034
|
+
return false;
|
|
17035
|
+
}
|
|
16983
17036
|
function isProjectId(arg) {
|
|
16984
17037
|
return arg.startsWith("proj_");
|
|
16985
17038
|
}
|
|
@@ -17126,7 +17179,7 @@ async function describeProjectResources(apiUrl, token, projectId) {
|
|
|
17126
17179
|
out.push("its Git repository on the forge, and its push token");
|
|
17127
17180
|
return out;
|
|
17128
17181
|
}
|
|
17129
|
-
async function
|
|
17182
|
+
async function runDbRemove(opts) {
|
|
17130
17183
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
17131
17184
|
const token = loadConfig().token;
|
|
17132
17185
|
if (!token) {
|
|
@@ -17136,27 +17189,36 @@ async function runDbDetach(opts) {
|
|
|
17136
17189
|
}
|
|
17137
17190
|
const project2 = resolveProjectId(opts.project, opts.cwd);
|
|
17138
17191
|
if (!project2) {
|
|
17139
|
-
reportNotLinked("kryd db
|
|
17192
|
+
reportNotLinked("kryd db remove <projectId>");
|
|
17140
17193
|
return;
|
|
17141
17194
|
}
|
|
17195
|
+
const proceed = await confirmResourceRemoval({
|
|
17196
|
+
yes: opts.yes,
|
|
17197
|
+
io: opts.io,
|
|
17198
|
+
subject: `the database from ${project2}`,
|
|
17199
|
+
question: `Remove the database from ${project2}? It is torn down and all the data in it is destroyed \u2014 this cannot be undone.`,
|
|
17200
|
+
declined: `Left the database on ${project2} in place.
|
|
17201
|
+
`
|
|
17202
|
+
});
|
|
17203
|
+
if (!proceed) return;
|
|
17142
17204
|
try {
|
|
17143
17205
|
await detachDatabase(apiUrl, token, project2);
|
|
17144
|
-
process.stdout.write(`
|
|
17206
|
+
process.stdout.write(`Removing the database from ${project2}\u2026
|
|
17145
17207
|
`);
|
|
17146
17208
|
const result = await pollResourceDetached(
|
|
17147
17209
|
() => getDatabaseStatus(apiUrl, token, project2)
|
|
17148
17210
|
);
|
|
17149
17211
|
reportDetachResult(result, {
|
|
17150
|
-
goneMsg: `
|
|
17212
|
+
goneMsg: `Removed the database from ${project2}.
|
|
17151
17213
|
`,
|
|
17152
|
-
failedLabel: "Database
|
|
17153
|
-
retryCmd: `kryd db
|
|
17214
|
+
failedLabel: "Database removal",
|
|
17215
|
+
retryCmd: `kryd db remove ${project2}`
|
|
17154
17216
|
});
|
|
17155
17217
|
} catch (err) {
|
|
17156
17218
|
reportError(err);
|
|
17157
17219
|
}
|
|
17158
17220
|
}
|
|
17159
|
-
async function
|
|
17221
|
+
async function runAiAdd(opts) {
|
|
17160
17222
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
17161
17223
|
const token = loadConfig().token;
|
|
17162
17224
|
if (!token) {
|
|
@@ -17166,13 +17228,13 @@ async function runAiEnable(opts) {
|
|
|
17166
17228
|
}
|
|
17167
17229
|
const project2 = resolveProjectId(opts.project, opts.cwd);
|
|
17168
17230
|
if (!project2) {
|
|
17169
|
-
reportNotLinked("kryd ai
|
|
17231
|
+
reportNotLinked("kryd ai add <projectId>");
|
|
17170
17232
|
return;
|
|
17171
17233
|
}
|
|
17172
17234
|
try {
|
|
17173
17235
|
const status = await enableAi(apiUrl, token, { projectId: project2 });
|
|
17174
17236
|
process.stdout.write(
|
|
17175
|
-
`AI
|
|
17237
|
+
`AI endpoint added to ${project2}.
|
|
17176
17238
|
` + (status.url ? `Endpoint: ${status.url} (OpenAI-compatible base URL \u2014 use it as-is)
|
|
17177
17239
|
` : "") + `AI_GATEWAY_URL + AI_GATEWAY_TOKEN will be injected on your next deploy (kryd deploy).
|
|
17178
17240
|
`
|
|
@@ -17181,7 +17243,7 @@ async function runAiEnable(opts) {
|
|
|
17181
17243
|
reportError(err);
|
|
17182
17244
|
}
|
|
17183
17245
|
}
|
|
17184
|
-
async function
|
|
17246
|
+
async function runAiRemove(opts) {
|
|
17185
17247
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
17186
17248
|
const token = loadConfig().token;
|
|
17187
17249
|
if (!token) {
|
|
@@ -17191,21 +17253,164 @@ async function runStorageDetach(opts) {
|
|
|
17191
17253
|
}
|
|
17192
17254
|
const project2 = resolveProjectId(opts.project, opts.cwd);
|
|
17193
17255
|
if (!project2) {
|
|
17194
|
-
reportNotLinked("kryd
|
|
17256
|
+
reportNotLinked("kryd ai remove <projectId>");
|
|
17195
17257
|
return;
|
|
17196
17258
|
}
|
|
17259
|
+
let current;
|
|
17260
|
+
try {
|
|
17261
|
+
current = await getAiStatus(apiUrl, token, project2);
|
|
17262
|
+
} catch (err) {
|
|
17263
|
+
reportError(err);
|
|
17264
|
+
return;
|
|
17265
|
+
}
|
|
17266
|
+
if (!current.enabled) {
|
|
17267
|
+
process.stdout.write(`${project2}: no AI endpoint \u2014 nothing to remove.
|
|
17268
|
+
`);
|
|
17269
|
+
return;
|
|
17270
|
+
}
|
|
17271
|
+
const proceed = await confirmResourceRemoval({
|
|
17272
|
+
yes: opts.yes,
|
|
17273
|
+
io: opts.io,
|
|
17274
|
+
subject: `the AI endpoint from ${project2}`,
|
|
17275
|
+
question: `Remove the AI endpoint from ${project2}? AI_GATEWAY_URL and AI_GATEWAY_TOKEN stop being injected from your next deploy.`,
|
|
17276
|
+
declined: `Left the AI endpoint on ${project2} in place.
|
|
17277
|
+
`
|
|
17278
|
+
});
|
|
17279
|
+
if (!proceed) return;
|
|
17280
|
+
try {
|
|
17281
|
+
await removeAi(apiUrl, token, project2);
|
|
17282
|
+
process.stdout.write(
|
|
17283
|
+
`AI endpoint removed from ${project2}.
|
|
17284
|
+
AI_GATEWAY_URL and AI_GATEWAY_TOKEN stop being injected from your next deploy (kryd deploy).
|
|
17285
|
+
`
|
|
17286
|
+
);
|
|
17287
|
+
} catch (err) {
|
|
17288
|
+
reportError(err);
|
|
17289
|
+
}
|
|
17290
|
+
}
|
|
17291
|
+
async function reportResourceStatus(opts) {
|
|
17292
|
+
try {
|
|
17293
|
+
const result = await opts.read();
|
|
17294
|
+
if (result.status === "failed") {
|
|
17295
|
+
process.stdout.write(
|
|
17296
|
+
`${opts.project}: ${opts.noun} failed \u2014 ${result.failureReason ?? "unknown error"}.
|
|
17297
|
+
Remove it before attaching another.
|
|
17298
|
+
`
|
|
17299
|
+
);
|
|
17300
|
+
return;
|
|
17301
|
+
}
|
|
17302
|
+
process.stdout.write(`${opts.project}: ${opts.noun} ${result.status}.
|
|
17303
|
+
`);
|
|
17304
|
+
} catch (err) {
|
|
17305
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
17306
|
+
process.stdout.write(`${opts.project}: no ${opts.noun} attached.
|
|
17307
|
+
`);
|
|
17308
|
+
return;
|
|
17309
|
+
}
|
|
17310
|
+
reportError(err);
|
|
17311
|
+
}
|
|
17312
|
+
}
|
|
17313
|
+
async function runDbStatus(opts) {
|
|
17314
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
17315
|
+
const token = loadConfig().token;
|
|
17316
|
+
if (!token) {
|
|
17317
|
+
process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
|
|
17318
|
+
process.exitCode = 1;
|
|
17319
|
+
return;
|
|
17320
|
+
}
|
|
17321
|
+
const project2 = resolveProjectId(opts.project, opts.cwd);
|
|
17322
|
+
if (!project2) {
|
|
17323
|
+
reportNotLinked("kryd db status <projectId>");
|
|
17324
|
+
return;
|
|
17325
|
+
}
|
|
17326
|
+
await reportResourceStatus({
|
|
17327
|
+
read: () => getDatabaseStatus(apiUrl, token, project2),
|
|
17328
|
+
noun: "database",
|
|
17329
|
+
project: project2
|
|
17330
|
+
});
|
|
17331
|
+
}
|
|
17332
|
+
async function runStorageStatus(opts) {
|
|
17333
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
17334
|
+
const token = loadConfig().token;
|
|
17335
|
+
if (!token) {
|
|
17336
|
+
process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
|
|
17337
|
+
process.exitCode = 1;
|
|
17338
|
+
return;
|
|
17339
|
+
}
|
|
17340
|
+
const project2 = resolveProjectId(opts.project, opts.cwd);
|
|
17341
|
+
if (!project2) {
|
|
17342
|
+
reportNotLinked("kryd storage status <projectId>");
|
|
17343
|
+
return;
|
|
17344
|
+
}
|
|
17345
|
+
await reportResourceStatus({
|
|
17346
|
+
read: () => getStorageStatus(apiUrl, token, project2),
|
|
17347
|
+
noun: "object storage",
|
|
17348
|
+
project: project2
|
|
17349
|
+
});
|
|
17350
|
+
}
|
|
17351
|
+
async function runAiStatus(opts) {
|
|
17352
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
17353
|
+
const token = loadConfig().token;
|
|
17354
|
+
if (!token) {
|
|
17355
|
+
process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
|
|
17356
|
+
process.exitCode = 1;
|
|
17357
|
+
return;
|
|
17358
|
+
}
|
|
17359
|
+
const project2 = resolveProjectId(opts.project, opts.cwd);
|
|
17360
|
+
if (!project2) {
|
|
17361
|
+
reportNotLinked("kryd ai status <projectId>");
|
|
17362
|
+
return;
|
|
17363
|
+
}
|
|
17364
|
+
try {
|
|
17365
|
+
const status = await getAiStatus(apiUrl, token, project2);
|
|
17366
|
+
if (!status.enabled) {
|
|
17367
|
+
process.stdout.write(`${project2}: no AI endpoint.
|
|
17368
|
+
`);
|
|
17369
|
+
return;
|
|
17370
|
+
}
|
|
17371
|
+
process.stdout.write(
|
|
17372
|
+
status.url ? `${project2}: AI endpoint ${status.url}
|
|
17373
|
+
` : `${project2}: AI endpoint enabled, but its URL could not be read back right now.
|
|
17374
|
+
`
|
|
17375
|
+
);
|
|
17376
|
+
} catch (err) {
|
|
17377
|
+
reportError(err);
|
|
17378
|
+
}
|
|
17379
|
+
}
|
|
17380
|
+
async function runStorageRemove(opts) {
|
|
17381
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
17382
|
+
const token = loadConfig().token;
|
|
17383
|
+
if (!token) {
|
|
17384
|
+
process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
|
|
17385
|
+
process.exitCode = 1;
|
|
17386
|
+
return;
|
|
17387
|
+
}
|
|
17388
|
+
const project2 = resolveProjectId(opts.project, opts.cwd);
|
|
17389
|
+
if (!project2) {
|
|
17390
|
+
reportNotLinked("kryd storage remove <projectId>");
|
|
17391
|
+
return;
|
|
17392
|
+
}
|
|
17393
|
+
const proceed = await confirmResourceRemoval({
|
|
17394
|
+
yes: opts.yes,
|
|
17395
|
+
io: opts.io,
|
|
17396
|
+
subject: `object storage from ${project2}`,
|
|
17397
|
+
question: `Remove object storage from ${project2}? The bucket is torn down and every object in it is destroyed \u2014 this cannot be undone.`,
|
|
17398
|
+
declined: `Left the object storage on ${project2} in place.
|
|
17399
|
+
`
|
|
17400
|
+
});
|
|
17401
|
+
if (!proceed) return;
|
|
17197
17402
|
try {
|
|
17198
17403
|
await detachStorage(apiUrl, token, project2);
|
|
17199
|
-
process.stdout.write(`
|
|
17404
|
+
process.stdout.write(`Removing object storage from ${project2}\u2026
|
|
17200
17405
|
`);
|
|
17201
17406
|
const result = await pollResourceDetached(
|
|
17202
17407
|
() => getStorageStatus(apiUrl, token, project2)
|
|
17203
17408
|
);
|
|
17204
17409
|
reportDetachResult(result, {
|
|
17205
|
-
goneMsg: `
|
|
17410
|
+
goneMsg: `Removed object storage from ${project2}.
|
|
17206
17411
|
`,
|
|
17207
|
-
failedLabel: "Storage
|
|
17208
|
-
retryCmd: `kryd storage
|
|
17412
|
+
failedLabel: "Storage removal",
|
|
17413
|
+
retryCmd: `kryd storage remove ${project2}`
|
|
17209
17414
|
});
|
|
17210
17415
|
} catch (err) {
|
|
17211
17416
|
reportError(err);
|
|
@@ -17591,7 +17796,7 @@ async function runEnvRemove(opts) {
|
|
|
17591
17796
|
if (isReservedEnvVarName(key)) {
|
|
17592
17797
|
process.stderr.write(
|
|
17593
17798
|
`${key} is reserved by Kryd and cannot be removed here.
|
|
17594
|
-
If it comes from a resource you attached,
|
|
17799
|
+
If it comes from a resource you attached, remove that instead (\`kryd db remove\`, \`kryd storage remove\`).
|
|
17595
17800
|
`
|
|
17596
17801
|
);
|
|
17597
17802
|
process.exitCode = 1;
|
|
@@ -17658,7 +17863,7 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
|
|
|
17658
17863
|
reportError(err);
|
|
17659
17864
|
}
|
|
17660
17865
|
}
|
|
17661
|
-
var CLI_VERSION = true ? "0.
|
|
17866
|
+
var CLI_VERSION = true ? "0.6.0" : "0.0.0-dev";
|
|
17662
17867
|
var program = new Command();
|
|
17663
17868
|
program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
|
|
17664
17869
|
program.command("login").description("Sign in via the browser (default) and store a token").option("--email <email>", "account email (with --password; non-interactive escape hatch)").option("--password <password>", "account password (with --email; visible in `ps` \u2014 prefer the browser flow)").option(
|
|
@@ -17710,14 +17915,16 @@ project.command("rm [project]").description(
|
|
|
17710
17915
|
"Permanently delete a project and everything Kryd created for it (by name or id; asks you to type its name)"
|
|
17711
17916
|
).option("--yes", "skip the confirmation (for non-interactive use)").option("--api-url <url>", "control-plane API base URL").action((projectArg, opts) => runProjectRemove({ ...opts, project: projectArg }));
|
|
17712
17917
|
var db = program.command("db").description("Manage project databases");
|
|
17713
|
-
db.command("
|
|
17918
|
+
db.command("add [project]").description("Attach a shared or bring-your-own Postgres database to a project").option("--shared", "attach a shared managed database (the default)").option(
|
|
17714
17919
|
"--connection-string <url>",
|
|
17715
17920
|
"attach an existing database (BYO) by pasting its connection string"
|
|
17716
|
-
).option("--api-url <url>", "control-plane API base URL").action((project2, opts) =>
|
|
17717
|
-
db.command("
|
|
17921
|
+
).option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbAdd({ ...opts, project: project2 }));
|
|
17922
|
+
db.command("remove [project]").description("Tear down the project's database and everything in it (asks for confirmation)").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbRemove({ ...opts, project: project2 }));
|
|
17923
|
+
db.command("status [project]").description("Show whether the project has a database, and what state it is in").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbStatus({ ...opts, project: project2 }));
|
|
17718
17924
|
var storage = program.command("storage").description("Manage project object storage");
|
|
17719
|
-
storage.command("
|
|
17720
|
-
storage.command("
|
|
17925
|
+
storage.command("add [project]").description("Create an S3-compatible object-storage bucket for a project").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runStorageAdd({ ...opts, project: project2 }));
|
|
17926
|
+
storage.command("remove [project]").description("Tear down the project's bucket and every object in it (asks for confirmation)").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runStorageRemove({ ...opts, project: project2 }));
|
|
17927
|
+
storage.command("status [project]").description("Show whether the project has object storage, and what state it is in").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runStorageStatus({ ...opts, project: project2 }));
|
|
17721
17928
|
var env = program.command("env").description("Manage your app's environment variables");
|
|
17722
17929
|
env.command("list [project]").description("List the project's environment variables (names and origin \u2014 never values)").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runEnvList({ ...opts, project: project2 }));
|
|
17723
17930
|
env.command("set <key> [project]").description(
|
|
@@ -17749,8 +17956,19 @@ env.command("pull [project]").description(
|
|
|
17749
17956
|
"file to write, relative to the project root (default: .env.kryd \u2014 never your hand-maintained .env)"
|
|
17750
17957
|
).option("--force", "overwrite the --out file if it already exists").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runEnvPull({ ...opts, project: project2 }));
|
|
17751
17958
|
var ai = program.command("ai").description("Manage the app's EU AI gateway");
|
|
17752
|
-
ai.command("
|
|
17753
|
-
|
|
17959
|
+
ai.command("add [project]").description("Give the project an authenticated EU AI endpoint (injects on next deploy)").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runAiAdd({ ...opts, project: project2 }));
|
|
17960
|
+
ai.command("remove [project]").description("Stop injecting the project's AI endpoint (asks for confirmation)").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runAiRemove({ ...opts, project: project2 }));
|
|
17961
|
+
ai.command("status [project]").description("Show whether the project has an AI endpoint, and what it is").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runAiStatus({ ...opts, project: project2 }));
|
|
17962
|
+
function invokedDirectly() {
|
|
17963
|
+
const entry = process.argv[1];
|
|
17964
|
+
if (!entry) return false;
|
|
17965
|
+
try {
|
|
17966
|
+
return import.meta.url === pathToFileURL(realpathSync(entry)).href;
|
|
17967
|
+
} catch {
|
|
17968
|
+
return false;
|
|
17969
|
+
}
|
|
17970
|
+
}
|
|
17971
|
+
if (invokedDirectly()) {
|
|
17754
17972
|
program.parseAsync().catch((err) => {
|
|
17755
17973
|
process.stderr.write(
|
|
17756
17974
|
`${err instanceof Error ? err.message : String(err)}
|
|
@@ -17761,9 +17979,12 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
17761
17979
|
}
|
|
17762
17980
|
export {
|
|
17763
17981
|
program,
|
|
17764
|
-
|
|
17765
|
-
|
|
17766
|
-
|
|
17982
|
+
runAiAdd,
|
|
17983
|
+
runAiRemove,
|
|
17984
|
+
runAiStatus,
|
|
17985
|
+
runDbAdd,
|
|
17986
|
+
runDbRemove,
|
|
17987
|
+
runDbStatus,
|
|
17767
17988
|
runDeploy,
|
|
17768
17989
|
runEnvList,
|
|
17769
17990
|
runEnvPull,
|
|
@@ -17779,8 +18000,9 @@ export {
|
|
|
17779
18000
|
runRedeploy,
|
|
17780
18001
|
runRollback,
|
|
17781
18002
|
runRuntimeLogs,
|
|
17782
|
-
|
|
17783
|
-
|
|
18003
|
+
runStorageAdd,
|
|
18004
|
+
runStorageRemove,
|
|
18005
|
+
runStorageStatus,
|
|
17784
18006
|
runWhoami,
|
|
17785
18007
|
splitKeyValue
|
|
17786
18008
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kryd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Kryd CLI — push a React / Vite / Next.js app to the European cloud for your AI: git push → live with SSL, one-click managed Postgres & object storage, and an EU-hosted AI gateway already wired in. Your code and your model calls stay in the EU.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kryd",
|
|
@@ -49,15 +49,16 @@
|
|
|
49
49
|
"tsx": "^4.19.2",
|
|
50
50
|
"typescript": "^5.6.3",
|
|
51
51
|
"vitest": "^2.1.8",
|
|
52
|
-
"@kryd/shared-types": "0.0.0",
|
|
53
52
|
"@kryd/config-ts": "0.0.0",
|
|
54
|
-
"@kryd/config-eslint": "0.0.0"
|
|
53
|
+
"@kryd/config-eslint": "0.0.0",
|
|
54
|
+
"@kryd/shared-types": "0.0.0"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --external:commander --define:__KRYD_VERSION__=\\\"$npm_package_version\\\" --outfile=dist/index.js",
|
|
58
58
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
59
59
|
"lint": "eslint src",
|
|
60
60
|
"test": "vitest run",
|
|
61
|
+
"smoke": "bash scripts/smoke-package.sh",
|
|
61
62
|
"dev": "tsx src/index.ts"
|
|
62
63
|
}
|
|
63
64
|
}
|