@kryd/cli 0.16.0 → 0.17.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 +2 -3
- package/dist/index.js +297 -20
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -17,8 +17,7 @@ Requires Node.js ≥ 22 and git (the CLI shells out to git for `kryd init` and `
|
|
|
17
17
|
## Quickstart
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
-
kryd
|
|
21
|
-
kryd init # link this folder to a Kryd project (+ a `kryd` git remote)
|
|
20
|
+
kryd init # link this folder to a Kryd project (+ a `kryd` git remote); not signed in? it asks, then signs you in via the browser
|
|
22
21
|
kryd push # push the current branch → build → deploy → live, with live progress
|
|
23
22
|
kryd logs # the full build log of the latest deploy (kryd push shows progress, not log lines)
|
|
24
23
|
kryd logs <project> --runtime # tail the live app's stdout/stderr
|
|
@@ -33,7 +32,7 @@ Once you've run `kryd init` in a directory, the commands below work **arg-less**
|
|
|
33
32
|
|---|---|
|
|
34
33
|
| `kryd login` / `logout` / `whoami` | Authenticate the CLI (browser flow); the token is stored in `~/.kryd`. `whoami` shows the account commands run in, your role there, and your other accounts. |
|
|
35
34
|
| `kryd account list \| use <slug>` | You can belong to several accounts (your own, a customer's as a member). `list` shows them with your role in each, `*` marking the current one; `use` makes every following command run in that account. |
|
|
36
|
-
| `kryd init` | Link the current repo to a Kryd project + register the deploy webhook + add a `kryd` git remote. |
|
|
35
|
+
| `kryd init` | Link the current repo to a Kryd project + register the deploy webhook + add a `kryd` git remote. Not logged in and at a terminal? It asks, then runs the same browser login as `kryd login`; without a terminal it refuses (a deploy token, `KRYD_TOKEN`, cannot create a project). |
|
|
37
36
|
| `kryd push [branch]` | Push to the `kryd` remote and follow the deploy it triggers (defaults to the current branch). Shows which step the deploy is on, how long it has taken and a progress bar; `--logs` streams the full build log instead. |
|
|
38
37
|
| `kryd deploy [project]` | Re-deploy the production-branch HEAD already on the forge — no new commit. |
|
|
39
38
|
| `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; `--worker` tails the project's workflow worker, also when it has a web app). |
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,11 @@ var ERROR_CODES = {
|
|
|
21
21
|
// KRYD-516b: a request that names no account while the caller is in several (400), and a member
|
|
22
22
|
// whose role lacks the route's permission (403, `details.permission` names it).
|
|
23
23
|
accountRequired: "account.required",
|
|
24
|
-
permissionRequired: "auth.permission_required"
|
|
24
|
+
permissionRequired: "auth.permission_required",
|
|
25
|
+
// KRYD-592: the analytics store (Umami's database) is unconfigured, down or too slow — a 503 the
|
|
26
|
+
// Analytics tab shows as its own error band while the rest of the dashboard carries on. (The spec
|
|
27
|
+
// wrote `analytics_unavailable`; dotted like every code here, which `api.test.ts` enforces.)
|
|
28
|
+
analyticsUnavailable: "analytics.unavailable"
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
// ../../packages/shared-types/dist/db-url.js
|
|
@@ -14737,6 +14741,12 @@ var accountUsageSchema = external_exports.object({
|
|
|
14737
14741
|
storageBytes: external_exports.number().int().nonnegative()
|
|
14738
14742
|
});
|
|
14739
14743
|
|
|
14744
|
+
// ../../packages/shared-types/dist/resource.js
|
|
14745
|
+
var BUCKET_UPLOAD_MAX_BYTES = 50 * 1024 * 1024;
|
|
14746
|
+
var BUCKET_UPLOAD_MAX_SIZE_LABEL = `${BUCKET_UPLOAD_MAX_BYTES / (1024 * 1024)} MB`;
|
|
14747
|
+
var BUCKET_UPLOAD_MAX_TOTAL_BYTES = 100 * 1024 * 1024;
|
|
14748
|
+
var BUCKET_UPLOAD_TOTAL_REFUSAL = `Je kunt maximaal ${BUCKET_UPLOAD_MAX_TOTAL_BYTES / (1024 * 1024)} MB per upload versturen.`;
|
|
14749
|
+
|
|
14740
14750
|
// ../../packages/shared-types/dist/env-vars.js
|
|
14741
14751
|
var MANAGED_ENV_VAR_NAMES = [
|
|
14742
14752
|
"AI_GATEWAY_TOKEN",
|
|
@@ -14928,6 +14938,7 @@ var ROLES = {
|
|
|
14928
14938
|
var ACCOUNT_HEADER = "Kryd-Account";
|
|
14929
14939
|
|
|
14930
14940
|
// ../../packages/shared-types/dist/workflow-run.js
|
|
14941
|
+
var WORKFLOW_RUN_PAYLOAD_MAX_BYTES = 64 * 1024;
|
|
14931
14942
|
var MAX_WEBHOOK_NAME = 32;
|
|
14932
14943
|
var WEBHOOK_NAME_RE = new RegExp(`^[a-z0-9](?:[a-z0-9-]{0,${MAX_WEBHOOK_NAME - 2}}[a-z0-9])?$`);
|
|
14933
14944
|
|
|
@@ -15255,6 +15266,9 @@ async function linkProject(apiUrl, token, input) {
|
|
|
15255
15266
|
}
|
|
15256
15267
|
return parsed;
|
|
15257
15268
|
}
|
|
15269
|
+
function environmentQuery(environment) {
|
|
15270
|
+
return environment === "preview" ? "?environment=preview" : "";
|
|
15271
|
+
}
|
|
15258
15272
|
async function attachDatabase(apiUrl, token, input) {
|
|
15259
15273
|
const res = await fetch(`${apiUrl}/databases`, {
|
|
15260
15274
|
method: "POST",
|
|
@@ -15266,7 +15280,9 @@ async function attachDatabase(apiUrl, token, input) {
|
|
|
15266
15280
|
projectId: input.projectId,
|
|
15267
15281
|
mode: input.mode,
|
|
15268
15282
|
// Conditional (exactOptionalPropertyTypes) — only BYO carries a connection string.
|
|
15269
|
-
...input.connectionString ? { connectionString: input.connectionString } : {}
|
|
15283
|
+
...input.connectionString ? { connectionString: input.connectionString } : {},
|
|
15284
|
+
// Only sent for preview, so a production attach is byte-identical to what older CLIs send.
|
|
15285
|
+
...input.environment === "preview" ? { environment: "preview" } : {}
|
|
15270
15286
|
})
|
|
15271
15287
|
});
|
|
15272
15288
|
if (!res.ok) {
|
|
@@ -15283,7 +15299,8 @@ async function attachDatabase(apiUrl, token, input) {
|
|
|
15283
15299
|
return {
|
|
15284
15300
|
id: parsed.id,
|
|
15285
15301
|
mode: parsed.mode,
|
|
15286
|
-
status: parsed.status
|
|
15302
|
+
status: parsed.status,
|
|
15303
|
+
...parsed.environment === "production" || parsed.environment === "preview" ? { environment: parsed.environment } : {}
|
|
15287
15304
|
};
|
|
15288
15305
|
}
|
|
15289
15306
|
async function createStorage(apiUrl, token, input) {
|
|
@@ -15534,14 +15551,15 @@ async function fetchResourceStatus(apiUrl, token, path) {
|
|
|
15534
15551
|
}
|
|
15535
15552
|
return {
|
|
15536
15553
|
status: parsed.status,
|
|
15537
|
-
failureReason: typeof parsed.failureReason === "string" ? parsed.failureReason : null
|
|
15554
|
+
failureReason: typeof parsed.failureReason === "string" ? parsed.failureReason : null,
|
|
15555
|
+
...parsed.environment === "production" || parsed.environment === "preview" ? { environment: parsed.environment } : {}
|
|
15538
15556
|
};
|
|
15539
15557
|
}
|
|
15540
|
-
function getDatabaseStatus(apiUrl, token, projectId) {
|
|
15558
|
+
function getDatabaseStatus(apiUrl, token, projectId, environment) {
|
|
15541
15559
|
return fetchResourceStatus(
|
|
15542
15560
|
apiUrl,
|
|
15543
15561
|
token,
|
|
15544
|
-
`/databases/${encodeURIComponent(projectId)}`
|
|
15562
|
+
`/databases/${encodeURIComponent(projectId)}${environmentQuery(environment)}`
|
|
15545
15563
|
);
|
|
15546
15564
|
}
|
|
15547
15565
|
function getStorageStatus(apiUrl, token, projectId) {
|
|
@@ -15599,9 +15617,9 @@ async function pollResourceDetached(fetchStatus, opts) {
|
|
|
15599
15617
|
await sleep(intervalMs);
|
|
15600
15618
|
}
|
|
15601
15619
|
}
|
|
15602
|
-
async function detachDatabase(apiUrl, token, projectId) {
|
|
15620
|
+
async function detachDatabase(apiUrl, token, projectId, environment) {
|
|
15603
15621
|
const res = await fetch(
|
|
15604
|
-
`${apiUrl}/databases/${encodeURIComponent(projectId)}`,
|
|
15622
|
+
`${apiUrl}/databases/${encodeURIComponent(projectId)}${environmentQuery(environment)}`,
|
|
15605
15623
|
{ method: "DELETE", headers: { ...authHeaders(token) } }
|
|
15606
15624
|
);
|
|
15607
15625
|
if (!res.ok) {
|
|
@@ -15612,6 +15630,19 @@ async function detachDatabase(apiUrl, token, projectId) {
|
|
|
15612
15630
|
);
|
|
15613
15631
|
}
|
|
15614
15632
|
}
|
|
15633
|
+
async function resetPreviewDatabase(apiUrl, token, projectId) {
|
|
15634
|
+
const res = await fetch(
|
|
15635
|
+
`${apiUrl}/databases/${encodeURIComponent(projectId)}/reset?environment=preview`,
|
|
15636
|
+
{ method: "POST", headers: { ...authHeaders(token) } }
|
|
15637
|
+
);
|
|
15638
|
+
if (!res.ok) {
|
|
15639
|
+
throw new ApiError(
|
|
15640
|
+
`Database reset failed (${res.status})`,
|
|
15641
|
+
await parseEnvelope(res),
|
|
15642
|
+
res.status
|
|
15643
|
+
);
|
|
15644
|
+
}
|
|
15645
|
+
}
|
|
15615
15646
|
async function getProject(apiUrl, token, projectId) {
|
|
15616
15647
|
const res = await fetch(`${apiUrl}/projects/${encodeURIComponent(projectId)}`, {
|
|
15617
15648
|
headers: { ...authHeaders(token) }
|
|
@@ -18010,11 +18041,14 @@ async function runLogin(opts) {
|
|
|
18010
18041
|
warnIfEnvTokenOverrides();
|
|
18011
18042
|
return;
|
|
18012
18043
|
}
|
|
18013
|
-
|
|
18044
|
+
await browserLoginAndSave(apiUrl, opts.dashboardUrl);
|
|
18045
|
+
warnIfEnvTokenOverrides();
|
|
18046
|
+
}
|
|
18047
|
+
async function browserLoginAndSave(apiUrl, dashboardUrl) {
|
|
18048
|
+
const { token, apiUrl: learnedApiUrl } = await browserLogin(resolveDashboardUrl(dashboardUrl));
|
|
18014
18049
|
saveToken(token, learnedApiUrl ?? apiUrl);
|
|
18015
18050
|
process.stdout.write("Logged in.\n");
|
|
18016
18051
|
await settleAccountAfterLogin(learnedApiUrl ?? apiUrl, token);
|
|
18017
|
-
warnIfEnvTokenOverrides();
|
|
18018
18052
|
}
|
|
18019
18053
|
async function runWhoami(opts = {}) {
|
|
18020
18054
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
@@ -18049,13 +18083,46 @@ function runLogout() {
|
|
|
18049
18083
|
warnIfEnvTokenOverrides();
|
|
18050
18084
|
}
|
|
18051
18085
|
async function runInit(opts) {
|
|
18052
|
-
|
|
18086
|
+
let apiUrl = resolveApiUrl(opts.apiUrl);
|
|
18053
18087
|
const cwd = opts.cwd ?? process.cwd();
|
|
18054
|
-
|
|
18088
|
+
let token = resolveToken();
|
|
18055
18089
|
if (!token) {
|
|
18056
|
-
|
|
18057
|
-
|
|
18058
|
-
|
|
18090
|
+
const refusal = "Not logged in \u2014 run `kryd login` first.\n";
|
|
18091
|
+
const io = opts.io ?? defaultPromptIO();
|
|
18092
|
+
if (opts.offerLogin === false) {
|
|
18093
|
+
process.stderr.write(refusal);
|
|
18094
|
+
process.exitCode = 1;
|
|
18095
|
+
return;
|
|
18096
|
+
}
|
|
18097
|
+
if (!isInteractive(io)) {
|
|
18098
|
+
process.stderr.write(
|
|
18099
|
+
refusal + // KRYD-385 iteration 1: no KRYD_TOKEN advice — `POST /repos` refuses a deploy token (KRYD-578).
|
|
18100
|
+
"No terminal to log in from: ask the user to run `kryd init` (or `kryd login`) in their own terminal. A deploy token (KRYD_TOKEN) cannot create a project.\n"
|
|
18101
|
+
);
|
|
18102
|
+
process.exitCode = 1;
|
|
18103
|
+
return;
|
|
18104
|
+
}
|
|
18105
|
+
io.output.write("You are not logged in to Kryd; `kryd init` can log you in through your browser first.\n");
|
|
18106
|
+
if (!await promptConfirm("Log in now in your browser?", io)) {
|
|
18107
|
+
process.stderr.write(refusal);
|
|
18108
|
+
process.exitCode = 1;
|
|
18109
|
+
return;
|
|
18110
|
+
}
|
|
18111
|
+
try {
|
|
18112
|
+
await browserLoginAndSave(apiUrl);
|
|
18113
|
+
} catch (err) {
|
|
18114
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
18115
|
+
`);
|
|
18116
|
+
process.exitCode = 1;
|
|
18117
|
+
return;
|
|
18118
|
+
}
|
|
18119
|
+
token = resolveToken();
|
|
18120
|
+
if (!token) {
|
|
18121
|
+
process.stderr.write(refusal);
|
|
18122
|
+
process.exitCode = 1;
|
|
18123
|
+
return;
|
|
18124
|
+
}
|
|
18125
|
+
if (!opts.apiUrl) apiUrl = resolveApiUrl();
|
|
18059
18126
|
}
|
|
18060
18127
|
const existingLink = directoryIsLinked(cwd);
|
|
18061
18128
|
if (existingLink) {
|
|
@@ -18650,6 +18717,17 @@ async function runDbAdd(opts) {
|
|
|
18650
18717
|
process.exitCode = 1;
|
|
18651
18718
|
return;
|
|
18652
18719
|
}
|
|
18720
|
+
if (opts.preview && opts.connectionString !== void 0) {
|
|
18721
|
+
process.stderr.write(
|
|
18722
|
+
"A preview database is always managed by Kryd \u2014 --preview cannot take a --connection-string.\n"
|
|
18723
|
+
);
|
|
18724
|
+
process.exitCode = 1;
|
|
18725
|
+
return;
|
|
18726
|
+
}
|
|
18727
|
+
if (opts.preview) {
|
|
18728
|
+
await addPreviewDatabase(apiUrl, token, project2);
|
|
18729
|
+
return;
|
|
18730
|
+
}
|
|
18653
18731
|
let mode;
|
|
18654
18732
|
let connectionString;
|
|
18655
18733
|
if (opts.connectionString !== void 0) {
|
|
@@ -18695,6 +18773,46 @@ The connection string will be injected as DATABASE_URL on your next deploy.
|
|
|
18695
18773
|
await reportError(err);
|
|
18696
18774
|
}
|
|
18697
18775
|
}
|
|
18776
|
+
async function addPreviewDatabase(apiUrl, token, project2) {
|
|
18777
|
+
try {
|
|
18778
|
+
const db2 = await attachDatabase(apiUrl, token, {
|
|
18779
|
+
projectId: project2,
|
|
18780
|
+
mode: "shared",
|
|
18781
|
+
environment: "preview"
|
|
18782
|
+
});
|
|
18783
|
+
if (db2.environment !== "preview") {
|
|
18784
|
+
process.stderr.write(
|
|
18785
|
+
`The Kryd API did not create a preview database (it does not support them yet) \u2014 run \`kryd db status ${project2}\` to see what is attached.
|
|
18786
|
+
`
|
|
18787
|
+
);
|
|
18788
|
+
process.exitCode = 1;
|
|
18789
|
+
return;
|
|
18790
|
+
}
|
|
18791
|
+
process.stdout.write(`Provisioning a preview database for ${project2}\u2026
|
|
18792
|
+
`);
|
|
18793
|
+
const result = await pollResourceUntilTerminal(
|
|
18794
|
+
() => getDatabaseStatus(apiUrl, token, project2, "preview")
|
|
18795
|
+
);
|
|
18796
|
+
reportSettleResult(result, {
|
|
18797
|
+
readyMsg: `Attached preview database ${db2.id} to ${project2}. It starts empty.
|
|
18798
|
+
Every preview deploy gets it as DATABASE_URL from its next deploy; production keeps its own.
|
|
18799
|
+
`,
|
|
18800
|
+
failedLabel: "Preview database provisioning",
|
|
18801
|
+
retryCmd: `kryd db status ${project2}`
|
|
18802
|
+
});
|
|
18803
|
+
} catch (err) {
|
|
18804
|
+
await reportError(err);
|
|
18805
|
+
}
|
|
18806
|
+
}
|
|
18807
|
+
async function readPreviewDatabase(apiUrl, token, project2) {
|
|
18808
|
+
try {
|
|
18809
|
+
const row = await getDatabaseStatus(apiUrl, token, project2, "preview");
|
|
18810
|
+
return row.environment === "preview" ? row : null;
|
|
18811
|
+
} catch (err) {
|
|
18812
|
+
if (err instanceof ApiError && err.status === 404) return null;
|
|
18813
|
+
throw err;
|
|
18814
|
+
}
|
|
18815
|
+
}
|
|
18698
18816
|
function reportSettleResult(result, opts) {
|
|
18699
18817
|
if (result.status === "ready") {
|
|
18700
18818
|
process.stdout.write(opts.readyMsg);
|
|
@@ -18915,6 +19033,8 @@ async function describeProjectResources(apiUrl, token, projectId) {
|
|
|
18915
19033
|
const out = ["its deployed app, its containers and its built images"];
|
|
18916
19034
|
const db2 = await getDatabaseStatus(apiUrl, token, projectId).catch(() => null);
|
|
18917
19035
|
if (db2) out.push("its Postgres database \u2014 and all the data in it");
|
|
19036
|
+
const preview = db2 ? await readPreviewDatabase(apiUrl, token, projectId).catch(() => null) : null;
|
|
19037
|
+
if (preview) out.push("its preview database \u2014 and all the data in it");
|
|
18918
19038
|
const storage2 = await getStorageStatus(apiUrl, token, projectId).catch(() => null);
|
|
18919
19039
|
if (storage2) out.push("its object-storage bucket \u2014 and every object in it");
|
|
18920
19040
|
out.push("its Git repository on the forge, and its push token");
|
|
@@ -18933,6 +19053,23 @@ async function runDbRemove(opts) {
|
|
|
18933
19053
|
reportNotLinked("kryd db remove <projectId>");
|
|
18934
19054
|
return;
|
|
18935
19055
|
}
|
|
19056
|
+
if (opts.preview) {
|
|
19057
|
+
await removePreviewDatabase(apiUrl, token, project2, opts);
|
|
19058
|
+
return;
|
|
19059
|
+
}
|
|
19060
|
+
try {
|
|
19061
|
+
if (await readPreviewDatabase(apiUrl, token, project2)) {
|
|
19062
|
+
process.stderr.write(
|
|
19063
|
+
`${project2} has a preview database \u2014 remove the preview first (\`kryd db remove --preview\`).
|
|
19064
|
+
`
|
|
19065
|
+
);
|
|
19066
|
+
process.exitCode = 1;
|
|
19067
|
+
return;
|
|
19068
|
+
}
|
|
19069
|
+
} catch (err) {
|
|
19070
|
+
await reportError(err);
|
|
19071
|
+
return;
|
|
19072
|
+
}
|
|
18936
19073
|
const proceed = await confirmResourceRemoval({
|
|
18937
19074
|
yes: opts.yes,
|
|
18938
19075
|
io: opts.io,
|
|
@@ -18959,6 +19096,105 @@ async function runDbRemove(opts) {
|
|
|
18959
19096
|
await reportError(err);
|
|
18960
19097
|
}
|
|
18961
19098
|
}
|
|
19099
|
+
async function removePreviewDatabase(apiUrl, token, project2, opts) {
|
|
19100
|
+
try {
|
|
19101
|
+
if (!await readPreviewDatabase(apiUrl, token, project2)) {
|
|
19102
|
+
process.stderr.write(`${project2} has no preview database.
|
|
19103
|
+
`);
|
|
19104
|
+
process.exitCode = 1;
|
|
19105
|
+
return;
|
|
19106
|
+
}
|
|
19107
|
+
} catch (err) {
|
|
19108
|
+
await reportError(err);
|
|
19109
|
+
return;
|
|
19110
|
+
}
|
|
19111
|
+
const proceed = await confirmResourceRemoval({
|
|
19112
|
+
yes: opts.yes,
|
|
19113
|
+
io: opts.io,
|
|
19114
|
+
subject: `the preview database from ${project2}`,
|
|
19115
|
+
question: `Remove the preview database from ${project2}? It is torn down and all the data in it is destroyed \u2014 this cannot be undone. Production is not touched; preview deploys use the production database again from their next deploy.`,
|
|
19116
|
+
declined: `Left the preview database on ${project2} in place.
|
|
19117
|
+
`
|
|
19118
|
+
});
|
|
19119
|
+
if (!proceed) return;
|
|
19120
|
+
try {
|
|
19121
|
+
await detachDatabase(apiUrl, token, project2, "preview");
|
|
19122
|
+
process.stdout.write(`Removing the preview database from ${project2}\u2026
|
|
19123
|
+
`);
|
|
19124
|
+
const result = await pollResourceDetached(
|
|
19125
|
+
() => getDatabaseStatus(apiUrl, token, project2, "preview")
|
|
19126
|
+
);
|
|
19127
|
+
reportDetachResult(result, {
|
|
19128
|
+
goneMsg: `Removed the preview database from ${project2}.
|
|
19129
|
+
Preview deploys use the production database again from their next deploy.
|
|
19130
|
+
`,
|
|
19131
|
+
failedLabel: "Preview database removal",
|
|
19132
|
+
retryCmd: `kryd db remove --preview ${project2}`
|
|
19133
|
+
});
|
|
19134
|
+
} catch (err) {
|
|
19135
|
+
await reportError(err);
|
|
19136
|
+
}
|
|
19137
|
+
}
|
|
19138
|
+
async function runDbReset(opts) {
|
|
19139
|
+
if (!opts.preview) {
|
|
19140
|
+
process.stderr.write(
|
|
19141
|
+
"Only the preview database can be reset \u2014 run `kryd db reset --preview`. Production is never emptied.\n"
|
|
19142
|
+
);
|
|
19143
|
+
process.exitCode = 1;
|
|
19144
|
+
return;
|
|
19145
|
+
}
|
|
19146
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
19147
|
+
const token = resolveToken();
|
|
19148
|
+
if (!token) {
|
|
19149
|
+
process.stderr.write("Not logged in \u2014 run `kryd login` first.\n");
|
|
19150
|
+
process.exitCode = 1;
|
|
19151
|
+
return;
|
|
19152
|
+
}
|
|
19153
|
+
const project2 = resolveProjectId(opts.project, opts.cwd);
|
|
19154
|
+
if (!project2) {
|
|
19155
|
+
reportNotLinked("kryd db reset --preview <projectId>");
|
|
19156
|
+
return;
|
|
19157
|
+
}
|
|
19158
|
+
try {
|
|
19159
|
+
if (!await readPreviewDatabase(apiUrl, token, project2)) {
|
|
19160
|
+
process.stderr.write(
|
|
19161
|
+
`${project2} has no preview database \u2014 add one with \`kryd db add --preview ${project2}\`.
|
|
19162
|
+
`
|
|
19163
|
+
);
|
|
19164
|
+
process.exitCode = 1;
|
|
19165
|
+
return;
|
|
19166
|
+
}
|
|
19167
|
+
} catch (err) {
|
|
19168
|
+
await reportError(err);
|
|
19169
|
+
return;
|
|
19170
|
+
}
|
|
19171
|
+
const proceed = await confirmResourceRemoval({
|
|
19172
|
+
yes: opts.yes,
|
|
19173
|
+
io: opts.io,
|
|
19174
|
+
subject: `everything in the preview database of ${project2}`,
|
|
19175
|
+
question: `Empty the preview database of ${project2}? Every table and all the data in it is dropped \u2014 this cannot be undone. Production is not touched.`,
|
|
19176
|
+
declined: `Left the preview database of ${project2} as it is.
|
|
19177
|
+
`
|
|
19178
|
+
});
|
|
19179
|
+
if (!proceed) return;
|
|
19180
|
+
try {
|
|
19181
|
+
await resetPreviewDatabase(apiUrl, token, project2);
|
|
19182
|
+
process.stdout.write(`Emptying the preview database of ${project2}\u2026
|
|
19183
|
+
`);
|
|
19184
|
+
const result = await pollResourceUntilTerminal(
|
|
19185
|
+
() => getDatabaseStatus(apiUrl, token, project2, "preview")
|
|
19186
|
+
);
|
|
19187
|
+
reportSettleResult(result, {
|
|
19188
|
+
readyMsg: `The preview database of ${project2} is empty again.
|
|
19189
|
+
Its credentials did not change, so no deploy is needed.
|
|
19190
|
+
`,
|
|
19191
|
+
failedLabel: "Preview database reset",
|
|
19192
|
+
retryCmd: `kryd db status ${project2}`
|
|
19193
|
+
});
|
|
19194
|
+
} catch (err) {
|
|
19195
|
+
await reportError(err);
|
|
19196
|
+
}
|
|
19197
|
+
}
|
|
18962
19198
|
async function runAiAdd(opts) {
|
|
18963
19199
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
18964
19200
|
const token = resolveToken();
|
|
@@ -19064,12 +19300,45 @@ async function runDbStatus(opts) {
|
|
|
19064
19300
|
reportNotLinked("kryd db status <projectId>");
|
|
19065
19301
|
return;
|
|
19066
19302
|
}
|
|
19303
|
+
let productionAttached = false;
|
|
19067
19304
|
await reportResourceStatus({
|
|
19068
|
-
read: () =>
|
|
19305
|
+
read: async () => {
|
|
19306
|
+
const row = await getDatabaseStatus(apiUrl, token, project2);
|
|
19307
|
+
productionAttached = true;
|
|
19308
|
+
return row;
|
|
19309
|
+
},
|
|
19069
19310
|
noun: "database",
|
|
19070
19311
|
project: project2,
|
|
19071
19312
|
token
|
|
19072
19313
|
});
|
|
19314
|
+
if (!productionAttached) return;
|
|
19315
|
+
try {
|
|
19316
|
+
const preview = await readPreviewDatabase(apiUrl, token, project2);
|
|
19317
|
+
if (!preview) {
|
|
19318
|
+
process.stdout.write(
|
|
19319
|
+
`${project2}: no preview database \u2014 preview deploys use the production database.
|
|
19320
|
+
`
|
|
19321
|
+
);
|
|
19322
|
+
} else if (preview.status === "failed") {
|
|
19323
|
+
process.stdout.write(
|
|
19324
|
+
`${project2}: preview database failed \u2014 ${preview.failureReason ?? "unknown error"}.
|
|
19325
|
+
Remove it with \`kryd db remove --preview\` and add it again.
|
|
19326
|
+
`
|
|
19327
|
+
);
|
|
19328
|
+
} else if (preview.status === "ready") {
|
|
19329
|
+
process.stdout.write(
|
|
19330
|
+
`${project2}: preview database ready \u2014 preview deploys get it as DATABASE_URL.
|
|
19331
|
+
`
|
|
19332
|
+
);
|
|
19333
|
+
} else {
|
|
19334
|
+
process.stdout.write(
|
|
19335
|
+
`${project2}: preview database ${preview.status} \u2014 preview deploys use the production database until it is ready.
|
|
19336
|
+
`
|
|
19337
|
+
);
|
|
19338
|
+
}
|
|
19339
|
+
} catch (err) {
|
|
19340
|
+
await reportError(err);
|
|
19341
|
+
}
|
|
19073
19342
|
}
|
|
19074
19343
|
async function runStorageStatus(opts) {
|
|
19075
19344
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
@@ -19529,6 +19798,7 @@ HATCHET_CLIENT_TOKEN is injected into your container on production deploys (prev
|
|
|
19529
19798
|
process.exitCode = 0;
|
|
19530
19799
|
await runInit({
|
|
19531
19800
|
cwd: target,
|
|
19801
|
+
offerLogin: false,
|
|
19532
19802
|
...opts.name ? { name: opts.name } : {},
|
|
19533
19803
|
...opts.apiUrl ? { apiUrl: opts.apiUrl } : {}
|
|
19534
19804
|
});
|
|
@@ -20159,7 +20429,7 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
|
|
|
20159
20429
|
await reportError(err);
|
|
20160
20430
|
}
|
|
20161
20431
|
}
|
|
20162
|
-
var CLI_VERSION = true ? "0.
|
|
20432
|
+
var CLI_VERSION = true ? "0.17.0" : "0.0.0-dev";
|
|
20163
20433
|
var program = new Command();
|
|
20164
20434
|
program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
|
|
20165
20435
|
program.hook("postAction", async () => {
|
|
@@ -20182,7 +20452,9 @@ program.command("git-credential <operation>", { hidden: true }).description("git
|
|
|
20182
20452
|
...opts.apiUrl ? { apiUrl: opts.apiUrl } : {}
|
|
20183
20453
|
});
|
|
20184
20454
|
});
|
|
20185
|
-
program.command("init").description(
|
|
20455
|
+
program.command("init").description(
|
|
20456
|
+
"Link this project's repo + register its push webhook; not logged in \u2192 offers the browser login first (terminal only)"
|
|
20457
|
+
).option("--name <name>", "project name (defaults to package.json name / dir)").option(
|
|
20186
20458
|
"--framework <framework>",
|
|
20187
20459
|
'react-router | nextjs | vite-spa | node (auto-detected if omitted; `workflow` comes from a committed kryd.json with "web": false)'
|
|
20188
20460
|
).option(
|
|
@@ -20243,9 +20515,13 @@ var db = program.command("db").description("Manage project databases");
|
|
|
20243
20515
|
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(
|
|
20244
20516
|
"--connection-string <url>",
|
|
20245
20517
|
"attach an existing database (BYO) by pasting its connection string"
|
|
20518
|
+
).option(
|
|
20519
|
+
"--preview",
|
|
20520
|
+
"attach an empty preview database that every preview deploy gets instead of production's"
|
|
20246
20521
|
).option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbAdd({ ...opts, project: project2 }));
|
|
20247
|
-
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 }));
|
|
20248
|
-
db.command("
|
|
20522
|
+
db.command("remove [project]").description("Tear down the project's database and everything in it (asks for confirmation)").option("--preview", "remove the preview database; production is not touched").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbRemove({ ...opts, project: project2 }));
|
|
20523
|
+
db.command("reset [project]").description("Empty the preview database: every table dropped, credentials kept (asks for confirmation)").option("--preview", "required \u2014 only the preview database can be reset").option("--yes", "skip the confirmation prompt").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbReset({ ...opts, project: project2 }));
|
|
20524
|
+
db.command("status [project]").description("Show the project's database and preview database, and what state they are in").option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbStatus({ ...opts, project: project2 }));
|
|
20249
20525
|
var storage = program.command("storage").description("Manage project object storage");
|
|
20250
20526
|
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 }));
|
|
20251
20527
|
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 }));
|
|
@@ -20340,6 +20616,7 @@ export {
|
|
|
20340
20616
|
runAiStatus,
|
|
20341
20617
|
runDbAdd,
|
|
20342
20618
|
runDbRemove,
|
|
20619
|
+
runDbReset,
|
|
20343
20620
|
runDbStatus,
|
|
20344
20621
|
runDeploy,
|
|
20345
20622
|
runDeployHookCreate,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kryd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Kryd CLI — git push → live on the European cloud for your AI: SSL, managed Postgres & object storage, and an EU-hosted AI gateway. Your code and model calls stay in the EU. Beta: sign up at https://kryd.eu/beta",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kryd",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"typescript": "^5.6.3",
|
|
43
43
|
"vitest": "^2.1.8",
|
|
44
44
|
"@kryd/shared-types": "0.0.0",
|
|
45
|
-
"@kryd/config-
|
|
46
|
-
"@kryd/config-
|
|
45
|
+
"@kryd/config-eslint": "0.0.0",
|
|
46
|
+
"@kryd/config-ts": "0.0.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --external:commander --define:__KRYD_VERSION__=\\\"$npm_package_version\\\" --outfile=dist/index.js",
|