@kryd/cli 0.15.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 +7 -5
- package/dist/index.js +748 -265
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -8,6 +8,26 @@ var __export = (target, all) => {
|
|
|
8
8
|
// src/index.ts
|
|
9
9
|
import { Command } from "commander";
|
|
10
10
|
|
|
11
|
+
// ../../packages/shared-types/dist/api.js
|
|
12
|
+
var ERROR_CODES = {
|
|
13
|
+
validationFailed: "validation.failed",
|
|
14
|
+
unauthorized: "auth.unauthorized",
|
|
15
|
+
forbidden: "auth.forbidden",
|
|
16
|
+
notFound: "resource.not_found",
|
|
17
|
+
conflict: "resource.conflict",
|
|
18
|
+
unprocessable: "request.unprocessable",
|
|
19
|
+
badRequest: "request.bad",
|
|
20
|
+
internal: "internal.error",
|
|
21
|
+
// KRYD-516b: a request that names no account while the caller is in several (400), and a member
|
|
22
|
+
// whose role lacks the route's permission (403, `details.permission` names it).
|
|
23
|
+
accountRequired: "account.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"
|
|
29
|
+
};
|
|
30
|
+
|
|
11
31
|
// ../../packages/shared-types/dist/db-url.js
|
|
12
32
|
function isValidPostgresConnectionString(value) {
|
|
13
33
|
if (value.length === 0 || /\s/.test(value))
|
|
@@ -14721,6 +14741,12 @@ var accountUsageSchema = external_exports.object({
|
|
|
14721
14741
|
storageBytes: external_exports.number().int().nonnegative()
|
|
14722
14742
|
});
|
|
14723
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
|
+
|
|
14724
14750
|
// ../../packages/shared-types/dist/env-vars.js
|
|
14725
14751
|
var MANAGED_ENV_VAR_NAMES = [
|
|
14726
14752
|
"AI_GATEWAY_TOKEN",
|
|
@@ -14875,7 +14901,44 @@ function parseTicketNumber(raw) {
|
|
|
14875
14901
|
var TICKET_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
14876
14902
|
var TICKET_ATTACHMENT_MAX_SIZE_LABEL = `${TICKET_ATTACHMENT_MAX_BYTES / (1024 * 1024)} MB`;
|
|
14877
14903
|
|
|
14904
|
+
// ../../packages/shared-types/dist/permissions.js
|
|
14905
|
+
var PERMISSIONS = [
|
|
14906
|
+
/** Open the site of a private preview or a closed production (enforced from KRYD-575). */
|
|
14907
|
+
"app:view",
|
|
14908
|
+
/** See the account itself: name, slug. */
|
|
14909
|
+
"account:view",
|
|
14910
|
+
/** Rename the account and change its slug. */
|
|
14911
|
+
"account:manage",
|
|
14912
|
+
/** See who is a member, and the invites still open. */
|
|
14913
|
+
"members:view",
|
|
14914
|
+
/** Invite people, change a member's role, remove a member. */
|
|
14915
|
+
"members:manage",
|
|
14916
|
+
/** See usage and costs. */
|
|
14917
|
+
"usage:view",
|
|
14918
|
+
/** See projects, deploys, domains, databases, storage, AI and workflows. */
|
|
14919
|
+
"projects:view",
|
|
14920
|
+
/** Change a project: env vars, domains, databases, storage, AI, workflows, remove it. */
|
|
14921
|
+
"projects:manage",
|
|
14922
|
+
/** Read env var VALUES (secrets), not only their names. */
|
|
14923
|
+
"env:read",
|
|
14924
|
+
/** Link a repository, deploy, roll back, get a push credential, manage deploy hooks. */
|
|
14925
|
+
"deploy",
|
|
14926
|
+
/** Read build and runtime logs. */
|
|
14927
|
+
"logs:view",
|
|
14928
|
+
/** Trigger, cancel and replay workflow runs; add and remove crons and schedules. */
|
|
14929
|
+
"workflows:run",
|
|
14930
|
+
/** Open and answer support tickets. */
|
|
14931
|
+
"support"
|
|
14932
|
+
];
|
|
14933
|
+
var ROLES = {
|
|
14934
|
+
owner: PERMISSIONS,
|
|
14935
|
+
member: PERMISSIONS.filter((p) => p !== "members:manage" && p !== "account:manage"),
|
|
14936
|
+
viewer: ["app:view"]
|
|
14937
|
+
};
|
|
14938
|
+
var ACCOUNT_HEADER = "Kryd-Account";
|
|
14939
|
+
|
|
14878
14940
|
// ../../packages/shared-types/dist/workflow-run.js
|
|
14941
|
+
var WORKFLOW_RUN_PAYLOAD_MAX_BYTES = 64 * 1024;
|
|
14879
14942
|
var MAX_WEBHOOK_NAME = 32;
|
|
14880
14943
|
var WEBHOOK_NAME_RE = new RegExp(`^[a-z0-9](?:[a-z0-9-]{0,${MAX_WEBHOOK_NAME - 2}}[a-z0-9])?$`);
|
|
14881
14944
|
|
|
@@ -14936,6 +14999,209 @@ import {
|
|
|
14936
14999
|
import { basename as basename2, dirname as dirname2, isAbsolute, join as join3, relative, resolve as resolve2 } from "node:path";
|
|
14937
15000
|
import { pathToFileURL } from "node:url";
|
|
14938
15001
|
|
|
15002
|
+
// src/config.ts
|
|
15003
|
+
import {
|
|
15004
|
+
appendFileSync,
|
|
15005
|
+
chmodSync,
|
|
15006
|
+
existsSync,
|
|
15007
|
+
mkdirSync,
|
|
15008
|
+
readFileSync,
|
|
15009
|
+
rmSync,
|
|
15010
|
+
writeFileSync
|
|
15011
|
+
} from "node:fs";
|
|
15012
|
+
import { homedir } from "node:os";
|
|
15013
|
+
import { dirname, join } from "node:path";
|
|
15014
|
+
var DEFAULT_API_URL = "http://localhost:3000";
|
|
15015
|
+
function configPath() {
|
|
15016
|
+
const dir = process.env.KRYD_CONFIG_DIR ?? join(homedir(), ".kryd");
|
|
15017
|
+
return join(dir, "config.json");
|
|
15018
|
+
}
|
|
15019
|
+
function loadConfig() {
|
|
15020
|
+
let raw;
|
|
15021
|
+
try {
|
|
15022
|
+
raw = readFileSync(configPath(), "utf8");
|
|
15023
|
+
} catch {
|
|
15024
|
+
return {};
|
|
15025
|
+
}
|
|
15026
|
+
try {
|
|
15027
|
+
return JSON.parse(raw);
|
|
15028
|
+
} catch {
|
|
15029
|
+
process.stderr.write(
|
|
15030
|
+
`warning: ${configPath()} is not valid JSON \u2014 ignoring it.
|
|
15031
|
+
`
|
|
15032
|
+
);
|
|
15033
|
+
return {};
|
|
15034
|
+
}
|
|
15035
|
+
}
|
|
15036
|
+
function resolveToken() {
|
|
15037
|
+
const fromEnv = process.env.KRYD_TOKEN?.trim();
|
|
15038
|
+
return fromEnv ? fromEnv : loadConfig().token;
|
|
15039
|
+
}
|
|
15040
|
+
function writeConfig(config2) {
|
|
15041
|
+
const path = configPath();
|
|
15042
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
15043
|
+
writeFileSync(path, JSON.stringify(config2, null, 2) + "\n", { mode: 384 });
|
|
15044
|
+
chmodSync(path, 384);
|
|
15045
|
+
}
|
|
15046
|
+
function saveToken(token, apiUrl) {
|
|
15047
|
+
const config2 = loadConfig();
|
|
15048
|
+
config2.token = token;
|
|
15049
|
+
if (apiUrl) config2.apiUrl = apiUrl;
|
|
15050
|
+
writeConfig(config2);
|
|
15051
|
+
}
|
|
15052
|
+
function clearToken() {
|
|
15053
|
+
const config2 = loadConfig();
|
|
15054
|
+
delete config2.token;
|
|
15055
|
+
delete config2.account;
|
|
15056
|
+
delete config2.accountSlug;
|
|
15057
|
+
writeConfig(config2);
|
|
15058
|
+
}
|
|
15059
|
+
function saveAccount(account) {
|
|
15060
|
+
const config2 = loadConfig();
|
|
15061
|
+
config2.account = account.accountId;
|
|
15062
|
+
if (account.slug) config2.accountSlug = account.slug;
|
|
15063
|
+
else delete config2.accountSlug;
|
|
15064
|
+
writeConfig(config2);
|
|
15065
|
+
}
|
|
15066
|
+
function clearAccount() {
|
|
15067
|
+
const config2 = loadConfig();
|
|
15068
|
+
delete config2.account;
|
|
15069
|
+
delete config2.accountSlug;
|
|
15070
|
+
writeConfig(config2);
|
|
15071
|
+
}
|
|
15072
|
+
var DEPLOY_TOKEN_PREFIX = "kryd_";
|
|
15073
|
+
function accountHeader(token) {
|
|
15074
|
+
if (!token || token.startsWith(DEPLOY_TOKEN_PREFIX)) return {};
|
|
15075
|
+
const account = loadConfig().account;
|
|
15076
|
+
return account ? { [ACCOUNT_HEADER]: account } : {};
|
|
15077
|
+
}
|
|
15078
|
+
function authHeaders(token) {
|
|
15079
|
+
return { authorization: `Bearer ${token}`, ...accountHeader(token) };
|
|
15080
|
+
}
|
|
15081
|
+
var lastApiUrl = null;
|
|
15082
|
+
function resolveApiUrl(flag) {
|
|
15083
|
+
lastApiUrl = flag ?? process.env.KRYD_API_URL ?? loadConfig().apiUrl ?? DEFAULT_API_URL;
|
|
15084
|
+
return lastApiUrl;
|
|
15085
|
+
}
|
|
15086
|
+
function currentApiUrl() {
|
|
15087
|
+
return lastApiUrl ?? resolveApiUrl();
|
|
15088
|
+
}
|
|
15089
|
+
var DEFAULT_DASHBOARD_URL = "https://app.kryd.eu";
|
|
15090
|
+
function resolveDashboardUrl(flag) {
|
|
15091
|
+
return flag ?? process.env.KRYD_DASHBOARD_URL ?? DEFAULT_DASHBOARD_URL;
|
|
15092
|
+
}
|
|
15093
|
+
var PROJECT_LINK_DIR = ".kryd";
|
|
15094
|
+
var PROJECT_LINK_FILE = "project.json";
|
|
15095
|
+
function findProjectLinkDir(cwd = process.cwd()) {
|
|
15096
|
+
let dir = cwd;
|
|
15097
|
+
for (; ; ) {
|
|
15098
|
+
if (existsSync(join(dir, PROJECT_LINK_DIR, PROJECT_LINK_FILE))) return dir;
|
|
15099
|
+
if (existsSync(join(dir, ".git"))) return null;
|
|
15100
|
+
const parent = dirname(dir);
|
|
15101
|
+
if (parent === dir) return null;
|
|
15102
|
+
dir = parent;
|
|
15103
|
+
}
|
|
15104
|
+
}
|
|
15105
|
+
function loadProjectLink(cwd = process.cwd()) {
|
|
15106
|
+
const root = findProjectLinkDir(cwd);
|
|
15107
|
+
if (!root) return null;
|
|
15108
|
+
const path = join(root, PROJECT_LINK_DIR, PROJECT_LINK_FILE);
|
|
15109
|
+
let raw;
|
|
15110
|
+
try {
|
|
15111
|
+
raw = readFileSync(path, "utf8");
|
|
15112
|
+
} catch {
|
|
15113
|
+
return null;
|
|
15114
|
+
}
|
|
15115
|
+
let parsed;
|
|
15116
|
+
try {
|
|
15117
|
+
parsed = JSON.parse(raw);
|
|
15118
|
+
} catch {
|
|
15119
|
+
process.stderr.write(`warning: ${path} is not valid JSON \u2014 ignoring it.
|
|
15120
|
+
`);
|
|
15121
|
+
return null;
|
|
15122
|
+
}
|
|
15123
|
+
if (!parsed || typeof parsed.projectId !== "string" || parsed.projectId.length === 0) {
|
|
15124
|
+
process.stderr.write(`warning: ${path} has no projectId \u2014 ignoring it.
|
|
15125
|
+
`);
|
|
15126
|
+
return null;
|
|
15127
|
+
}
|
|
15128
|
+
return parsed;
|
|
15129
|
+
}
|
|
15130
|
+
function saveProjectLink(cwd, link) {
|
|
15131
|
+
const dir = join(cwd, PROJECT_LINK_DIR);
|
|
15132
|
+
mkdirSync(dir, { recursive: true });
|
|
15133
|
+
const path = join(dir, PROJECT_LINK_FILE);
|
|
15134
|
+
writeFileSync(path, JSON.stringify(link, null, 2) + "\n");
|
|
15135
|
+
return path;
|
|
15136
|
+
}
|
|
15137
|
+
function ensureIgnored(cwd, entry, aliases = []) {
|
|
15138
|
+
const path = join(cwd, ".gitignore");
|
|
15139
|
+
let current = "";
|
|
15140
|
+
try {
|
|
15141
|
+
current = readFileSync(path, "utf8");
|
|
15142
|
+
} catch {
|
|
15143
|
+
}
|
|
15144
|
+
const present = current.split("\n").map((l) => l.trim()).some((l) => l === entry || aliases.includes(l));
|
|
15145
|
+
if (present) return false;
|
|
15146
|
+
const prefix = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
|
|
15147
|
+
appendFileSync(path, `${prefix}${entry}
|
|
15148
|
+
`);
|
|
15149
|
+
return true;
|
|
15150
|
+
}
|
|
15151
|
+
function ensureKrydIgnored(cwd) {
|
|
15152
|
+
ensureIgnored(cwd, `${PROJECT_LINK_DIR}/`, [PROJECT_LINK_DIR]);
|
|
15153
|
+
}
|
|
15154
|
+
function directoryIsLinked(cwd) {
|
|
15155
|
+
const path = join(cwd, PROJECT_LINK_DIR, PROJECT_LINK_FILE);
|
|
15156
|
+
if (!existsSync(path)) return null;
|
|
15157
|
+
return loadProjectLink(cwd);
|
|
15158
|
+
}
|
|
15159
|
+
function writeSecretFile(path, contents) {
|
|
15160
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
15161
|
+
writeFileSync(path, contents, { mode: 384 });
|
|
15162
|
+
chmodSync(path, 384);
|
|
15163
|
+
}
|
|
15164
|
+
function clearProjectLinkIfMatches(projectId, cwd = process.cwd()) {
|
|
15165
|
+
const root = findProjectLinkDir(cwd);
|
|
15166
|
+
if (!root) return false;
|
|
15167
|
+
const link = loadProjectLink(cwd);
|
|
15168
|
+
if (link?.projectId !== projectId) return false;
|
|
15169
|
+
try {
|
|
15170
|
+
rmSync(join(root, PROJECT_LINK_DIR, PROJECT_LINK_FILE));
|
|
15171
|
+
return true;
|
|
15172
|
+
} catch {
|
|
15173
|
+
return false;
|
|
15174
|
+
}
|
|
15175
|
+
}
|
|
15176
|
+
function resolvedLink() {
|
|
15177
|
+
return lastLinkResolution;
|
|
15178
|
+
}
|
|
15179
|
+
var lastLinkResolution = null;
|
|
15180
|
+
function resolveProjectId(explicit, cwd = process.cwd()) {
|
|
15181
|
+
lastLinkResolution = null;
|
|
15182
|
+
if (explicit) return explicit;
|
|
15183
|
+
const root = findProjectLinkDir(cwd);
|
|
15184
|
+
if (!root) return null;
|
|
15185
|
+
const link = loadProjectLink(cwd);
|
|
15186
|
+
if (!link) return null;
|
|
15187
|
+
lastLinkResolution = { root, link };
|
|
15188
|
+
return link.projectId;
|
|
15189
|
+
}
|
|
15190
|
+
function rememberLinkAccount(root, account) {
|
|
15191
|
+
if (!account.accountId) return false;
|
|
15192
|
+
const link = directoryIsLinked(root);
|
|
15193
|
+
if (!link) return false;
|
|
15194
|
+
const slug = account.accountSlug ?? link.accountSlug;
|
|
15195
|
+
if (link.accountId === account.accountId && link.accountSlug === slug) return false;
|
|
15196
|
+
saveProjectLink(root, {
|
|
15197
|
+
...link,
|
|
15198
|
+
accountId: account.accountId,
|
|
15199
|
+
// `exactOptionalPropertyTypes`: an absent key and an explicit `undefined` are different types.
|
|
15200
|
+
...slug ? { accountSlug: slug } : {}
|
|
15201
|
+
});
|
|
15202
|
+
return true;
|
|
15203
|
+
}
|
|
15204
|
+
|
|
14939
15205
|
// src/client.ts
|
|
14940
15206
|
var ApiError = class extends Error {
|
|
14941
15207
|
envelope;
|
|
@@ -14965,7 +15231,7 @@ async function parseEnvelope(res) {
|
|
|
14965
15231
|
}
|
|
14966
15232
|
async function whoami(apiUrl, token) {
|
|
14967
15233
|
const res = await fetch(`${apiUrl}/me`, {
|
|
14968
|
-
headers: {
|
|
15234
|
+
headers: { ...authHeaders(token) }
|
|
14969
15235
|
});
|
|
14970
15236
|
if (!res.ok) {
|
|
14971
15237
|
throw new ApiError(
|
|
@@ -14981,7 +15247,7 @@ async function linkProject(apiUrl, token, input) {
|
|
|
14981
15247
|
method: "POST",
|
|
14982
15248
|
headers: {
|
|
14983
15249
|
"content-type": "application/json",
|
|
14984
|
-
|
|
15250
|
+
...authHeaders(token)
|
|
14985
15251
|
},
|
|
14986
15252
|
body: JSON.stringify(input)
|
|
14987
15253
|
});
|
|
@@ -15000,18 +15266,23 @@ async function linkProject(apiUrl, token, input) {
|
|
|
15000
15266
|
}
|
|
15001
15267
|
return parsed;
|
|
15002
15268
|
}
|
|
15269
|
+
function environmentQuery(environment) {
|
|
15270
|
+
return environment === "preview" ? "?environment=preview" : "";
|
|
15271
|
+
}
|
|
15003
15272
|
async function attachDatabase(apiUrl, token, input) {
|
|
15004
15273
|
const res = await fetch(`${apiUrl}/databases`, {
|
|
15005
15274
|
method: "POST",
|
|
15006
15275
|
headers: {
|
|
15007
15276
|
"content-type": "application/json",
|
|
15008
|
-
|
|
15277
|
+
...authHeaders(token)
|
|
15009
15278
|
},
|
|
15010
15279
|
body: JSON.stringify({
|
|
15011
15280
|
projectId: input.projectId,
|
|
15012
15281
|
mode: input.mode,
|
|
15013
15282
|
// Conditional (exactOptionalPropertyTypes) — only BYO carries a connection string.
|
|
15014
|
-
...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" } : {}
|
|
15015
15286
|
})
|
|
15016
15287
|
});
|
|
15017
15288
|
if (!res.ok) {
|
|
@@ -15028,7 +15299,8 @@ async function attachDatabase(apiUrl, token, input) {
|
|
|
15028
15299
|
return {
|
|
15029
15300
|
id: parsed.id,
|
|
15030
15301
|
mode: parsed.mode,
|
|
15031
|
-
status: parsed.status
|
|
15302
|
+
status: parsed.status,
|
|
15303
|
+
...parsed.environment === "production" || parsed.environment === "preview" ? { environment: parsed.environment } : {}
|
|
15032
15304
|
};
|
|
15033
15305
|
}
|
|
15034
15306
|
async function createStorage(apiUrl, token, input) {
|
|
@@ -15036,7 +15308,7 @@ async function createStorage(apiUrl, token, input) {
|
|
|
15036
15308
|
method: "POST",
|
|
15037
15309
|
headers: {
|
|
15038
15310
|
"content-type": "application/json",
|
|
15039
|
-
|
|
15311
|
+
...authHeaders(token)
|
|
15040
15312
|
},
|
|
15041
15313
|
body: JSON.stringify({ projectId: input.projectId })
|
|
15042
15314
|
});
|
|
@@ -15058,7 +15330,7 @@ async function enableAi(apiUrl, token, input) {
|
|
|
15058
15330
|
method: "POST",
|
|
15059
15331
|
headers: {
|
|
15060
15332
|
"content-type": "application/json",
|
|
15061
|
-
|
|
15333
|
+
...authHeaders(token)
|
|
15062
15334
|
},
|
|
15063
15335
|
body: JSON.stringify({ projectId: input.projectId })
|
|
15064
15336
|
});
|
|
@@ -15077,7 +15349,7 @@ async function enableAi(apiUrl, token, input) {
|
|
|
15077
15349
|
}
|
|
15078
15350
|
async function getAiStatus(apiUrl, token, projectId) {
|
|
15079
15351
|
const res = await fetch(`${apiUrl}/ai/${encodeURIComponent(projectId)}`, {
|
|
15080
|
-
headers: {
|
|
15352
|
+
headers: { ...authHeaders(token) }
|
|
15081
15353
|
});
|
|
15082
15354
|
if (!res.ok) {
|
|
15083
15355
|
throw new ApiError(
|
|
@@ -15095,7 +15367,7 @@ async function getAiStatus(apiUrl, token, projectId) {
|
|
|
15095
15367
|
async function removeAi(apiUrl, token, projectId) {
|
|
15096
15368
|
const res = await fetch(`${apiUrl}/ai/${encodeURIComponent(projectId)}`, {
|
|
15097
15369
|
method: "DELETE",
|
|
15098
|
-
headers: {
|
|
15370
|
+
headers: { ...authHeaders(token) }
|
|
15099
15371
|
});
|
|
15100
15372
|
if (!res.ok) {
|
|
15101
15373
|
throw new ApiError(
|
|
@@ -15117,7 +15389,7 @@ async function addWorkflows(apiUrl, token, input) {
|
|
|
15117
15389
|
method: "POST",
|
|
15118
15390
|
headers: {
|
|
15119
15391
|
"content-type": "application/json",
|
|
15120
|
-
|
|
15392
|
+
...authHeaders(token)
|
|
15121
15393
|
},
|
|
15122
15394
|
body: JSON.stringify({ projectId: input.projectId })
|
|
15123
15395
|
});
|
|
@@ -15132,7 +15404,7 @@ async function addWorkflows(apiUrl, token, input) {
|
|
|
15132
15404
|
}
|
|
15133
15405
|
async function getWorkflowStatus(apiUrl, token, projectId) {
|
|
15134
15406
|
const res = await fetch(`${apiUrl}/workflows/${encodeURIComponent(projectId)}`, {
|
|
15135
|
-
headers: {
|
|
15407
|
+
headers: { ...authHeaders(token) }
|
|
15136
15408
|
});
|
|
15137
15409
|
if (!res.ok) {
|
|
15138
15410
|
throw new ApiError(
|
|
@@ -15146,7 +15418,7 @@ async function getWorkflowStatus(apiUrl, token, projectId) {
|
|
|
15146
15418
|
async function removeWorkflows(apiUrl, token, projectId) {
|
|
15147
15419
|
const res = await fetch(`${apiUrl}/workflows/${encodeURIComponent(projectId)}`, {
|
|
15148
15420
|
method: "DELETE",
|
|
15149
|
-
headers: {
|
|
15421
|
+
headers: { ...authHeaders(token) }
|
|
15150
15422
|
});
|
|
15151
15423
|
if (!res.ok) {
|
|
15152
15424
|
throw new ApiError(
|
|
@@ -15197,7 +15469,7 @@ function domainsUrl(apiUrl, projectId) {
|
|
|
15197
15469
|
}
|
|
15198
15470
|
async function listDomains(apiUrl, token, projectId) {
|
|
15199
15471
|
const res = await fetch(domainsUrl(apiUrl, projectId), {
|
|
15200
|
-
headers: {
|
|
15472
|
+
headers: { ...authHeaders(token) }
|
|
15201
15473
|
});
|
|
15202
15474
|
if (!res.ok) {
|
|
15203
15475
|
throw new ApiError(`Domain listing failed (${res.status})`, await parseEnvelope(res), res.status);
|
|
@@ -15214,7 +15486,7 @@ async function listDomains(apiUrl, token, projectId) {
|
|
|
15214
15486
|
async function addDomain(apiUrl, token, projectId, host, app, redirectTo) {
|
|
15215
15487
|
const res = await fetch(domainsUrl(apiUrl, projectId), {
|
|
15216
15488
|
method: "POST",
|
|
15217
|
-
headers: { "content-type": "application/json",
|
|
15489
|
+
headers: { "content-type": "application/json", ...authHeaders(token) },
|
|
15218
15490
|
body: JSON.stringify({ host, ...app ? { app } : {}, ...redirectTo !== void 0 ? { redirectTo } : {} })
|
|
15219
15491
|
});
|
|
15220
15492
|
if (!res.ok) {
|
|
@@ -15225,7 +15497,7 @@ async function addDomain(apiUrl, token, projectId, host, app, redirectTo) {
|
|
|
15225
15497
|
async function removeDomain(apiUrl, token, projectId, host) {
|
|
15226
15498
|
const res = await fetch(`${domainsUrl(apiUrl, projectId)}/${encodeURIComponent(host)}`, {
|
|
15227
15499
|
method: "DELETE",
|
|
15228
|
-
headers: {
|
|
15500
|
+
headers: { ...authHeaders(token) }
|
|
15229
15501
|
});
|
|
15230
15502
|
if (!res.ok) {
|
|
15231
15503
|
throw new ApiError(`Domain removal failed (${res.status})`, await parseEnvelope(res), res.status);
|
|
@@ -15237,7 +15509,7 @@ function deployHookUrl(apiUrl, projectId) {
|
|
|
15237
15509
|
async function createDeployHook(apiUrl, token, projectId) {
|
|
15238
15510
|
const res = await fetch(deployHookUrl(apiUrl, projectId), {
|
|
15239
15511
|
method: "PUT",
|
|
15240
|
-
headers: {
|
|
15512
|
+
headers: { ...authHeaders(token) }
|
|
15241
15513
|
});
|
|
15242
15514
|
if (!res.ok) {
|
|
15243
15515
|
throw new ApiError(`Deploy hook creation failed (${res.status})`, await parseEnvelope(res), res.status);
|
|
@@ -15251,7 +15523,7 @@ async function createDeployHook(apiUrl, token, projectId) {
|
|
|
15251
15523
|
async function removeDeployHook(apiUrl, token, projectId) {
|
|
15252
15524
|
const res = await fetch(deployHookUrl(apiUrl, projectId), {
|
|
15253
15525
|
method: "DELETE",
|
|
15254
|
-
headers: {
|
|
15526
|
+
headers: { ...authHeaders(token) }
|
|
15255
15527
|
});
|
|
15256
15528
|
if (!res.ok) {
|
|
15257
15529
|
throw new ApiError(`Deploy hook removal failed (${res.status})`, await parseEnvelope(res), res.status);
|
|
@@ -15264,7 +15536,7 @@ async function removeDeployHook(apiUrl, token, projectId) {
|
|
|
15264
15536
|
}
|
|
15265
15537
|
async function fetchResourceStatus(apiUrl, token, path) {
|
|
15266
15538
|
const res = await fetch(`${apiUrl}${path}`, {
|
|
15267
|
-
headers: {
|
|
15539
|
+
headers: { ...authHeaders(token) }
|
|
15268
15540
|
});
|
|
15269
15541
|
if (!res.ok) {
|
|
15270
15542
|
throw new ApiError(
|
|
@@ -15279,14 +15551,15 @@ async function fetchResourceStatus(apiUrl, token, path) {
|
|
|
15279
15551
|
}
|
|
15280
15552
|
return {
|
|
15281
15553
|
status: parsed.status,
|
|
15282
|
-
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 } : {}
|
|
15283
15556
|
};
|
|
15284
15557
|
}
|
|
15285
|
-
function getDatabaseStatus(apiUrl, token, projectId) {
|
|
15558
|
+
function getDatabaseStatus(apiUrl, token, projectId, environment) {
|
|
15286
15559
|
return fetchResourceStatus(
|
|
15287
15560
|
apiUrl,
|
|
15288
15561
|
token,
|
|
15289
|
-
`/databases/${encodeURIComponent(projectId)}`
|
|
15562
|
+
`/databases/${encodeURIComponent(projectId)}${environmentQuery(environment)}`
|
|
15290
15563
|
);
|
|
15291
15564
|
}
|
|
15292
15565
|
function getStorageStatus(apiUrl, token, projectId) {
|
|
@@ -15344,10 +15617,10 @@ async function pollResourceDetached(fetchStatus, opts) {
|
|
|
15344
15617
|
await sleep(intervalMs);
|
|
15345
15618
|
}
|
|
15346
15619
|
}
|
|
15347
|
-
async function detachDatabase(apiUrl, token, projectId) {
|
|
15620
|
+
async function detachDatabase(apiUrl, token, projectId, environment) {
|
|
15348
15621
|
const res = await fetch(
|
|
15349
|
-
`${apiUrl}/databases/${encodeURIComponent(projectId)}`,
|
|
15350
|
-
{ method: "DELETE", headers: {
|
|
15622
|
+
`${apiUrl}/databases/${encodeURIComponent(projectId)}${environmentQuery(environment)}`,
|
|
15623
|
+
{ method: "DELETE", headers: { ...authHeaders(token) } }
|
|
15351
15624
|
);
|
|
15352
15625
|
if (!res.ok) {
|
|
15353
15626
|
throw new ApiError(
|
|
@@ -15357,9 +15630,22 @@ async function detachDatabase(apiUrl, token, projectId) {
|
|
|
15357
15630
|
);
|
|
15358
15631
|
}
|
|
15359
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
|
+
}
|
|
15360
15646
|
async function getProject(apiUrl, token, projectId) {
|
|
15361
15647
|
const res = await fetch(`${apiUrl}/projects/${encodeURIComponent(projectId)}`, {
|
|
15362
|
-
headers: {
|
|
15648
|
+
headers: { ...authHeaders(token) }
|
|
15363
15649
|
});
|
|
15364
15650
|
if (res.status === 404) return null;
|
|
15365
15651
|
if (!res.ok) {
|
|
@@ -15369,7 +15655,7 @@ async function getProject(apiUrl, token, projectId) {
|
|
|
15369
15655
|
}
|
|
15370
15656
|
async function listProjects(apiUrl, token) {
|
|
15371
15657
|
const res = await fetch(`${apiUrl}/projects`, {
|
|
15372
|
-
headers: {
|
|
15658
|
+
headers: { ...authHeaders(token) }
|
|
15373
15659
|
});
|
|
15374
15660
|
if (!res.ok) {
|
|
15375
15661
|
throw new ApiError(`Listing projects failed (${res.status})`, await parseEnvelope(res), res.status);
|
|
@@ -15379,7 +15665,7 @@ async function listProjects(apiUrl, token) {
|
|
|
15379
15665
|
async function deleteProject(apiUrl, token, projectId) {
|
|
15380
15666
|
const res = await fetch(`${apiUrl}/projects/${encodeURIComponent(projectId)}`, {
|
|
15381
15667
|
method: "DELETE",
|
|
15382
|
-
headers: {
|
|
15668
|
+
headers: { ...authHeaders(token) }
|
|
15383
15669
|
});
|
|
15384
15670
|
if (!res.ok) {
|
|
15385
15671
|
throw new ApiError(`Project delete failed (${res.status})`, await parseEnvelope(res), res.status);
|
|
@@ -15412,7 +15698,7 @@ async function pollProjectDeleted(read, opts) {
|
|
|
15412
15698
|
async function detachStorage(apiUrl, token, projectId) {
|
|
15413
15699
|
const res = await fetch(`${apiUrl}/storage/${encodeURIComponent(projectId)}`, {
|
|
15414
15700
|
method: "DELETE",
|
|
15415
|
-
headers: {
|
|
15701
|
+
headers: { ...authHeaders(token) }
|
|
15416
15702
|
});
|
|
15417
15703
|
if (!res.ok) {
|
|
15418
15704
|
throw new ApiError(
|
|
@@ -15425,7 +15711,7 @@ async function detachStorage(apiUrl, token, projectId) {
|
|
|
15425
15711
|
async function listEnvVars(apiUrl, token, projectId, include = "all") {
|
|
15426
15712
|
const res = await fetch(
|
|
15427
15713
|
`${apiUrl}/projects/${encodeURIComponent(projectId)}/env?scope=${encodeURIComponent(include)}`,
|
|
15428
|
-
{ headers: {
|
|
15714
|
+
{ headers: { ...authHeaders(token) } }
|
|
15429
15715
|
);
|
|
15430
15716
|
if (!res.ok) {
|
|
15431
15717
|
throw new ApiError(
|
|
@@ -15478,7 +15764,7 @@ async function pullEnvValues(apiUrl, token, projectId, environment = "production
|
|
|
15478
15764
|
const query = environment === "production" ? "" : `?environment=${encodeURIComponent(environment)}`;
|
|
15479
15765
|
const res = await fetch(
|
|
15480
15766
|
`${apiUrl}/projects/${encodeURIComponent(projectId)}/env/values${query}`,
|
|
15481
|
-
{ headers: {
|
|
15767
|
+
{ headers: { ...authHeaders(token) } }
|
|
15482
15768
|
);
|
|
15483
15769
|
if (!res.ok) {
|
|
15484
15770
|
throw new ApiError(
|
|
@@ -15512,7 +15798,7 @@ async function setEnvVar(apiUrl, token, projectId, key, value, scope = "runtime"
|
|
|
15512
15798
|
method: "PUT",
|
|
15513
15799
|
headers: {
|
|
15514
15800
|
"content-type": "application/json",
|
|
15515
|
-
|
|
15801
|
+
...authHeaders(token)
|
|
15516
15802
|
},
|
|
15517
15803
|
// `scope`/`environment` are sent only when NOT their defaults. An api older than KRYD-156/159
|
|
15518
15804
|
// ignores unknown body fields, so this is belt-and-braces — but it keeps a plain runtime
|
|
@@ -15553,7 +15839,7 @@ async function removeEnvVar(apiUrl, token, projectId, key, scope = "runtime", en
|
|
|
15553
15839
|
const query = params.toString() ? `?${params.toString()}` : "";
|
|
15554
15840
|
const res = await fetch(
|
|
15555
15841
|
`${apiUrl}/projects/${encodeURIComponent(projectId)}/env/${encodeURIComponent(key)}${query}`,
|
|
15556
|
-
{ method: "DELETE", headers: {
|
|
15842
|
+
{ method: "DELETE", headers: { ...authHeaders(token) } }
|
|
15557
15843
|
);
|
|
15558
15844
|
if (!res.ok) {
|
|
15559
15845
|
throw new ApiError(
|
|
@@ -15579,7 +15865,7 @@ async function triggerDeploy(apiUrl, token, projectId) {
|
|
|
15579
15865
|
method: "POST",
|
|
15580
15866
|
headers: {
|
|
15581
15867
|
"content-type": "application/json",
|
|
15582
|
-
|
|
15868
|
+
...authHeaders(token)
|
|
15583
15869
|
},
|
|
15584
15870
|
body: JSON.stringify({ projectId })
|
|
15585
15871
|
});
|
|
@@ -15597,7 +15883,7 @@ async function rollbackDeploy(apiUrl, token, projectId, deploymentId, app) {
|
|
|
15597
15883
|
method: "POST",
|
|
15598
15884
|
headers: {
|
|
15599
15885
|
"content-type": "application/json",
|
|
15600
|
-
|
|
15886
|
+
...authHeaders(token)
|
|
15601
15887
|
},
|
|
15602
15888
|
body: JSON.stringify({
|
|
15603
15889
|
projectId,
|
|
@@ -15615,7 +15901,7 @@ async function redeployProject(apiUrl, token, projectId, app) {
|
|
|
15615
15901
|
method: "POST",
|
|
15616
15902
|
headers: {
|
|
15617
15903
|
"content-type": "application/json",
|
|
15618
|
-
|
|
15904
|
+
...authHeaders(token)
|
|
15619
15905
|
},
|
|
15620
15906
|
body: JSON.stringify({ projectId, ...app ? { app } : {} })
|
|
15621
15907
|
});
|
|
@@ -15627,7 +15913,7 @@ async function redeployProject(apiUrl, token, projectId, app) {
|
|
|
15627
15913
|
async function listDeployments(apiUrl, token, projectId) {
|
|
15628
15914
|
const url2 = new URL(`${apiUrl}/deployments`);
|
|
15629
15915
|
if (projectId) url2.searchParams.set("projectId", projectId);
|
|
15630
|
-
const res = await fetch(url2, { headers: {
|
|
15916
|
+
const res = await fetch(url2, { headers: { ...authHeaders(token) } });
|
|
15631
15917
|
if (!res.ok) {
|
|
15632
15918
|
throw new ApiError(`Listing deploys failed (${res.status})`, await parseEnvelope(res), res.status);
|
|
15633
15919
|
}
|
|
@@ -15731,7 +16017,7 @@ async function streamDeploy(apiUrl, token, deploymentId, handlers, opts = {}) {
|
|
|
15731
16017
|
let dropped = false;
|
|
15732
16018
|
try {
|
|
15733
16019
|
const res = await doFetch(`${apiUrl}/deployments/${deploymentId}/logs`, {
|
|
15734
|
-
headers: {
|
|
16020
|
+
headers: { ...authHeaders(token), accept: "text/event-stream" }
|
|
15735
16021
|
});
|
|
15736
16022
|
if (!res.ok) {
|
|
15737
16023
|
throw new ApiError(`Log stream failed (${res.status})`, await parseEnvelope(res), res.status);
|
|
@@ -15773,7 +16059,7 @@ async function streamRuntime(apiUrl, token, id, handlers, opts = {}) {
|
|
|
15773
16059
|
if (afterNs) url2.searchParams.set("after", afterNs);
|
|
15774
16060
|
else if (opts.since) url2.searchParams.set("since", opts.since);
|
|
15775
16061
|
const res = await doFetch(url2, {
|
|
15776
|
-
headers: {
|
|
16062
|
+
headers: { ...authHeaders(token), accept: "text/event-stream" },
|
|
15777
16063
|
...opts.signal ? { signal: opts.signal } : {}
|
|
15778
16064
|
});
|
|
15779
16065
|
if (!res.ok) {
|
|
@@ -15803,7 +16089,7 @@ async function streamRuntime(apiUrl, token, id, handlers, opts = {}) {
|
|
|
15803
16089
|
async function fetchDeployLog(apiUrl, token, deploymentId, opts = {}) {
|
|
15804
16090
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
15805
16091
|
const res = await doFetch(`${apiUrl}/deployments/${deploymentId}/log`, {
|
|
15806
|
-
headers: {
|
|
16092
|
+
headers: { ...authHeaders(token) }
|
|
15807
16093
|
});
|
|
15808
16094
|
if (res.status === 404) return null;
|
|
15809
16095
|
if (!res.ok) {
|
|
@@ -15818,7 +16104,7 @@ async function fetchDeployLog(apiUrl, token, deploymentId, opts = {}) {
|
|
|
15818
16104
|
async function fetchPushCredential(apiUrl, token, projectId) {
|
|
15819
16105
|
const res = await fetch(
|
|
15820
16106
|
`${apiUrl}/repos/${encodeURIComponent(projectId)}/push-credential`,
|
|
15821
|
-
{ headers: {
|
|
16107
|
+
{ headers: { ...authHeaders(token) } }
|
|
15822
16108
|
);
|
|
15823
16109
|
if (!res.ok) {
|
|
15824
16110
|
throw new ApiError(
|
|
@@ -15847,7 +16133,7 @@ async function supportFetch(apiUrl, token, path, what, init = {}) {
|
|
|
15847
16133
|
const res = await fetch(`${apiUrl}${path}`, {
|
|
15848
16134
|
method: init.method ?? "GET",
|
|
15849
16135
|
headers: {
|
|
15850
|
-
|
|
16136
|
+
...authHeaders(token),
|
|
15851
16137
|
...init.body !== void 0 ? { "content-type": "application/json" } : {}
|
|
15852
16138
|
},
|
|
15853
16139
|
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {}
|
|
@@ -15897,204 +16183,25 @@ async function replySupportTicket(apiUrl, token, ref, text) {
|
|
|
15897
16183
|
return body;
|
|
15898
16184
|
}
|
|
15899
16185
|
|
|
15900
|
-
// src/
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
15910
|
-
import { homedir } from "node:os";
|
|
15911
|
-
import { dirname, join } from "node:path";
|
|
15912
|
-
var DEFAULT_API_URL = "http://localhost:3000";
|
|
15913
|
-
function configPath() {
|
|
15914
|
-
const dir = process.env.KRYD_CONFIG_DIR ?? join(homedir(), ".kryd");
|
|
15915
|
-
return join(dir, "config.json");
|
|
16186
|
+
// src/support.ts
|
|
16187
|
+
var SUPPORT_TYPES = TICKET_TYPES;
|
|
16188
|
+
var MESSAGE_MAX = 2e4;
|
|
16189
|
+
var SUBJECT_MAX = 200;
|
|
16190
|
+
var LOG_TAIL_LINES = 40;
|
|
16191
|
+
var CONTEXT_SEPARATOR = "\n\n---\n";
|
|
16192
|
+
var URL_CREDENTIAL = /\b([a-z][a-z0-9+.-]*:\/\/)([^\s:@/]+):([^\s@/]+)@/gi;
|
|
16193
|
+
var SECRET_ASSIGNMENT = /\b([A-Za-z0-9_.-]*(?:TOKEN|SECRET|PASSWORD|PASSWD|KEY|DSN|DATABASE_URL|CREDENTIAL)[A-Za-z0-9_.-]*)(\s*[=:]\s*)(["']?)([^\s"']+)/gi;
|
|
16194
|
+
function maskSecrets(line) {
|
|
16195
|
+
return line.replace(URL_CREDENTIAL, "$1$2:***@").replace(SECRET_ASSIGNMENT, "$1$2$3***");
|
|
15916
16196
|
}
|
|
15917
|
-
function
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
15921
|
-
} catch {
|
|
15922
|
-
return {};
|
|
15923
|
-
}
|
|
15924
|
-
try {
|
|
15925
|
-
return JSON.parse(raw);
|
|
15926
|
-
} catch {
|
|
15927
|
-
process.stderr.write(
|
|
15928
|
-
`warning: ${configPath()} is not valid JSON \u2014 ignoring it.
|
|
15929
|
-
`
|
|
15930
|
-
);
|
|
15931
|
-
return {};
|
|
15932
|
-
}
|
|
16197
|
+
function logTail(log, n = LOG_TAIL_LINES) {
|
|
16198
|
+
const lines = log.split(/\r?\n/);
|
|
16199
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "") lines.pop();
|
|
16200
|
+
return lines.slice(-n).map(maskSecrets);
|
|
15933
16201
|
}
|
|
15934
|
-
function
|
|
15935
|
-
const
|
|
15936
|
-
return
|
|
15937
|
-
}
|
|
15938
|
-
function writeConfig(config2) {
|
|
15939
|
-
const path = configPath();
|
|
15940
|
-
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
15941
|
-
writeFileSync(path, JSON.stringify(config2, null, 2) + "\n", { mode: 384 });
|
|
15942
|
-
chmodSync(path, 384);
|
|
15943
|
-
}
|
|
15944
|
-
function saveToken(token, apiUrl) {
|
|
15945
|
-
const config2 = loadConfig();
|
|
15946
|
-
config2.token = token;
|
|
15947
|
-
if (apiUrl) config2.apiUrl = apiUrl;
|
|
15948
|
-
writeConfig(config2);
|
|
15949
|
-
}
|
|
15950
|
-
function clearToken() {
|
|
15951
|
-
const config2 = loadConfig();
|
|
15952
|
-
delete config2.token;
|
|
15953
|
-
writeConfig(config2);
|
|
15954
|
-
}
|
|
15955
|
-
var lastApiUrl = null;
|
|
15956
|
-
function resolveApiUrl(flag) {
|
|
15957
|
-
lastApiUrl = flag ?? process.env.KRYD_API_URL ?? loadConfig().apiUrl ?? DEFAULT_API_URL;
|
|
15958
|
-
return lastApiUrl;
|
|
15959
|
-
}
|
|
15960
|
-
function currentApiUrl() {
|
|
15961
|
-
return lastApiUrl ?? resolveApiUrl();
|
|
15962
|
-
}
|
|
15963
|
-
var DEFAULT_DASHBOARD_URL = "https://app.kryd.eu";
|
|
15964
|
-
function resolveDashboardUrl(flag) {
|
|
15965
|
-
return flag ?? process.env.KRYD_DASHBOARD_URL ?? DEFAULT_DASHBOARD_URL;
|
|
15966
|
-
}
|
|
15967
|
-
var PROJECT_LINK_DIR = ".kryd";
|
|
15968
|
-
var PROJECT_LINK_FILE = "project.json";
|
|
15969
|
-
function findProjectLinkDir(cwd = process.cwd()) {
|
|
15970
|
-
let dir = cwd;
|
|
15971
|
-
for (; ; ) {
|
|
15972
|
-
if (existsSync(join(dir, PROJECT_LINK_DIR, PROJECT_LINK_FILE))) return dir;
|
|
15973
|
-
if (existsSync(join(dir, ".git"))) return null;
|
|
15974
|
-
const parent = dirname(dir);
|
|
15975
|
-
if (parent === dir) return null;
|
|
15976
|
-
dir = parent;
|
|
15977
|
-
}
|
|
15978
|
-
}
|
|
15979
|
-
function loadProjectLink(cwd = process.cwd()) {
|
|
15980
|
-
const root = findProjectLinkDir(cwd);
|
|
15981
|
-
if (!root) return null;
|
|
15982
|
-
const path = join(root, PROJECT_LINK_DIR, PROJECT_LINK_FILE);
|
|
15983
|
-
let raw;
|
|
15984
|
-
try {
|
|
15985
|
-
raw = readFileSync(path, "utf8");
|
|
15986
|
-
} catch {
|
|
15987
|
-
return null;
|
|
15988
|
-
}
|
|
15989
|
-
let parsed;
|
|
15990
|
-
try {
|
|
15991
|
-
parsed = JSON.parse(raw);
|
|
15992
|
-
} catch {
|
|
15993
|
-
process.stderr.write(`warning: ${path} is not valid JSON \u2014 ignoring it.
|
|
15994
|
-
`);
|
|
15995
|
-
return null;
|
|
15996
|
-
}
|
|
15997
|
-
if (!parsed || typeof parsed.projectId !== "string" || parsed.projectId.length === 0) {
|
|
15998
|
-
process.stderr.write(`warning: ${path} has no projectId \u2014 ignoring it.
|
|
15999
|
-
`);
|
|
16000
|
-
return null;
|
|
16001
|
-
}
|
|
16002
|
-
return parsed;
|
|
16003
|
-
}
|
|
16004
|
-
function saveProjectLink(cwd, link) {
|
|
16005
|
-
const dir = join(cwd, PROJECT_LINK_DIR);
|
|
16006
|
-
mkdirSync(dir, { recursive: true });
|
|
16007
|
-
const path = join(dir, PROJECT_LINK_FILE);
|
|
16008
|
-
writeFileSync(path, JSON.stringify(link, null, 2) + "\n");
|
|
16009
|
-
return path;
|
|
16010
|
-
}
|
|
16011
|
-
function ensureIgnored(cwd, entry, aliases = []) {
|
|
16012
|
-
const path = join(cwd, ".gitignore");
|
|
16013
|
-
let current = "";
|
|
16014
|
-
try {
|
|
16015
|
-
current = readFileSync(path, "utf8");
|
|
16016
|
-
} catch {
|
|
16017
|
-
}
|
|
16018
|
-
const present = current.split("\n").map((l) => l.trim()).some((l) => l === entry || aliases.includes(l));
|
|
16019
|
-
if (present) return false;
|
|
16020
|
-
const prefix = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
|
|
16021
|
-
appendFileSync(path, `${prefix}${entry}
|
|
16022
|
-
`);
|
|
16023
|
-
return true;
|
|
16024
|
-
}
|
|
16025
|
-
function ensureKrydIgnored(cwd) {
|
|
16026
|
-
ensureIgnored(cwd, `${PROJECT_LINK_DIR}/`, [PROJECT_LINK_DIR]);
|
|
16027
|
-
}
|
|
16028
|
-
function directoryIsLinked(cwd) {
|
|
16029
|
-
const path = join(cwd, PROJECT_LINK_DIR, PROJECT_LINK_FILE);
|
|
16030
|
-
if (!existsSync(path)) return null;
|
|
16031
|
-
return loadProjectLink(cwd);
|
|
16032
|
-
}
|
|
16033
|
-
function writeSecretFile(path, contents) {
|
|
16034
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
16035
|
-
writeFileSync(path, contents, { mode: 384 });
|
|
16036
|
-
chmodSync(path, 384);
|
|
16037
|
-
}
|
|
16038
|
-
function clearProjectLinkIfMatches(projectId, cwd = process.cwd()) {
|
|
16039
|
-
const root = findProjectLinkDir(cwd);
|
|
16040
|
-
if (!root) return false;
|
|
16041
|
-
const link = loadProjectLink(cwd);
|
|
16042
|
-
if (link?.projectId !== projectId) return false;
|
|
16043
|
-
try {
|
|
16044
|
-
rmSync(join(root, PROJECT_LINK_DIR, PROJECT_LINK_FILE));
|
|
16045
|
-
return true;
|
|
16046
|
-
} catch {
|
|
16047
|
-
return false;
|
|
16048
|
-
}
|
|
16049
|
-
}
|
|
16050
|
-
function resolvedLink() {
|
|
16051
|
-
return lastLinkResolution;
|
|
16052
|
-
}
|
|
16053
|
-
var lastLinkResolution = null;
|
|
16054
|
-
function resolveProjectId(explicit, cwd = process.cwd()) {
|
|
16055
|
-
lastLinkResolution = null;
|
|
16056
|
-
if (explicit) return explicit;
|
|
16057
|
-
const root = findProjectLinkDir(cwd);
|
|
16058
|
-
if (!root) return null;
|
|
16059
|
-
const link = loadProjectLink(cwd);
|
|
16060
|
-
if (!link) return null;
|
|
16061
|
-
lastLinkResolution = { root, link };
|
|
16062
|
-
return link.projectId;
|
|
16063
|
-
}
|
|
16064
|
-
function rememberLinkAccount(root, account) {
|
|
16065
|
-
if (!account.accountId) return false;
|
|
16066
|
-
const link = directoryIsLinked(root);
|
|
16067
|
-
if (!link) return false;
|
|
16068
|
-
const slug = account.accountSlug ?? link.accountSlug;
|
|
16069
|
-
if (link.accountId === account.accountId && link.accountSlug === slug) return false;
|
|
16070
|
-
saveProjectLink(root, {
|
|
16071
|
-
...link,
|
|
16072
|
-
accountId: account.accountId,
|
|
16073
|
-
// `exactOptionalPropertyTypes`: an absent key and an explicit `undefined` are different types.
|
|
16074
|
-
...slug ? { accountSlug: slug } : {}
|
|
16075
|
-
});
|
|
16076
|
-
return true;
|
|
16077
|
-
}
|
|
16078
|
-
|
|
16079
|
-
// src/support.ts
|
|
16080
|
-
var SUPPORT_TYPES = TICKET_TYPES;
|
|
16081
|
-
var MESSAGE_MAX = 2e4;
|
|
16082
|
-
var SUBJECT_MAX = 200;
|
|
16083
|
-
var LOG_TAIL_LINES = 40;
|
|
16084
|
-
var CONTEXT_SEPARATOR = "\n\n---\n";
|
|
16085
|
-
var URL_CREDENTIAL = /\b([a-z][a-z0-9+.-]*:\/\/)([^\s:@/]+):([^\s@/]+)@/gi;
|
|
16086
|
-
var SECRET_ASSIGNMENT = /\b([A-Za-z0-9_.-]*(?:TOKEN|SECRET|PASSWORD|PASSWD|KEY|DSN|DATABASE_URL|CREDENTIAL)[A-Za-z0-9_.-]*)(\s*[=:]\s*)(["']?)([^\s"']+)/gi;
|
|
16087
|
-
function maskSecrets(line) {
|
|
16088
|
-
return line.replace(URL_CREDENTIAL, "$1$2:***@").replace(SECRET_ASSIGNMENT, "$1$2$3***");
|
|
16089
|
-
}
|
|
16090
|
-
function logTail(log, n = LOG_TAIL_LINES) {
|
|
16091
|
-
const lines = log.split(/\r?\n/);
|
|
16092
|
-
while (lines.length > 0 && lines[lines.length - 1].trim() === "") lines.pop();
|
|
16093
|
-
return lines.slice(-n).map(maskSecrets);
|
|
16094
|
-
}
|
|
16095
|
-
function fenceFor(lines) {
|
|
16096
|
-
const longest = Math.max(0, ...lines.map((l) => Math.max(0, ...(l.match(/`+/g) ?? []).map((r) => r.length))));
|
|
16097
|
-
return "`".repeat(Math.max(3, longest + 1));
|
|
16202
|
+
function fenceFor(lines) {
|
|
16203
|
+
const longest = Math.max(0, ...lines.map((l) => Math.max(0, ...(l.match(/`+/g) ?? []).map((r) => r.length))));
|
|
16204
|
+
return "`".repeat(Math.max(3, longest + 1));
|
|
16098
16205
|
}
|
|
16099
16206
|
function deployLine(deploy) {
|
|
16100
16207
|
if (deploy === "unreadable") return "Last production deploy: could not be read";
|
|
@@ -16312,6 +16419,110 @@ async function runSupportReply(raw, opts) {
|
|
|
16312
16419
|
}
|
|
16313
16420
|
}
|
|
16314
16421
|
|
|
16422
|
+
// src/account.ts
|
|
16423
|
+
function fail(message) {
|
|
16424
|
+
process.stderr.write(`${message}
|
|
16425
|
+
`);
|
|
16426
|
+
process.exitCode = 1;
|
|
16427
|
+
}
|
|
16428
|
+
function membershipLabel(m) {
|
|
16429
|
+
return m.slug ?? m.name ?? m.accountId;
|
|
16430
|
+
}
|
|
16431
|
+
function accountHint(err) {
|
|
16432
|
+
if (!(err instanceof ApiError) || !err.envelope) return "";
|
|
16433
|
+
const { code, details } = err.envelope.error;
|
|
16434
|
+
if (code === ERROR_CODES.accountRequired) {
|
|
16435
|
+
return "You belong to several accounts. Choose one with `kryd account use <slug>` (`kryd account list` shows them).\n";
|
|
16436
|
+
}
|
|
16437
|
+
if (code === ERROR_CODES.permissionRequired) {
|
|
16438
|
+
const permission = typeof details?.permission === "string" ? ` \`${details.permission}\`` : " this permission";
|
|
16439
|
+
const slug = loadConfig().accountSlug;
|
|
16440
|
+
return `Your role in ${slug ? `account ${slug}` : "this account"} lacks${permission}. Ask the account's owner, or switch with \`kryd account use <slug>\`.
|
|
16441
|
+
`;
|
|
16442
|
+
}
|
|
16443
|
+
const stored = loadConfig();
|
|
16444
|
+
if (code === ERROR_CODES.forbidden && stored.account && /not a member/i.test(err.envelope.error.message)) {
|
|
16445
|
+
const name = stored.accountSlug ?? stored.account;
|
|
16446
|
+
return `You are no longer a member of account ${name}, which this machine has chosen. See your accounts with \`kryd account list\`, then pick one with \`kryd account use <slug>\`.
|
|
16447
|
+
`;
|
|
16448
|
+
}
|
|
16449
|
+
return "";
|
|
16450
|
+
}
|
|
16451
|
+
async function runAccountList(opts = {}) {
|
|
16452
|
+
const token = resolveToken();
|
|
16453
|
+
if (!token) return fail("Not logged in \u2014 run `kryd login` first.");
|
|
16454
|
+
let memberships;
|
|
16455
|
+
let current;
|
|
16456
|
+
try {
|
|
16457
|
+
const me = await whoami(resolveApiUrl(opts.apiUrl), token);
|
|
16458
|
+
memberships = me.memberships ?? [];
|
|
16459
|
+
current = me.accountId;
|
|
16460
|
+
} catch (err) {
|
|
16461
|
+
return fail(err instanceof ApiError && err.envelope ? err.envelope.error.message : String(err));
|
|
16462
|
+
}
|
|
16463
|
+
if (opts.json) {
|
|
16464
|
+
process.stdout.write(`${JSON.stringify(memberships.map((m) => ({ ...m, current: m.accountId === current })), null, 2)}
|
|
16465
|
+
`);
|
|
16466
|
+
return;
|
|
16467
|
+
}
|
|
16468
|
+
if (memberships.length === 0) {
|
|
16469
|
+
process.stdout.write("You do not belong to any account.\n");
|
|
16470
|
+
return;
|
|
16471
|
+
}
|
|
16472
|
+
const rows = [
|
|
16473
|
+
["", "SLUG", "NAME", "ROLE", "ID"],
|
|
16474
|
+
...memberships.map((m) => [m.accountId === current ? "*" : "", m.slug ?? "\u2014", m.name, m.role, m.accountId])
|
|
16475
|
+
];
|
|
16476
|
+
const widths = rows[0].map((_, i) => Math.max(...rows.map((r) => r[i].length)));
|
|
16477
|
+
for (const r of rows) {
|
|
16478
|
+
process.stdout.write(`${r.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd()}
|
|
16479
|
+
`);
|
|
16480
|
+
}
|
|
16481
|
+
}
|
|
16482
|
+
async function runAccountUse(target, opts = {}) {
|
|
16483
|
+
const token = resolveToken();
|
|
16484
|
+
if (!token) return fail("Not logged in \u2014 run `kryd login` first.");
|
|
16485
|
+
if (token.startsWith("kryd_")) {
|
|
16486
|
+
return fail("A deploy token is bound to one account and cannot switch. Unset KRYD_TOKEN to use your login.");
|
|
16487
|
+
}
|
|
16488
|
+
let memberships;
|
|
16489
|
+
try {
|
|
16490
|
+
memberships = (await whoami(resolveApiUrl(opts.apiUrl), token)).memberships ?? [];
|
|
16491
|
+
} catch (err) {
|
|
16492
|
+
return fail(err instanceof ApiError && err.envelope ? err.envelope.error.message : String(err));
|
|
16493
|
+
}
|
|
16494
|
+
const wanted = target.trim();
|
|
16495
|
+
const match = memberships.find((m) => m.slug === wanted || m.accountId === wanted);
|
|
16496
|
+
if (!match) {
|
|
16497
|
+
const known = memberships.map(membershipLabel).join(", ") || "none";
|
|
16498
|
+
return fail(`You are not a member of an account "${wanted}". Your accounts: ${known}.`);
|
|
16499
|
+
}
|
|
16500
|
+
saveAccount({ accountId: match.accountId, slug: match.slug });
|
|
16501
|
+
process.stdout.write(`Now using account ${membershipLabel(match)} (${match.role}).
|
|
16502
|
+
`);
|
|
16503
|
+
}
|
|
16504
|
+
async function settleAccountAfterLogin(apiUrl, token) {
|
|
16505
|
+
if (token.startsWith("kryd_")) return;
|
|
16506
|
+
let memberships;
|
|
16507
|
+
try {
|
|
16508
|
+
memberships = (await whoami(apiUrl, token)).memberships ?? [];
|
|
16509
|
+
} catch {
|
|
16510
|
+
return;
|
|
16511
|
+
}
|
|
16512
|
+
const stored = loadConfig().account;
|
|
16513
|
+
if (stored && memberships.some((m) => m.accountId === stored)) return;
|
|
16514
|
+
if (memberships.length <= 1) {
|
|
16515
|
+
if (stored) clearAccount();
|
|
16516
|
+
return;
|
|
16517
|
+
}
|
|
16518
|
+
const pick2 = memberships.find((m) => m.role === "owner") ?? memberships[0];
|
|
16519
|
+
saveAccount({ accountId: pick2.accountId, slug: pick2.slug });
|
|
16520
|
+
process.stdout.write(
|
|
16521
|
+
`You belong to ${memberships.length} accounts; using ${membershipLabel(pick2)}. Switch with \`kryd account use <slug>\`.
|
|
16522
|
+
`
|
|
16523
|
+
);
|
|
16524
|
+
}
|
|
16525
|
+
|
|
16315
16526
|
// src/tokens.ts
|
|
16316
16527
|
var TOKEN_EXPIRY_CHOICES = ["30", "90", "365", "never"];
|
|
16317
16528
|
var DEFAULT_TOKEN_EXPIRY = "90";
|
|
@@ -16325,7 +16536,7 @@ async function tokenFetch(apiUrl, token, path, what, body) {
|
|
|
16325
16536
|
const res = await fetch(`${apiUrl}/api/auth/api-key/${path}`, {
|
|
16326
16537
|
method: body === void 0 ? "GET" : "POST",
|
|
16327
16538
|
headers: {
|
|
16328
|
-
|
|
16539
|
+
...authHeaders(token),
|
|
16329
16540
|
...body !== void 0 ? { "content-type": "application/json" } : {}
|
|
16330
16541
|
},
|
|
16331
16542
|
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
@@ -16363,50 +16574,53 @@ async function listDeployTokens(apiUrl, token) {
|
|
|
16363
16574
|
async function revokeDeployToken(apiUrl, token, id) {
|
|
16364
16575
|
await tokenFetch(apiUrl, token, "delete", "Revoking the token", { keyId: id });
|
|
16365
16576
|
}
|
|
16366
|
-
function
|
|
16577
|
+
function fail2(message) {
|
|
16367
16578
|
process.stderr.write(`${message}
|
|
16368
16579
|
`);
|
|
16369
16580
|
process.exitCode = 1;
|
|
16370
16581
|
}
|
|
16371
16582
|
function reportTokenError(err) {
|
|
16372
16583
|
if (err instanceof ApiError && err.status === 401) {
|
|
16373
|
-
|
|
16584
|
+
fail2("Not logged in, or the session expired \u2014 run `kryd login`.");
|
|
16374
16585
|
} else if (err instanceof ApiError && err.status === 403 && resolveToken()?.startsWith("kryd_")) {
|
|
16375
|
-
|
|
16586
|
+
fail2(
|
|
16376
16587
|
"A deploy token cannot manage tokens. Sign in with `kryd login` (and unset KRYD_TOKEN for this command)."
|
|
16377
16588
|
);
|
|
16378
16589
|
} else if (err instanceof ApiError && err.status === 404) {
|
|
16379
|
-
|
|
16590
|
+
fail2("No such token on your account. `kryd token list` shows their ids.");
|
|
16380
16591
|
} else if (err instanceof ApiError && err.envelope) {
|
|
16381
|
-
|
|
16592
|
+
fail2(err.envelope.error.message);
|
|
16593
|
+
process.stderr.write(accountHint(err));
|
|
16382
16594
|
} else {
|
|
16383
|
-
|
|
16595
|
+
fail2(err instanceof Error ? err.message : String(err));
|
|
16384
16596
|
}
|
|
16385
16597
|
}
|
|
16386
16598
|
function requireToken2() {
|
|
16387
16599
|
const token = resolveToken();
|
|
16388
|
-
if (!token)
|
|
16600
|
+
if (!token) fail2("Not logged in \u2014 run `kryd login` first.");
|
|
16389
16601
|
return token ?? null;
|
|
16390
16602
|
}
|
|
16391
16603
|
var day = (iso) => iso ? iso.slice(0, 10) : "\u2014";
|
|
16392
16604
|
async function runTokenCreate(name, opts) {
|
|
16393
16605
|
const trimmed = name.trim();
|
|
16394
16606
|
if (trimmed.length === 0 || trimmed.length > MAX_TOKEN_NAME) {
|
|
16395
|
-
return
|
|
16607
|
+
return fail2(`A token name is 1 to ${MAX_TOKEN_NAME} characters.`);
|
|
16396
16608
|
}
|
|
16397
16609
|
const choice = opts.expires ?? DEFAULT_TOKEN_EXPIRY;
|
|
16398
16610
|
const expiresIn = expiresInSeconds(choice);
|
|
16399
16611
|
if (expiresIn === void 0) {
|
|
16400
|
-
return
|
|
16612
|
+
return fail2(`--expires is one of ${TOKEN_EXPIRY_CHOICES.join(", ")} (days, or never).`);
|
|
16401
16613
|
}
|
|
16402
16614
|
const token = requireToken2();
|
|
16403
16615
|
if (!token) return;
|
|
16404
16616
|
try {
|
|
16405
|
-
const
|
|
16617
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
16618
|
+
const created = await createDeployToken(apiUrl, token, { name: trimmed, expiresIn });
|
|
16406
16619
|
process.stdout.write(`${created.key}
|
|
16407
16620
|
`);
|
|
16621
|
+
const account = await boundAccountLabel(apiUrl, token, created);
|
|
16408
16622
|
process.stderr.write(
|
|
16409
|
-
`Created deploy token "${trimmed}" (${created.id}), ${created.expiresAt ? `valid until ${day(created.expiresAt)}` : "without expiry"}.
|
|
16623
|
+
`Created deploy token "${trimmed}" (${created.id})${account ? ` for account ${account}` : ""}, ${created.expiresAt ? `valid until ${day(created.expiresAt)}` : "without expiry"}.
|
|
16410
16624
|
This token will not be shown again. Store it now, e.g. as the CI secret KRYD_TOKEN.
|
|
16411
16625
|
`
|
|
16412
16626
|
);
|
|
@@ -16414,6 +16628,17 @@ This token will not be shown again. Store it now, e.g. as the CI secret KRYD_TOK
|
|
|
16414
16628
|
reportTokenError(err);
|
|
16415
16629
|
}
|
|
16416
16630
|
}
|
|
16631
|
+
async function boundAccountLabel(apiUrl, token, created) {
|
|
16632
|
+
const id = typeof created.metadata?.accountId === "string" ? created.metadata.accountId : null;
|
|
16633
|
+
if (!id) return null;
|
|
16634
|
+
try {
|
|
16635
|
+
const me = await whoami(apiUrl, token);
|
|
16636
|
+
const m = me.memberships?.find((x) => x.accountId === id);
|
|
16637
|
+
return m?.slug ?? id;
|
|
16638
|
+
} catch {
|
|
16639
|
+
return id;
|
|
16640
|
+
}
|
|
16641
|
+
}
|
|
16417
16642
|
async function runTokenList(opts) {
|
|
16418
16643
|
const token = requireToken2();
|
|
16419
16644
|
if (!token) return;
|
|
@@ -17740,6 +17965,7 @@ async function reportError(err) {
|
|
|
17740
17965
|
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
17741
17966
|
`);
|
|
17742
17967
|
}
|
|
17968
|
+
process.stderr.write(accountHint(err));
|
|
17743
17969
|
process.exitCode = 1;
|
|
17744
17970
|
if (err instanceof ApiError) {
|
|
17745
17971
|
process.stderr.write(await explainMissingProject(err));
|
|
@@ -17815,20 +18041,31 @@ async function runLogin(opts) {
|
|
|
17815
18041
|
warnIfEnvTokenOverrides();
|
|
17816
18042
|
return;
|
|
17817
18043
|
}
|
|
17818
|
-
|
|
18044
|
+
await browserLoginAndSave(apiUrl, opts.dashboardUrl);
|
|
18045
|
+
warnIfEnvTokenOverrides();
|
|
18046
|
+
}
|
|
18047
|
+
async function browserLoginAndSave(apiUrl, dashboardUrl) {
|
|
18048
|
+
const { token, apiUrl: learnedApiUrl } = await browserLogin(resolveDashboardUrl(dashboardUrl));
|
|
17819
18049
|
saveToken(token, learnedApiUrl ?? apiUrl);
|
|
17820
18050
|
process.stdout.write("Logged in.\n");
|
|
17821
|
-
|
|
18051
|
+
await settleAccountAfterLogin(learnedApiUrl ?? apiUrl, token);
|
|
17822
18052
|
}
|
|
17823
18053
|
async function runWhoami(opts = {}) {
|
|
17824
18054
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
17825
18055
|
const token = resolveToken() ?? "";
|
|
17826
18056
|
try {
|
|
17827
18057
|
const me = await whoami(apiUrl, token);
|
|
17828
|
-
|
|
17829
|
-
|
|
18058
|
+
const current = me.memberships?.find((m) => m.accountId === me.accountId);
|
|
18059
|
+
const label = current?.slug ?? me.accountSlug ?? me.accountId ?? "?";
|
|
18060
|
+
process.stdout.write(`${me.email} (account ${label}, role ${current?.role ?? me.role ?? "?"})
|
|
18061
|
+
`);
|
|
18062
|
+
const others = (me.memberships ?? []).filter((m) => m.accountId !== me.accountId);
|
|
18063
|
+
if (others.length > 0) {
|
|
18064
|
+
process.stdout.write(
|
|
18065
|
+
`Also in: ${others.map((m) => `${m.slug ?? m.accountId} (${m.role})`).join(", ")} \u2014 switch with \`kryd account use <slug>\`.
|
|
17830
18066
|
`
|
|
17831
|
-
|
|
18067
|
+
);
|
|
18068
|
+
}
|
|
17832
18069
|
} catch (err) {
|
|
17833
18070
|
await reportError(err);
|
|
17834
18071
|
}
|
|
@@ -17846,13 +18083,46 @@ function runLogout() {
|
|
|
17846
18083
|
warnIfEnvTokenOverrides();
|
|
17847
18084
|
}
|
|
17848
18085
|
async function runInit(opts) {
|
|
17849
|
-
|
|
18086
|
+
let apiUrl = resolveApiUrl(opts.apiUrl);
|
|
17850
18087
|
const cwd = opts.cwd ?? process.cwd();
|
|
17851
|
-
|
|
18088
|
+
let token = resolveToken();
|
|
17852
18089
|
if (!token) {
|
|
17853
|
-
|
|
17854
|
-
|
|
17855
|
-
|
|
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();
|
|
17856
18126
|
}
|
|
17857
18127
|
const existingLink = directoryIsLinked(cwd);
|
|
17858
18128
|
if (existingLink) {
|
|
@@ -18447,6 +18717,17 @@ async function runDbAdd(opts) {
|
|
|
18447
18717
|
process.exitCode = 1;
|
|
18448
18718
|
return;
|
|
18449
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
|
+
}
|
|
18450
18731
|
let mode;
|
|
18451
18732
|
let connectionString;
|
|
18452
18733
|
if (opts.connectionString !== void 0) {
|
|
@@ -18492,6 +18773,46 @@ The connection string will be injected as DATABASE_URL on your next deploy.
|
|
|
18492
18773
|
await reportError(err);
|
|
18493
18774
|
}
|
|
18494
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
|
+
}
|
|
18495
18816
|
function reportSettleResult(result, opts) {
|
|
18496
18817
|
if (result.status === "ready") {
|
|
18497
18818
|
process.stdout.write(opts.readyMsg);
|
|
@@ -18712,6 +19033,8 @@ async function describeProjectResources(apiUrl, token, projectId) {
|
|
|
18712
19033
|
const out = ["its deployed app, its containers and its built images"];
|
|
18713
19034
|
const db2 = await getDatabaseStatus(apiUrl, token, projectId).catch(() => null);
|
|
18714
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");
|
|
18715
19038
|
const storage2 = await getStorageStatus(apiUrl, token, projectId).catch(() => null);
|
|
18716
19039
|
if (storage2) out.push("its object-storage bucket \u2014 and every object in it");
|
|
18717
19040
|
out.push("its Git repository on the forge, and its push token");
|
|
@@ -18730,6 +19053,23 @@ async function runDbRemove(opts) {
|
|
|
18730
19053
|
reportNotLinked("kryd db remove <projectId>");
|
|
18731
19054
|
return;
|
|
18732
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
|
+
}
|
|
18733
19073
|
const proceed = await confirmResourceRemoval({
|
|
18734
19074
|
yes: opts.yes,
|
|
18735
19075
|
io: opts.io,
|
|
@@ -18756,6 +19096,105 @@ async function runDbRemove(opts) {
|
|
|
18756
19096
|
await reportError(err);
|
|
18757
19097
|
}
|
|
18758
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
|
+
}
|
|
18759
19198
|
async function runAiAdd(opts) {
|
|
18760
19199
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
18761
19200
|
const token = resolveToken();
|
|
@@ -18861,12 +19300,45 @@ async function runDbStatus(opts) {
|
|
|
18861
19300
|
reportNotLinked("kryd db status <projectId>");
|
|
18862
19301
|
return;
|
|
18863
19302
|
}
|
|
19303
|
+
let productionAttached = false;
|
|
18864
19304
|
await reportResourceStatus({
|
|
18865
|
-
read: () =>
|
|
19305
|
+
read: async () => {
|
|
19306
|
+
const row = await getDatabaseStatus(apiUrl, token, project2);
|
|
19307
|
+
productionAttached = true;
|
|
19308
|
+
return row;
|
|
19309
|
+
},
|
|
18866
19310
|
noun: "database",
|
|
18867
19311
|
project: project2,
|
|
18868
19312
|
token
|
|
18869
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
|
+
}
|
|
18870
19342
|
}
|
|
18871
19343
|
async function runStorageStatus(opts) {
|
|
18872
19344
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
@@ -19326,6 +19798,7 @@ HATCHET_CLIENT_TOKEN is injected into your container on production deploys (prev
|
|
|
19326
19798
|
process.exitCode = 0;
|
|
19327
19799
|
await runInit({
|
|
19328
19800
|
cwd: target,
|
|
19801
|
+
offerLogin: false,
|
|
19329
19802
|
...opts.name ? { name: opts.name } : {},
|
|
19330
19803
|
...opts.apiUrl ? { apiUrl: opts.apiUrl } : {}
|
|
19331
19804
|
});
|
|
@@ -19956,7 +20429,7 @@ Run \`kryd env list\` to see what is set. (If you meant the runtime variable of
|
|
|
19956
20429
|
await reportError(err);
|
|
19957
20430
|
}
|
|
19958
20431
|
}
|
|
19959
|
-
var CLI_VERSION = true ? "0.
|
|
20432
|
+
var CLI_VERSION = true ? "0.17.0" : "0.0.0-dev";
|
|
19960
20433
|
var program = new Command();
|
|
19961
20434
|
program.name("kryd").description("Kryd CLI").version(CLI_VERSION);
|
|
19962
20435
|
program.hook("postAction", async () => {
|
|
@@ -19967,6 +20440,9 @@ program.command("login").description("Sign in via the browser (default) and stor
|
|
|
19967
20440
|
"supply a token instead of the browser flow (prefer $KRYD_TOKEN \u2014 flags are visible in `ps`)"
|
|
19968
20441
|
).option("--api-url <url>", "control-plane API base URL").option("--dashboard-url <url>", "dashboard base URL for the browser approve page (default app.kryd.eu)").action((opts) => runLogin(opts));
|
|
19969
20442
|
program.command("whoami").description("Show the current account").option("--api-url <url>", "control-plane API base URL").action((opts) => runWhoami(opts));
|
|
20443
|
+
var accountCommand = program.command("account").description("The accounts you belong to, and which one commands run in");
|
|
20444
|
+
accountCommand.command("list").description("List your accounts and your role in each; * marks the one commands run in").option("--json", "print the accounts as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((opts) => runAccountList(opts));
|
|
20445
|
+
accountCommand.command("use <slug>").description("Run every following command in this account").option("--api-url <url>", "control-plane API base URL").action((slug, opts) => runAccountUse(slug, opts));
|
|
19970
20446
|
program.command("logout").description("Clear the stored token").action(() => runLogout());
|
|
19971
20447
|
program.command("git-credential <operation>", { hidden: true }).description("git credential helper (invoked by git, not by you)").option("--api-url <url>", "control-plane API base URL").action(async (operation, opts) => {
|
|
19972
20448
|
const chunks = [];
|
|
@@ -19976,7 +20452,9 @@ program.command("git-credential <operation>", { hidden: true }).description("git
|
|
|
19976
20452
|
...opts.apiUrl ? { apiUrl: opts.apiUrl } : {}
|
|
19977
20453
|
});
|
|
19978
20454
|
});
|
|
19979
|
-
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(
|
|
19980
20458
|
"--framework <framework>",
|
|
19981
20459
|
'react-router | nextjs | vite-spa | node (auto-detected if omitted; `workflow` comes from a committed kryd.json with "web": false)'
|
|
19982
20460
|
).option(
|
|
@@ -20037,9 +20515,13 @@ var db = program.command("db").description("Manage project databases");
|
|
|
20037
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(
|
|
20038
20516
|
"--connection-string <url>",
|
|
20039
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"
|
|
20040
20521
|
).option("--api-url <url>", "control-plane API base URL").action((project2, opts) => runDbAdd({ ...opts, project: project2 }));
|
|
20041
|
-
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 }));
|
|
20042
|
-
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 }));
|
|
20043
20525
|
var storage = program.command("storage").description("Manage project object storage");
|
|
20044
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 }));
|
|
20045
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 }));
|
|
@@ -20105,7 +20587,7 @@ support.command("list").description("Your account's tickets, with their status")
|
|
|
20105
20587
|
support.command("show <ticket>").description("One ticket and its conversation, e.g. `kryd support show T-1042`").option("--json", "print the ticket and its messages as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((ticket, opts) => runSupportShow(ticket, opts));
|
|
20106
20588
|
support.command("reply <ticket>").description("Answer in a ticket's conversation \u2014 the ticket goes back to Kryd support (status open)").option("--message <text>", "your reply (required)").option("--json", "print the updated ticket as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((ticket, opts) => runSupportReply(ticket, opts));
|
|
20107
20589
|
var tokenCommand = program.command("token").description("Deploy tokens for CI and scripts: set one as KRYD_TOKEN and every command uses it, no login needed");
|
|
20108
|
-
tokenCommand.command("create <name>").description("Create a deploy token and print it once \u2014 it acts as you,
|
|
20590
|
+
tokenCommand.command("create <name>").description("Create a deploy token and print it once \u2014 it acts as you, bound to the current account").option("--expires <days>", "30 | 90 | 365 | never", DEFAULT_TOKEN_EXPIRY).option("--api-url <url>", "control-plane API base URL").action((name, opts) => runTokenCreate(name, opts));
|
|
20109
20591
|
tokenCommand.command("list").description("Your deploy tokens: name, first characters, created, last used, expiry \u2014 never the value").option("--json", "print the tokens as JSON on stdout").option("--api-url <url>", "control-plane API base URL").action((opts) => runTokenList(opts));
|
|
20110
20592
|
tokenCommand.command("revoke <id>").description("Revoke a deploy token; anything still using it gets 401").option("--api-url <url>", "control-plane API base URL").action((id, opts) => runTokenRevoke(id, opts));
|
|
20111
20593
|
function invokedDirectly() {
|
|
@@ -20134,6 +20616,7 @@ export {
|
|
|
20134
20616
|
runAiStatus,
|
|
20135
20617
|
runDbAdd,
|
|
20136
20618
|
runDbRemove,
|
|
20619
|
+
runDbReset,
|
|
20137
20620
|
runDbStatus,
|
|
20138
20621
|
runDeploy,
|
|
20139
20622
|
runDeployHookCreate,
|