@mgsoftwarebv/mg-dashboard-mcp 7.0.5 → 7.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1396 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { sql } from 'drizzle-orm';
|
|
|
20
20
|
import { once } from 'events';
|
|
21
21
|
import { lookup } from 'dns/promises';
|
|
22
22
|
import { connect } from 'tls';
|
|
23
|
+
import { pgTable, timestamp, jsonb, uuid, boolean, text, pgEnum, integer, uniqueIndex, index, bigint } from 'drizzle-orm/pg-core';
|
|
23
24
|
import { tasks } from '@trigger.dev/sdk/v3';
|
|
24
25
|
import { HeadObjectCommand, S3Client, ListObjectsV2Command, DeleteObjectsCommand, DeleteObjectCommand, CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, AbortMultipartUploadCommand, PutObjectCommand, GetObjectCommand, CopyObjectCommand } from '@aws-sdk/client-s3';
|
|
25
26
|
|
|
@@ -677,10 +678,10 @@ var TRIGGER_TOOL_MODULE_MAP = {
|
|
|
677
678
|
"trigger-run": "ci_cd"
|
|
678
679
|
};
|
|
679
680
|
async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
|
|
680
|
-
const
|
|
681
|
+
const sql28 = `SELECT re.\\"apiKey\\" FROM \\"RuntimeEnvironment\\" re JOIN \\"Project\\" p ON re.\\"projectId\\" = p.id WHERE p.slug='${projectSlug}' AND re.slug='prod' LIMIT 1`;
|
|
681
682
|
const cmd = [
|
|
682
683
|
`PORT=$(docker port "${WA_CONTAINER}" 3000/tcp 2>/dev/null | head -1 | sed 's/.*://')`,
|
|
683
|
-
`KEY=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
684
|
+
`KEY=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql28}" 2>/dev/null | tr -d '[:space:]')`,
|
|
684
685
|
'echo "$PORT|$KEY"'
|
|
685
686
|
].join(" && ");
|
|
686
687
|
const result = await sshExec2(conn, cmd, proxy);
|
|
@@ -701,8 +702,8 @@ async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
|
|
|
701
702
|
return { port, apiKey: apiKey2 };
|
|
702
703
|
}
|
|
703
704
|
async function fetchRunLogs(runId, conn, proxy, sshExec2) {
|
|
704
|
-
const
|
|
705
|
-
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
705
|
+
const sql28 = `SELECT level, message, \\"isError\\", \\"createdAt\\" FROM \\"TaskEvent\\" WHERE \\"runId\\" = '${runId}' AND level IN ('INFO','WARN','ERROR','DEBUG','LOG','TRACE') ORDER BY \\"startTime\\" ASC LIMIT 200`;
|
|
706
|
+
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql28}" 2>/dev/null`;
|
|
706
707
|
const result = await sshExec2(conn, cmd, proxy);
|
|
707
708
|
const output = result.stdout.trim();
|
|
708
709
|
if (!output) return "";
|
|
@@ -784,8 +785,8 @@ async function handleTriggerTool(name, args2, deps) {
|
|
|
784
785
|
switch (name) {
|
|
785
786
|
// -----------------------------------------------------------------
|
|
786
787
|
case "trigger-list": {
|
|
787
|
-
const
|
|
788
|
-
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
788
|
+
const sql28 = 'SELECT slug, name FROM \\"Project\\" ORDER BY name';
|
|
789
|
+
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql28}" 2>/dev/null`;
|
|
789
790
|
const result = await sshExec2(conn, cmd, proxy);
|
|
790
791
|
const output = result.stdout.trim();
|
|
791
792
|
if (!output) {
|
|
@@ -943,9 +944,9 @@ async function fetchAndFormatRun(conn, proxy, sshExec2, instance, runId) {
|
|
|
943
944
|
return { content: [{ type: "text", text: `Invalid API response:
|
|
944
945
|
${rawJson.substring(0, 500)}` }] };
|
|
945
946
|
}
|
|
946
|
-
let
|
|
947
|
-
if (logs)
|
|
948
|
-
return { content: [{ type: "text", text }] };
|
|
947
|
+
let text6 = formatRunDetail(run);
|
|
948
|
+
if (logs) text6 += "\n\n--- Logs ---\n" + logs;
|
|
949
|
+
return { content: [{ type: "text", text: text6 }] };
|
|
949
950
|
}
|
|
950
951
|
async function waitForCompletion(conn, proxy, sshExec2, instance, runId, waitSeconds) {
|
|
951
952
|
const pollInterval = 3e3;
|
|
@@ -967,10 +968,10 @@ async function waitForCompletion(conn, proxy, sshExec2, instance, runId, waitSec
|
|
|
967
968
|
continue;
|
|
968
969
|
}
|
|
969
970
|
if (TERMINAL_STATUSES.has(run.status)) {
|
|
970
|
-
let
|
|
971
|
+
let text6 = formatRunDetail(run);
|
|
971
972
|
const logs = await fetchRunLogs(runId, conn, proxy, sshExec2);
|
|
972
|
-
if (logs)
|
|
973
|
-
return { content: [{ type: "text", text }] };
|
|
973
|
+
if (logs) text6 += "\n\n--- Logs ---\n" + logs;
|
|
974
|
+
return { content: [{ type: "text", text: text6 }] };
|
|
974
975
|
}
|
|
975
976
|
}
|
|
976
977
|
return {
|
|
@@ -1024,6 +1025,39 @@ async function getMgBoilerGitHubToken() {
|
|
|
1024
1025
|
return { token: null, error: "Failed to decrypt GitHub token" };
|
|
1025
1026
|
}
|
|
1026
1027
|
}
|
|
1028
|
+
async function getGitHubTokenById(id) {
|
|
1029
|
+
try {
|
|
1030
|
+
const rows = await getDb().execute(sql`
|
|
1031
|
+
SELECT id, label, owner, notes, token_encrypted, created_at, updated_at
|
|
1032
|
+
FROM github_token
|
|
1033
|
+
WHERE id = ${id}
|
|
1034
|
+
LIMIT 1
|
|
1035
|
+
`);
|
|
1036
|
+
const row = rows[0];
|
|
1037
|
+
if (!row)
|
|
1038
|
+
return { token: null, owner: null, error: null };
|
|
1039
|
+
return {
|
|
1040
|
+
token: decrypt(row.token_encrypted),
|
|
1041
|
+
owner: row.owner ?? null,
|
|
1042
|
+
error: null
|
|
1043
|
+
};
|
|
1044
|
+
} catch (error) {
|
|
1045
|
+
console.error("[getGitHubTokenById] error:", error);
|
|
1046
|
+
return { token: null, owner: null, error: "Failed to decrypt GitHub token" };
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
async function resolveReleaseGitHubToken(githubTokenId) {
|
|
1050
|
+
if (githubTokenId) {
|
|
1051
|
+
const { token, error } = await getGitHubTokenById(githubTokenId);
|
|
1052
|
+
if (token)
|
|
1053
|
+
return { token, error: null };
|
|
1054
|
+
return {
|
|
1055
|
+
token: null,
|
|
1056
|
+
error: error || "Configured GitHub token not found"
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
return getMgBoilerGitHubToken();
|
|
1060
|
+
}
|
|
1027
1061
|
|
|
1028
1062
|
// ../../node_modules/zod/dist/esm/v3/external.js
|
|
1029
1063
|
var external_exports = {};
|
|
@@ -3624,10 +3658,10 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
3624
3658
|
// }) as any;
|
|
3625
3659
|
// return merged;
|
|
3626
3660
|
// }
|
|
3627
|
-
catchall(
|
|
3661
|
+
catchall(index4) {
|
|
3628
3662
|
return new _ZodObject({
|
|
3629
3663
|
...this._def,
|
|
3630
|
-
catchall:
|
|
3664
|
+
catchall: index4
|
|
3631
3665
|
});
|
|
3632
3666
|
}
|
|
3633
3667
|
pick(mask) {
|
|
@@ -3945,9 +3979,9 @@ function mergeValues(a, b) {
|
|
|
3945
3979
|
return { valid: false };
|
|
3946
3980
|
}
|
|
3947
3981
|
const newArray = [];
|
|
3948
|
-
for (let
|
|
3949
|
-
const itemA = a[
|
|
3950
|
-
const itemB = b[
|
|
3982
|
+
for (let index4 = 0; index4 < a.length; index4++) {
|
|
3983
|
+
const itemA = a[index4];
|
|
3984
|
+
const itemB = b[index4];
|
|
3951
3985
|
const sharedValue = mergeValues(itemA, itemB);
|
|
3952
3986
|
if (!sharedValue.valid) {
|
|
3953
3987
|
return { valid: false };
|
|
@@ -4153,10 +4187,10 @@ var ZodMap = class extends ZodType {
|
|
|
4153
4187
|
}
|
|
4154
4188
|
const keyType = this._def.keyType;
|
|
4155
4189
|
const valueType = this._def.valueType;
|
|
4156
|
-
const pairs = [...ctx.data.entries()].map(([key, value],
|
|
4190
|
+
const pairs = [...ctx.data.entries()].map(([key, value], index4) => {
|
|
4157
4191
|
return {
|
|
4158
|
-
key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [
|
|
4159
|
-
value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [
|
|
4192
|
+
key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index4, "key"])),
|
|
4193
|
+
value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index4, "value"]))
|
|
4160
4194
|
};
|
|
4161
4195
|
});
|
|
4162
4196
|
if (ctx.common.async) {
|
|
@@ -5061,7 +5095,7 @@ var coerce = {
|
|
|
5061
5095
|
var NEVER = INVALID;
|
|
5062
5096
|
|
|
5063
5097
|
// ../platform/dist/utils/litespeed-vhost.js
|
|
5064
|
-
var LITESPEED_STANDARD_APP_PATH = /^apps\/(backoffice|portal|web|api)(\/[\w-]+)
|
|
5098
|
+
var LITESPEED_STANDARD_APP_PATH = /^(\.|apps\/(backoffice|portal|web|api)(\/[\w-]+)?)$/;
|
|
5065
5099
|
var LitespeedVhostMappingSchema = external_exports.object({
|
|
5066
5100
|
domain: external_exports.string().min(1).transform((value) => normalizeVhostDomain(value)),
|
|
5067
5101
|
appPath: external_exports.string().min(1).regex(LITESPEED_STANDARD_APP_PATH, "Unsupported appPath")
|
|
@@ -5070,6 +5104,618 @@ external_exports.array(LitespeedVhostMappingSchema);
|
|
|
5070
5104
|
function normalizeVhostDomain(value) {
|
|
5071
5105
|
return value.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/\.+$/, "").toLowerCase();
|
|
5072
5106
|
}
|
|
5107
|
+
var users = pgTable("user", {
|
|
5108
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5109
|
+
email: text("email").notNull().unique(),
|
|
5110
|
+
fullName: text("full_name"),
|
|
5111
|
+
avatarUrl: text("avatar_url"),
|
|
5112
|
+
emailVerified: boolean("email_verified").notNull().default(false),
|
|
5113
|
+
roleId: uuid("role_id"),
|
|
5114
|
+
permissions: jsonb("permissions"),
|
|
5115
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5116
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5117
|
+
});
|
|
5118
|
+
pgTable("session", {
|
|
5119
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5120
|
+
userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
5121
|
+
token: text("token").notNull().unique(),
|
|
5122
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
5123
|
+
ipAddress: text("ip_address"),
|
|
5124
|
+
userAgent: text("user_agent"),
|
|
5125
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5126
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5127
|
+
});
|
|
5128
|
+
pgTable("account", {
|
|
5129
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5130
|
+
userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
5131
|
+
providerId: text("provider_id").notNull(),
|
|
5132
|
+
accountId: text("account_id").notNull(),
|
|
5133
|
+
password: text("password"),
|
|
5134
|
+
accessToken: text("access_token"),
|
|
5135
|
+
refreshToken: text("refresh_token"),
|
|
5136
|
+
accessTokenExpiresAt: timestamp("access_token_expires_at", {
|
|
5137
|
+
withTimezone: true
|
|
5138
|
+
}),
|
|
5139
|
+
refreshTokenExpiresAt: timestamp("refresh_token_expires_at", {
|
|
5140
|
+
withTimezone: true
|
|
5141
|
+
}),
|
|
5142
|
+
scope: text("scope"),
|
|
5143
|
+
idToken: text("id_token"),
|
|
5144
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5145
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5146
|
+
});
|
|
5147
|
+
pgTable("verification", {
|
|
5148
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5149
|
+
identifier: text("identifier").notNull(),
|
|
5150
|
+
value: text("value").notNull(),
|
|
5151
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
5152
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5153
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5154
|
+
});
|
|
5155
|
+
pgTable("two_factor", {
|
|
5156
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5157
|
+
userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
5158
|
+
secret: text("secret").notNull(),
|
|
5159
|
+
backupCodes: text("backup_codes")
|
|
5160
|
+
});
|
|
5161
|
+
var managedServerOs = pgEnum("managed_server_os", [
|
|
5162
|
+
"linux",
|
|
5163
|
+
"windows",
|
|
5164
|
+
"unknown"
|
|
5165
|
+
]);
|
|
5166
|
+
var serverAgentStatus = pgEnum("server_agent_status", [
|
|
5167
|
+
"not_installed",
|
|
5168
|
+
"installing",
|
|
5169
|
+
"online",
|
|
5170
|
+
"degraded",
|
|
5171
|
+
"offline",
|
|
5172
|
+
"error"
|
|
5173
|
+
]);
|
|
5174
|
+
var resourceKind = pgEnum("resource_kind", [
|
|
5175
|
+
"wordpress",
|
|
5176
|
+
"prestashop",
|
|
5177
|
+
"postgres_cluster",
|
|
5178
|
+
"postgres_database",
|
|
5179
|
+
"mysql_database",
|
|
5180
|
+
"mariadb_database",
|
|
5181
|
+
"boiler_project",
|
|
5182
|
+
"boiler_web_project",
|
|
5183
|
+
"static_site",
|
|
5184
|
+
"docker_app",
|
|
5185
|
+
"domain",
|
|
5186
|
+
"backup_policy",
|
|
5187
|
+
"server_service"
|
|
5188
|
+
]);
|
|
5189
|
+
var resourceStatus = pgEnum("resource_status", [
|
|
5190
|
+
"unknown",
|
|
5191
|
+
"healthy",
|
|
5192
|
+
"degraded",
|
|
5193
|
+
"failed",
|
|
5194
|
+
"missing",
|
|
5195
|
+
"provisioning",
|
|
5196
|
+
"disabled"
|
|
5197
|
+
]);
|
|
5198
|
+
var resourceOwnership = pgEnum("resource_ownership", [
|
|
5199
|
+
"managed",
|
|
5200
|
+
"discovered",
|
|
5201
|
+
"external"
|
|
5202
|
+
]);
|
|
5203
|
+
var operationStatus = pgEnum("operation_status", [
|
|
5204
|
+
"queued",
|
|
5205
|
+
"running",
|
|
5206
|
+
"succeeded",
|
|
5207
|
+
"failed",
|
|
5208
|
+
"cancelled"
|
|
5209
|
+
]);
|
|
5210
|
+
var alertSeverity = pgEnum("alert_severity", [
|
|
5211
|
+
"info",
|
|
5212
|
+
"warning",
|
|
5213
|
+
"critical"
|
|
5214
|
+
]);
|
|
5215
|
+
var managedServer = pgTable(
|
|
5216
|
+
"managed_server",
|
|
5217
|
+
{
|
|
5218
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5219
|
+
name: text("name").notNull(),
|
|
5220
|
+
hostname: text("hostname").notNull(),
|
|
5221
|
+
port: integer("port").notNull().default(22),
|
|
5222
|
+
username: text("username").notNull(),
|
|
5223
|
+
authMethod: text("auth_method").notNull().default("ssh_key"),
|
|
5224
|
+
os: managedServerOs("os").notNull().default("unknown"),
|
|
5225
|
+
provider: text("provider"),
|
|
5226
|
+
region: text("region"),
|
|
5227
|
+
tags: jsonb("tags").$type().notNull().default([]),
|
|
5228
|
+
agentStatus: serverAgentStatus("agent_status").notNull().default("not_installed"),
|
|
5229
|
+
agentVersion: text("agent_version"),
|
|
5230
|
+
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }),
|
|
5231
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5232
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5233
|
+
},
|
|
5234
|
+
(table) => [
|
|
5235
|
+
uniqueIndex("managed_server_hostname_port_uidx").on(
|
|
5236
|
+
table.hostname,
|
|
5237
|
+
table.port
|
|
5238
|
+
),
|
|
5239
|
+
index("managed_server_agent_status_idx").on(table.agentStatus)
|
|
5240
|
+
]
|
|
5241
|
+
);
|
|
5242
|
+
pgTable("server_credential", {
|
|
5243
|
+
serverId: uuid("server_id").primaryKey().references(() => managedServer.id, { onDelete: "cascade" }),
|
|
5244
|
+
passwordEncrypted: text("password_encrypted"),
|
|
5245
|
+
sshKeyEncrypted: text("ssh_key_encrypted"),
|
|
5246
|
+
sshKeyPassphraseEncrypted: text("ssh_key_passphrase_encrypted"),
|
|
5247
|
+
dbRootPasswordEncrypted: text("db_root_password_encrypted"),
|
|
5248
|
+
monitoringApiKeyEncrypted: text("monitoring_api_key_encrypted"),
|
|
5249
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5250
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5251
|
+
});
|
|
5252
|
+
pgTable(
|
|
5253
|
+
"server_connection_log",
|
|
5254
|
+
{
|
|
5255
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5256
|
+
serverId: uuid("server_id").notNull().references(() => managedServer.id, { onDelete: "cascade" }),
|
|
5257
|
+
userId: uuid("user_id").notNull(),
|
|
5258
|
+
connectedAt: timestamp("connected_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5259
|
+
disconnectedAt: timestamp("disconnected_at", { withTimezone: true }),
|
|
5260
|
+
durationSeconds: integer("duration_seconds"),
|
|
5261
|
+
status: text("status").notNull(),
|
|
5262
|
+
errorMessage: text("error_message"),
|
|
5263
|
+
clientIp: text("client_ip"),
|
|
5264
|
+
terminalCols: integer("terminal_cols"),
|
|
5265
|
+
terminalRows: integer("terminal_rows"),
|
|
5266
|
+
outputSizeBytes: bigint("output_size_bytes", { mode: "number" }).notNull().default(0),
|
|
5267
|
+
inputSizeBytes: bigint("input_size_bytes", { mode: "number" }).notNull().default(0),
|
|
5268
|
+
commandCount: integer("command_count").notNull().default(0),
|
|
5269
|
+
connectionType: text("connection_type").notNull().default("terminal"),
|
|
5270
|
+
action: text("action"),
|
|
5271
|
+
rawOutput: text("raw_output"),
|
|
5272
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
5273
|
+
},
|
|
5274
|
+
(table) => [
|
|
5275
|
+
index("server_connection_log_server_idx").on(table.serverId),
|
|
5276
|
+
index("server_connection_log_user_idx").on(table.userId),
|
|
5277
|
+
index("server_connection_log_connected_idx").on(table.connectedAt),
|
|
5278
|
+
index("server_connection_log_status_idx").on(table.status)
|
|
5279
|
+
]
|
|
5280
|
+
);
|
|
5281
|
+
pgTable(
|
|
5282
|
+
"mcp_audit_log",
|
|
5283
|
+
{
|
|
5284
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5285
|
+
apiKeyId: uuid("api_key_id"),
|
|
5286
|
+
userId: uuid("user_id"),
|
|
5287
|
+
toolName: text("tool_name"),
|
|
5288
|
+
arguments: jsonb("arguments"),
|
|
5289
|
+
ipAddress: text("ip_address"),
|
|
5290
|
+
serverId: uuid("server_id"),
|
|
5291
|
+
resultStatus: text("result_status").notNull().default("success"),
|
|
5292
|
+
errorMessage: text("error_message"),
|
|
5293
|
+
durationMs: integer("duration_ms"),
|
|
5294
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
5295
|
+
},
|
|
5296
|
+
(table) => [
|
|
5297
|
+
index("mcp_audit_log_created_at_idx").on(table.createdAt),
|
|
5298
|
+
index("mcp_audit_log_user_idx").on(table.userId),
|
|
5299
|
+
index("mcp_audit_log_tool_name_idx").on(table.toolName),
|
|
5300
|
+
index("mcp_audit_log_server_idx").on(table.serverId),
|
|
5301
|
+
index("mcp_audit_log_api_key_idx").on(table.apiKeyId),
|
|
5302
|
+
index("mcp_audit_log_result_status_idx").on(table.resultStatus)
|
|
5303
|
+
]
|
|
5304
|
+
);
|
|
5305
|
+
var resource = pgTable(
|
|
5306
|
+
"resource",
|
|
5307
|
+
{
|
|
5308
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5309
|
+
serverId: uuid("server_id").notNull().references(() => managedServer.id, { onDelete: "cascade" }),
|
|
5310
|
+
parentResourceId: uuid("parent_resource_id"),
|
|
5311
|
+
kind: resourceKind("kind").notNull(),
|
|
5312
|
+
slug: text("slug").notNull(),
|
|
5313
|
+
displayName: text("display_name").notNull(),
|
|
5314
|
+
spec: jsonb("spec").$type().notNull().default({}),
|
|
5315
|
+
state: jsonb("state").$type().notNull().default({}),
|
|
5316
|
+
status: resourceStatus("status").notNull().default("unknown"),
|
|
5317
|
+
ownership: resourceOwnership("ownership").notNull().default("managed"),
|
|
5318
|
+
lastDetectedAt: timestamp("last_detected_at", { withTimezone: true }),
|
|
5319
|
+
lastEventId: uuid("last_event_id"),
|
|
5320
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5321
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5322
|
+
},
|
|
5323
|
+
(table) => [
|
|
5324
|
+
uniqueIndex("resource_server_kind_slug_uidx").on(
|
|
5325
|
+
table.serverId,
|
|
5326
|
+
table.kind,
|
|
5327
|
+
table.slug
|
|
5328
|
+
),
|
|
5329
|
+
index("resource_server_idx").on(table.serverId),
|
|
5330
|
+
index("resource_parent_idx").on(table.parentResourceId),
|
|
5331
|
+
index("resource_kind_status_idx").on(table.kind, table.status)
|
|
5332
|
+
]
|
|
5333
|
+
);
|
|
5334
|
+
pgTable(
|
|
5335
|
+
"resource_event",
|
|
5336
|
+
{
|
|
5337
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5338
|
+
resourceId: uuid("resource_id").notNull().references(() => resource.id, { onDelete: "cascade" }),
|
|
5339
|
+
kind: text("kind").notNull(),
|
|
5340
|
+
before: jsonb("before").$type(),
|
|
5341
|
+
after: jsonb("after").$type(),
|
|
5342
|
+
actorUserId: text("actor_user_id"),
|
|
5343
|
+
operationId: uuid("operation_id"),
|
|
5344
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
5345
|
+
},
|
|
5346
|
+
(table) => [
|
|
5347
|
+
index("resource_event_resource_created_idx").on(
|
|
5348
|
+
table.resourceId,
|
|
5349
|
+
table.createdAt
|
|
5350
|
+
),
|
|
5351
|
+
index("resource_event_operation_idx").on(table.operationId)
|
|
5352
|
+
]
|
|
5353
|
+
);
|
|
5354
|
+
var operation = pgTable(
|
|
5355
|
+
"operation",
|
|
5356
|
+
{
|
|
5357
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5358
|
+
kind: text("kind").notNull(),
|
|
5359
|
+
resourceId: uuid("resource_id").references(() => resource.id, {
|
|
5360
|
+
onDelete: "set null"
|
|
5361
|
+
}),
|
|
5362
|
+
serverId: uuid("server_id").references(() => managedServer.id, {
|
|
5363
|
+
onDelete: "set null"
|
|
5364
|
+
}),
|
|
5365
|
+
status: operationStatus("status").notNull().default("queued"),
|
|
5366
|
+
steps: jsonb("steps").$type().notNull().default([]),
|
|
5367
|
+
idempotencyKey: text("idempotency_key").notNull(),
|
|
5368
|
+
triggerRunId: text("trigger_run_id"),
|
|
5369
|
+
logsR2Key: text("logs_r2_key"),
|
|
5370
|
+
logs: text("logs"),
|
|
5371
|
+
startedBy: text("started_by"),
|
|
5372
|
+
startedAt: timestamp("started_at", { withTimezone: true }),
|
|
5373
|
+
endedAt: timestamp("ended_at", { withTimezone: true }),
|
|
5374
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5375
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5376
|
+
},
|
|
5377
|
+
(table) => [
|
|
5378
|
+
uniqueIndex("operation_idempotency_uidx").on(table.idempotencyKey),
|
|
5379
|
+
index("operation_resource_idx").on(table.resourceId),
|
|
5380
|
+
index("operation_server_idx").on(table.serverId),
|
|
5381
|
+
index("operation_status_created_idx").on(table.status, table.createdAt)
|
|
5382
|
+
]
|
|
5383
|
+
);
|
|
5384
|
+
pgTable(
|
|
5385
|
+
"agent_installation",
|
|
5386
|
+
{
|
|
5387
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5388
|
+
serverId: uuid("server_id").notNull().references(() => managedServer.id, { onDelete: "cascade" }),
|
|
5389
|
+
agentId: text("agent_id"),
|
|
5390
|
+
version: text("version").notNull(),
|
|
5391
|
+
installPath: text("install_path").notNull().default("/usr/local/bin/mg-agent"),
|
|
5392
|
+
configHash: text("config_hash").notNull(),
|
|
5393
|
+
status: serverAgentStatus("status").notNull().default("installing"),
|
|
5394
|
+
lastHeartbeatAt: timestamp("last_heartbeat_at", { withTimezone: true }),
|
|
5395
|
+
lastError: text("last_error"),
|
|
5396
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5397
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5398
|
+
},
|
|
5399
|
+
(table) => [
|
|
5400
|
+
uniqueIndex("agent_installation_server_uidx").on(table.serverId),
|
|
5401
|
+
index("agent_installation_status_idx").on(table.status)
|
|
5402
|
+
]
|
|
5403
|
+
);
|
|
5404
|
+
pgTable(
|
|
5405
|
+
"monitoring_sample",
|
|
5406
|
+
{
|
|
5407
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5408
|
+
serverId: uuid("server_id").notNull().references(() => managedServer.id, { onDelete: "cascade" }),
|
|
5409
|
+
resourceId: uuid("resource_id").references(() => resource.id, {
|
|
5410
|
+
onDelete: "cascade"
|
|
5411
|
+
}),
|
|
5412
|
+
metric: text("metric").notNull(),
|
|
5413
|
+
value: integer("value").notNull(),
|
|
5414
|
+
unit: text("unit").notNull(),
|
|
5415
|
+
tags: jsonb("tags").$type().notNull().default({}),
|
|
5416
|
+
sampledAt: timestamp("sampled_at", { withTimezone: true }).notNull().defaultNow()
|
|
5417
|
+
},
|
|
5418
|
+
(table) => [
|
|
5419
|
+
index("monitoring_sample_server_metric_time_idx").on(
|
|
5420
|
+
table.serverId,
|
|
5421
|
+
table.metric,
|
|
5422
|
+
table.sampledAt
|
|
5423
|
+
),
|
|
5424
|
+
// Descending on sampled_at so "latest metric per (server, metric)" lookups
|
|
5425
|
+
// (DISTINCT ON ... ORDER BY sampled_at DESC) are served by an index-only
|
|
5426
|
+
// scan instead of a full-table sort. The migration also adds
|
|
5427
|
+
// INCLUDE (value, unit) to make it covering for the overview snapshot query
|
|
5428
|
+
// (Drizzle 0.44 can't express INCLUDE here). See migration 20260529210000.
|
|
5429
|
+
index("monitoring_sample_server_metric_time_desc_idx").on(
|
|
5430
|
+
table.serverId,
|
|
5431
|
+
table.metric,
|
|
5432
|
+
table.sampledAt.desc()
|
|
5433
|
+
),
|
|
5434
|
+
index("monitoring_sample_resource_metric_time_idx").on(
|
|
5435
|
+
table.resourceId,
|
|
5436
|
+
table.metric,
|
|
5437
|
+
table.sampledAt
|
|
5438
|
+
)
|
|
5439
|
+
]
|
|
5440
|
+
);
|
|
5441
|
+
var appLogSource = pgTable(
|
|
5442
|
+
"app_log_source",
|
|
5443
|
+
{
|
|
5444
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5445
|
+
serverId: uuid("server_id").notNull().references(() => managedServer.id, { onDelete: "cascade" }),
|
|
5446
|
+
resourceId: uuid("resource_id").references(() => resource.id, {
|
|
5447
|
+
onDelete: "set null"
|
|
5448
|
+
}),
|
|
5449
|
+
sourceType: text("source_type").notNull(),
|
|
5450
|
+
sourceKey: text("source_key").notNull(),
|
|
5451
|
+
displayName: text("display_name").notNull(),
|
|
5452
|
+
path: text("path").notNull(),
|
|
5453
|
+
enabled: boolean("enabled").notNull().default(true),
|
|
5454
|
+
mutedUntil: timestamp("muted_until", { withTimezone: true }),
|
|
5455
|
+
metadata: jsonb("metadata").$type().notNull().default({}),
|
|
5456
|
+
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5457
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5458
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5459
|
+
},
|
|
5460
|
+
(table) => [
|
|
5461
|
+
uniqueIndex("app_log_source_server_type_key_uidx").on(
|
|
5462
|
+
table.serverId,
|
|
5463
|
+
table.sourceType,
|
|
5464
|
+
table.sourceKey
|
|
5465
|
+
),
|
|
5466
|
+
index("app_log_source_server_idx").on(table.serverId),
|
|
5467
|
+
index("app_log_source_resource_idx").on(table.resourceId),
|
|
5468
|
+
index("app_log_source_type_seen_idx").on(
|
|
5469
|
+
table.sourceType,
|
|
5470
|
+
table.lastSeenAt
|
|
5471
|
+
)
|
|
5472
|
+
]
|
|
5473
|
+
);
|
|
5474
|
+
pgTable(
|
|
5475
|
+
"app_error_event",
|
|
5476
|
+
{
|
|
5477
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5478
|
+
serverId: uuid("server_id").notNull().references(() => managedServer.id, { onDelete: "cascade" }),
|
|
5479
|
+
resourceId: uuid("resource_id").references(() => resource.id, {
|
|
5480
|
+
onDelete: "set null"
|
|
5481
|
+
}),
|
|
5482
|
+
logSourceId: uuid("log_source_id").references(() => appLogSource.id, {
|
|
5483
|
+
onDelete: "set null"
|
|
5484
|
+
}),
|
|
5485
|
+
fingerprint: text("fingerprint").notNull(),
|
|
5486
|
+
severity: text("severity").notNull().default("critical"),
|
|
5487
|
+
category: text("category").notNull(),
|
|
5488
|
+
sourceType: text("source_type").notNull(),
|
|
5489
|
+
sourcePath: text("source_path").notNull(),
|
|
5490
|
+
sampleMessage: text("sample_message").notNull(),
|
|
5491
|
+
sampleLines: jsonb("sample_lines").$type().notNull().default([]),
|
|
5492
|
+
signals: jsonb("signals").$type().notNull().default({}),
|
|
5493
|
+
occurrences: integer("occurrences").notNull().default(1),
|
|
5494
|
+
firstSeenAt: timestamp("first_seen_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5495
|
+
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5496
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
5497
|
+
},
|
|
5498
|
+
(table) => [
|
|
5499
|
+
index("app_error_event_fingerprint_created_idx").on(
|
|
5500
|
+
table.fingerprint,
|
|
5501
|
+
table.createdAt
|
|
5502
|
+
),
|
|
5503
|
+
index("app_error_event_server_created_idx").on(
|
|
5504
|
+
table.serverId,
|
|
5505
|
+
table.createdAt
|
|
5506
|
+
),
|
|
5507
|
+
index("app_error_event_resource_created_idx").on(
|
|
5508
|
+
table.resourceId,
|
|
5509
|
+
table.createdAt
|
|
5510
|
+
),
|
|
5511
|
+
index("app_error_event_severity_created_idx").on(
|
|
5512
|
+
table.severity,
|
|
5513
|
+
table.createdAt
|
|
5514
|
+
)
|
|
5515
|
+
]
|
|
5516
|
+
);
|
|
5517
|
+
pgTable(
|
|
5518
|
+
"app_error_alert",
|
|
5519
|
+
{
|
|
5520
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5521
|
+
fingerprint: text("fingerprint").notNull(),
|
|
5522
|
+
serverId: uuid("server_id").notNull().references(() => managedServer.id, { onDelete: "cascade" }),
|
|
5523
|
+
resourceId: uuid("resource_id").references(() => resource.id, {
|
|
5524
|
+
onDelete: "set null"
|
|
5525
|
+
}),
|
|
5526
|
+
logSourceId: uuid("log_source_id").references(() => appLogSource.id, {
|
|
5527
|
+
onDelete: "set null"
|
|
5528
|
+
}),
|
|
5529
|
+
severity: text("severity").notNull().default("critical"),
|
|
5530
|
+
status: text("status").notNull().default("open"),
|
|
5531
|
+
category: text("category").notNull(),
|
|
5532
|
+
title: text("title").notNull(),
|
|
5533
|
+
lastError: text("last_error").notNull(),
|
|
5534
|
+
sampleLines: jsonb("sample_lines").$type().notNull().default([]),
|
|
5535
|
+
signals: jsonb("signals").$type().notNull().default({}),
|
|
5536
|
+
failureCount: integer("failure_count").notNull().default(1),
|
|
5537
|
+
firstSeenAt: timestamp("first_seen_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5538
|
+
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5539
|
+
lastAlertAt: timestamp("last_alert_at", { withTimezone: true }),
|
|
5540
|
+
resolvedAt: timestamp("resolved_at", { withTimezone: true }),
|
|
5541
|
+
mutedUntil: timestamp("muted_until", { withTimezone: true }),
|
|
5542
|
+
cursorPrompt: text("cursor_prompt"),
|
|
5543
|
+
telegramChatId: text("telegram_chat_id"),
|
|
5544
|
+
telegramMessageId: bigint("telegram_message_id", { mode: "number" }),
|
|
5545
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5546
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5547
|
+
},
|
|
5548
|
+
(table) => [
|
|
5549
|
+
uniqueIndex("app_error_alert_fingerprint_uidx").on(table.fingerprint),
|
|
5550
|
+
index("app_error_alert_status_seen_idx").on(
|
|
5551
|
+
table.status,
|
|
5552
|
+
table.lastSeenAt
|
|
5553
|
+
),
|
|
5554
|
+
index("app_error_alert_server_seen_idx").on(
|
|
5555
|
+
table.serverId,
|
|
5556
|
+
table.lastSeenAt
|
|
5557
|
+
),
|
|
5558
|
+
index("app_error_alert_resource_seen_idx").on(
|
|
5559
|
+
table.resourceId,
|
|
5560
|
+
table.lastSeenAt
|
|
5561
|
+
),
|
|
5562
|
+
index("app_error_alert_alerted_idx").on(table.lastAlertAt)
|
|
5563
|
+
]
|
|
5564
|
+
);
|
|
5565
|
+
pgTable(
|
|
5566
|
+
"alert_rule",
|
|
5567
|
+
{
|
|
5568
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5569
|
+
serverId: uuid("server_id").references(() => managedServer.id, {
|
|
5570
|
+
onDelete: "cascade"
|
|
5571
|
+
}),
|
|
5572
|
+
resourceId: uuid("resource_id").references(() => resource.id, {
|
|
5573
|
+
onDelete: "cascade"
|
|
5574
|
+
}),
|
|
5575
|
+
metric: text("metric").notNull(),
|
|
5576
|
+
expression: text("expression").notNull(),
|
|
5577
|
+
severity: alertSeverity("severity").notNull().default("warning"),
|
|
5578
|
+
cooldownSeconds: integer("cooldown_seconds").notNull().default(900),
|
|
5579
|
+
enabled: boolean("enabled").notNull().default(true),
|
|
5580
|
+
routes: jsonb("routes").$type().notNull().default({}),
|
|
5581
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5582
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5583
|
+
},
|
|
5584
|
+
(table) => [
|
|
5585
|
+
index("alert_rule_server_idx").on(table.serverId),
|
|
5586
|
+
index("alert_rule_resource_idx").on(table.resourceId),
|
|
5587
|
+
index("alert_rule_enabled_severity_idx").on(table.enabled, table.severity)
|
|
5588
|
+
]
|
|
5589
|
+
);
|
|
5590
|
+
pgTable(
|
|
5591
|
+
"deployment_project_server",
|
|
5592
|
+
{
|
|
5593
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5594
|
+
releaseProfileStageId: uuid("release_profile_stage_id").notNull(),
|
|
5595
|
+
sshServerId: uuid("ssh_server_id").notNull(),
|
|
5596
|
+
deployPath: text("deploy_path").notNull(),
|
|
5597
|
+
deployPathNormalized: text("deploy_path_normalized"),
|
|
5598
|
+
pm2PortBase: integer("pm2_port_base").notNull(),
|
|
5599
|
+
postgresHost: text("postgres_host"),
|
|
5600
|
+
postgresPort: integer("postgres_port"),
|
|
5601
|
+
buildFilters: text("build_filters"),
|
|
5602
|
+
envSymlinkDirs: jsonb("env_symlink_dirs"),
|
|
5603
|
+
litespeedVhosts: jsonb("litespeed_vhosts"),
|
|
5604
|
+
litespeedVhostsCheckedAt: timestamp("litespeed_vhosts_checked_at", {
|
|
5605
|
+
withTimezone: true
|
|
5606
|
+
}),
|
|
5607
|
+
litespeedVhostsCheckOk: boolean("litespeed_vhosts_check_ok"),
|
|
5608
|
+
ecosystemConfig: text("ecosystem_config").notNull().default("ecosystem.config.cjs"),
|
|
5609
|
+
usePipelineDeploy: boolean("use_pipeline_deploy").notNull().default(true),
|
|
5610
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5611
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5612
|
+
},
|
|
5613
|
+
(table) => [
|
|
5614
|
+
uniqueIndex("deployment_project_server_stage_server_uidx").on(
|
|
5615
|
+
table.releaseProfileStageId,
|
|
5616
|
+
table.sshServerId
|
|
5617
|
+
),
|
|
5618
|
+
uniqueIndex("deployment_project_server_server_path_uidx").on(
|
|
5619
|
+
table.sshServerId,
|
|
5620
|
+
table.deployPathNormalized
|
|
5621
|
+
),
|
|
5622
|
+
uniqueIndex("deployment_project_server_server_port_uidx").on(
|
|
5623
|
+
table.sshServerId,
|
|
5624
|
+
table.pm2PortBase
|
|
5625
|
+
)
|
|
5626
|
+
]
|
|
5627
|
+
);
|
|
5628
|
+
var dnsMigrationStatus = pgEnum("dns_migration_status", [
|
|
5629
|
+
"pending",
|
|
5630
|
+
"scanned",
|
|
5631
|
+
"imported",
|
|
5632
|
+
"verified"
|
|
5633
|
+
]);
|
|
5634
|
+
var dnsMigration = pgTable(
|
|
5635
|
+
"dns_migration",
|
|
5636
|
+
{
|
|
5637
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5638
|
+
domain: text("domain").notNull(),
|
|
5639
|
+
sourceProvider: text("source_provider").notNull().default("hostnet"),
|
|
5640
|
+
status: dnsMigrationStatus("status").notNull().default("pending"),
|
|
5641
|
+
scannedRecords: jsonb("scanned_records").$type().notNull().default([]),
|
|
5642
|
+
sourceNameservers: jsonb("source_nameservers").$type().notNull().default([]),
|
|
5643
|
+
currentNameservers: jsonb("current_nameservers").$type().notNull().default([]),
|
|
5644
|
+
importResult: jsonb("import_result").$type(),
|
|
5645
|
+
emailChecklist: jsonb("email_checklist").$type().notNull().default({}),
|
|
5646
|
+
directadminHost: text("directadmin_host"),
|
|
5647
|
+
directadminUsername: text("directadmin_username"),
|
|
5648
|
+
directadminPasswordEncrypted: text("directadmin_password_encrypted"),
|
|
5649
|
+
syncServerId: uuid("sync_server_id").references(() => managedServer.id, {
|
|
5650
|
+
onDelete: "set null"
|
|
5651
|
+
}),
|
|
5652
|
+
syncOperationId: uuid("sync_operation_id").references(() => operation.id, {
|
|
5653
|
+
onDelete: "set null"
|
|
5654
|
+
}),
|
|
5655
|
+
mailRecordsImportedAt: timestamp("mail_records_imported_at", {
|
|
5656
|
+
withTimezone: true
|
|
5657
|
+
}),
|
|
5658
|
+
notes: text("notes"),
|
|
5659
|
+
lastError: text("last_error"),
|
|
5660
|
+
lastScannedAt: timestamp("last_scanned_at", { withTimezone: true }),
|
|
5661
|
+
importedAt: timestamp("imported_at", { withTimezone: true }),
|
|
5662
|
+
verifiedAt: timestamp("verified_at", { withTimezone: true }),
|
|
5663
|
+
createdBy: text("created_by"),
|
|
5664
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5665
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5666
|
+
},
|
|
5667
|
+
(table) => [
|
|
5668
|
+
index("dns_migration_domain_idx").on(table.domain),
|
|
5669
|
+
index("dns_migration_status_created_idx").on(
|
|
5670
|
+
table.status,
|
|
5671
|
+
table.createdAt
|
|
5672
|
+
)
|
|
5673
|
+
]
|
|
5674
|
+
);
|
|
5675
|
+
var dnsMigrationMailboxStatus = pgEnum(
|
|
5676
|
+
"dns_migration_mailbox_status",
|
|
5677
|
+
["pending", "provisioned", "syncing", "synced", "delta_synced", "failed"]
|
|
5678
|
+
);
|
|
5679
|
+
pgTable(
|
|
5680
|
+
"dns_migration_mailbox",
|
|
5681
|
+
{
|
|
5682
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5683
|
+
migrationId: uuid("migration_id").notNull().references(() => dnsMigration.id, { onDelete: "cascade" }),
|
|
5684
|
+
address: text("address").notNull(),
|
|
5685
|
+
sourcePasswordEncrypted: text("source_password_encrypted"),
|
|
5686
|
+
destPasswordEncrypted: text("dest_password_encrypted"),
|
|
5687
|
+
status: dnsMigrationMailboxStatus("status").notNull().default("pending"),
|
|
5688
|
+
syncStats: jsonb("sync_stats").$type(),
|
|
5689
|
+
lastError: text("last_error"),
|
|
5690
|
+
provisionedAt: timestamp("provisioned_at", { withTimezone: true }),
|
|
5691
|
+
lastSyncedAt: timestamp("last_synced_at", { withTimezone: true }),
|
|
5692
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5693
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5694
|
+
},
|
|
5695
|
+
(table) => [
|
|
5696
|
+
uniqueIndex("dns_migration_mailbox_migration_address_uidx").on(
|
|
5697
|
+
table.migrationId,
|
|
5698
|
+
table.address
|
|
5699
|
+
),
|
|
5700
|
+
index("dns_migration_mailbox_migration_idx").on(table.migrationId)
|
|
5701
|
+
]
|
|
5702
|
+
);
|
|
5703
|
+
pgTable(
|
|
5704
|
+
"github_token",
|
|
5705
|
+
{
|
|
5706
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5707
|
+
label: text("label").notNull(),
|
|
5708
|
+
owner: text("owner"),
|
|
5709
|
+
notes: text("notes"),
|
|
5710
|
+
tokenEncrypted: text("token_encrypted").notNull(),
|
|
5711
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5712
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5713
|
+
},
|
|
5714
|
+
(table) => [
|
|
5715
|
+
index("github_token_label_idx").on(table.label),
|
|
5716
|
+
index("github_token_owner_idx").on(table.owner)
|
|
5717
|
+
]
|
|
5718
|
+
);
|
|
5073
5719
|
async function triggerPipeline(payload) {
|
|
5074
5720
|
return tasks.trigger(
|
|
5075
5721
|
"execute-pipeline",
|
|
@@ -5473,7 +6119,9 @@ async function createReleaseForStage(userId, params) {
|
|
|
5473
6119
|
"BAD_REQUEST"
|
|
5474
6120
|
);
|
|
5475
6121
|
}
|
|
5476
|
-
const { token } = await
|
|
6122
|
+
const { token } = await resolveReleaseGitHubToken(
|
|
6123
|
+
profile.github_token_id ?? null
|
|
6124
|
+
);
|
|
5477
6125
|
if (!token) {
|
|
5478
6126
|
throw new CreateReleaseError(
|
|
5479
6127
|
"GitHub token is not configured",
|
|
@@ -5612,6 +6260,449 @@ async function createReleaseForStage(userId, params) {
|
|
|
5612
6260
|
authorName: triggerAuthorName
|
|
5613
6261
|
};
|
|
5614
6262
|
}
|
|
6263
|
+
var GITHUB_API3 = "https://api.github.com";
|
|
6264
|
+
var ReleaseProfileQuickCreateError = class extends Error {
|
|
6265
|
+
constructor(message, code = "INTERNAL_SERVER_ERROR") {
|
|
6266
|
+
super(message);
|
|
6267
|
+
this.code = code;
|
|
6268
|
+
this.name = "ReleaseProfileQuickCreateError";
|
|
6269
|
+
}
|
|
6270
|
+
};
|
|
6271
|
+
function inferDeployMethod(folderName, hasNextConfig, hasDockerfile, hasPm2Ecosystem) {
|
|
6272
|
+
if (hasPm2Ecosystem && hasNextConfig) return "pm2";
|
|
6273
|
+
if (hasPm2Ecosystem && !hasDockerfile && !folderName.toLowerCase().includes("api"))
|
|
6274
|
+
return "pm2";
|
|
6275
|
+
if (hasNextConfig) return "pm2";
|
|
6276
|
+
if (hasDockerfile || folderName.toLowerCase().includes("api"))
|
|
6277
|
+
return "docker";
|
|
6278
|
+
return "none";
|
|
6279
|
+
}
|
|
6280
|
+
function toLabel(folderName) {
|
|
6281
|
+
return folderName.charAt(0).toUpperCase() + folderName.slice(1);
|
|
6282
|
+
}
|
|
6283
|
+
async function scanRepoForApps(repoFullName, githubToken2, ref = "main") {
|
|
6284
|
+
const headers = {
|
|
6285
|
+
Accept: "application/vnd.github.v3+json",
|
|
6286
|
+
Authorization: `Bearer ${githubToken2}`,
|
|
6287
|
+
"Content-Type": "application/json"
|
|
6288
|
+
};
|
|
6289
|
+
async function fetchContents(path) {
|
|
6290
|
+
const res = await fetch(
|
|
6291
|
+
`${GITHUB_API3}/repos/${repoFullName}/contents/${path}?ref=${ref}`,
|
|
6292
|
+
{ headers }
|
|
6293
|
+
);
|
|
6294
|
+
if (!res.ok) return null;
|
|
6295
|
+
const data = await res.json();
|
|
6296
|
+
return Array.isArray(data) ? data : null;
|
|
6297
|
+
}
|
|
6298
|
+
async function fileExists(path) {
|
|
6299
|
+
const res = await fetch(
|
|
6300
|
+
`${GITHUB_API3}/repos/${repoFullName}/contents/${path}?ref=${ref}`,
|
|
6301
|
+
{ headers, method: "HEAD" }
|
|
6302
|
+
);
|
|
6303
|
+
return res.ok;
|
|
6304
|
+
}
|
|
6305
|
+
const apps = [];
|
|
6306
|
+
const appsDir = await fetchContents("apps");
|
|
6307
|
+
const isMonorepo = appsDir !== null && appsDir.length > 0;
|
|
6308
|
+
const hasPm2Ecosystem = await Promise.any([
|
|
6309
|
+
fileExists("ecosystem.config.cjs"),
|
|
6310
|
+
fileExists("ecosystem.config.js"),
|
|
6311
|
+
fileExists("ecosystem.config.mjs")
|
|
6312
|
+
]).catch(() => false);
|
|
6313
|
+
if (isMonorepo) {
|
|
6314
|
+
const subfolders = appsDir.filter((item) => item.type === "dir");
|
|
6315
|
+
const detections = await Promise.all(
|
|
6316
|
+
subfolders.map(async (folder) => {
|
|
6317
|
+
const folderPath = `apps/${folder.name}`;
|
|
6318
|
+
const [hasNextMjs, hasNextJs, hasNextTs, hasDockerfile, hasPkgJson] = await Promise.all([
|
|
6319
|
+
fileExists(`${folderPath}/next.config.mjs`),
|
|
6320
|
+
fileExists(`${folderPath}/next.config.js`),
|
|
6321
|
+
fileExists(`${folderPath}/next.config.ts`),
|
|
6322
|
+
fileExists(`${folderPath}/Dockerfile`),
|
|
6323
|
+
fileExists(`${folderPath}/package.json`)
|
|
6324
|
+
]);
|
|
6325
|
+
const hasNextConfig = hasNextMjs || hasNextJs || hasNextTs;
|
|
6326
|
+
const deployMethod = inferDeployMethod(
|
|
6327
|
+
folder.name,
|
|
6328
|
+
hasNextConfig,
|
|
6329
|
+
hasDockerfile,
|
|
6330
|
+
!!hasPm2Ecosystem
|
|
6331
|
+
);
|
|
6332
|
+
return {
|
|
6333
|
+
path: folderPath,
|
|
6334
|
+
label: toLabel(folder.name),
|
|
6335
|
+
deployMethod,
|
|
6336
|
+
hasNextConfig,
|
|
6337
|
+
hasDockerfile,
|
|
6338
|
+
hasPackageJson: hasPkgJson,
|
|
6339
|
+
enabled: true
|
|
6340
|
+
};
|
|
6341
|
+
})
|
|
6342
|
+
);
|
|
6343
|
+
apps.push(...detections);
|
|
6344
|
+
} else {
|
|
6345
|
+
const [hasNextMjs, hasNextJs, hasNextTs, hasDockerfile, hasPkgJson] = await Promise.all([
|
|
6346
|
+
fileExists("next.config.mjs"),
|
|
6347
|
+
fileExists("next.config.js"),
|
|
6348
|
+
fileExists("next.config.ts"),
|
|
6349
|
+
fileExists("Dockerfile"),
|
|
6350
|
+
fileExists("package.json")
|
|
6351
|
+
]);
|
|
6352
|
+
const hasNextConfig = hasNextMjs || hasNextJs || hasNextTs;
|
|
6353
|
+
const repoName = repoFullName.split("/").pop() ?? "app";
|
|
6354
|
+
const deployMethod = inferDeployMethod(
|
|
6355
|
+
repoName,
|
|
6356
|
+
hasNextConfig,
|
|
6357
|
+
hasDockerfile,
|
|
6358
|
+
!!hasPm2Ecosystem
|
|
6359
|
+
);
|
|
6360
|
+
apps.push({
|
|
6361
|
+
path: ".",
|
|
6362
|
+
label: toLabel(repoName),
|
|
6363
|
+
deployMethod,
|
|
6364
|
+
hasNextConfig,
|
|
6365
|
+
hasDockerfile,
|
|
6366
|
+
hasPackageJson: hasPkgJson,
|
|
6367
|
+
enabled: true
|
|
6368
|
+
});
|
|
6369
|
+
}
|
|
6370
|
+
const workDirectory = isMonorepo ? apps.find((a) => a.deployMethod !== "none")?.path ?? "." : ".";
|
|
6371
|
+
const hasTriggerDev = await Promise.any([
|
|
6372
|
+
fileExists("packages/jobs/trigger.config.ts"),
|
|
6373
|
+
fileExists("trigger.config.ts")
|
|
6374
|
+
]).catch(() => false);
|
|
6375
|
+
return {
|
|
6376
|
+
isMonorepo,
|
|
6377
|
+
apps,
|
|
6378
|
+
workDirectory,
|
|
6379
|
+
hasTriggerDev: !!hasTriggerDev
|
|
6380
|
+
};
|
|
6381
|
+
}
|
|
6382
|
+
var QUICK_STAGE_DEFAULTS = {
|
|
6383
|
+
dev: {
|
|
6384
|
+
name: "Development",
|
|
6385
|
+
releaseBranch: "release-dev",
|
|
6386
|
+
triggerMode: "pr_webhook",
|
|
6387
|
+
autoApprove: true,
|
|
6388
|
+
requireReview: false,
|
|
6389
|
+
backupDb: false,
|
|
6390
|
+
migrateDb: true
|
|
6391
|
+
},
|
|
6392
|
+
staging: {
|
|
6393
|
+
name: "Staging",
|
|
6394
|
+
releaseBranch: "release-staging",
|
|
6395
|
+
triggerMode: "manual",
|
|
6396
|
+
autoApprove: false,
|
|
6397
|
+
requireReview: false,
|
|
6398
|
+
backupDb: true,
|
|
6399
|
+
migrateDb: true
|
|
6400
|
+
},
|
|
6401
|
+
prod: {
|
|
6402
|
+
name: "Production",
|
|
6403
|
+
releaseBranch: "release-prod",
|
|
6404
|
+
triggerMode: "manual",
|
|
6405
|
+
autoApprove: false,
|
|
6406
|
+
requireReview: false,
|
|
6407
|
+
backupDb: true,
|
|
6408
|
+
migrateDb: true
|
|
6409
|
+
}
|
|
6410
|
+
};
|
|
6411
|
+
var QUICK_STAGES_FOR_TYPE = {
|
|
6412
|
+
dev_only: ["dev"],
|
|
6413
|
+
prod_only: ["prod"],
|
|
6414
|
+
both: ["dev", "prod"]
|
|
6415
|
+
};
|
|
6416
|
+
function deriveProfileName(repoFullName) {
|
|
6417
|
+
const repoName = repoFullName.split("/").pop() ?? repoFullName;
|
|
6418
|
+
return repoName.split(/[-_]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
6419
|
+
}
|
|
6420
|
+
function toStageApps(apps) {
|
|
6421
|
+
return apps.filter((a) => a.enabled).map((a) => ({
|
|
6422
|
+
path: a.path,
|
|
6423
|
+
label: a.label,
|
|
6424
|
+
deployMethod: a.deployMethod,
|
|
6425
|
+
deploymentAppId: null,
|
|
6426
|
+
deploymentServerId: null,
|
|
6427
|
+
deployPath: null,
|
|
6428
|
+
deployScript: null,
|
|
6429
|
+
enabled: a.enabled
|
|
6430
|
+
}));
|
|
6431
|
+
}
|
|
6432
|
+
async function ensureGitHubBranchExists(repoFullName, branchName, fromBranch, githubToken2) {
|
|
6433
|
+
const headers = {
|
|
6434
|
+
Accept: "application/vnd.github.v3+json",
|
|
6435
|
+
Authorization: `Bearer ${githubToken2}`,
|
|
6436
|
+
"Content-Type": "application/json"
|
|
6437
|
+
};
|
|
6438
|
+
const checkRes = await fetch(
|
|
6439
|
+
`${GITHUB_API3}/repos/${repoFullName}/git/ref/heads/${branchName}`,
|
|
6440
|
+
{ headers }
|
|
6441
|
+
);
|
|
6442
|
+
if (checkRes.ok) return;
|
|
6443
|
+
let sha;
|
|
6444
|
+
const sourceRes = await fetch(
|
|
6445
|
+
`${GITHUB_API3}/repos/${repoFullName}/git/ref/heads/${fromBranch}`,
|
|
6446
|
+
{ headers }
|
|
6447
|
+
);
|
|
6448
|
+
if (sourceRes.ok) {
|
|
6449
|
+
const data = await sourceRes.json();
|
|
6450
|
+
sha = data.object?.sha;
|
|
6451
|
+
} else {
|
|
6452
|
+
const repoRes = await fetch(`${GITHUB_API3}/repos/${repoFullName}`, {
|
|
6453
|
+
headers
|
|
6454
|
+
});
|
|
6455
|
+
if (!repoRes.ok) {
|
|
6456
|
+
throw new Error(`Could not access repository ${repoFullName}`);
|
|
6457
|
+
}
|
|
6458
|
+
const repo = await repoRes.json();
|
|
6459
|
+
const defaultBranch = repo.default_branch ?? "main";
|
|
6460
|
+
const defaultRes = await fetch(
|
|
6461
|
+
`${GITHUB_API3}/repos/${repoFullName}/git/ref/heads/${defaultBranch}`,
|
|
6462
|
+
{ headers }
|
|
6463
|
+
);
|
|
6464
|
+
if (!defaultRes.ok) {
|
|
6465
|
+
throw new Error(`Could not resolve a source branch for ${repoFullName}`);
|
|
6466
|
+
}
|
|
6467
|
+
const data = await defaultRes.json();
|
|
6468
|
+
sha = data.object?.sha;
|
|
6469
|
+
}
|
|
6470
|
+
if (!sha) {
|
|
6471
|
+
throw new Error(`Could not determine SHA to create branch ${branchName}`);
|
|
6472
|
+
}
|
|
6473
|
+
const createRes = await fetch(
|
|
6474
|
+
`${GITHUB_API3}/repos/${repoFullName}/git/refs`,
|
|
6475
|
+
{
|
|
6476
|
+
method: "POST",
|
|
6477
|
+
headers,
|
|
6478
|
+
body: JSON.stringify({ ref: `refs/heads/${branchName}`, sha })
|
|
6479
|
+
}
|
|
6480
|
+
);
|
|
6481
|
+
if (!createRes.ok && createRes.status !== 422) {
|
|
6482
|
+
const body = await createRes.text().catch(() => "");
|
|
6483
|
+
throw new Error(
|
|
6484
|
+
`Failed to create branch ${branchName}: ${createRes.status} ${body}`
|
|
6485
|
+
);
|
|
6486
|
+
}
|
|
6487
|
+
}
|
|
6488
|
+
async function insertQuickStages(profileId, releaseType, stageApps) {
|
|
6489
|
+
const db2 = getDb();
|
|
6490
|
+
const stages = [];
|
|
6491
|
+
const wanted = QUICK_STAGES_FOR_TYPE[releaseType];
|
|
6492
|
+
for (let i = 0; i < wanted.length; i++) {
|
|
6493
|
+
const stageType = wanted[i];
|
|
6494
|
+
const defaults = QUICK_STAGE_DEFAULTS[stageType];
|
|
6495
|
+
const rows = await db2.execute(sql`
|
|
6496
|
+
INSERT INTO release_profile_stage (
|
|
6497
|
+
release_profile_id, name, stage, stage_order, release_branch,
|
|
6498
|
+
trigger_mode, auto_approve, require_review,
|
|
6499
|
+
backup_db, migrate_db, stage_apps, enabled,
|
|
6500
|
+
trigger_branch, trigger_event
|
|
6501
|
+
) VALUES (
|
|
6502
|
+
${profileId}, ${defaults.name}, ${stageType}, ${i + 1},
|
|
6503
|
+
${defaults.releaseBranch},
|
|
6504
|
+
${defaults.triggerMode}, ${defaults.autoApprove}, ${defaults.requireReview},
|
|
6505
|
+
${defaults.backupDb}, ${defaults.migrateDb},
|
|
6506
|
+
${JSON.stringify(stageApps)}::jsonb, true,
|
|
6507
|
+
${defaults.releaseBranch}, 'push'
|
|
6508
|
+
)
|
|
6509
|
+
RETURNING id, name, stage, release_branch, trigger_mode
|
|
6510
|
+
`);
|
|
6511
|
+
if (rows[0]) stages.push(rows[0]);
|
|
6512
|
+
}
|
|
6513
|
+
return stages;
|
|
6514
|
+
}
|
|
6515
|
+
async function quickCreateReleaseProfile(userId, options) {
|
|
6516
|
+
const db2 = getDb();
|
|
6517
|
+
const repoFullName = options.repoFullName.trim();
|
|
6518
|
+
if (!repoFullName.includes("/")) {
|
|
6519
|
+
throw new ReleaseProfileQuickCreateError(
|
|
6520
|
+
`Repository must be in "owner/name" format, got "${repoFullName}"`,
|
|
6521
|
+
"BAD_REQUEST"
|
|
6522
|
+
);
|
|
6523
|
+
}
|
|
6524
|
+
const existing = await db2.execute(sql`
|
|
6525
|
+
SELECT id, name FROM release_profile
|
|
6526
|
+
WHERE lower(repo_full_name) = ${repoFullName.toLowerCase()}
|
|
6527
|
+
LIMIT 1
|
|
6528
|
+
`);
|
|
6529
|
+
if (existing[0]) {
|
|
6530
|
+
throw new ReleaseProfileQuickCreateError(
|
|
6531
|
+
`Repository ${repoFullName} already has a release profile ("${existing[0].name}")`,
|
|
6532
|
+
"CONFLICT"
|
|
6533
|
+
);
|
|
6534
|
+
}
|
|
6535
|
+
const githubTokenId = options.githubTokenId ?? null;
|
|
6536
|
+
const { token, error: tokenError } = await resolveReleaseGitHubToken(githubTokenId);
|
|
6537
|
+
if (!token) {
|
|
6538
|
+
throw new ReleaseProfileQuickCreateError(
|
|
6539
|
+
tokenError || "GitHub token is not configured",
|
|
6540
|
+
"PRECONDITION_FAILED"
|
|
6541
|
+
);
|
|
6542
|
+
}
|
|
6543
|
+
let scan;
|
|
6544
|
+
try {
|
|
6545
|
+
scan = await scanRepoForApps(repoFullName, token);
|
|
6546
|
+
} catch (err) {
|
|
6547
|
+
throw new ReleaseProfileQuickCreateError(
|
|
6548
|
+
`Failed to scan repository ${repoFullName}: ${err instanceof Error ? err.message : String(err)}`,
|
|
6549
|
+
"BAD_REQUEST"
|
|
6550
|
+
);
|
|
6551
|
+
}
|
|
6552
|
+
const name = options.name?.trim() || deriveProfileName(repoFullName);
|
|
6553
|
+
const releaseType = options.releaseType ?? "both";
|
|
6554
|
+
const detectedApps = toStageApps(scan.apps);
|
|
6555
|
+
let profile;
|
|
6556
|
+
try {
|
|
6557
|
+
const rows = await db2.execute(sql`
|
|
6558
|
+
INSERT INTO release_profile (
|
|
6559
|
+
name, repo_full_name, work_directory, enabled, release_type,
|
|
6560
|
+
current_version, active_version, created_by, use_changes_branch,
|
|
6561
|
+
detected_apps, has_trigger_dev, github_token_id
|
|
6562
|
+
) VALUES (
|
|
6563
|
+
${name}, ${repoFullName}, ${scan.workDirectory}, true, ${releaseType},
|
|
6564
|
+
NULL, NULL, ${userId}, false,
|
|
6565
|
+
${JSON.stringify(detectedApps)}::jsonb, ${scan.hasTriggerDev},
|
|
6566
|
+
${githubTokenId}
|
|
6567
|
+
)
|
|
6568
|
+
RETURNING *
|
|
6569
|
+
`);
|
|
6570
|
+
profile = rows[0];
|
|
6571
|
+
} catch (err) {
|
|
6572
|
+
throw new ReleaseProfileQuickCreateError(
|
|
6573
|
+
`Failed to create release profile: ${err instanceof Error ? err.message : String(err)}`,
|
|
6574
|
+
"INTERNAL_SERVER_ERROR"
|
|
6575
|
+
);
|
|
6576
|
+
}
|
|
6577
|
+
if (!profile) {
|
|
6578
|
+
throw new ReleaseProfileQuickCreateError(
|
|
6579
|
+
"Release profile insert returned no rows",
|
|
6580
|
+
"INTERNAL_SERVER_ERROR"
|
|
6581
|
+
);
|
|
6582
|
+
}
|
|
6583
|
+
const stages = await insertQuickStages(profile.id, releaseType, detectedApps);
|
|
6584
|
+
const branchWarnings = [];
|
|
6585
|
+
for (const stage of stages) {
|
|
6586
|
+
if (!stage.release_branch) continue;
|
|
6587
|
+
try {
|
|
6588
|
+
await ensureGitHubBranchExists(
|
|
6589
|
+
repoFullName,
|
|
6590
|
+
stage.release_branch,
|
|
6591
|
+
"main",
|
|
6592
|
+
token
|
|
6593
|
+
);
|
|
6594
|
+
} catch (err) {
|
|
6595
|
+
branchWarnings.push(
|
|
6596
|
+
`Could not create branch ${stage.release_branch}: ${err instanceof Error ? err.message : String(err)}`
|
|
6597
|
+
);
|
|
6598
|
+
}
|
|
6599
|
+
}
|
|
6600
|
+
return { profile, stages, scan, branchWarnings };
|
|
6601
|
+
}
|
|
6602
|
+
async function updateReleaseProfileSettings(profileId, fields) {
|
|
6603
|
+
const db2 = getDb();
|
|
6604
|
+
const profileRows = await db2.execute(sql`
|
|
6605
|
+
SELECT id, release_type, detected_apps
|
|
6606
|
+
FROM release_profile WHERE id = ${profileId} LIMIT 1
|
|
6607
|
+
`);
|
|
6608
|
+
const current = profileRows[0];
|
|
6609
|
+
if (!current) {
|
|
6610
|
+
throw new ReleaseProfileQuickCreateError(
|
|
6611
|
+
"Release profile not found",
|
|
6612
|
+
"NOT_FOUND"
|
|
6613
|
+
);
|
|
6614
|
+
}
|
|
6615
|
+
const setExprs = [];
|
|
6616
|
+
if (fields.name !== void 0) setExprs.push(sql`name = ${fields.name}`);
|
|
6617
|
+
if (fields.enabled !== void 0)
|
|
6618
|
+
setExprs.push(sql`enabled = ${fields.enabled}`);
|
|
6619
|
+
if (fields.releaseType !== void 0)
|
|
6620
|
+
setExprs.push(sql`release_type = ${fields.releaseType}`);
|
|
6621
|
+
if (fields.githubTokenId !== void 0)
|
|
6622
|
+
setExprs.push(sql`github_token_id = ${fields.githubTokenId}`);
|
|
6623
|
+
if (fields.useChangesBranch !== void 0)
|
|
6624
|
+
setExprs.push(sql`use_changes_branch = ${fields.useChangesBranch}`);
|
|
6625
|
+
if (fields.hasTriggerDev !== void 0)
|
|
6626
|
+
setExprs.push(sql`has_trigger_dev = ${fields.hasTriggerDev}`);
|
|
6627
|
+
if (fields.workDirectory !== void 0)
|
|
6628
|
+
setExprs.push(sql`work_directory = ${fields.workDirectory}`);
|
|
6629
|
+
if (setExprs.length === 0) {
|
|
6630
|
+
throw new ReleaseProfileQuickCreateError(
|
|
6631
|
+
"No fields to update",
|
|
6632
|
+
"BAD_REQUEST"
|
|
6633
|
+
);
|
|
6634
|
+
}
|
|
6635
|
+
const rows = await db2.execute(sql`
|
|
6636
|
+
UPDATE release_profile
|
|
6637
|
+
SET ${sql.join(setExprs, sql`, `)}
|
|
6638
|
+
WHERE id = ${profileId}
|
|
6639
|
+
RETURNING *
|
|
6640
|
+
`);
|
|
6641
|
+
const profile = rows[0];
|
|
6642
|
+
if (!profile) {
|
|
6643
|
+
throw new ReleaseProfileQuickCreateError(
|
|
6644
|
+
"Release profile update returned no rows",
|
|
6645
|
+
"INTERNAL_SERVER_ERROR"
|
|
6646
|
+
);
|
|
6647
|
+
}
|
|
6648
|
+
let stagesReconfigured = false;
|
|
6649
|
+
if (fields.releaseType !== void 0 && fields.releaseType !== current.release_type) {
|
|
6650
|
+
await reconfigureQuickStages(
|
|
6651
|
+
profileId,
|
|
6652
|
+
fields.releaseType,
|
|
6653
|
+
current.detected_apps ?? []
|
|
6654
|
+
);
|
|
6655
|
+
stagesReconfigured = true;
|
|
6656
|
+
}
|
|
6657
|
+
return { profile, stagesReconfigured };
|
|
6658
|
+
}
|
|
6659
|
+
async function reconfigureQuickStages(profileId, releaseType, detectedApps) {
|
|
6660
|
+
const db2 = getDb();
|
|
6661
|
+
const wanted = QUICK_STAGES_FOR_TYPE[releaseType];
|
|
6662
|
+
const existingStages = await db2.execute(sql`
|
|
6663
|
+
SELECT id, stage, release_branch
|
|
6664
|
+
FROM release_profile_stage
|
|
6665
|
+
WHERE release_profile_id = ${profileId}
|
|
6666
|
+
`);
|
|
6667
|
+
const existingByStage = new Map(existingStages.map((s) => [s.stage, s]));
|
|
6668
|
+
for (let i = 0; i < wanted.length; i++) {
|
|
6669
|
+
const stageType = wanted[i];
|
|
6670
|
+
const defaults = QUICK_STAGE_DEFAULTS[stageType];
|
|
6671
|
+
const existing = existingByStage.get(stageType);
|
|
6672
|
+
if (existing) {
|
|
6673
|
+
await db2.execute(sql`
|
|
6674
|
+
UPDATE release_profile_stage
|
|
6675
|
+
SET stage_order = ${i + 1},
|
|
6676
|
+
release_branch = ${existing.release_branch ?? defaults.releaseBranch},
|
|
6677
|
+
enabled = true
|
|
6678
|
+
WHERE id = ${existing.id}
|
|
6679
|
+
`);
|
|
6680
|
+
} else {
|
|
6681
|
+
await db2.execute(sql`
|
|
6682
|
+
INSERT INTO release_profile_stage (
|
|
6683
|
+
release_profile_id, name, stage, stage_order, release_branch,
|
|
6684
|
+
trigger_mode, auto_approve, require_review,
|
|
6685
|
+
backup_db, migrate_db, stage_apps, enabled,
|
|
6686
|
+
trigger_branch, trigger_event
|
|
6687
|
+
) VALUES (
|
|
6688
|
+
${profileId}, ${defaults.name}, ${stageType}, ${i + 1},
|
|
6689
|
+
${defaults.releaseBranch},
|
|
6690
|
+
${defaults.triggerMode}, ${defaults.autoApprove}, ${defaults.requireReview},
|
|
6691
|
+
${defaults.backupDb}, ${defaults.migrateDb},
|
|
6692
|
+
${JSON.stringify(detectedApps)}::jsonb, true,
|
|
6693
|
+
${defaults.releaseBranch}, 'push'
|
|
6694
|
+
)
|
|
6695
|
+
`);
|
|
6696
|
+
}
|
|
6697
|
+
}
|
|
6698
|
+
const wantedSet = new Set(wanted);
|
|
6699
|
+
for (const existing of existingStages) {
|
|
6700
|
+
if (wantedSet.has(existing.stage)) continue;
|
|
6701
|
+
await db2.execute(sql`
|
|
6702
|
+
UPDATE release_profile_stage SET enabled = false WHERE id = ${existing.id}
|
|
6703
|
+
`);
|
|
6704
|
+
}
|
|
6705
|
+
}
|
|
5615
6706
|
var args = process.argv.slice(2);
|
|
5616
6707
|
function getArg2(name) {
|
|
5617
6708
|
return args.find((a) => a.startsWith(`--${name}=`))?.split("=").slice(1).join("=");
|
|
@@ -5853,6 +6944,9 @@ var TOOL_MODULE_MAP = {
|
|
|
5853
6944
|
"env-get": "ci_cd",
|
|
5854
6945
|
"env-store": "ci_cd",
|
|
5855
6946
|
"release-trigger": "ci_cd",
|
|
6947
|
+
"release-profile-list": "ci_cd",
|
|
6948
|
+
"release-profile-create": "ci_cd",
|
|
6949
|
+
"release-profile-update": "ci_cd",
|
|
5856
6950
|
"domain-list": "domains",
|
|
5857
6951
|
"dns-list": "domains",
|
|
5858
6952
|
"dns-record": "domains",
|
|
@@ -6233,6 +7327,23 @@ async function resolveReleaseProfileStageIds(profileName) {
|
|
|
6233
7327
|
}
|
|
6234
7328
|
return { stageIds: stages.map((s) => s.id), profileId: profile.id };
|
|
6235
7329
|
}
|
|
7330
|
+
async function resolveGitHubTokenIdByKey(key) {
|
|
7331
|
+
const trimmed = key.trim();
|
|
7332
|
+
const rows = await db.execute(sql`
|
|
7333
|
+
SELECT id FROM github_token
|
|
7334
|
+
WHERE label ILIKE ${trimmed} OR owner ILIKE ${trimmed} OR id::text = ${trimmed}
|
|
7335
|
+
ORDER BY label
|
|
7336
|
+
LIMIT 1
|
|
7337
|
+
`);
|
|
7338
|
+
if (rows[0]) return rows[0].id;
|
|
7339
|
+
const all = await db.execute(
|
|
7340
|
+
sql`SELECT label, owner FROM github_token ORDER BY label`
|
|
7341
|
+
);
|
|
7342
|
+
const available = all.map((t) => t.owner ? `${t.label} (${t.owner})` : t.label).join(", ");
|
|
7343
|
+
throw new Error(
|
|
7344
|
+
`GitHub key "${key}" not found. Available keys: ${available || "(none)"}. Omit githubKey to use the default MG Software token.`
|
|
7345
|
+
);
|
|
7346
|
+
}
|
|
6236
7347
|
async function resolveReleaseTriggerStage(profileName, stageFilter) {
|
|
6237
7348
|
const profileRows = await db.execute(sql`
|
|
6238
7349
|
SELECT id, name FROM release_profile
|
|
@@ -6324,7 +7435,7 @@ function getEncryptionKey() {
|
|
|
6324
7435
|
throw new Error("ENCRYPTION_KEY must be a 64-character hex string");
|
|
6325
7436
|
return buf;
|
|
6326
7437
|
}
|
|
6327
|
-
function encrypt(
|
|
7438
|
+
function encrypt(text6) {
|
|
6328
7439
|
const key = getEncryptionKey();
|
|
6329
7440
|
const iv = randomBytes(ENC_IV_LENGTH);
|
|
6330
7441
|
const cipher = createCipheriv(
|
|
@@ -6332,7 +7443,7 @@ function encrypt(text) {
|
|
|
6332
7443
|
new Uint8Array(key),
|
|
6333
7444
|
new Uint8Array(iv)
|
|
6334
7445
|
);
|
|
6335
|
-
let encrypted = cipher.update(
|
|
7446
|
+
let encrypted = cipher.update(text6, "utf8", "hex");
|
|
6336
7447
|
encrypted += cipher.final("hex");
|
|
6337
7448
|
const authTag = cipher.getAuthTag();
|
|
6338
7449
|
return Buffer.concat([
|
|
@@ -7052,10 +8163,10 @@ async function r2GetObjectRange(bucket, key, range) {
|
|
|
7052
8163
|
const body = result.Body;
|
|
7053
8164
|
if (!body?.transformToString)
|
|
7054
8165
|
throw new Error("R2 returned no readable body");
|
|
7055
|
-
const
|
|
8166
|
+
const text6 = await body.transformToString();
|
|
7056
8167
|
const header = `# range: bytes ${range.offset}-${end} of ${size} (${effectiveLen} bytes)`;
|
|
7057
8168
|
return `${header}
|
|
7058
|
-
${
|
|
8169
|
+
${text6}`;
|
|
7059
8170
|
} catch (e) {
|
|
7060
8171
|
throw r2WrapError(bucket, key, e);
|
|
7061
8172
|
}
|
|
@@ -7418,15 +8529,15 @@ async function sftpRead(opts, filePath, proxy, options) {
|
|
|
7418
8529
|
clearTimeout(timer);
|
|
7419
8530
|
cleanup?.();
|
|
7420
8531
|
cleanup = void 0;
|
|
7421
|
-
const
|
|
8532
|
+
const text6 = Buffer.concat(
|
|
7422
8533
|
chunks.map((ch) => new Uint8Array(ch))
|
|
7423
8534
|
).toString("utf-8");
|
|
7424
8535
|
if (!isWholeFileRequest) {
|
|
7425
8536
|
const header = `# range: bytes ${offset}-${offset + effectiveLen - 1} of ${total} (${effectiveLen} bytes)`;
|
|
7426
8537
|
resolve(`${header}
|
|
7427
|
-
${
|
|
8538
|
+
${text6}`);
|
|
7428
8539
|
} else {
|
|
7429
|
-
resolve(
|
|
8540
|
+
resolve(text6);
|
|
7430
8541
|
}
|
|
7431
8542
|
});
|
|
7432
8543
|
rs.on("error", (e) => {
|
|
@@ -7556,11 +8667,11 @@ function getKnownContainers(serverId, maxAgeMs = 5 * 6e4) {
|
|
|
7556
8667
|
if (Date.now() - e.capturedAt > maxAgeMs) return void 0;
|
|
7557
8668
|
return e.names;
|
|
7558
8669
|
}
|
|
7559
|
-
function truncateForLLM(
|
|
7560
|
-
const totalBytes = Buffer.byteLength(
|
|
8670
|
+
function truncateForLLM(text6, maxBytes) {
|
|
8671
|
+
const totalBytes = Buffer.byteLength(text6, "utf8");
|
|
7561
8672
|
if (totalBytes <= maxBytes)
|
|
7562
|
-
return { text, truncated: false, totalBytes, shownBytes: totalBytes };
|
|
7563
|
-
const buf = Buffer.from(
|
|
8673
|
+
return { text: text6, truncated: false, totalBytes, shownBytes: totalBytes };
|
|
8674
|
+
const buf = Buffer.from(text6, "utf8");
|
|
7564
8675
|
let cut = maxBytes;
|
|
7565
8676
|
while (cut > 0 && (buf[cut] & 192) === 128) cut--;
|
|
7566
8677
|
const head = buf.subarray(0, cut).toString("utf8");
|
|
@@ -7590,10 +8701,10 @@ function isTransientSshError(stderr, exitCode) {
|
|
|
7590
8701
|
function postprocessResult(result, meta) {
|
|
7591
8702
|
if (!result.content?.length) return result;
|
|
7592
8703
|
const block = result.content[0];
|
|
7593
|
-
let
|
|
7594
|
-
const trunc = truncateForLLM(
|
|
8704
|
+
let text6 = String(block.text ?? "");
|
|
8705
|
+
const trunc = truncateForLLM(text6, RESPONSE_MAX_BYTES);
|
|
7595
8706
|
if (trunc.truncated) {
|
|
7596
|
-
|
|
8707
|
+
text6 = trunc.text + "\n\n... " + buildTruncationHint(
|
|
7597
8708
|
meta.toolName,
|
|
7598
8709
|
meta.args,
|
|
7599
8710
|
trunc.totalBytes,
|
|
@@ -7607,11 +8718,11 @@ function postprocessResult(result, meta) {
|
|
|
7607
8718
|
const parts = [`took ${tookStr}`, sizeStr];
|
|
7608
8719
|
if (meta.serverIdLabel) parts.push(`server: ${meta.serverIdLabel}`);
|
|
7609
8720
|
if (meta.cached) parts.push("cached");
|
|
7610
|
-
|
|
8721
|
+
text6 = `${text6}
|
|
7611
8722
|
|
|
7612
8723
|
[${parts.join(", ")}]`;
|
|
7613
8724
|
}
|
|
7614
|
-
return { ...result, content: [{ ...block, text }] };
|
|
8725
|
+
return { ...result, content: [{ ...block, text: text6 }] };
|
|
7615
8726
|
}
|
|
7616
8727
|
function buildPipelineScript(commands, shell, marker, stopOnError) {
|
|
7617
8728
|
if (shell === "powershell") {
|
|
@@ -8397,11 +9508,11 @@ CREATE TABLE IF NOT EXISTS _mcp_migrations (
|
|
|
8397
9508
|
applied_by TEXT
|
|
8398
9509
|
);
|
|
8399
9510
|
`.trim();
|
|
8400
|
-
function normaliseMigrationSql(
|
|
8401
|
-
return
|
|
9511
|
+
function normaliseMigrationSql(sql28) {
|
|
9512
|
+
return sql28.replace(/\r\n/g, "\n").trim() + "\n";
|
|
8402
9513
|
}
|
|
8403
|
-
function migrationSha256(
|
|
8404
|
-
return createHash("sha256").update(normaliseMigrationSql(
|
|
9514
|
+
function migrationSha256(sql28) {
|
|
9515
|
+
return createHash("sha256").update(normaliseMigrationSql(sql28), "utf8").digest("hex");
|
|
8405
9516
|
}
|
|
8406
9517
|
function dollarQuoteTag(value) {
|
|
8407
9518
|
let tag = "_mcp";
|
|
@@ -9468,6 +10579,79 @@ var TOOLS = [
|
|
|
9468
10579
|
required: ["releaseProfile"]
|
|
9469
10580
|
}
|
|
9470
10581
|
},
|
|
10582
|
+
{
|
|
10583
|
+
name: "release-profile-list",
|
|
10584
|
+
description: "List all release profiles with repo, enabled state, release type, GitHub key and a summary of their stages. Requires ci_cd permission.",
|
|
10585
|
+
inputSchema: {
|
|
10586
|
+
type: "object",
|
|
10587
|
+
properties: {},
|
|
10588
|
+
required: []
|
|
10589
|
+
}
|
|
10590
|
+
},
|
|
10591
|
+
{
|
|
10592
|
+
name: "release-profile-create",
|
|
10593
|
+
description: "One-step release profile creation: scans the GitHub repo, detects apps (Next.js/Docker/PM2), creates the profile plus dev/prod stages with smart defaults (dev: release-dev + PR webhook + auto-approve, prod: release-prod + manual + DB backup) and seeds the stage apps. Release branches are created on GitHub automatically. Deployment servers must be assigned afterwards in the dashboard (Releases \u2192 Profiles). Requires ci_cd permission.",
|
|
10594
|
+
inputSchema: {
|
|
10595
|
+
type: "object",
|
|
10596
|
+
properties: {
|
|
10597
|
+
repoFullName: {
|
|
10598
|
+
type: "string",
|
|
10599
|
+
description: 'Repository in "owner/name" format (e.g. MGSoftwareBV/mg-dashboard).'
|
|
10600
|
+
},
|
|
10601
|
+
githubKey: {
|
|
10602
|
+
type: "string",
|
|
10603
|
+
description: 'Named GitHub key (github_token label or owner, e.g. "AVARC Solutions") used for all GitHub API calls for this profile. Omit for the default MG Software token.'
|
|
10604
|
+
},
|
|
10605
|
+
name: {
|
|
10606
|
+
type: "string",
|
|
10607
|
+
description: 'Profile name override. Default: title-cased repo name (mg-dashboard \u2192 "Mg Dashboard").'
|
|
10608
|
+
},
|
|
10609
|
+
releaseType: {
|
|
10610
|
+
type: "string",
|
|
10611
|
+
enum: ["dev_only", "prod_only", "both"],
|
|
10612
|
+
description: "Which stages to create (default: both = dev + prod)."
|
|
10613
|
+
}
|
|
10614
|
+
},
|
|
10615
|
+
required: ["repoFullName"]
|
|
10616
|
+
}
|
|
10617
|
+
},
|
|
10618
|
+
{
|
|
10619
|
+
name: "release-profile-update",
|
|
10620
|
+
description: "Update release profile settings. Locate the profile by name or repo. Changing releaseType reconfigures the stages (missing stages are created with smart defaults, out-of-scope stages are disabled). Requires ci_cd permission.",
|
|
10621
|
+
inputSchema: {
|
|
10622
|
+
type: "object",
|
|
10623
|
+
properties: {
|
|
10624
|
+
releaseProfile: {
|
|
10625
|
+
type: "string",
|
|
10626
|
+
description: "Profile name or repo full name to locate the profile."
|
|
10627
|
+
},
|
|
10628
|
+
name: { type: "string", description: "New profile name." },
|
|
10629
|
+
enabled: { type: "boolean", description: "Enable/disable the profile." },
|
|
10630
|
+
releaseType: {
|
|
10631
|
+
type: "string",
|
|
10632
|
+
enum: ["dev_only", "prod_only", "both"],
|
|
10633
|
+
description: "New release type \u2014 reconfigures stages accordingly."
|
|
10634
|
+
},
|
|
10635
|
+
githubKey: {
|
|
10636
|
+
type: "string",
|
|
10637
|
+
description: 'Named GitHub key (github_token label or owner). Pass "default" to reset to the default MG Software token.'
|
|
10638
|
+
},
|
|
10639
|
+
useChangesBranch: {
|
|
10640
|
+
type: "boolean",
|
|
10641
|
+
description: "Use a long-lived -changes accumulation branch."
|
|
10642
|
+
},
|
|
10643
|
+
hasTriggerDev: {
|
|
10644
|
+
type: "boolean",
|
|
10645
|
+
description: "Deploy Trigger.dev jobs during production releases."
|
|
10646
|
+
},
|
|
10647
|
+
workDirectory: {
|
|
10648
|
+
type: "string",
|
|
10649
|
+
description: 'Working directory within the repo (e.g. ".").'
|
|
10650
|
+
}
|
|
10651
|
+
},
|
|
10652
|
+
required: ["releaseProfile"]
|
|
10653
|
+
}
|
|
10654
|
+
},
|
|
9471
10655
|
{
|
|
9472
10656
|
name: "cache-purge",
|
|
9473
10657
|
description: 'Purge caches on a server. Default mode is **safe** (recommended): LiteSpeed file cache, WordPress/LiteSpeed plugin caches via wp-cli, PrestaShop file caches, graceful LiteSpeed reload \u2014 does NOT kill lsphp, FLUSHALL Redis, or hard-restart the web server.\n\nUse `mode: "full"` only when safe purge is insufficient: kills all lsphp (OPcache reset), clears Redis/Memcached entirely, and may hard-restart LiteSpeed \u2014 can briefly take sites offline or serve stale error pages until caches warm up (VCA multi-site risk).',
|
|
@@ -9567,7 +10751,7 @@ var TOOLS = [
|
|
|
9567
10751
|
// ----- Trigger.dev -----
|
|
9568
10752
|
...TRIGGER_TOOLS
|
|
9569
10753
|
];
|
|
9570
|
-
var MCP_VERSION = "7.0.
|
|
10754
|
+
var MCP_VERSION = "7.0.5";
|
|
9571
10755
|
async function handleListTools() {
|
|
9572
10756
|
if (!authContext) return { tools: TOOLS };
|
|
9573
10757
|
const accessible = TOOLS.filter((tool) => {
|
|
@@ -10593,8 +11777,8 @@ ${sample.join("\n")}${files.length > 5 ? `
|
|
|
10593
11777
|
};
|
|
10594
11778
|
const filtered = sortRows(applyFilter(only.rows));
|
|
10595
11779
|
if (format === "json") {
|
|
10596
|
-
const
|
|
10597
|
-
return { content: [{ type: "text", text:
|
|
11780
|
+
const text7 = filtered.map((c) => JSON.stringify(c)).join("\n") || "(no containers)";
|
|
11781
|
+
return { content: [{ type: "text", text: text7 }] };
|
|
10598
11782
|
}
|
|
10599
11783
|
if (groupByProject) {
|
|
10600
11784
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -10621,8 +11805,8 @@ ${sample.join("\n")}${files.length > 5 ? `
|
|
|
10621
11805
|
}
|
|
10622
11806
|
const header = `${"NAMES".padEnd(36)} ${"IMAGE".padEnd(40)} ${"STATUS".padEnd(24)} ${"HEALTH".padEnd(10)} ${"PORTS".padEnd(40)} PROJECT`;
|
|
10623
11807
|
const body = filtered.map((r) => `${fmtRow(r)} ${r.Project || ""}`);
|
|
10624
|
-
const
|
|
10625
|
-
return { content: [{ type: "text", text }] };
|
|
11808
|
+
const text6 = filtered.length === 0 ? "(no containers match)" : [header, ...body].join("\n");
|
|
11809
|
+
return { content: [{ type: "text", text: text6 }] };
|
|
10626
11810
|
}
|
|
10627
11811
|
if (format === "json") {
|
|
10628
11812
|
const lines = [];
|
|
@@ -12009,6 +13193,170 @@ LIMIT ${limit};
|
|
|
12009
13193
|
throw err;
|
|
12010
13194
|
}
|
|
12011
13195
|
}
|
|
13196
|
+
case "release-profile-list": {
|
|
13197
|
+
const profiles = await db.execute(sql`
|
|
13198
|
+
SELECT
|
|
13199
|
+
p.id, p.name, p.repo_full_name, p.enabled, p.release_type,
|
|
13200
|
+
p.work_directory, p.use_changes_branch, p.has_trigger_dev,
|
|
13201
|
+
gt.label AS github_key,
|
|
13202
|
+
(
|
|
13203
|
+
SELECT string_agg(
|
|
13204
|
+
s.stage || ' (' || COALESCE(s.release_branch, '-') ||
|
|
13205
|
+
', ' || COALESCE(s.trigger_mode, 'manual') ||
|
|
13206
|
+
CASE WHEN s.enabled THEN '' ELSE ', disabled' END || ')',
|
|
13207
|
+
'; ' ORDER BY s.stage_order
|
|
13208
|
+
)
|
|
13209
|
+
FROM release_profile_stage s
|
|
13210
|
+
WHERE s.release_profile_id = p.id
|
|
13211
|
+
) AS stages
|
|
13212
|
+
FROM release_profile p
|
|
13213
|
+
LEFT JOIN github_token gt ON gt.id = p.github_token_id
|
|
13214
|
+
ORDER BY p.name
|
|
13215
|
+
`);
|
|
13216
|
+
if (profiles.length === 0) {
|
|
13217
|
+
return {
|
|
13218
|
+
content: [{ type: "text", text: "No release profiles found" }]
|
|
13219
|
+
};
|
|
13220
|
+
}
|
|
13221
|
+
const lines = profiles.map((p) => {
|
|
13222
|
+
const flags = [
|
|
13223
|
+
p.enabled ? "enabled" : "disabled",
|
|
13224
|
+
`type=${p.release_type ?? "both"}`,
|
|
13225
|
+
`key=${p.github_key ?? "MG Software (default)"}`,
|
|
13226
|
+
p.use_changes_branch ? "changes-branch" : null,
|
|
13227
|
+
p.has_trigger_dev ? "trigger.dev" : null
|
|
13228
|
+
].filter(Boolean).join(", ");
|
|
13229
|
+
return `${p.name} \u2014 ${p.repo_full_name} [${flags}]
|
|
13230
|
+
stages: ${p.stages ?? "(none)"}`;
|
|
13231
|
+
});
|
|
13232
|
+
return {
|
|
13233
|
+
content: [
|
|
13234
|
+
{
|
|
13235
|
+
type: "text",
|
|
13236
|
+
text: `${profiles.length} release profile(s):
|
|
13237
|
+
|
|
13238
|
+
${lines.join("\n\n")}`
|
|
13239
|
+
}
|
|
13240
|
+
]
|
|
13241
|
+
};
|
|
13242
|
+
}
|
|
13243
|
+
case "release-profile-create": {
|
|
13244
|
+
const repoFullName = String(a.repoFullName ?? "").trim();
|
|
13245
|
+
const githubTokenId = a.githubKey ? await resolveGitHubTokenIdByKey(String(a.githubKey)) : null;
|
|
13246
|
+
const releaseType = a.releaseType ? String(a.releaseType) : null;
|
|
13247
|
+
if (releaseType !== null && releaseType !== "dev_only" && releaseType !== "prod_only" && releaseType !== "both") {
|
|
13248
|
+
throw new Error(
|
|
13249
|
+
`Invalid releaseType "${releaseType}". Expected dev_only, prod_only, or both.`
|
|
13250
|
+
);
|
|
13251
|
+
}
|
|
13252
|
+
try {
|
|
13253
|
+
const result = await quickCreateReleaseProfile(
|
|
13254
|
+
authContext.userId,
|
|
13255
|
+
{
|
|
13256
|
+
repoFullName,
|
|
13257
|
+
githubTokenId,
|
|
13258
|
+
name: a.name ? String(a.name) : null,
|
|
13259
|
+
releaseType
|
|
13260
|
+
}
|
|
13261
|
+
);
|
|
13262
|
+
return {
|
|
13263
|
+
content: [
|
|
13264
|
+
{
|
|
13265
|
+
type: "text",
|
|
13266
|
+
text: JSON.stringify(
|
|
13267
|
+
{
|
|
13268
|
+
profileId: result.profile.id,
|
|
13269
|
+
name: result.profile.name,
|
|
13270
|
+
repo: repoFullName,
|
|
13271
|
+
releaseType: releaseType ?? "both",
|
|
13272
|
+
appsDetected: result.scan.apps.map((app) => ({
|
|
13273
|
+
path: app.path,
|
|
13274
|
+
label: app.label,
|
|
13275
|
+
deployMethod: app.deployMethod
|
|
13276
|
+
})),
|
|
13277
|
+
stages: result.stages.map((s) => ({
|
|
13278
|
+
stage: s.stage,
|
|
13279
|
+
name: s.name,
|
|
13280
|
+
releaseBranch: s.release_branch,
|
|
13281
|
+
triggerMode: s.trigger_mode
|
|
13282
|
+
})),
|
|
13283
|
+
branchWarnings: result.branchWarnings,
|
|
13284
|
+
hint: "Assign deployment servers per stage app in the dashboard (Releases \u2192 Profiles) before triggering a release."
|
|
13285
|
+
},
|
|
13286
|
+
null,
|
|
13287
|
+
2
|
|
13288
|
+
)
|
|
13289
|
+
}
|
|
13290
|
+
]
|
|
13291
|
+
};
|
|
13292
|
+
} catch (err) {
|
|
13293
|
+
if (err instanceof ReleaseProfileQuickCreateError) {
|
|
13294
|
+
throw new Error(err.message);
|
|
13295
|
+
}
|
|
13296
|
+
throw err;
|
|
13297
|
+
}
|
|
13298
|
+
}
|
|
13299
|
+
case "release-profile-update": {
|
|
13300
|
+
const identifier = String(a.releaseProfile ?? "").trim();
|
|
13301
|
+
const profileRows = await db.execute(sql`
|
|
13302
|
+
SELECT id, name FROM release_profile
|
|
13303
|
+
WHERE name ILIKE ${identifier} OR repo_full_name ILIKE ${identifier}
|
|
13304
|
+
LIMIT 1
|
|
13305
|
+
`);
|
|
13306
|
+
const profile = profileRows[0];
|
|
13307
|
+
if (!profile) {
|
|
13308
|
+
const all = await db.execute(
|
|
13309
|
+
sql`SELECT name FROM release_profile ORDER BY name`
|
|
13310
|
+
);
|
|
13311
|
+
throw new Error(
|
|
13312
|
+
`Release profile "${identifier}" not found. Available profiles: ${all.map((p) => p.name).join(", ") || "(none)"}`
|
|
13313
|
+
);
|
|
13314
|
+
}
|
|
13315
|
+
const releaseType = a.releaseType ? String(a.releaseType) : void 0;
|
|
13316
|
+
if (releaseType !== void 0 && releaseType !== "dev_only" && releaseType !== "prod_only" && releaseType !== "both") {
|
|
13317
|
+
throw new Error(
|
|
13318
|
+
`Invalid releaseType "${releaseType}". Expected dev_only, prod_only, or both.`
|
|
13319
|
+
);
|
|
13320
|
+
}
|
|
13321
|
+
let githubTokenId;
|
|
13322
|
+
if (a.githubKey !== void 0) {
|
|
13323
|
+
const key = String(a.githubKey).trim();
|
|
13324
|
+
githubTokenId = !key || key.toLowerCase() === "default" ? null : await resolveGitHubTokenIdByKey(key);
|
|
13325
|
+
}
|
|
13326
|
+
try {
|
|
13327
|
+
const result = await updateReleaseProfileSettings(profile.id, {
|
|
13328
|
+
name: a.name !== void 0 ? String(a.name) : void 0,
|
|
13329
|
+
enabled: a.enabled !== void 0 ? a.enabled === true : void 0,
|
|
13330
|
+
releaseType,
|
|
13331
|
+
githubTokenId,
|
|
13332
|
+
useChangesBranch: a.useChangesBranch !== void 0 ? a.useChangesBranch === true : void 0,
|
|
13333
|
+
hasTriggerDev: a.hasTriggerDev !== void 0 ? a.hasTriggerDev === true : void 0,
|
|
13334
|
+
workDirectory: a.workDirectory !== void 0 ? String(a.workDirectory) : void 0
|
|
13335
|
+
});
|
|
13336
|
+
return {
|
|
13337
|
+
content: [
|
|
13338
|
+
{
|
|
13339
|
+
type: "text",
|
|
13340
|
+
text: JSON.stringify(
|
|
13341
|
+
{
|
|
13342
|
+
profileId: profile.id,
|
|
13343
|
+
name: result.profile.name,
|
|
13344
|
+
updated: true,
|
|
13345
|
+
stagesReconfigured: result.stagesReconfigured
|
|
13346
|
+
},
|
|
13347
|
+
null,
|
|
13348
|
+
2
|
|
13349
|
+
)
|
|
13350
|
+
}
|
|
13351
|
+
]
|
|
13352
|
+
};
|
|
13353
|
+
} catch (err) {
|
|
13354
|
+
if (err instanceof ReleaseProfileQuickCreateError) {
|
|
13355
|
+
throw new Error(err.message);
|
|
13356
|
+
}
|
|
13357
|
+
throw err;
|
|
13358
|
+
}
|
|
13359
|
+
}
|
|
12012
13360
|
// ----- Cache Purge -----
|
|
12013
13361
|
case "cache-purge": {
|
|
12014
13362
|
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
@@ -12031,7 +13379,7 @@ LIMIT ${limit};
|
|
|
12031
13379
|
// ----- Domains (mijn.host) -----
|
|
12032
13380
|
case "domain-list": {
|
|
12033
13381
|
const res = await mijnhostFetch(
|
|
12034
|
-
"/domains"
|
|
13382
|
+
"/domains/"
|
|
12035
13383
|
);
|
|
12036
13384
|
const domains = res.data.domains;
|
|
12037
13385
|
if (!domains.length) {
|