@mgsoftwarebv/mg-dashboard-mcp 7.0.4 → 7.0.6
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 +748 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -677,10 +677,10 @@ var TRIGGER_TOOL_MODULE_MAP = {
|
|
|
677
677
|
"trigger-run": "ci_cd"
|
|
678
678
|
};
|
|
679
679
|
async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
|
|
680
|
-
const
|
|
680
|
+
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
681
|
const cmd = [
|
|
682
682
|
`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 "${
|
|
683
|
+
`KEY=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql28}" 2>/dev/null | tr -d '[:space:]')`,
|
|
684
684
|
'echo "$PORT|$KEY"'
|
|
685
685
|
].join(" && ");
|
|
686
686
|
const result = await sshExec2(conn, cmd, proxy);
|
|
@@ -701,8 +701,8 @@ async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
|
|
|
701
701
|
return { port, apiKey: apiKey2 };
|
|
702
702
|
}
|
|
703
703
|
async function fetchRunLogs(runId, conn, proxy, sshExec2) {
|
|
704
|
-
const
|
|
705
|
-
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
704
|
+
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`;
|
|
705
|
+
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql28}" 2>/dev/null`;
|
|
706
706
|
const result = await sshExec2(conn, cmd, proxy);
|
|
707
707
|
const output = result.stdout.trim();
|
|
708
708
|
if (!output) return "";
|
|
@@ -784,8 +784,8 @@ async function handleTriggerTool(name, args2, deps) {
|
|
|
784
784
|
switch (name) {
|
|
785
785
|
// -----------------------------------------------------------------
|
|
786
786
|
case "trigger-list": {
|
|
787
|
-
const
|
|
788
|
-
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
787
|
+
const sql28 = 'SELECT slug, name FROM \\"Project\\" ORDER BY name';
|
|
788
|
+
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql28}" 2>/dev/null`;
|
|
789
789
|
const result = await sshExec2(conn, cmd, proxy);
|
|
790
790
|
const output = result.stdout.trim();
|
|
791
791
|
if (!output) {
|
|
@@ -1024,6 +1024,39 @@ async function getMgBoilerGitHubToken() {
|
|
|
1024
1024
|
return { token: null, error: "Failed to decrypt GitHub token" };
|
|
1025
1025
|
}
|
|
1026
1026
|
}
|
|
1027
|
+
async function getGitHubTokenById(id) {
|
|
1028
|
+
try {
|
|
1029
|
+
const rows = await getDb().execute(sql`
|
|
1030
|
+
SELECT id, label, owner, notes, token_encrypted, created_at, updated_at
|
|
1031
|
+
FROM github_token
|
|
1032
|
+
WHERE id = ${id}
|
|
1033
|
+
LIMIT 1
|
|
1034
|
+
`);
|
|
1035
|
+
const row = rows[0];
|
|
1036
|
+
if (!row)
|
|
1037
|
+
return { token: null, owner: null, error: null };
|
|
1038
|
+
return {
|
|
1039
|
+
token: decrypt(row.token_encrypted),
|
|
1040
|
+
owner: row.owner ?? null,
|
|
1041
|
+
error: null
|
|
1042
|
+
};
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
console.error("[getGitHubTokenById] error:", error);
|
|
1045
|
+
return { token: null, owner: null, error: "Failed to decrypt GitHub token" };
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
async function resolveReleaseGitHubToken(githubTokenId) {
|
|
1049
|
+
if (githubTokenId) {
|
|
1050
|
+
const { token, error } = await getGitHubTokenById(githubTokenId);
|
|
1051
|
+
if (token)
|
|
1052
|
+
return { token, error: null };
|
|
1053
|
+
return {
|
|
1054
|
+
token: null,
|
|
1055
|
+
error: error || "Configured GitHub token not found"
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
return getMgBoilerGitHubToken();
|
|
1059
|
+
}
|
|
1027
1060
|
|
|
1028
1061
|
// ../../node_modules/zod/dist/esm/v3/external.js
|
|
1029
1062
|
var external_exports = {};
|
|
@@ -5061,7 +5094,7 @@ var coerce = {
|
|
|
5061
5094
|
var NEVER = INVALID;
|
|
5062
5095
|
|
|
5063
5096
|
// ../platform/dist/utils/litespeed-vhost.js
|
|
5064
|
-
var LITESPEED_STANDARD_APP_PATH = /^apps\/(backoffice|portal|web|api)(\/[\w-]+)
|
|
5097
|
+
var LITESPEED_STANDARD_APP_PATH = /^(\.|apps\/(backoffice|portal|web|api)(\/[\w-]+)?)$/;
|
|
5065
5098
|
var LitespeedVhostMappingSchema = external_exports.object({
|
|
5066
5099
|
domain: external_exports.string().min(1).transform((value) => normalizeVhostDomain(value)),
|
|
5067
5100
|
appPath: external_exports.string().min(1).regex(LITESPEED_STANDARD_APP_PATH, "Unsupported appPath")
|
|
@@ -5473,7 +5506,9 @@ async function createReleaseForStage(userId, params) {
|
|
|
5473
5506
|
"BAD_REQUEST"
|
|
5474
5507
|
);
|
|
5475
5508
|
}
|
|
5476
|
-
const { token } = await
|
|
5509
|
+
const { token } = await resolveReleaseGitHubToken(
|
|
5510
|
+
profile.github_token_id ?? null
|
|
5511
|
+
);
|
|
5477
5512
|
if (!token) {
|
|
5478
5513
|
throw new CreateReleaseError(
|
|
5479
5514
|
"GitHub token is not configured",
|
|
@@ -5612,6 +5647,449 @@ async function createReleaseForStage(userId, params) {
|
|
|
5612
5647
|
authorName: triggerAuthorName
|
|
5613
5648
|
};
|
|
5614
5649
|
}
|
|
5650
|
+
var GITHUB_API3 = "https://api.github.com";
|
|
5651
|
+
var ReleaseProfileQuickCreateError = class extends Error {
|
|
5652
|
+
constructor(message, code = "INTERNAL_SERVER_ERROR") {
|
|
5653
|
+
super(message);
|
|
5654
|
+
this.code = code;
|
|
5655
|
+
this.name = "ReleaseProfileQuickCreateError";
|
|
5656
|
+
}
|
|
5657
|
+
};
|
|
5658
|
+
function inferDeployMethod(folderName, hasNextConfig, hasDockerfile, hasPm2Ecosystem) {
|
|
5659
|
+
if (hasPm2Ecosystem && hasNextConfig) return "pm2";
|
|
5660
|
+
if (hasPm2Ecosystem && !hasDockerfile && !folderName.toLowerCase().includes("api"))
|
|
5661
|
+
return "pm2";
|
|
5662
|
+
if (hasNextConfig) return "pm2";
|
|
5663
|
+
if (hasDockerfile || folderName.toLowerCase().includes("api"))
|
|
5664
|
+
return "docker";
|
|
5665
|
+
return "none";
|
|
5666
|
+
}
|
|
5667
|
+
function toLabel(folderName) {
|
|
5668
|
+
return folderName.charAt(0).toUpperCase() + folderName.slice(1);
|
|
5669
|
+
}
|
|
5670
|
+
async function scanRepoForApps(repoFullName, githubToken, ref = "main") {
|
|
5671
|
+
const headers = {
|
|
5672
|
+
Accept: "application/vnd.github.v3+json",
|
|
5673
|
+
Authorization: `Bearer ${githubToken}`,
|
|
5674
|
+
"Content-Type": "application/json"
|
|
5675
|
+
};
|
|
5676
|
+
async function fetchContents(path) {
|
|
5677
|
+
const res = await fetch(
|
|
5678
|
+
`${GITHUB_API3}/repos/${repoFullName}/contents/${path}?ref=${ref}`,
|
|
5679
|
+
{ headers }
|
|
5680
|
+
);
|
|
5681
|
+
if (!res.ok) return null;
|
|
5682
|
+
const data = await res.json();
|
|
5683
|
+
return Array.isArray(data) ? data : null;
|
|
5684
|
+
}
|
|
5685
|
+
async function fileExists(path) {
|
|
5686
|
+
const res = await fetch(
|
|
5687
|
+
`${GITHUB_API3}/repos/${repoFullName}/contents/${path}?ref=${ref}`,
|
|
5688
|
+
{ headers, method: "HEAD" }
|
|
5689
|
+
);
|
|
5690
|
+
return res.ok;
|
|
5691
|
+
}
|
|
5692
|
+
const apps = [];
|
|
5693
|
+
const appsDir = await fetchContents("apps");
|
|
5694
|
+
const isMonorepo = appsDir !== null && appsDir.length > 0;
|
|
5695
|
+
const hasPm2Ecosystem = await Promise.any([
|
|
5696
|
+
fileExists("ecosystem.config.cjs"),
|
|
5697
|
+
fileExists("ecosystem.config.js"),
|
|
5698
|
+
fileExists("ecosystem.config.mjs")
|
|
5699
|
+
]).catch(() => false);
|
|
5700
|
+
if (isMonorepo) {
|
|
5701
|
+
const subfolders = appsDir.filter((item) => item.type === "dir");
|
|
5702
|
+
const detections = await Promise.all(
|
|
5703
|
+
subfolders.map(async (folder) => {
|
|
5704
|
+
const folderPath = `apps/${folder.name}`;
|
|
5705
|
+
const [hasNextMjs, hasNextJs, hasNextTs, hasDockerfile, hasPkgJson] = await Promise.all([
|
|
5706
|
+
fileExists(`${folderPath}/next.config.mjs`),
|
|
5707
|
+
fileExists(`${folderPath}/next.config.js`),
|
|
5708
|
+
fileExists(`${folderPath}/next.config.ts`),
|
|
5709
|
+
fileExists(`${folderPath}/Dockerfile`),
|
|
5710
|
+
fileExists(`${folderPath}/package.json`)
|
|
5711
|
+
]);
|
|
5712
|
+
const hasNextConfig = hasNextMjs || hasNextJs || hasNextTs;
|
|
5713
|
+
const deployMethod = inferDeployMethod(
|
|
5714
|
+
folder.name,
|
|
5715
|
+
hasNextConfig,
|
|
5716
|
+
hasDockerfile,
|
|
5717
|
+
!!hasPm2Ecosystem
|
|
5718
|
+
);
|
|
5719
|
+
return {
|
|
5720
|
+
path: folderPath,
|
|
5721
|
+
label: toLabel(folder.name),
|
|
5722
|
+
deployMethod,
|
|
5723
|
+
hasNextConfig,
|
|
5724
|
+
hasDockerfile,
|
|
5725
|
+
hasPackageJson: hasPkgJson,
|
|
5726
|
+
enabled: true
|
|
5727
|
+
};
|
|
5728
|
+
})
|
|
5729
|
+
);
|
|
5730
|
+
apps.push(...detections);
|
|
5731
|
+
} else {
|
|
5732
|
+
const [hasNextMjs, hasNextJs, hasNextTs, hasDockerfile, hasPkgJson] = await Promise.all([
|
|
5733
|
+
fileExists("next.config.mjs"),
|
|
5734
|
+
fileExists("next.config.js"),
|
|
5735
|
+
fileExists("next.config.ts"),
|
|
5736
|
+
fileExists("Dockerfile"),
|
|
5737
|
+
fileExists("package.json")
|
|
5738
|
+
]);
|
|
5739
|
+
const hasNextConfig = hasNextMjs || hasNextJs || hasNextTs;
|
|
5740
|
+
const repoName = repoFullName.split("/").pop() ?? "app";
|
|
5741
|
+
const deployMethod = inferDeployMethod(
|
|
5742
|
+
repoName,
|
|
5743
|
+
hasNextConfig,
|
|
5744
|
+
hasDockerfile,
|
|
5745
|
+
!!hasPm2Ecosystem
|
|
5746
|
+
);
|
|
5747
|
+
apps.push({
|
|
5748
|
+
path: ".",
|
|
5749
|
+
label: toLabel(repoName),
|
|
5750
|
+
deployMethod,
|
|
5751
|
+
hasNextConfig,
|
|
5752
|
+
hasDockerfile,
|
|
5753
|
+
hasPackageJson: hasPkgJson,
|
|
5754
|
+
enabled: true
|
|
5755
|
+
});
|
|
5756
|
+
}
|
|
5757
|
+
const workDirectory = isMonorepo ? apps.find((a) => a.deployMethod !== "none")?.path ?? "." : ".";
|
|
5758
|
+
const hasTriggerDev = await Promise.any([
|
|
5759
|
+
fileExists("packages/jobs/trigger.config.ts"),
|
|
5760
|
+
fileExists("trigger.config.ts")
|
|
5761
|
+
]).catch(() => false);
|
|
5762
|
+
return {
|
|
5763
|
+
isMonorepo,
|
|
5764
|
+
apps,
|
|
5765
|
+
workDirectory,
|
|
5766
|
+
hasTriggerDev: !!hasTriggerDev
|
|
5767
|
+
};
|
|
5768
|
+
}
|
|
5769
|
+
var QUICK_STAGE_DEFAULTS = {
|
|
5770
|
+
dev: {
|
|
5771
|
+
name: "Development",
|
|
5772
|
+
releaseBranch: "release-dev",
|
|
5773
|
+
triggerMode: "pr_webhook",
|
|
5774
|
+
autoApprove: true,
|
|
5775
|
+
requireReview: false,
|
|
5776
|
+
backupDb: false,
|
|
5777
|
+
migrateDb: true
|
|
5778
|
+
},
|
|
5779
|
+
staging: {
|
|
5780
|
+
name: "Staging",
|
|
5781
|
+
releaseBranch: "release-staging",
|
|
5782
|
+
triggerMode: "manual",
|
|
5783
|
+
autoApprove: false,
|
|
5784
|
+
requireReview: false,
|
|
5785
|
+
backupDb: true,
|
|
5786
|
+
migrateDb: true
|
|
5787
|
+
},
|
|
5788
|
+
prod: {
|
|
5789
|
+
name: "Production",
|
|
5790
|
+
releaseBranch: "release-prod",
|
|
5791
|
+
triggerMode: "manual",
|
|
5792
|
+
autoApprove: false,
|
|
5793
|
+
requireReview: false,
|
|
5794
|
+
backupDb: true,
|
|
5795
|
+
migrateDb: true
|
|
5796
|
+
}
|
|
5797
|
+
};
|
|
5798
|
+
var QUICK_STAGES_FOR_TYPE = {
|
|
5799
|
+
dev_only: ["dev"],
|
|
5800
|
+
prod_only: ["prod"],
|
|
5801
|
+
both: ["dev", "prod"]
|
|
5802
|
+
};
|
|
5803
|
+
function deriveProfileName(repoFullName) {
|
|
5804
|
+
const repoName = repoFullName.split("/").pop() ?? repoFullName;
|
|
5805
|
+
return repoName.split(/[-_]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
5806
|
+
}
|
|
5807
|
+
function toStageApps(apps) {
|
|
5808
|
+
return apps.filter((a) => a.enabled).map((a) => ({
|
|
5809
|
+
path: a.path,
|
|
5810
|
+
label: a.label,
|
|
5811
|
+
deployMethod: a.deployMethod,
|
|
5812
|
+
deploymentAppId: null,
|
|
5813
|
+
deploymentServerId: null,
|
|
5814
|
+
deployPath: null,
|
|
5815
|
+
deployScript: null,
|
|
5816
|
+
enabled: a.enabled
|
|
5817
|
+
}));
|
|
5818
|
+
}
|
|
5819
|
+
async function ensureGitHubBranchExists(repoFullName, branchName, fromBranch, githubToken) {
|
|
5820
|
+
const headers = {
|
|
5821
|
+
Accept: "application/vnd.github.v3+json",
|
|
5822
|
+
Authorization: `Bearer ${githubToken}`,
|
|
5823
|
+
"Content-Type": "application/json"
|
|
5824
|
+
};
|
|
5825
|
+
const checkRes = await fetch(
|
|
5826
|
+
`${GITHUB_API3}/repos/${repoFullName}/git/ref/heads/${branchName}`,
|
|
5827
|
+
{ headers }
|
|
5828
|
+
);
|
|
5829
|
+
if (checkRes.ok) return;
|
|
5830
|
+
let sha;
|
|
5831
|
+
const sourceRes = await fetch(
|
|
5832
|
+
`${GITHUB_API3}/repos/${repoFullName}/git/ref/heads/${fromBranch}`,
|
|
5833
|
+
{ headers }
|
|
5834
|
+
);
|
|
5835
|
+
if (sourceRes.ok) {
|
|
5836
|
+
const data = await sourceRes.json();
|
|
5837
|
+
sha = data.object?.sha;
|
|
5838
|
+
} else {
|
|
5839
|
+
const repoRes = await fetch(`${GITHUB_API3}/repos/${repoFullName}`, {
|
|
5840
|
+
headers
|
|
5841
|
+
});
|
|
5842
|
+
if (!repoRes.ok) {
|
|
5843
|
+
throw new Error(`Could not access repository ${repoFullName}`);
|
|
5844
|
+
}
|
|
5845
|
+
const repo = await repoRes.json();
|
|
5846
|
+
const defaultBranch = repo.default_branch ?? "main";
|
|
5847
|
+
const defaultRes = await fetch(
|
|
5848
|
+
`${GITHUB_API3}/repos/${repoFullName}/git/ref/heads/${defaultBranch}`,
|
|
5849
|
+
{ headers }
|
|
5850
|
+
);
|
|
5851
|
+
if (!defaultRes.ok) {
|
|
5852
|
+
throw new Error(`Could not resolve a source branch for ${repoFullName}`);
|
|
5853
|
+
}
|
|
5854
|
+
const data = await defaultRes.json();
|
|
5855
|
+
sha = data.object?.sha;
|
|
5856
|
+
}
|
|
5857
|
+
if (!sha) {
|
|
5858
|
+
throw new Error(`Could not determine SHA to create branch ${branchName}`);
|
|
5859
|
+
}
|
|
5860
|
+
const createRes = await fetch(
|
|
5861
|
+
`${GITHUB_API3}/repos/${repoFullName}/git/refs`,
|
|
5862
|
+
{
|
|
5863
|
+
method: "POST",
|
|
5864
|
+
headers,
|
|
5865
|
+
body: JSON.stringify({ ref: `refs/heads/${branchName}`, sha })
|
|
5866
|
+
}
|
|
5867
|
+
);
|
|
5868
|
+
if (!createRes.ok && createRes.status !== 422) {
|
|
5869
|
+
const body = await createRes.text().catch(() => "");
|
|
5870
|
+
throw new Error(
|
|
5871
|
+
`Failed to create branch ${branchName}: ${createRes.status} ${body}`
|
|
5872
|
+
);
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
async function insertQuickStages(profileId, releaseType, stageApps) {
|
|
5876
|
+
const db2 = getDb();
|
|
5877
|
+
const stages = [];
|
|
5878
|
+
const wanted = QUICK_STAGES_FOR_TYPE[releaseType];
|
|
5879
|
+
for (let i = 0; i < wanted.length; i++) {
|
|
5880
|
+
const stageType = wanted[i];
|
|
5881
|
+
const defaults = QUICK_STAGE_DEFAULTS[stageType];
|
|
5882
|
+
const rows = await db2.execute(sql`
|
|
5883
|
+
INSERT INTO release_profile_stage (
|
|
5884
|
+
release_profile_id, name, stage, stage_order, release_branch,
|
|
5885
|
+
trigger_mode, auto_approve, require_review,
|
|
5886
|
+
backup_db, migrate_db, stage_apps, enabled,
|
|
5887
|
+
trigger_branch, trigger_event
|
|
5888
|
+
) VALUES (
|
|
5889
|
+
${profileId}, ${defaults.name}, ${stageType}, ${i + 1},
|
|
5890
|
+
${defaults.releaseBranch},
|
|
5891
|
+
${defaults.triggerMode}, ${defaults.autoApprove}, ${defaults.requireReview},
|
|
5892
|
+
${defaults.backupDb}, ${defaults.migrateDb},
|
|
5893
|
+
${JSON.stringify(stageApps)}::jsonb, true,
|
|
5894
|
+
${defaults.releaseBranch}, 'push'
|
|
5895
|
+
)
|
|
5896
|
+
RETURNING id, name, stage, release_branch, trigger_mode
|
|
5897
|
+
`);
|
|
5898
|
+
if (rows[0]) stages.push(rows[0]);
|
|
5899
|
+
}
|
|
5900
|
+
return stages;
|
|
5901
|
+
}
|
|
5902
|
+
async function quickCreateReleaseProfile(userId, options) {
|
|
5903
|
+
const db2 = getDb();
|
|
5904
|
+
const repoFullName = options.repoFullName.trim();
|
|
5905
|
+
if (!repoFullName.includes("/")) {
|
|
5906
|
+
throw new ReleaseProfileQuickCreateError(
|
|
5907
|
+
`Repository must be in "owner/name" format, got "${repoFullName}"`,
|
|
5908
|
+
"BAD_REQUEST"
|
|
5909
|
+
);
|
|
5910
|
+
}
|
|
5911
|
+
const existing = await db2.execute(sql`
|
|
5912
|
+
SELECT id, name FROM release_profile
|
|
5913
|
+
WHERE lower(repo_full_name) = ${repoFullName.toLowerCase()}
|
|
5914
|
+
LIMIT 1
|
|
5915
|
+
`);
|
|
5916
|
+
if (existing[0]) {
|
|
5917
|
+
throw new ReleaseProfileQuickCreateError(
|
|
5918
|
+
`Repository ${repoFullName} already has a release profile ("${existing[0].name}")`,
|
|
5919
|
+
"CONFLICT"
|
|
5920
|
+
);
|
|
5921
|
+
}
|
|
5922
|
+
const githubTokenId = options.githubTokenId ?? null;
|
|
5923
|
+
const { token, error: tokenError } = await resolveReleaseGitHubToken(githubTokenId);
|
|
5924
|
+
if (!token) {
|
|
5925
|
+
throw new ReleaseProfileQuickCreateError(
|
|
5926
|
+
tokenError || "GitHub token is not configured",
|
|
5927
|
+
"PRECONDITION_FAILED"
|
|
5928
|
+
);
|
|
5929
|
+
}
|
|
5930
|
+
let scan;
|
|
5931
|
+
try {
|
|
5932
|
+
scan = await scanRepoForApps(repoFullName, token);
|
|
5933
|
+
} catch (err) {
|
|
5934
|
+
throw new ReleaseProfileQuickCreateError(
|
|
5935
|
+
`Failed to scan repository ${repoFullName}: ${err instanceof Error ? err.message : String(err)}`,
|
|
5936
|
+
"BAD_REQUEST"
|
|
5937
|
+
);
|
|
5938
|
+
}
|
|
5939
|
+
const name = options.name?.trim() || deriveProfileName(repoFullName);
|
|
5940
|
+
const releaseType = options.releaseType ?? "both";
|
|
5941
|
+
const detectedApps = toStageApps(scan.apps);
|
|
5942
|
+
let profile;
|
|
5943
|
+
try {
|
|
5944
|
+
const rows = await db2.execute(sql`
|
|
5945
|
+
INSERT INTO release_profile (
|
|
5946
|
+
name, repo_full_name, work_directory, enabled, release_type,
|
|
5947
|
+
current_version, active_version, created_by, use_changes_branch,
|
|
5948
|
+
detected_apps, has_trigger_dev, github_token_id
|
|
5949
|
+
) VALUES (
|
|
5950
|
+
${name}, ${repoFullName}, ${scan.workDirectory}, true, ${releaseType},
|
|
5951
|
+
NULL, NULL, ${userId}, false,
|
|
5952
|
+
${JSON.stringify(detectedApps)}::jsonb, ${scan.hasTriggerDev},
|
|
5953
|
+
${githubTokenId}
|
|
5954
|
+
)
|
|
5955
|
+
RETURNING *
|
|
5956
|
+
`);
|
|
5957
|
+
profile = rows[0];
|
|
5958
|
+
} catch (err) {
|
|
5959
|
+
throw new ReleaseProfileQuickCreateError(
|
|
5960
|
+
`Failed to create release profile: ${err instanceof Error ? err.message : String(err)}`,
|
|
5961
|
+
"INTERNAL_SERVER_ERROR"
|
|
5962
|
+
);
|
|
5963
|
+
}
|
|
5964
|
+
if (!profile) {
|
|
5965
|
+
throw new ReleaseProfileQuickCreateError(
|
|
5966
|
+
"Release profile insert returned no rows",
|
|
5967
|
+
"INTERNAL_SERVER_ERROR"
|
|
5968
|
+
);
|
|
5969
|
+
}
|
|
5970
|
+
const stages = await insertQuickStages(profile.id, releaseType, detectedApps);
|
|
5971
|
+
const branchWarnings = [];
|
|
5972
|
+
for (const stage of stages) {
|
|
5973
|
+
if (!stage.release_branch) continue;
|
|
5974
|
+
try {
|
|
5975
|
+
await ensureGitHubBranchExists(
|
|
5976
|
+
repoFullName,
|
|
5977
|
+
stage.release_branch,
|
|
5978
|
+
"main",
|
|
5979
|
+
token
|
|
5980
|
+
);
|
|
5981
|
+
} catch (err) {
|
|
5982
|
+
branchWarnings.push(
|
|
5983
|
+
`Could not create branch ${stage.release_branch}: ${err instanceof Error ? err.message : String(err)}`
|
|
5984
|
+
);
|
|
5985
|
+
}
|
|
5986
|
+
}
|
|
5987
|
+
return { profile, stages, scan, branchWarnings };
|
|
5988
|
+
}
|
|
5989
|
+
async function updateReleaseProfileSettings(profileId, fields) {
|
|
5990
|
+
const db2 = getDb();
|
|
5991
|
+
const profileRows = await db2.execute(sql`
|
|
5992
|
+
SELECT id, release_type, detected_apps
|
|
5993
|
+
FROM release_profile WHERE id = ${profileId} LIMIT 1
|
|
5994
|
+
`);
|
|
5995
|
+
const current = profileRows[0];
|
|
5996
|
+
if (!current) {
|
|
5997
|
+
throw new ReleaseProfileQuickCreateError(
|
|
5998
|
+
"Release profile not found",
|
|
5999
|
+
"NOT_FOUND"
|
|
6000
|
+
);
|
|
6001
|
+
}
|
|
6002
|
+
const setExprs = [];
|
|
6003
|
+
if (fields.name !== void 0) setExprs.push(sql`name = ${fields.name}`);
|
|
6004
|
+
if (fields.enabled !== void 0)
|
|
6005
|
+
setExprs.push(sql`enabled = ${fields.enabled}`);
|
|
6006
|
+
if (fields.releaseType !== void 0)
|
|
6007
|
+
setExprs.push(sql`release_type = ${fields.releaseType}`);
|
|
6008
|
+
if (fields.githubTokenId !== void 0)
|
|
6009
|
+
setExprs.push(sql`github_token_id = ${fields.githubTokenId}`);
|
|
6010
|
+
if (fields.useChangesBranch !== void 0)
|
|
6011
|
+
setExprs.push(sql`use_changes_branch = ${fields.useChangesBranch}`);
|
|
6012
|
+
if (fields.hasTriggerDev !== void 0)
|
|
6013
|
+
setExprs.push(sql`has_trigger_dev = ${fields.hasTriggerDev}`);
|
|
6014
|
+
if (fields.workDirectory !== void 0)
|
|
6015
|
+
setExprs.push(sql`work_directory = ${fields.workDirectory}`);
|
|
6016
|
+
if (setExprs.length === 0) {
|
|
6017
|
+
throw new ReleaseProfileQuickCreateError(
|
|
6018
|
+
"No fields to update",
|
|
6019
|
+
"BAD_REQUEST"
|
|
6020
|
+
);
|
|
6021
|
+
}
|
|
6022
|
+
const rows = await db2.execute(sql`
|
|
6023
|
+
UPDATE release_profile
|
|
6024
|
+
SET ${sql.join(setExprs, sql`, `)}
|
|
6025
|
+
WHERE id = ${profileId}
|
|
6026
|
+
RETURNING *
|
|
6027
|
+
`);
|
|
6028
|
+
const profile = rows[0];
|
|
6029
|
+
if (!profile) {
|
|
6030
|
+
throw new ReleaseProfileQuickCreateError(
|
|
6031
|
+
"Release profile update returned no rows",
|
|
6032
|
+
"INTERNAL_SERVER_ERROR"
|
|
6033
|
+
);
|
|
6034
|
+
}
|
|
6035
|
+
let stagesReconfigured = false;
|
|
6036
|
+
if (fields.releaseType !== void 0 && fields.releaseType !== current.release_type) {
|
|
6037
|
+
await reconfigureQuickStages(
|
|
6038
|
+
profileId,
|
|
6039
|
+
fields.releaseType,
|
|
6040
|
+
current.detected_apps ?? []
|
|
6041
|
+
);
|
|
6042
|
+
stagesReconfigured = true;
|
|
6043
|
+
}
|
|
6044
|
+
return { profile, stagesReconfigured };
|
|
6045
|
+
}
|
|
6046
|
+
async function reconfigureQuickStages(profileId, releaseType, detectedApps) {
|
|
6047
|
+
const db2 = getDb();
|
|
6048
|
+
const wanted = QUICK_STAGES_FOR_TYPE[releaseType];
|
|
6049
|
+
const existingStages = await db2.execute(sql`
|
|
6050
|
+
SELECT id, stage, release_branch
|
|
6051
|
+
FROM release_profile_stage
|
|
6052
|
+
WHERE release_profile_id = ${profileId}
|
|
6053
|
+
`);
|
|
6054
|
+
const existingByStage = new Map(existingStages.map((s) => [s.stage, s]));
|
|
6055
|
+
for (let i = 0; i < wanted.length; i++) {
|
|
6056
|
+
const stageType = wanted[i];
|
|
6057
|
+
const defaults = QUICK_STAGE_DEFAULTS[stageType];
|
|
6058
|
+
const existing = existingByStage.get(stageType);
|
|
6059
|
+
if (existing) {
|
|
6060
|
+
await db2.execute(sql`
|
|
6061
|
+
UPDATE release_profile_stage
|
|
6062
|
+
SET stage_order = ${i + 1},
|
|
6063
|
+
release_branch = ${existing.release_branch ?? defaults.releaseBranch},
|
|
6064
|
+
enabled = true
|
|
6065
|
+
WHERE id = ${existing.id}
|
|
6066
|
+
`);
|
|
6067
|
+
} else {
|
|
6068
|
+
await db2.execute(sql`
|
|
6069
|
+
INSERT INTO release_profile_stage (
|
|
6070
|
+
release_profile_id, name, stage, stage_order, release_branch,
|
|
6071
|
+
trigger_mode, auto_approve, require_review,
|
|
6072
|
+
backup_db, migrate_db, stage_apps, enabled,
|
|
6073
|
+
trigger_branch, trigger_event
|
|
6074
|
+
) VALUES (
|
|
6075
|
+
${profileId}, ${defaults.name}, ${stageType}, ${i + 1},
|
|
6076
|
+
${defaults.releaseBranch},
|
|
6077
|
+
${defaults.triggerMode}, ${defaults.autoApprove}, ${defaults.requireReview},
|
|
6078
|
+
${defaults.backupDb}, ${defaults.migrateDb},
|
|
6079
|
+
${JSON.stringify(detectedApps)}::jsonb, true,
|
|
6080
|
+
${defaults.releaseBranch}, 'push'
|
|
6081
|
+
)
|
|
6082
|
+
`);
|
|
6083
|
+
}
|
|
6084
|
+
}
|
|
6085
|
+
const wantedSet = new Set(wanted);
|
|
6086
|
+
for (const existing of existingStages) {
|
|
6087
|
+
if (wantedSet.has(existing.stage)) continue;
|
|
6088
|
+
await db2.execute(sql`
|
|
6089
|
+
UPDATE release_profile_stage SET enabled = false WHERE id = ${existing.id}
|
|
6090
|
+
`);
|
|
6091
|
+
}
|
|
6092
|
+
}
|
|
5615
6093
|
var args = process.argv.slice(2);
|
|
5616
6094
|
function getArg2(name) {
|
|
5617
6095
|
return args.find((a) => a.startsWith(`--${name}=`))?.split("=").slice(1).join("=");
|
|
@@ -5853,6 +6331,9 @@ var TOOL_MODULE_MAP = {
|
|
|
5853
6331
|
"env-get": "ci_cd",
|
|
5854
6332
|
"env-store": "ci_cd",
|
|
5855
6333
|
"release-trigger": "ci_cd",
|
|
6334
|
+
"release-profile-list": "ci_cd",
|
|
6335
|
+
"release-profile-create": "ci_cd",
|
|
6336
|
+
"release-profile-update": "ci_cd",
|
|
5856
6337
|
"domain-list": "domains",
|
|
5857
6338
|
"dns-list": "domains",
|
|
5858
6339
|
"dns-record": "domains",
|
|
@@ -6233,6 +6714,23 @@ async function resolveReleaseProfileStageIds(profileName) {
|
|
|
6233
6714
|
}
|
|
6234
6715
|
return { stageIds: stages.map((s) => s.id), profileId: profile.id };
|
|
6235
6716
|
}
|
|
6717
|
+
async function resolveGitHubTokenIdByKey(key) {
|
|
6718
|
+
const trimmed = key.trim();
|
|
6719
|
+
const rows = await db.execute(sql`
|
|
6720
|
+
SELECT id FROM github_token
|
|
6721
|
+
WHERE label ILIKE ${trimmed} OR owner ILIKE ${trimmed} OR id::text = ${trimmed}
|
|
6722
|
+
ORDER BY label
|
|
6723
|
+
LIMIT 1
|
|
6724
|
+
`);
|
|
6725
|
+
if (rows[0]) return rows[0].id;
|
|
6726
|
+
const all = await db.execute(
|
|
6727
|
+
sql`SELECT label, owner FROM github_token ORDER BY label`
|
|
6728
|
+
);
|
|
6729
|
+
const available = all.map((t) => t.owner ? `${t.label} (${t.owner})` : t.label).join(", ");
|
|
6730
|
+
throw new Error(
|
|
6731
|
+
`GitHub key "${key}" not found. Available keys: ${available || "(none)"}. Omit githubKey to use the default MG Software token.`
|
|
6732
|
+
);
|
|
6733
|
+
}
|
|
6236
6734
|
async function resolveReleaseTriggerStage(profileName, stageFilter) {
|
|
6237
6735
|
const profileRows = await db.execute(sql`
|
|
6238
6736
|
SELECT id, name FROM release_profile
|
|
@@ -8397,11 +8895,11 @@ CREATE TABLE IF NOT EXISTS _mcp_migrations (
|
|
|
8397
8895
|
applied_by TEXT
|
|
8398
8896
|
);
|
|
8399
8897
|
`.trim();
|
|
8400
|
-
function normaliseMigrationSql(
|
|
8401
|
-
return
|
|
8898
|
+
function normaliseMigrationSql(sql28) {
|
|
8899
|
+
return sql28.replace(/\r\n/g, "\n").trim() + "\n";
|
|
8402
8900
|
}
|
|
8403
|
-
function migrationSha256(
|
|
8404
|
-
return createHash("sha256").update(normaliseMigrationSql(
|
|
8901
|
+
function migrationSha256(sql28) {
|
|
8902
|
+
return createHash("sha256").update(normaliseMigrationSql(sql28), "utf8").digest("hex");
|
|
8405
8903
|
}
|
|
8406
8904
|
function dollarQuoteTag(value) {
|
|
8407
8905
|
let tag = "_mcp";
|
|
@@ -9468,6 +9966,79 @@ var TOOLS = [
|
|
|
9468
9966
|
required: ["releaseProfile"]
|
|
9469
9967
|
}
|
|
9470
9968
|
},
|
|
9969
|
+
{
|
|
9970
|
+
name: "release-profile-list",
|
|
9971
|
+
description: "List all release profiles with repo, enabled state, release type, GitHub key and a summary of their stages. Requires ci_cd permission.",
|
|
9972
|
+
inputSchema: {
|
|
9973
|
+
type: "object",
|
|
9974
|
+
properties: {},
|
|
9975
|
+
required: []
|
|
9976
|
+
}
|
|
9977
|
+
},
|
|
9978
|
+
{
|
|
9979
|
+
name: "release-profile-create",
|
|
9980
|
+
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.",
|
|
9981
|
+
inputSchema: {
|
|
9982
|
+
type: "object",
|
|
9983
|
+
properties: {
|
|
9984
|
+
repoFullName: {
|
|
9985
|
+
type: "string",
|
|
9986
|
+
description: 'Repository in "owner/name" format (e.g. MGSoftwareBV/mg-dashboard).'
|
|
9987
|
+
},
|
|
9988
|
+
githubKey: {
|
|
9989
|
+
type: "string",
|
|
9990
|
+
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.'
|
|
9991
|
+
},
|
|
9992
|
+
name: {
|
|
9993
|
+
type: "string",
|
|
9994
|
+
description: 'Profile name override. Default: title-cased repo name (mg-dashboard \u2192 "Mg Dashboard").'
|
|
9995
|
+
},
|
|
9996
|
+
releaseType: {
|
|
9997
|
+
type: "string",
|
|
9998
|
+
enum: ["dev_only", "prod_only", "both"],
|
|
9999
|
+
description: "Which stages to create (default: both = dev + prod)."
|
|
10000
|
+
}
|
|
10001
|
+
},
|
|
10002
|
+
required: ["repoFullName"]
|
|
10003
|
+
}
|
|
10004
|
+
},
|
|
10005
|
+
{
|
|
10006
|
+
name: "release-profile-update",
|
|
10007
|
+
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.",
|
|
10008
|
+
inputSchema: {
|
|
10009
|
+
type: "object",
|
|
10010
|
+
properties: {
|
|
10011
|
+
releaseProfile: {
|
|
10012
|
+
type: "string",
|
|
10013
|
+
description: "Profile name or repo full name to locate the profile."
|
|
10014
|
+
},
|
|
10015
|
+
name: { type: "string", description: "New profile name." },
|
|
10016
|
+
enabled: { type: "boolean", description: "Enable/disable the profile." },
|
|
10017
|
+
releaseType: {
|
|
10018
|
+
type: "string",
|
|
10019
|
+
enum: ["dev_only", "prod_only", "both"],
|
|
10020
|
+
description: "New release type \u2014 reconfigures stages accordingly."
|
|
10021
|
+
},
|
|
10022
|
+
githubKey: {
|
|
10023
|
+
type: "string",
|
|
10024
|
+
description: 'Named GitHub key (github_token label or owner). Pass "default" to reset to the default MG Software token.'
|
|
10025
|
+
},
|
|
10026
|
+
useChangesBranch: {
|
|
10027
|
+
type: "boolean",
|
|
10028
|
+
description: "Use a long-lived -changes accumulation branch."
|
|
10029
|
+
},
|
|
10030
|
+
hasTriggerDev: {
|
|
10031
|
+
type: "boolean",
|
|
10032
|
+
description: "Deploy Trigger.dev jobs during production releases."
|
|
10033
|
+
},
|
|
10034
|
+
workDirectory: {
|
|
10035
|
+
type: "string",
|
|
10036
|
+
description: 'Working directory within the repo (e.g. ".").'
|
|
10037
|
+
}
|
|
10038
|
+
},
|
|
10039
|
+
required: ["releaseProfile"]
|
|
10040
|
+
}
|
|
10041
|
+
},
|
|
9471
10042
|
{
|
|
9472
10043
|
name: "cache-purge",
|
|
9473
10044
|
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 +10138,7 @@ var TOOLS = [
|
|
|
9567
10138
|
// ----- Trigger.dev -----
|
|
9568
10139
|
...TRIGGER_TOOLS
|
|
9569
10140
|
];
|
|
9570
|
-
var MCP_VERSION = "7.0.
|
|
10141
|
+
var MCP_VERSION = "7.0.5";
|
|
9571
10142
|
async function handleListTools() {
|
|
9572
10143
|
if (!authContext) return { tools: TOOLS };
|
|
9573
10144
|
const accessible = TOOLS.filter((tool) => {
|
|
@@ -12009,6 +12580,170 @@ LIMIT ${limit};
|
|
|
12009
12580
|
throw err;
|
|
12010
12581
|
}
|
|
12011
12582
|
}
|
|
12583
|
+
case "release-profile-list": {
|
|
12584
|
+
const profiles = await db.execute(sql`
|
|
12585
|
+
SELECT
|
|
12586
|
+
p.id, p.name, p.repo_full_name, p.enabled, p.release_type,
|
|
12587
|
+
p.work_directory, p.use_changes_branch, p.has_trigger_dev,
|
|
12588
|
+
gt.label AS github_key,
|
|
12589
|
+
(
|
|
12590
|
+
SELECT string_agg(
|
|
12591
|
+
s.stage || ' (' || COALESCE(s.release_branch, '-') ||
|
|
12592
|
+
', ' || COALESCE(s.trigger_mode, 'manual') ||
|
|
12593
|
+
CASE WHEN s.enabled THEN '' ELSE ', disabled' END || ')',
|
|
12594
|
+
'; ' ORDER BY s.stage_order
|
|
12595
|
+
)
|
|
12596
|
+
FROM release_profile_stage s
|
|
12597
|
+
WHERE s.release_profile_id = p.id
|
|
12598
|
+
) AS stages
|
|
12599
|
+
FROM release_profile p
|
|
12600
|
+
LEFT JOIN github_token gt ON gt.id = p.github_token_id
|
|
12601
|
+
ORDER BY p.name
|
|
12602
|
+
`);
|
|
12603
|
+
if (profiles.length === 0) {
|
|
12604
|
+
return {
|
|
12605
|
+
content: [{ type: "text", text: "No release profiles found" }]
|
|
12606
|
+
};
|
|
12607
|
+
}
|
|
12608
|
+
const lines = profiles.map((p) => {
|
|
12609
|
+
const flags = [
|
|
12610
|
+
p.enabled ? "enabled" : "disabled",
|
|
12611
|
+
`type=${p.release_type ?? "both"}`,
|
|
12612
|
+
`key=${p.github_key ?? "MG Software (default)"}`,
|
|
12613
|
+
p.use_changes_branch ? "changes-branch" : null,
|
|
12614
|
+
p.has_trigger_dev ? "trigger.dev" : null
|
|
12615
|
+
].filter(Boolean).join(", ");
|
|
12616
|
+
return `${p.name} \u2014 ${p.repo_full_name} [${flags}]
|
|
12617
|
+
stages: ${p.stages ?? "(none)"}`;
|
|
12618
|
+
});
|
|
12619
|
+
return {
|
|
12620
|
+
content: [
|
|
12621
|
+
{
|
|
12622
|
+
type: "text",
|
|
12623
|
+
text: `${profiles.length} release profile(s):
|
|
12624
|
+
|
|
12625
|
+
${lines.join("\n\n")}`
|
|
12626
|
+
}
|
|
12627
|
+
]
|
|
12628
|
+
};
|
|
12629
|
+
}
|
|
12630
|
+
case "release-profile-create": {
|
|
12631
|
+
const repoFullName = String(a.repoFullName ?? "").trim();
|
|
12632
|
+
const githubTokenId = a.githubKey ? await resolveGitHubTokenIdByKey(String(a.githubKey)) : null;
|
|
12633
|
+
const releaseType = a.releaseType ? String(a.releaseType) : null;
|
|
12634
|
+
if (releaseType !== null && releaseType !== "dev_only" && releaseType !== "prod_only" && releaseType !== "both") {
|
|
12635
|
+
throw new Error(
|
|
12636
|
+
`Invalid releaseType "${releaseType}". Expected dev_only, prod_only, or both.`
|
|
12637
|
+
);
|
|
12638
|
+
}
|
|
12639
|
+
try {
|
|
12640
|
+
const result = await quickCreateReleaseProfile(
|
|
12641
|
+
authContext.userId,
|
|
12642
|
+
{
|
|
12643
|
+
repoFullName,
|
|
12644
|
+
githubTokenId,
|
|
12645
|
+
name: a.name ? String(a.name) : null,
|
|
12646
|
+
releaseType
|
|
12647
|
+
}
|
|
12648
|
+
);
|
|
12649
|
+
return {
|
|
12650
|
+
content: [
|
|
12651
|
+
{
|
|
12652
|
+
type: "text",
|
|
12653
|
+
text: JSON.stringify(
|
|
12654
|
+
{
|
|
12655
|
+
profileId: result.profile.id,
|
|
12656
|
+
name: result.profile.name,
|
|
12657
|
+
repo: repoFullName,
|
|
12658
|
+
releaseType: releaseType ?? "both",
|
|
12659
|
+
appsDetected: result.scan.apps.map((app) => ({
|
|
12660
|
+
path: app.path,
|
|
12661
|
+
label: app.label,
|
|
12662
|
+
deployMethod: app.deployMethod
|
|
12663
|
+
})),
|
|
12664
|
+
stages: result.stages.map((s) => ({
|
|
12665
|
+
stage: s.stage,
|
|
12666
|
+
name: s.name,
|
|
12667
|
+
releaseBranch: s.release_branch,
|
|
12668
|
+
triggerMode: s.trigger_mode
|
|
12669
|
+
})),
|
|
12670
|
+
branchWarnings: result.branchWarnings,
|
|
12671
|
+
hint: "Assign deployment servers per stage app in the dashboard (Releases \u2192 Profiles) before triggering a release."
|
|
12672
|
+
},
|
|
12673
|
+
null,
|
|
12674
|
+
2
|
|
12675
|
+
)
|
|
12676
|
+
}
|
|
12677
|
+
]
|
|
12678
|
+
};
|
|
12679
|
+
} catch (err) {
|
|
12680
|
+
if (err instanceof ReleaseProfileQuickCreateError) {
|
|
12681
|
+
throw new Error(err.message);
|
|
12682
|
+
}
|
|
12683
|
+
throw err;
|
|
12684
|
+
}
|
|
12685
|
+
}
|
|
12686
|
+
case "release-profile-update": {
|
|
12687
|
+
const identifier = String(a.releaseProfile ?? "").trim();
|
|
12688
|
+
const profileRows = await db.execute(sql`
|
|
12689
|
+
SELECT id, name FROM release_profile
|
|
12690
|
+
WHERE name ILIKE ${identifier} OR repo_full_name ILIKE ${identifier}
|
|
12691
|
+
LIMIT 1
|
|
12692
|
+
`);
|
|
12693
|
+
const profile = profileRows[0];
|
|
12694
|
+
if (!profile) {
|
|
12695
|
+
const all = await db.execute(
|
|
12696
|
+
sql`SELECT name FROM release_profile ORDER BY name`
|
|
12697
|
+
);
|
|
12698
|
+
throw new Error(
|
|
12699
|
+
`Release profile "${identifier}" not found. Available profiles: ${all.map((p) => p.name).join(", ") || "(none)"}`
|
|
12700
|
+
);
|
|
12701
|
+
}
|
|
12702
|
+
const releaseType = a.releaseType ? String(a.releaseType) : void 0;
|
|
12703
|
+
if (releaseType !== void 0 && releaseType !== "dev_only" && releaseType !== "prod_only" && releaseType !== "both") {
|
|
12704
|
+
throw new Error(
|
|
12705
|
+
`Invalid releaseType "${releaseType}". Expected dev_only, prod_only, or both.`
|
|
12706
|
+
);
|
|
12707
|
+
}
|
|
12708
|
+
let githubTokenId;
|
|
12709
|
+
if (a.githubKey !== void 0) {
|
|
12710
|
+
const key = String(a.githubKey).trim();
|
|
12711
|
+
githubTokenId = !key || key.toLowerCase() === "default" ? null : await resolveGitHubTokenIdByKey(key);
|
|
12712
|
+
}
|
|
12713
|
+
try {
|
|
12714
|
+
const result = await updateReleaseProfileSettings(profile.id, {
|
|
12715
|
+
name: a.name !== void 0 ? String(a.name) : void 0,
|
|
12716
|
+
enabled: a.enabled !== void 0 ? a.enabled === true : void 0,
|
|
12717
|
+
releaseType,
|
|
12718
|
+
githubTokenId,
|
|
12719
|
+
useChangesBranch: a.useChangesBranch !== void 0 ? a.useChangesBranch === true : void 0,
|
|
12720
|
+
hasTriggerDev: a.hasTriggerDev !== void 0 ? a.hasTriggerDev === true : void 0,
|
|
12721
|
+
workDirectory: a.workDirectory !== void 0 ? String(a.workDirectory) : void 0
|
|
12722
|
+
});
|
|
12723
|
+
return {
|
|
12724
|
+
content: [
|
|
12725
|
+
{
|
|
12726
|
+
type: "text",
|
|
12727
|
+
text: JSON.stringify(
|
|
12728
|
+
{
|
|
12729
|
+
profileId: profile.id,
|
|
12730
|
+
name: result.profile.name,
|
|
12731
|
+
updated: true,
|
|
12732
|
+
stagesReconfigured: result.stagesReconfigured
|
|
12733
|
+
},
|
|
12734
|
+
null,
|
|
12735
|
+
2
|
|
12736
|
+
)
|
|
12737
|
+
}
|
|
12738
|
+
]
|
|
12739
|
+
};
|
|
12740
|
+
} catch (err) {
|
|
12741
|
+
if (err instanceof ReleaseProfileQuickCreateError) {
|
|
12742
|
+
throw new Error(err.message);
|
|
12743
|
+
}
|
|
12744
|
+
throw err;
|
|
12745
|
+
}
|
|
12746
|
+
}
|
|
12012
12747
|
// ----- Cache Purge -----
|
|
12013
12748
|
case "cache-purge": {
|
|
12014
12749
|
const { conn, proxy } = await getServerConnection(String(a.serverId));
|