@sashido/mcp-server 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +201 -2
- package/dist/access.d.ts +47 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +156 -0
- package/dist/access.js.map +1 -0
- package/dist/advice/format.d.ts +22 -0
- package/dist/advice/format.d.ts.map +1 -0
- package/dist/advice/format.js +70 -0
- package/dist/advice/format.js.map +1 -0
- package/dist/advice/schemaChecks.d.ts +17 -0
- package/dist/advice/schemaChecks.d.ts.map +1 -0
- package/dist/advice/schemaChecks.js +64 -0
- package/dist/advice/schemaChecks.js.map +1 -0
- package/dist/audit.d.ts +32 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +41 -0
- package/dist/audit.js.map +1 -0
- package/dist/auth.d.ts +17 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +72 -0
- package/dist/auth.js.map +1 -0
- package/dist/bin/login.d.ts +3 -0
- package/dist/bin/login.d.ts.map +1 -0
- package/dist/bin/login.js +16 -0
- package/dist/bin/login.js.map +1 -0
- package/dist/bin/mcp-server.d.ts +3 -0
- package/dist/bin/mcp-server.d.ts.map +1 -0
- package/dist/bin/mcp-server.js +27 -0
- package/dist/bin/mcp-server.js.map +1 -0
- package/dist/client/parse.d.ts +219 -0
- package/dist/client/parse.d.ts.map +1 -0
- package/dist/client/parse.js +414 -0
- package/dist/client/parse.js.map +1 -0
- package/dist/client/sashido.d.ts +213 -0
- package/dist/client/sashido.d.ts.map +1 -0
- package/dist/client/sashido.js +289 -0
- package/dist/client/sashido.js.map +1 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +70 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +33 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +132 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +60 -0
- package/dist/server.js.map +1 -0
- package/dist/session/redis-client.d.ts +23 -0
- package/dist/session/redis-client.d.ts.map +1 -0
- package/dist/session/redis-client.js +136 -0
- package/dist/session/redis-client.js.map +1 -0
- package/dist/session/session-manager.d.ts +6 -0
- package/dist/session/session-manager.d.ts.map +1 -0
- package/dist/session/session-manager.js +68 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/session/sse-pubsub.d.ts +23 -0
- package/dist/session/sse-pubsub.d.ts.map +1 -0
- package/dist/session/sse-pubsub.js +121 -0
- package/dist/session/sse-pubsub.js.map +1 -0
- package/dist/session/types.d.ts +43 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +2 -0
- package/dist/session/types.js.map +1 -0
- package/dist/tools/advisor/index.d.ts +3 -0
- package/dist/tools/advisor/index.d.ts.map +1 -0
- package/dist/tools/advisor/index.js +149 -0
- package/dist/tools/advisor/index.js.map +1 -0
- package/dist/tools/apps/index.d.ts +3 -0
- package/dist/tools/apps/index.d.ts.map +1 -0
- package/dist/tools/apps/index.js +113 -0
- package/dist/tools/apps/index.js.map +1 -0
- package/dist/tools/audit/index.d.ts +6 -0
- package/dist/tools/audit/index.d.ts.map +1 -0
- package/dist/tools/audit/index.js +69 -0
- package/dist/tools/audit/index.js.map +1 -0
- package/dist/tools/builds/index.d.ts +3 -0
- package/dist/tools/builds/index.d.ts.map +1 -0
- package/dist/tools/builds/index.js +85 -0
- package/dist/tools/builds/index.js.map +1 -0
- package/dist/tools/classes/index.d.ts +3 -0
- package/dist/tools/classes/index.d.ts.map +1 -0
- package/dist/tools/classes/index.js +223 -0
- package/dist/tools/classes/index.js.map +1 -0
- package/dist/tools/cloud-functions/index.d.ts +3 -0
- package/dist/tools/cloud-functions/index.d.ts.map +1 -0
- package/dist/tools/cloud-functions/index.js +154 -0
- package/dist/tools/cloud-functions/index.js.map +1 -0
- package/dist/tools/deployments/index.d.ts +3 -0
- package/dist/tools/deployments/index.d.ts.map +1 -0
- package/dist/tools/deployments/index.js +124 -0
- package/dist/tools/deployments/index.js.map +1 -0
- package/dist/tools/engines/index.d.ts +3 -0
- package/dist/tools/engines/index.d.ts.map +1 -0
- package/dist/tools/engines/index.js +68 -0
- package/dist/tools/engines/index.js.map +1 -0
- package/dist/tools/env/index.d.ts +3 -0
- package/dist/tools/env/index.d.ts.map +1 -0
- package/dist/tools/env/index.js +116 -0
- package/dist/tools/env/index.js.map +1 -0
- package/dist/tools/files/index.d.ts +3 -0
- package/dist/tools/files/index.d.ts.map +1 -0
- package/dist/tools/files/index.js +132 -0
- package/dist/tools/files/index.js.map +1 -0
- package/dist/tools/health/index.d.ts +3 -0
- package/dist/tools/health/index.d.ts.map +1 -0
- package/dist/tools/health/index.js +47 -0
- package/dist/tools/health/index.js.map +1 -0
- package/dist/tools/infrastructure/deployments.d.ts +23 -0
- package/dist/tools/infrastructure/deployments.d.ts.map +1 -0
- package/dist/tools/infrastructure/deployments.js +76 -0
- package/dist/tools/infrastructure/deployments.js.map +1 -0
- package/dist/tools/infrastructure/index.d.ts +9 -0
- package/dist/tools/infrastructure/index.d.ts.map +1 -0
- package/dist/tools/infrastructure/index.js +164 -0
- package/dist/tools/infrastructure/index.js.map +1 -0
- package/dist/tools/jobs/index.d.ts +3 -0
- package/dist/tools/jobs/index.d.ts.map +1 -0
- package/dist/tools/jobs/index.js +338 -0
- package/dist/tools/jobs/index.js.map +1 -0
- package/dist/tools/logs/index.d.ts +3 -0
- package/dist/tools/logs/index.d.ts.map +1 -0
- package/dist/tools/logs/index.js +133 -0
- package/dist/tools/logs/index.js.map +1 -0
- package/dist/tools/objects/index.d.ts +3 -0
- package/dist/tools/objects/index.d.ts.map +1 -0
- package/dist/tools/objects/index.js +185 -0
- package/dist/tools/objects/index.js.map +1 -0
- package/dist/tools/push/index.d.ts +3 -0
- package/dist/tools/push/index.d.ts.map +1 -0
- package/dist/tools/push/index.js +434 -0
- package/dist/tools/push/index.js.map +1 -0
- package/dist/tools/types/index.d.ts +3 -0
- package/dist/tools/types/index.d.ts.map +1 -0
- package/dist/tools/types/index.js +45 -0
- package/dist/tools/types/index.js.map +1 -0
- package/dist/tools/webhooks/index.d.ts +3 -0
- package/dist/tools/webhooks/index.d.ts.map +1 -0
- package/dist/tools/webhooks/index.js +443 -0
- package/dist/tools/webhooks/index.js.map +1 -0
- package/dist/transports/http.d.ts +4 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +241 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/transports/stdio.d.ts +7 -0
- package/dist/transports/stdio.d.ts.map +1 -0
- package/dist/transports/stdio.js +29 -0
- package/dist/transports/stdio.js.map +1 -0
- package/dist/typegen/dart.d.ts +3 -0
- package/dist/typegen/dart.d.ts.map +1 -0
- package/dist/typegen/dart.js +84 -0
- package/dist/typegen/dart.js.map +1 -0
- package/dist/typegen/kotlin.d.ts +3 -0
- package/dist/typegen/kotlin.d.ts.map +1 -0
- package/dist/typegen/kotlin.js +84 -0
- package/dist/typegen/kotlin.js.map +1 -0
- package/dist/typegen/model.d.ts +37 -0
- package/dist/typegen/model.d.ts.map +1 -0
- package/dist/typegen/model.js +82 -0
- package/dist/typegen/model.js.map +1 -0
- package/dist/typegen/swift.d.ts +3 -0
- package/dist/typegen/swift.d.ts.map +1 -0
- package/dist/typegen/swift.js +105 -0
- package/dist/typegen/swift.js.map +1 -0
- package/dist/typegen/typescript.d.ts +3 -0
- package/dist/typegen/typescript.d.ts.map +1 -0
- package/dist/typegen/typescript.js +73 -0
- package/dist/typegen/typescript.js.map +1 -0
- package/dist/upgrade/rules.d.ts +27 -0
- package/dist/upgrade/rules.d.ts.map +1 -0
- package/dist/upgrade/rules.js +133 -0
- package/dist/upgrade/rules.js.map +1 -0
- package/dist/upgrade/scan.d.ts +18 -0
- package/dist/upgrade/scan.d.ts.map +1 -0
- package/dist/upgrade/scan.js +38 -0
- package/dist/upgrade/scan.js.map +1 -0
- package/dist/upgrade/versions.d.ts +8 -0
- package/dist/upgrade/versions.d.ts.map +1 -0
- package/dist/upgrade/versions.js +25 -0
- package/dist/upgrade/versions.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +67 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/env/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AA0C3D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,GAAG,IAAI,CA4G3E"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SashidoClient } from "../../client/sashido.js";
|
|
3
|
+
// SECURITY: Patterns that indicate sensitive env var names
|
|
4
|
+
const SENSITIVE_PATTERNS = [
|
|
5
|
+
/key/i,
|
|
6
|
+
/secret/i,
|
|
7
|
+
/password/i,
|
|
8
|
+
/token/i,
|
|
9
|
+
/credential/i,
|
|
10
|
+
/api_key/i,
|
|
11
|
+
/apikey/i,
|
|
12
|
+
/private/i,
|
|
13
|
+
/auth/i,
|
|
14
|
+
/cert/i,
|
|
15
|
+
/database/i,
|
|
16
|
+
/mongodb/i,
|
|
17
|
+
/postgres/i,
|
|
18
|
+
/mysql/i,
|
|
19
|
+
/redis/i,
|
|
20
|
+
/aws/i,
|
|
21
|
+
/azure/i,
|
|
22
|
+
/gcp/i,
|
|
23
|
+
/stripe/i,
|
|
24
|
+
/twilio/i,
|
|
25
|
+
/sendgrid/i,
|
|
26
|
+
];
|
|
27
|
+
function isSensitiveEnvVar(name) {
|
|
28
|
+
return SENSITIVE_PATTERNS.some((pattern) => pattern.test(name));
|
|
29
|
+
}
|
|
30
|
+
function redactEnvVars(envVars) {
|
|
31
|
+
return envVars.map((env) => ({
|
|
32
|
+
name: env.name,
|
|
33
|
+
value: isSensitiveEnvVar(env.name) ? "[REDACTED]" : env.value,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
export function registerEnvTools(server, ctx) {
|
|
37
|
+
const getAuthFromRequest = (_args, _extra) => ctx.token;
|
|
38
|
+
// List Environment Variables
|
|
39
|
+
// SECURITY: Redacts values of sensitive env vars
|
|
40
|
+
server.tool("list_env_vars", "List environment variables for an application. Note: Sensitive values are redacted for security.", {
|
|
41
|
+
appObjectId: z.string().describe("The application object ID"),
|
|
42
|
+
}, async (args, extra) => {
|
|
43
|
+
const auth = getAuthFromRequest(args, extra);
|
|
44
|
+
const client = new SashidoClient(auth);
|
|
45
|
+
const envVars = await client.listEnvVars(args.appObjectId);
|
|
46
|
+
// Redact sensitive values before returning
|
|
47
|
+
const redactedEnvVars = redactEnvVars(envVars);
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: JSON.stringify(redactedEnvVars, null, 2),
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
// Set Environment Variable
|
|
58
|
+
server.tool("set_env_var", "Set or update an environment variable for an application", {
|
|
59
|
+
appObjectId: z.string().describe("The application object ID"),
|
|
60
|
+
name: z.string().describe("Environment variable name"),
|
|
61
|
+
value: z.string().describe("Environment variable value"),
|
|
62
|
+
}, async (args, extra) => {
|
|
63
|
+
const auth = getAuthFromRequest(args, extra);
|
|
64
|
+
const client = new SashidoClient(auth);
|
|
65
|
+
// Get current env vars
|
|
66
|
+
const currentEnvVars = await client.listEnvVars(args.appObjectId);
|
|
67
|
+
// Update or add the new variable
|
|
68
|
+
const envIndex = currentEnvVars.findIndex((e) => e.name === args.name);
|
|
69
|
+
if (envIndex >= 0) {
|
|
70
|
+
currentEnvVars[envIndex] = { name: args.name, value: args.value };
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
currentEnvVars.push({ name: args.name, value: args.value });
|
|
74
|
+
}
|
|
75
|
+
await client.updateEnvVars(args.appObjectId, currentEnvVars);
|
|
76
|
+
return {
|
|
77
|
+
content: [
|
|
78
|
+
{
|
|
79
|
+
type: "text",
|
|
80
|
+
text: `Environment variable ${args.name} set successfully`,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
// Delete Environment Variable
|
|
86
|
+
server.tool("delete_env_var", "Delete an environment variable from an application", {
|
|
87
|
+
appObjectId: z.string().describe("The application object ID"),
|
|
88
|
+
name: z.string().describe("Environment variable name to delete"),
|
|
89
|
+
}, async (args, extra) => {
|
|
90
|
+
const auth = getAuthFromRequest(args, extra);
|
|
91
|
+
const client = new SashidoClient(auth);
|
|
92
|
+
// Get current env vars and remove the specified one
|
|
93
|
+
const currentEnvVars = await client.listEnvVars(args.appObjectId);
|
|
94
|
+
const filteredEnvVars = currentEnvVars.filter((e) => e.name !== args.name);
|
|
95
|
+
if (filteredEnvVars.length === currentEnvVars.length) {
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: `Environment variable ${args.name} not found`,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
await client.updateEnvVars(args.appObjectId, filteredEnvVars);
|
|
106
|
+
return {
|
|
107
|
+
content: [
|
|
108
|
+
{
|
|
109
|
+
type: "text",
|
|
110
|
+
text: `Environment variable ${args.name} deleted successfully`,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/env/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/files/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAwC3D,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,GAAG,IAAI,CAyH5E"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
// Helper to guess content type from filename
|
|
6
|
+
function guessContentType(fileName) {
|
|
7
|
+
const ext = fileName.toLowerCase().split('.').pop();
|
|
8
|
+
const contentTypes = {
|
|
9
|
+
// Images
|
|
10
|
+
'jpg': 'image/jpeg',
|
|
11
|
+
'jpeg': 'image/jpeg',
|
|
12
|
+
'png': 'image/png',
|
|
13
|
+
'gif': 'image/gif',
|
|
14
|
+
'webp': 'image/webp',
|
|
15
|
+
'svg': 'image/svg+xml',
|
|
16
|
+
'ico': 'image/x-icon',
|
|
17
|
+
// Documents
|
|
18
|
+
'pdf': 'application/pdf',
|
|
19
|
+
'doc': 'application/msword',
|
|
20
|
+
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
21
|
+
'xls': 'application/vnd.ms-excel',
|
|
22
|
+
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
23
|
+
// Text
|
|
24
|
+
'txt': 'text/plain',
|
|
25
|
+
'csv': 'text/csv',
|
|
26
|
+
'json': 'application/json',
|
|
27
|
+
'xml': 'application/xml',
|
|
28
|
+
// Media
|
|
29
|
+
'mp3': 'audio/mpeg',
|
|
30
|
+
'mp4': 'video/mp4',
|
|
31
|
+
'wav': 'audio/wav',
|
|
32
|
+
// Archives
|
|
33
|
+
'zip': 'application/zip',
|
|
34
|
+
'gz': 'application/gzip',
|
|
35
|
+
};
|
|
36
|
+
return contentTypes[ext || ''] || 'application/octet-stream';
|
|
37
|
+
}
|
|
38
|
+
export function registerFileTools(server, ctx) {
|
|
39
|
+
const getParseClient = async (args, _extra) => ctx.parse(args.appObjectId);
|
|
40
|
+
// Upload File from Base64
|
|
41
|
+
server.tool('upload_file', 'Upload a file to Parse Server from base64-encoded data', {
|
|
42
|
+
appObjectId: z.string().describe('The application object ID'),
|
|
43
|
+
fileName: z.string().describe('The file name with extension (e.g., "image.png")'),
|
|
44
|
+
base64Data: z.string().describe('Base64-encoded file data'),
|
|
45
|
+
contentType: z
|
|
46
|
+
.string()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Content type (e.g., "image/png"). Will be guessed from extension if not provided.'),
|
|
49
|
+
}, async (args, extra) => {
|
|
50
|
+
const parseClient = await getParseClient(args, extra);
|
|
51
|
+
const contentType = args.contentType || guessContentType(args.fileName);
|
|
52
|
+
const fileData = Buffer.from(args.base64Data, 'base64');
|
|
53
|
+
const result = await parseClient.uploadFile(args.fileName, fileData, contentType);
|
|
54
|
+
return {
|
|
55
|
+
content: [
|
|
56
|
+
{
|
|
57
|
+
type: 'text',
|
|
58
|
+
text: `File uploaded successfully!\n\nName: ${result.name}\nURL: ${result.url}\n\nTo use this file in an object, set the field value to:\n${JSON.stringify({ __type: 'File', name: result.name, url: result.url }, null, 2)}`,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
// Upload File from URL
|
|
64
|
+
server.tool('upload_file_from_url', 'Download a file from a URL and upload it to Parse Server', {
|
|
65
|
+
appObjectId: z.string().describe('The application object ID'),
|
|
66
|
+
sourceUrl: z.string().url().describe('The URL to download the file from'),
|
|
67
|
+
fileName: z
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe('Optional file name. Will be extracted from URL if not provided.'),
|
|
71
|
+
}, async (args, extra) => {
|
|
72
|
+
// Download file from URL
|
|
73
|
+
const response = await axios.get(args.sourceUrl, {
|
|
74
|
+
responseType: 'arraybuffer',
|
|
75
|
+
timeout: 30000,
|
|
76
|
+
});
|
|
77
|
+
// Extract filename from URL or use provided
|
|
78
|
+
let fileName = args.fileName;
|
|
79
|
+
if (!fileName) {
|
|
80
|
+
const urlPath = new URL(args.sourceUrl).pathname;
|
|
81
|
+
fileName = urlPath.split('/').pop() || 'file';
|
|
82
|
+
}
|
|
83
|
+
const contentType = response.headers['content-type'] || guessContentType(fileName);
|
|
84
|
+
const fileData = Buffer.from(response.data);
|
|
85
|
+
const parseClient = await getParseClient(args, extra);
|
|
86
|
+
const result = await parseClient.uploadFile(fileName, fileData, contentType);
|
|
87
|
+
return {
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
type: 'text',
|
|
91
|
+
text: `File uploaded successfully!\n\nSource: ${args.sourceUrl}\nName: ${result.name}\nURL: ${result.url}\nSize: ${fileData.length} bytes\n\nTo use this file in an object, set the field value to:\n${JSON.stringify({ __type: 'File', name: result.name, url: result.url }, null, 2)}`,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
// Upload File from local path (fastest - no LLM processing needed)
|
|
97
|
+
server.tool('upload_file_from_path', 'Upload a local file to Parse Server. This is the fastest method - just provide the file path.', {
|
|
98
|
+
appObjectId: z.string().describe('The application object ID'),
|
|
99
|
+
filePath: z.string().describe('Absolute path to the local file (e.g., "/Users/me/image.png")'),
|
|
100
|
+
fileName: z
|
|
101
|
+
.string()
|
|
102
|
+
.optional()
|
|
103
|
+
.describe('Optional custom file name. Will use original filename if not provided.'),
|
|
104
|
+
}, async (args, extra) => {
|
|
105
|
+
// Check if file exists
|
|
106
|
+
if (!fs.existsSync(args.filePath)) {
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: `Error: File not found at path: ${args.filePath}`,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Read file from disk
|
|
117
|
+
const fileData = fs.readFileSync(args.filePath);
|
|
118
|
+
const fileName = args.fileName || path.basename(args.filePath);
|
|
119
|
+
const contentType = guessContentType(fileName);
|
|
120
|
+
const parseClient = await getParseClient(args, extra);
|
|
121
|
+
const result = await parseClient.uploadFile(fileName, fileData, contentType);
|
|
122
|
+
return {
|
|
123
|
+
content: [
|
|
124
|
+
{
|
|
125
|
+
type: 'text',
|
|
126
|
+
text: `File uploaded successfully!\n\nSource: ${args.filePath}\nName: ${result.name}\nURL: ${result.url}\nSize: ${fileData.length} bytes\n\nTo use this file in an object, set the field value to:\n${JSON.stringify({ __type: 'File', name: result.name, url: result.url }, null, 2)}`,
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/files/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/health/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG3D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,GAAG,IAAI,CAoD9E"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerHealthTools(server, ctx) {
|
|
3
|
+
const getParseClient = async (args, _extra) => ctx.parse(args.appObjectId);
|
|
4
|
+
// Check App Health
|
|
5
|
+
server.tool('check_app_health', 'Check if an application\'s Parse Server is healthy and running', {
|
|
6
|
+
appObjectId: z.string().describe('The application object ID'),
|
|
7
|
+
}, async (args, extra) => {
|
|
8
|
+
try {
|
|
9
|
+
const parseClient = await getParseClient(args, extra);
|
|
10
|
+
const health = await parseClient.checkHealth();
|
|
11
|
+
const statusEmoji = health.status === 'ok' ? '✅' : '⚠️';
|
|
12
|
+
const dbEmoji = health.database === 'ok' ? '✅' : '❌';
|
|
13
|
+
const output = [
|
|
14
|
+
`${statusEmoji} Status: ${health.status?.toString().toUpperCase() || 'UNKNOWN'}`,
|
|
15
|
+
`${dbEmoji} Database: ${health.database || 'unknown'}`,
|
|
16
|
+
``,
|
|
17
|
+
`Parse Server: v${health.version || 'unknown'}`,
|
|
18
|
+
`Node.js: ${health.nodeVersion || 'unknown'}`,
|
|
19
|
+
`Region: ${health.region || 'unknown'}`,
|
|
20
|
+
``,
|
|
21
|
+
`Build: #${health.build || 'N/A'}`,
|
|
22
|
+
`Deployment: #${health.deployment || 'N/A'}`,
|
|
23
|
+
`State: ${health.state || 'unknown'}`,
|
|
24
|
+
].join('\n');
|
|
25
|
+
return {
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
type: 'text',
|
|
29
|
+
text: output,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: 'text',
|
|
40
|
+
text: `❌ Health check failed: ${message}\n\nThe app may be down, not deployed, or unreachable.`,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/health/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,UAAU,mBAAmB,CAAC,MAAkB,EAAE,GAAgB;IACtE,MAAM,cAAc,GAAG,KAAK,EAAE,IAA6B,EAAE,MAAgB,EAAE,EAAE,CAC/E,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC9B,mBAAmB;IACnB,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,gEAAgE,EAChE;QACE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;KAC9D,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACtD,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,WAAW,EAAE,CAAC;YAE/C,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YACxD,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YAErD,MAAM,MAAM,GAAG;gBACb,GAAG,WAAW,YAAY,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,WAAW,EAAE,IAAI,SAAS,EAAE;gBAChF,GAAG,OAAO,cAAc,MAAM,CAAC,QAAQ,IAAI,SAAS,EAAE;gBACtD,EAAE;gBACF,kBAAkB,MAAM,CAAC,OAAO,IAAI,SAAS,EAAE;gBAC/C,YAAY,MAAM,CAAC,WAAW,IAAI,SAAS,EAAE;gBAC7C,WAAW,MAAM,CAAC,MAAM,IAAI,SAAS,EAAE;gBACvC,EAAE;gBACF,WAAW,MAAM,CAAC,KAAK,IAAI,KAAK,EAAE;gBAClC,gBAAgB,MAAM,CAAC,UAAU,IAAI,KAAK,EAAE;gBAC5C,UAAU,MAAM,CAAC,KAAK,IAAI,SAAS,EAAE;aACtC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAEb,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,MAAM;qBACb;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YACzE,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,0BAA0B,OAAO,wDAAwD;qBAChG;iBACF;aACF,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Deployment, SashidoClient } from "../../client/sashido.js";
|
|
2
|
+
export declare const IN_PROGRESS: string[];
|
|
3
|
+
export interface DeploymentState {
|
|
4
|
+
/** The deployment whose containers report now */
|
|
5
|
+
serving: {
|
|
6
|
+
name: string;
|
|
7
|
+
number: number;
|
|
8
|
+
containers: number;
|
|
9
|
+
} | null;
|
|
10
|
+
/** The deployment history, newest first; null when it can't be read */
|
|
11
|
+
records: Deployment[] | null;
|
|
12
|
+
/** In progress in the history, recently started: can be cancelled */
|
|
13
|
+
rolling: Deployment | null;
|
|
14
|
+
/** "In progress" in the history for too long: never ended there */
|
|
15
|
+
stuck: Deployment[];
|
|
16
|
+
}
|
|
17
|
+
/** The history's record of a deployment started after `before`, if any */
|
|
18
|
+
export declare function newRecord(client: SashidoClient, appObjectId: string, before: DeploymentState): Promise<Deployment | null>;
|
|
19
|
+
export declare function deploymentState(client: SashidoClient, appObjectId: string): Promise<DeploymentState>;
|
|
20
|
+
/** Of the records in progress before, one that ended badly since */
|
|
21
|
+
export declare function endedBadly(state: DeploymentState, watched: string[]): Deployment | null;
|
|
22
|
+
export declare function describeDeploymentState(state: DeploymentState): string[];
|
|
23
|
+
//# sourceMappingURL=deployments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployments.d.ts","sourceRoot":"","sources":["../../../src/tools/infrastructure/deployments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAQpE,eAAO,MAAM,WAAW,UAAgC,CAAC;AAKzD,MAAM,WAAW,eAAe;IAC9B,iDAAiD;IACjD,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrE,uEAAuE;IACvE,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAC7B,qEAAqE;IACrE,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,mEAAmE;IACnE,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAOD,0EAA0E;AAC1E,wBAAsB,SAAS,CAC7B,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAW5B;AAID,wBAAsB,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAoB1G;AAED,oEAAoE;AACpE,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,UAAU,GAAG,IAAI,CAEvF;AAID,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,EAAE,CA0BxE"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Two numberings, never compared with each other:
|
|
2
|
+
// - what serves traffic: the app's running instances, as they report now
|
|
3
|
+
// - the deployment history the dashboard shows, with its own numbering;
|
|
4
|
+
// cancel_deployment takes these names. Not every rollout appears in it,
|
|
5
|
+
// so it never decides what serves.
|
|
6
|
+
export const IN_PROGRESS = ["New", "Pending", "Running"];
|
|
7
|
+
const ENDED_BADLY = ["Failed", "Cancelled", "Error"];
|
|
8
|
+
// a record still "in progress" after this is stuck, not rolling out
|
|
9
|
+
const STUCK_AFTER_MS = 30 * 60 * 1000;
|
|
10
|
+
// how long deploy_app looks for the new deployment in the history (tests
|
|
11
|
+
// make it short)
|
|
12
|
+
const RECORD_POLL_MS = Number(process.env.SASHIDO_POLL_MS) || 1500;
|
|
13
|
+
const RECORD_POLLS = 4;
|
|
14
|
+
/** The history's record of a deployment started after `before`, if any */
|
|
15
|
+
export async function newRecord(client, appObjectId, before) {
|
|
16
|
+
const known = new Set((before.records || []).map((d) => d.objectId));
|
|
17
|
+
for (let i = 0; i < RECORD_POLLS; i++) {
|
|
18
|
+
await new Promise((resolve) => setTimeout(resolve, RECORD_POLL_MS));
|
|
19
|
+
const records = await client.listDeployments(appObjectId, 5).catch(() => null);
|
|
20
|
+
const found = (records || []).find((d) => !known.has(d.objectId));
|
|
21
|
+
if (found) {
|
|
22
|
+
return found;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const age = (d) => Date.now() - Date.parse(d.createdAt);
|
|
28
|
+
export async function deploymentState(client, appObjectId) {
|
|
29
|
+
const [live, records] = await Promise.all([
|
|
30
|
+
client.getActiveDeployment(appObjectId).catch(() => null),
|
|
31
|
+
client.listDeployments(appObjectId, 5).catch(() => null),
|
|
32
|
+
]);
|
|
33
|
+
const serving = live && live.deploymentName && live.deploymentNumber !== null
|
|
34
|
+
? {
|
|
35
|
+
name: live.deploymentName,
|
|
36
|
+
number: live.deploymentNumber,
|
|
37
|
+
containers: Object.keys(live.processes || {}).length,
|
|
38
|
+
}
|
|
39
|
+
: null;
|
|
40
|
+
const inProgress = (records || []).filter((d) => IN_PROGRESS.includes(d.phase));
|
|
41
|
+
return {
|
|
42
|
+
serving,
|
|
43
|
+
records,
|
|
44
|
+
rolling: inProgress.find((d) => age(d) < STUCK_AFTER_MS) || null,
|
|
45
|
+
stuck: inProgress.filter((d) => age(d) >= STUCK_AFTER_MS),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Of the records in progress before, one that ended badly since */
|
|
49
|
+
export function endedBadly(state, watched) {
|
|
50
|
+
return (state.records || []).find((d) => watched.includes(d.objectId) && ENDED_BADLY.includes(d.phase)) || null;
|
|
51
|
+
}
|
|
52
|
+
const minutes = (ms) => `${Math.max(1, Math.round(ms / 60000))} min`;
|
|
53
|
+
export function describeDeploymentState(state) {
|
|
54
|
+
const lines = [
|
|
55
|
+
state.serving
|
|
56
|
+
? `Serving traffic: ${state.serving.name}, ${state.serving.containers} container${state.serving.containers === 1 ? "" : "s"} reporting.`
|
|
57
|
+
: "Serving traffic: no containers report now (the app may be stopped, or just being deployed).",
|
|
58
|
+
];
|
|
59
|
+
if (!state.records) {
|
|
60
|
+
return lines;
|
|
61
|
+
}
|
|
62
|
+
if (state.rolling) {
|
|
63
|
+
lines.push(`Rolling out: ${state.rolling.name}, ${state.rolling.phase} for ${minutes(age(state.rolling))}. wait_for_deployment waits for it; cancel_deployment with deploymentName="${state.rolling.name}" stops it.`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
lines.push("No deployment in the history is rolling out.");
|
|
67
|
+
}
|
|
68
|
+
for (const d of state.stuck) {
|
|
69
|
+
lines.push(`${d.name} shows ${d.phase} for ${minutes(age(d))}: it never ended in the history (not rolling out).`);
|
|
70
|
+
}
|
|
71
|
+
if (state.records.length) {
|
|
72
|
+
lines.push("Deployment history (the dashboard's numbering, which may miss some deployments):", ...state.records.map((d) => ` ${d.name}: ${d.phase}, ${d.createdAt.replace("T", " ").slice(0, 16)}`));
|
|
73
|
+
}
|
|
74
|
+
return lines;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=deployments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployments.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/deployments.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ToolContext, ToolServer } from "../../context.js";
|
|
2
|
+
import { App } from "../../client/sashido.js";
|
|
3
|
+
import { DeploymentState } from "./deployments.js";
|
|
4
|
+
/** "release-6.5.11-preview" -> "6.5.11 (preview)" */
|
|
5
|
+
export declare function versionLabel(tag?: string): string;
|
|
6
|
+
/** What runs the app: version, engine, scaling, HA, deployments */
|
|
7
|
+
export declare function describeInfrastructure(app: App, state: DeploymentState | null): string;
|
|
8
|
+
export declare function registerInfrastructureTools(server: ToolServer, ctx: ToolContext): void;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/infrastructure/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,GAAG,EAAsB,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAwD,MAAM,kBAAkB,CAAC;AAQzG,qDAAqD;AACrD,wBAAgB,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAUjD;AAID,mEAAmE;AACnE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,GAAG,MAAM,CAuBtF;AAUD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,GAAG,IAAI,CA8HtF"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { deploymentState, describeDeploymentState, endedBadly } from "./deployments.js";
|
|
3
|
+
// how often wait_for_deployment looks (tests make it short)
|
|
4
|
+
const POLL_MS = Number(process.env.SASHIDO_POLL_MS) || 5000;
|
|
5
|
+
// time a poll's API calls may take, kept free before the deadline
|
|
6
|
+
const POLL_SLACK_MS = POLL_MS >= 1000 ? 8000 : 0;
|
|
7
|
+
const text = (t) => ({ content: [{ type: "text", text: t }] });
|
|
8
|
+
/** "release-6.5.11-preview" -> "6.5.11 (preview)" */
|
|
9
|
+
export function versionLabel(tag) {
|
|
10
|
+
if (!tag) {
|
|
11
|
+
return "unknown";
|
|
12
|
+
}
|
|
13
|
+
const bare = tag.replace(/^release-/, "");
|
|
14
|
+
const match = /^(\d+\.\d+\.\d+)(?:-(.+))?$/.exec(bare);
|
|
15
|
+
if (!match) {
|
|
16
|
+
return bare;
|
|
17
|
+
}
|
|
18
|
+
return match[2] ? `${match[1]} (${match[2].replace(/-/g, ", ")})` : match[1];
|
|
19
|
+
}
|
|
20
|
+
const major = (tag) => Number((/(\d+)\./.exec(tag || "") || [])[1] || 0);
|
|
21
|
+
/** What runs the app: version, engine, scaling, HA, deployments */
|
|
22
|
+
export function describeInfrastructure(app, state) {
|
|
23
|
+
const settings = app.settings || {};
|
|
24
|
+
const engine = (settings.engine || {})[app.region];
|
|
25
|
+
const replicas = app.deployment && app.deployment.replicas;
|
|
26
|
+
const limits = settings.engine_scaling_config || {};
|
|
27
|
+
const lines = [
|
|
28
|
+
`${app.name} (${app.objectId}), region ${app.region}`,
|
|
29
|
+
`Parse Server: ${versionLabel(app.parseServerVersion)}${app.parseServerVersion ? ` [${app.parseServerVersion}]` : ""}`,
|
|
30
|
+
// never guess: older APIs don't tell the engine at all
|
|
31
|
+
`Engine: ${engine ? `${engine.name}, billed ${engine.billingPolicy}` : settings.engine ? "the default (Development 1X)" : "unknown (the API doesn't tell yet)"}`,
|
|
32
|
+
replicas
|
|
33
|
+
? `Scaling: ${replicas.auto ? `auto, ${replicas.min} to ${replicas.max} engines` : `fixed, ${replicas.min} engine${replicas.min === 1 ? "" : "s"}`}`
|
|
34
|
+
: "Scaling: unknown",
|
|
35
|
+
// newer APIs tell the version and engine; then no HA flag means off
|
|
36
|
+
`High availability: ${settings.highly_available ? "on" : settings.highly_available === false || app.parseServerVersion ? "off" : "unknown"}`,
|
|
37
|
+
];
|
|
38
|
+
if (limits.max) {
|
|
39
|
+
lines.push(`Your plan allows up to ${limits.max} engines for this app.`);
|
|
40
|
+
}
|
|
41
|
+
if (state) {
|
|
42
|
+
lines.push("", ...describeDeploymentState(state));
|
|
43
|
+
}
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
}
|
|
46
|
+
async function findApp(ctx, appObjectId) {
|
|
47
|
+
const app = (await ctx.sashido().listApps()).find((a) => a.objectId === appObjectId);
|
|
48
|
+
if (!app) {
|
|
49
|
+
throw new Error(`App ${appObjectId} not found.`);
|
|
50
|
+
}
|
|
51
|
+
return app;
|
|
52
|
+
}
|
|
53
|
+
export function registerInfrastructureTools(server, ctx) {
|
|
54
|
+
server.tool("get_app_infrastructure", "What runs an app: its Parse Server version, engine and billing, scaling, high availability, and its deployments (whether one is rolling out). Check it before changing any of them.", {
|
|
55
|
+
appObjectId: z.string().describe("The application object ID"),
|
|
56
|
+
}, async (args) => {
|
|
57
|
+
const app = await findApp(ctx, args.appObjectId);
|
|
58
|
+
const state = ctx.access.scopes.indexOf("apps:read") !== -1 ? await deploymentState(ctx.sashido(), app.objectId) : null;
|
|
59
|
+
return text(describeInfrastructure(app, state));
|
|
60
|
+
});
|
|
61
|
+
server.tool("wait_for_deployment", "Wait until a newer deployment of the app serves traffic, e.g. after deploy_app, set_env_var, set_engine or change_parse_server_version, and say how it went. Returns before clients give up on the call (timeoutSeconds): call it again if it's still rolling out.", {
|
|
62
|
+
appObjectId: z.string().describe("The application object ID"),
|
|
63
|
+
timeoutSeconds: z
|
|
64
|
+
.number()
|
|
65
|
+
.int()
|
|
66
|
+
.min(5)
|
|
67
|
+
.max(300)
|
|
68
|
+
.optional()
|
|
69
|
+
.describe("How long to wait at most (default 45; some clients give up on calls after 60s)"),
|
|
70
|
+
after: z
|
|
71
|
+
.number()
|
|
72
|
+
.int()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe("Wait for a deployment newer than this number: the one deploy_app or change_parse_server_version tell, or the previous wait_for_deployment's. Default: the one serving now"),
|
|
75
|
+
}, async (args) => {
|
|
76
|
+
const client = ctx.sashido();
|
|
77
|
+
const deadline = Date.now() + (args.timeoutSeconds || 45) * 1000;
|
|
78
|
+
const start = await deploymentState(client, args.appObjectId);
|
|
79
|
+
// counted in the numbering of what serves (the containers)
|
|
80
|
+
const before = args.after !== undefined ? args.after : start.serving ? start.serving.number : 0;
|
|
81
|
+
// the history's deployments in progress now, or started from here on
|
|
82
|
+
const known = new Set((start.records || []).map((d) => d.objectId));
|
|
83
|
+
const watched = (start.rolling ? [start.rolling.objectId] : []).slice();
|
|
84
|
+
// without after, nothing was just requested: say so if nothing rolls out
|
|
85
|
+
const quietBy = args.after === undefined ? Date.now() + 30000 : Infinity;
|
|
86
|
+
let state = start;
|
|
87
|
+
for (;;) {
|
|
88
|
+
if (state.serving && state.serving.number > before) {
|
|
89
|
+
return text(`${state.serving.name} serves traffic now, ${state.serving.containers} container${state.serving.containers === 1 ? "" : "s"} reporting.`);
|
|
90
|
+
}
|
|
91
|
+
for (const d of state.records || []) {
|
|
92
|
+
if (!known.has(d.objectId)) {
|
|
93
|
+
known.add(d.objectId);
|
|
94
|
+
watched.push(d.objectId);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const failed = endedBadly(state, watched);
|
|
98
|
+
if (failed) {
|
|
99
|
+
return text(`${failed.name} ended: ${failed.phase}. ${state.serving ? `${state.serving.name} still serves traffic. ` : ""}Check get_build_log and get_error_logs.`);
|
|
100
|
+
}
|
|
101
|
+
if (!state.rolling && watched.length === 0 && Date.now() > quietBy) {
|
|
102
|
+
return text(`Nothing is rolling out. ${describeDeploymentState(state)[0]}`);
|
|
103
|
+
}
|
|
104
|
+
if (Date.now() + POLL_MS + POLL_SLACK_MS > deadline) {
|
|
105
|
+
return text(`${state.rolling ? `Still rolling out: ${state.rolling.name} (${state.rolling.phase}).` : `No newer deployment serves traffic yet${state.serving ? ` (${state.serving.name} still does)` : ""}.`} Deployments usually take one to three minutes: call wait_for_deployment again with after: ${before}.`);
|
|
106
|
+
}
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_MS));
|
|
108
|
+
state = await deploymentState(client, args.appObjectId);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
server.tool("list_parse_server_versions", "The Parse Server versions apps can be created on (create_app) or switched to (change_parse_server_version), newest first.", {}, async () => {
|
|
112
|
+
const versions = await ctx.sashido().listParseServerVersions();
|
|
113
|
+
return text(formatVersions(versions));
|
|
114
|
+
});
|
|
115
|
+
server.tool("change_parse_server_version", "Switch an app to another Parse Server version; the app is redeployed. A new major version can break cloud code and clients: read the Parse Server migration guides first. Without confirm: true it only shows what would change.", {
|
|
116
|
+
appObjectId: z.string().describe("The application object ID"),
|
|
117
|
+
imageTag: z.string().describe("The version's imageTag, from list_parse_server_versions"),
|
|
118
|
+
confirm: z.boolean().optional().describe("true to switch; otherwise only a preview"),
|
|
119
|
+
}, async (args) => {
|
|
120
|
+
const client = ctx.sashido();
|
|
121
|
+
const [app, versions] = await Promise.all([findApp(ctx, args.appObjectId), client.listParseServerVersions()]);
|
|
122
|
+
const target = versions.find((v) => v.imageTag === args.imageTag);
|
|
123
|
+
if (!target) {
|
|
124
|
+
return text(`❌ ${args.imageTag} isn't a version apps can use.\n\n${formatVersions(versions)}`);
|
|
125
|
+
}
|
|
126
|
+
if (app.parseServerVersion === target.imageTag) {
|
|
127
|
+
return text(`${app.name} already runs Parse Server ${versionLabel(target.imageTag)}.`);
|
|
128
|
+
}
|
|
129
|
+
const from = app.parseServerVersion;
|
|
130
|
+
const crossesMajor = major(from) !== major(target.imageTag);
|
|
131
|
+
const summary = [
|
|
132
|
+
`${app.name}: Parse Server ${versionLabel(from)} → ${versionLabel(target.imageTag)}`,
|
|
133
|
+
crossesMajor
|
|
134
|
+
? `This changes the major version (${major(from)} → ${major(target.imageTag)}): cloud code and client SDKs may need changes. Try it on a test app first.`
|
|
135
|
+
: "Same major version.",
|
|
136
|
+
"The app is redeployed; during a deployment without high availability it may be briefly unavailable.",
|
|
137
|
+
];
|
|
138
|
+
if (!args.confirm) {
|
|
139
|
+
return text(`Preview (nothing changed):\n${summary.join("\n")}\n\nCall again with confirm: true to switch.`);
|
|
140
|
+
}
|
|
141
|
+
const serving = (await deploymentState(client, app.objectId)).serving;
|
|
142
|
+
await client.changeParseServerVersion(app.objectId, target.imageTag);
|
|
143
|
+
return text(`✅ Switching.\n${summary.join("\n")}\n\nwait_for_deployment with after: ${serving ? serving.number : 0} shows when the new version serves traffic.`);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function formatVersions(versions) {
|
|
147
|
+
const sorted = [...versions].sort((a, b) => compareTags(b.imageTag, a.imageTag));
|
|
148
|
+
return [
|
|
149
|
+
"Parse Server versions (newest first):",
|
|
150
|
+
...sorted.map((v) => ` ${versionLabel(v.imageTag)} [${v.imageTag}]${v.latest ? " ← default for new apps" : ""}`),
|
|
151
|
+
].join("\n");
|
|
152
|
+
}
|
|
153
|
+
function compareTags(a, b) {
|
|
154
|
+
const parts = (t) => ((/(\d+)\.(\d+)\.(\d+)/.exec(t) || []).slice(1).map(Number));
|
|
155
|
+
const x = parts(a);
|
|
156
|
+
const y = parts(b);
|
|
157
|
+
for (let i = 0; i < 3; i++) {
|
|
158
|
+
if ((x[i] || 0) !== (y[i] || 0)) {
|
|
159
|
+
return (x[i] || 0) - (y[i] || 0);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=index.js.map
|