@mesh-tech/mesh-cli 0.39.0 → 0.40.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/dist/bin/{mesh.auth-preflight-JO5G7SOF.js → mesh.auth-preflight-AUFQ2FGD.js} +1 -1
- package/dist/bin/{mesh.chunk-CNLSVKYY.js → mesh.chunk-2ASXJ5DO.js} +233 -581
- package/dist/bin/mesh.chunk-2ASXJ5DO.js.map +6 -0
- package/dist/bin/{mesh.chunk-WODQO6TP.js → mesh.chunk-6AIG5OJM.js} +3 -3
- package/dist/bin/{mesh.chunk-OSV3EZQ3.js → mesh.chunk-6SSOVZOI.js} +1 -1
- package/dist/bin/{mesh.chunk-2ZJR5LGN.js → mesh.chunk-7HTKSUWR.js} +1507 -548
- package/dist/bin/mesh.chunk-7HTKSUWR.js.map +6 -0
- package/dist/bin/{mesh.chunk-UI6EFAMD.js → mesh.chunk-F44A3ACW.js} +2 -2
- package/dist/bin/{mesh.chunk-C2TKCYHP.js → mesh.chunk-HJGMOEUG.js} +1 -1
- package/dist/bin/{mesh.chunk-O6WRZU4X.js → mesh.chunk-NSQJEX36.js} +1 -1
- package/dist/bin/{mesh.chunk-C4ZM7DM2.js → mesh.chunk-PZVI7V3J.js} +1 -1
- package/dist/bin/mesh.chunk-TDCH4SFQ.js +419 -0
- package/dist/bin/mesh.chunk-TDCH4SFQ.js.map +6 -0
- package/dist/bin/mesh.chunk-YE2WIY3Y.js +473 -0
- package/dist/bin/mesh.chunk-YE2WIY3Y.js.map +6 -0
- package/dist/bin/{mesh.create-app-SOHBL75U.js → mesh.create-app-Z3RKSTBO.js} +3 -3
- package/dist/bin/{mesh.dev-token-server-SS7QEQOQ.js → mesh.dev-token-server-XG26C6IQ.js} +2 -2
- package/dist/bin/mesh.generate-XSM4C6FQ.js +10 -0
- package/dist/bin/mesh.js +9 -8
- package/dist/bin/mesh.js.map +1 -1
- package/dist/bin/{mesh.login-6VPMWHHU.js → mesh.login-3AWVGPCM.js} +2 -2
- package/dist/bin/{mesh.platform-setup-DJJB2PWL.js → mesh.platform-setup-2ZDMHRCI.js} +1 -1
- package/dist/bin/{mesh.program-Y4HUNBFP.js → mesh.program-K5JQXEHX.js} +9 -8
- package/dist/bin/{mesh.registry-5P4ZLFLJ.js → mesh.registry-JLQYFUNN.js} +4 -4
- package/dist/bin/{mesh.registry-identity-ONGUJQAY.js → mesh.registry-identity-3AR27UGO.js} +3 -3
- package/dist/bin/mesh.registry-identity-3AR27UGO.js.map +6 -0
- package/dist/bin/mesh.review-command-LUVZQN2T.js +238 -0
- package/dist/bin/mesh.review-command-LUVZQN2T.js.map +6 -0
- package/dist/bin/{mesh.start-5CPWOHDJ.js → mesh.start-KZ5L6SOJ.js} +2 -2
- package/dist/bin/{mesh.wizard-FNRTSGVN.js → mesh.wizard-FCTT55KI.js} +7 -7
- package/dist/bin/mesh.workspace-membership-DI3YQGEK.js +65 -0
- package/dist/bin/mesh.workspace-membership-DI3YQGEK.js.map +6 -0
- package/dist/build-info.json +2 -2
- package/dist/src/commands/dev-doctor.d.ts +22 -0
- package/dist/src/commands/dev-doctor.js +149 -0
- package/dist/src/commands/dev-env-leaks.d.ts +24 -0
- package/dist/src/commands/dev-env-leaks.js +178 -0
- package/dist/src/commands/dev-launch.d.ts +17 -1
- package/dist/src/commands/dev-launch.js +130 -5
- package/dist/src/commands/dev-readiness.d.ts +49 -0
- package/dist/src/commands/dev-readiness.js +78 -0
- package/dist/src/commands/dev.d.ts +60 -0
- package/dist/src/commands/dev.js +539 -140
- package/dist/src/commands/hub/index.js +10 -7
- package/dist/src/commands/mcp/generate.d.ts +11 -0
- package/dist/src/commands/mcp/generate.js +78 -0
- package/dist/src/commands/mcp/index.d.ts +2 -0
- package/dist/src/commands/mcp/index.js +70 -0
- package/dist/src/commands/mcp/manifest-file.d.ts +5 -0
- package/dist/src/commands/mcp/manifest-file.js +24 -0
- package/dist/src/commands/mcp/review-command.d.ts +24 -0
- package/dist/src/commands/mcp/review-command.js +66 -0
- package/dist/src/commands/mcp/review.d.ts +41 -0
- package/dist/src/commands/mcp/review.js +140 -0
- package/dist/src/commands/mcp/workspace-membership.d.ts +7 -0
- package/dist/src/commands/mcp/workspace-membership.js +77 -0
- package/dist/src/commands/secrets/reindex.js +2 -7
- package/dist/src/commands/secrets/set.d.ts +22 -1
- package/dist/src/commands/secrets/set.js +31 -8
- package/dist/src/commands/temporal.js +35 -4
- package/dist/src/commands/temporal.js.map +2 -2
- package/dist/src/program.js +2 -0
- package/dist/src/utils/aws-auth.d.ts +4 -0
- package/dist/src/utils/aws-auth.js +7 -3
- package/dist/src/utils/dev-sessions-root.d.ts +3 -0
- package/dist/src/utils/dev-sessions-root.js +45 -0
- package/dist/src/utils/kubeconfig.js +2 -2
- package/dist/src/utils/tailscale.js +7 -1
- package/dist/src/utils/temporal-auth.js +2 -1
- package/dist/src/utils/workflow-fingerprint.js +23 -12
- package/fragments/base/index.ts.hbs +3 -0
- package/package.json +7 -5
- package/skills/mesh/SKILL.md +10 -6
- package/templates/api-auth/index.ts.hbs +1 -0
- package/templates/api-role-gating/index.ts.hbs +1 -0
- package/templates/external-service/index.ts.hbs +1 -0
- package/templates/workflow/index.ts.hbs +2 -0
- package/dist/bin/mesh.chunk-2ZJR5LGN.js.map +0 -6
- package/dist/bin/mesh.chunk-CNLSVKYY.js.map +0 -6
- /package/dist/bin/{mesh.auth-preflight-JO5G7SOF.js.map → mesh.auth-preflight-AUFQ2FGD.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-WODQO6TP.js.map → mesh.chunk-6AIG5OJM.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-OSV3EZQ3.js.map → mesh.chunk-6SSOVZOI.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-UI6EFAMD.js.map → mesh.chunk-F44A3ACW.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-C2TKCYHP.js.map → mesh.chunk-HJGMOEUG.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-O6WRZU4X.js.map → mesh.chunk-NSQJEX36.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-C4ZM7DM2.js.map → mesh.chunk-PZVI7V3J.js.map} +0 -0
- /package/dist/bin/{mesh.create-app-SOHBL75U.js.map → mesh.create-app-Z3RKSTBO.js.map} +0 -0
- /package/dist/bin/{mesh.dev-token-server-SS7QEQOQ.js.map → mesh.dev-token-server-XG26C6IQ.js.map} +0 -0
- /package/dist/bin/{mesh.login-6VPMWHHU.js.map → mesh.generate-XSM4C6FQ.js.map} +0 -0
- /package/dist/bin/{mesh.platform-setup-DJJB2PWL.js.map → mesh.login-3AWVGPCM.js.map} +0 -0
- /package/dist/bin/{mesh.program-Y4HUNBFP.js.map → mesh.platform-setup-2ZDMHRCI.js.map} +0 -0
- /package/dist/bin/{mesh.registry-5P4ZLFLJ.js.map → mesh.program-K5JQXEHX.js.map} +0 -0
- /package/dist/bin/{mesh.registry-identity-ONGUJQAY.js.map → mesh.registry-JLQYFUNN.js.map} +0 -0
- /package/dist/bin/{mesh.start-5CPWOHDJ.js.map → mesh.start-KZ5L6SOJ.js.map} +0 -0
- /package/dist/bin/{mesh.wizard-FNRTSGVN.js.map → mesh.wizard-FCTT55KI.js.map} +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as net from "node:net";
|
|
4
|
-
import * as os from "node:os";
|
|
5
4
|
import * as path from "node:path";
|
|
6
5
|
import chalk from "chalk";
|
|
7
|
-
import { buildLaunchCommand, waitForPort, writeEnvFile } from "../dev-launch.js";
|
|
6
|
+
import { buildLaunchCommand, pickLaunchHostEnv, waitForPort, writeEnvFile } from "../dev-launch.js";
|
|
8
7
|
import { tunnelClientAddress } from "../dev.js";
|
|
9
8
|
import { MeshCliError } from "../../utils/errors.js";
|
|
10
9
|
import { DEFAULT_HUB_PORT } from "../local/stack.js";
|
|
11
10
|
import { logInfo, logSuccess, logWarn } from "../../utils/log.js";
|
|
12
11
|
import { findAppRoot } from "../../utils/pulumi.js";
|
|
12
|
+
import { devSessionsRoot } from "../../utils/dev-sessions-root.js";
|
|
13
13
|
export const HUB_SESSION_PREFIX = "mesh-hub-dev";
|
|
14
14
|
export function hubSessionName(apiPort) {
|
|
15
15
|
return `${HUB_SESSION_PREFIX}-${apiPort}`;
|
|
@@ -44,7 +44,7 @@ export function killInstructions(targets) {
|
|
|
44
44
|
export const DEFAULT_HUB_UI_PORT = Number(DEFAULT_HUB_PORT);
|
|
45
45
|
export const DEFAULT_HUB_API_PORT = 3002;
|
|
46
46
|
export function devSessionsDir() {
|
|
47
|
-
return
|
|
47
|
+
return devSessionsRoot();
|
|
48
48
|
}
|
|
49
49
|
export function listDevSessions(dir = devSessionsDir()) {
|
|
50
50
|
if (!fs.existsSync(dir))
|
|
@@ -279,7 +279,7 @@ function isPortListening(port) {
|
|
|
279
279
|
});
|
|
280
280
|
}
|
|
281
281
|
function hubEnvDir(sessionName) {
|
|
282
|
-
return path.join(
|
|
282
|
+
return path.join(devSessionsRoot(), sessionName);
|
|
283
283
|
}
|
|
284
284
|
export function parsePort(raw, fallback, source) {
|
|
285
285
|
if (raw === undefined)
|
|
@@ -375,6 +375,7 @@ async function hubDevAction(opts) {
|
|
|
375
375
|
const apiDir = path.join(platformDir, "apps", "hub", "api");
|
|
376
376
|
const uiDir = path.join(platformDir, "apps", "hub", "ui");
|
|
377
377
|
execFileSync("tmux", ["new-session", "-d", "-s", hubSession, "-n", "api", "-c", apiDir]);
|
|
378
|
+
const hostEnv = pickLaunchHostEnv(process.env);
|
|
378
379
|
if (!assembled.apiEnv.DEV_USER_TOKEN_URL && assembled.apiEnv.DEV_USER_ID_TOKEN) {
|
|
379
380
|
const platform = session.state.devOutput.platform;
|
|
380
381
|
const credContext = `mesh.${platform.env}`;
|
|
@@ -384,11 +385,13 @@ async function hubDevAction(opts) {
|
|
|
384
385
|
execFileSync("tmux", ["set-option", "-t", `${hubSession}:token-server`, "remain-on-exit", "on"], {
|
|
385
386
|
stdio: "ignore",
|
|
386
387
|
});
|
|
388
|
+
const tokenServerEnvFile = path.join(hubEnvDir(hubSession), "token-server.env.sh");
|
|
389
|
+
writeEnvFile(tokenServerEnvFile, hostEnv);
|
|
387
390
|
execFileSync("tmux", [
|
|
388
391
|
"send-keys",
|
|
389
392
|
"-t",
|
|
390
393
|
`${hubSession}:token-server`,
|
|
391
|
-
`npx mesh dev __token-server ${tokenPort} ${credContext}`,
|
|
394
|
+
buildLaunchCommand(tokenServerEnvFile, platformDir, `npx mesh dev __token-server ${tokenPort} ${credContext}`, undefined, hostEnv),
|
|
392
395
|
"Enter",
|
|
393
396
|
]);
|
|
394
397
|
for (let i = 0; i < 15 && !(await isPortListening(tokenPort)); i++) {
|
|
@@ -400,14 +403,14 @@ async function hubDevAction(opts) {
|
|
|
400
403
|
}
|
|
401
404
|
const launch = (window, dir, env, cmd, createWindow) => {
|
|
402
405
|
const envFile = path.join(hubEnvDir(hubSession), `${window}.env.sh`);
|
|
403
|
-
writeEnvFile(envFile, env);
|
|
406
|
+
writeEnvFile(envFile, { ...hostEnv, ...env });
|
|
404
407
|
if (createWindow) {
|
|
405
408
|
execFileSync("tmux", ["new-window", "-t", hubSession, "-n", window, "-c", dir]);
|
|
406
409
|
}
|
|
407
410
|
execFileSync("tmux", ["set-option", "-t", `${hubSession}:${window}`, "remain-on-exit", "on"], {
|
|
408
411
|
stdio: "ignore",
|
|
409
412
|
});
|
|
410
|
-
execFileSync("tmux", ["send-keys", "-t", `${hubSession}:${window}`, buildLaunchCommand(envFile, dir, cmd), "Enter"]);
|
|
413
|
+
execFileSync("tmux", ["send-keys", "-t", `${hubSession}:${window}`, buildLaunchCommand(envFile, dir, cmd, undefined, hostEnv), "Enter"]);
|
|
411
414
|
};
|
|
412
415
|
launch("api", apiDir, assembled.apiEnv, "pnpm dev", false);
|
|
413
416
|
launch("ui", uiDir, assembled.uiEnv, `pnpm dev --port ${uiPort} --strictPort`, true);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function resolveServeDependencies(from: string, pinned?: Readonly<Record<string, string>>, declared?: Readonly<Record<string, string>>): Record<string, string>;
|
|
2
|
+
export declare function defaultServeDir(manifestPath: string, name: string): string;
|
|
3
|
+
export interface GeneratedMcpServeDir {
|
|
4
|
+
serveDir: string;
|
|
5
|
+
written: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function generateMcpServeDir(manifestPath: string, opts?: {
|
|
8
|
+
out?: string;
|
|
9
|
+
refreshVersions?: boolean;
|
|
10
|
+
dependencies?: Record<string, string>;
|
|
11
|
+
}): GeneratedMcpServeDir;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { MCP_SERVE_DEPENDENCIES, MCP_SERVE_ENTRY, MCP_SERVE_SPEC, mcpServeEntrySource, mcpServePackageJson, mcpServeSpecSource, } from "@mesh-tech/api-registry/mcp-manifest";
|
|
5
|
+
import { readMcpManifestFile } from "./manifest-file.js";
|
|
6
|
+
const SERVE_HOST_PACKAGE = "@mesh-tech/app-kit";
|
|
7
|
+
function installedManifest(require_, from, name) {
|
|
8
|
+
try {
|
|
9
|
+
return require_(`${name}/package.json`);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
}
|
|
13
|
+
let dir = path.resolve(from);
|
|
14
|
+
for (;;) {
|
|
15
|
+
const file = path.join(dir, "node_modules", ...name.split("/"), "package.json");
|
|
16
|
+
if (fs.existsSync(file)) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const parent = path.dirname(dir);
|
|
25
|
+
if (parent === dir)
|
|
26
|
+
return undefined;
|
|
27
|
+
dir = parent;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function resolveServeDependencies(from, pinned = {}, declared = MCP_SERVE_DEPENDENCIES) {
|
|
31
|
+
const require_ = createRequire(path.join(path.resolve(from), "noop.js"));
|
|
32
|
+
const host = installedManifest(require_, from, SERVE_HOST_PACKAGE);
|
|
33
|
+
const peers = host?.peerDependencies ?? {};
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const [name, fallback] of Object.entries(declared)) {
|
|
36
|
+
const peer = name === SERVE_HOST_PACKAGE ? undefined : peers[name];
|
|
37
|
+
const usablePeer = peer && !peer.startsWith("workspace:") ? peer : undefined;
|
|
38
|
+
out[name] =
|
|
39
|
+
pinned[name] ?? usablePeer ?? installedManifest(require_, from, name)?.version ?? fallback;
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
export function defaultServeDir(manifestPath, name) {
|
|
44
|
+
return path.join(path.dirname(path.resolve(manifestPath)), ".mesh-mcp", name);
|
|
45
|
+
}
|
|
46
|
+
function readPinned(packageJsonPath) {
|
|
47
|
+
if (!fs.existsSync(packageJsonPath))
|
|
48
|
+
return {};
|
|
49
|
+
try {
|
|
50
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
51
|
+
return Object.fromEntries(Object.entries(pkg.dependencies ?? {}).filter((entry) => typeof entry[1] === "string"));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function generateMcpServeDir(manifestPath, opts = {}) {
|
|
58
|
+
const loaded = readMcpManifestFile(manifestPath);
|
|
59
|
+
const serveDir = path.resolve(opts.out ?? defaultServeDir(manifestPath, loaded.manifest.name));
|
|
60
|
+
fs.mkdirSync(serveDir, { recursive: true });
|
|
61
|
+
const packageJsonPath = path.join(serveDir, "package.json");
|
|
62
|
+
const dependencies = opts.dependencies ??
|
|
63
|
+
resolveServeDependencies(serveDir, opts.refreshVersions ? {} : readPinned(packageJsonPath));
|
|
64
|
+
const files = {
|
|
65
|
+
"package.json": `${JSON.stringify(mcpServePackageJson(loaded.manifest.name, dependencies), null, 2)}\n`,
|
|
66
|
+
[MCP_SERVE_ENTRY]: mcpServeEntrySource(loaded.manifest, loaded.tools),
|
|
67
|
+
[MCP_SERVE_SPEC]: mcpServeSpecSource(loaded),
|
|
68
|
+
};
|
|
69
|
+
const written = [];
|
|
70
|
+
for (const [name, content] of Object.entries(files)) {
|
|
71
|
+
const file = path.join(serveDir, name);
|
|
72
|
+
if (fs.existsSync(file) && fs.readFileSync(file, "utf8") === content)
|
|
73
|
+
continue;
|
|
74
|
+
fs.writeFileSync(file, content);
|
|
75
|
+
written.push(name);
|
|
76
|
+
}
|
|
77
|
+
return { serveDir, written };
|
|
78
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { logError, logInfo, logWarn } from "../../utils/log.js";
|
|
3
|
+
export function registerMcpCommands(program) {
|
|
4
|
+
const mcp = program
|
|
5
|
+
.command("mcp")
|
|
6
|
+
.description("Manifest-driven MCP Integrations (reviewed tool catalogs)");
|
|
7
|
+
mcp
|
|
8
|
+
.command("review <manifest>")
|
|
9
|
+
.description("Fetch a manifest's MCP server tools/list with the Integration's credential, diff it against the checked-in snapshot, and write the snapshot on confirmation")
|
|
10
|
+
.option("--yes", "Write without prompting")
|
|
11
|
+
.option("--dry-run", "Report the diff and write nothing")
|
|
12
|
+
.option("--stack <stack>", "Pulumi stack name (override detection)")
|
|
13
|
+
.option("-t, --tenant <tenant>", "Tenant to address (override detection)")
|
|
14
|
+
.option("--region <region>", "AWS region (default: us-east-2)")
|
|
15
|
+
.option("--secret <id>", "Secrets Manager secret holding the Integration credential (default: the deployed ExternalService's own secret)")
|
|
16
|
+
.action(async (manifest, opts) => {
|
|
17
|
+
const { runMcpReviewCommand } = await import("./review-command.js");
|
|
18
|
+
try {
|
|
19
|
+
await runMcpReviewCommand(manifest, opts);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
mcp
|
|
27
|
+
.command("generate <manifest>")
|
|
28
|
+
.description("Write the worker package mesh.apps.McpIntegration deploys (package.json, main.ts, openapi.json) from a manifest and its reviewed snapshot; commit it with the lockfile")
|
|
29
|
+
.option("--out <dir>", "Package directory (default: <manifest dir>/.mesh-mcp/<name>)")
|
|
30
|
+
.option("--refresh-versions", "Re-resolve every dependency version instead of keeping the committed ones")
|
|
31
|
+
.action(async (manifest, opts) => {
|
|
32
|
+
const { generateMcpServeDir } = await import("./generate.js");
|
|
33
|
+
const { workspaceMembership } = await import("./workspace-membership.js");
|
|
34
|
+
try {
|
|
35
|
+
const { serveDir, written } = generateMcpServeDir(manifest, {
|
|
36
|
+
...(opts.out ? { out: opts.out } : {}),
|
|
37
|
+
...(opts.refreshVersions ? { refreshVersions: true } : {}),
|
|
38
|
+
});
|
|
39
|
+
if (written.length === 0) {
|
|
40
|
+
logInfo(`${serveDir} is current.`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
logInfo(`Wrote ${written.join(", ")} in ${serveDir}.`);
|
|
44
|
+
if (written.includes("package.json")) {
|
|
45
|
+
logInfo("Run `pnpm install` so the lockfile records it, then commit both.");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const membership = workspaceMembership(serveDir);
|
|
49
|
+
if (!membership.workspaceFile) {
|
|
50
|
+
logWarn(`No pnpm-workspace.yaml above ${serveDir}: the worker image installs this package from a pnpm workspace, so it must be in one.`);
|
|
51
|
+
}
|
|
52
|
+
else if (!membership.member && membership.suggestedGlob) {
|
|
53
|
+
const { ensureWorkspaceGlobs } = await import("../create-app.js");
|
|
54
|
+
const added = ensureWorkspaceGlobs(path.dirname(membership.workspaceFile), [
|
|
55
|
+
membership.suggestedGlob,
|
|
56
|
+
]);
|
|
57
|
+
if (added.length > 0) {
|
|
58
|
+
logInfo(`${membership.workspaceFile}: added ${added.join(", ")} so \`pnpm install\` records the package.`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
logWarn(`${serveDir} is not a member of the workspace in ${membership.workspaceFile}, so \`pnpm install\` will not record it. Add "${membership.suggestedGlob}" to its packages.`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { parseMcpManifest, resolveMcpManifest, } from "@mesh-tech/api-registry/mcp-manifest";
|
|
4
|
+
function readJson(file) {
|
|
5
|
+
let text;
|
|
6
|
+
try {
|
|
7
|
+
text = readFileSync(file, "utf8");
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
throw new Error(`MCP manifest: cannot read ${file} (${error instanceof Error ? error.message : String(error)})`);
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(text);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
throw new Error(`MCP manifest: ${file} is not valid JSON (${error instanceof Error ? error.message : String(error)})`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function readMcpManifestFile(manifestPath) {
|
|
20
|
+
const absolute = path.resolve(manifestPath);
|
|
21
|
+
const manifest = parseMcpManifest(readJson(absolute));
|
|
22
|
+
const toolsPath = path.resolve(path.dirname(absolute), manifest.tools);
|
|
23
|
+
return { ...resolveMcpManifest(manifest, readJson(toolsPath), { toolsPath }), toolsPath };
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { SSMClient } from "@aws-sdk/client-ssm";
|
|
2
|
+
import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
|
|
3
|
+
import { type FetchMcpTools } from "./review.js";
|
|
4
|
+
export declare function resolveIntegrationCredentials(name: string, opts: {
|
|
5
|
+
stack?: string;
|
|
6
|
+
stage?: string;
|
|
7
|
+
tenant?: string;
|
|
8
|
+
region?: string;
|
|
9
|
+
secret?: string;
|
|
10
|
+
}, clients?: {
|
|
11
|
+
ssm?: SSMClient;
|
|
12
|
+
secretsManager?: Pick<SecretsManagerClient, "send">;
|
|
13
|
+
ensureCredentials?: (stack: string | undefined) => Promise<void>;
|
|
14
|
+
}): Promise<Record<string, string>>;
|
|
15
|
+
export declare const fetchMcpToolsOverHttp: FetchMcpTools;
|
|
16
|
+
export declare function runMcpReviewCommand(manifestPath: string, opts: {
|
|
17
|
+
yes?: boolean;
|
|
18
|
+
dryRun?: boolean;
|
|
19
|
+
stack?: string;
|
|
20
|
+
stage?: string;
|
|
21
|
+
tenant?: string;
|
|
22
|
+
region?: string;
|
|
23
|
+
secret?: string;
|
|
24
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { SSMClient } from "@aws-sdk/client-ssm";
|
|
2
|
+
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
|
|
3
|
+
import confirm from "@inquirer/confirm";
|
|
4
|
+
import { logInfo } from "../../utils/log.js";
|
|
5
|
+
import { discoverStackExternalServices, ensureAwsCredentialsForStack, resolveExternalServiceScope, } from "../secrets/set.js";
|
|
6
|
+
import { readMcpManifestFile } from "./manifest-file.js";
|
|
7
|
+
import { reviewMcpCatalog } from "./review.js";
|
|
8
|
+
export async function resolveIntegrationCredentials(name, opts, clients = {}) {
|
|
9
|
+
const region = opts.region ?? process.env.AWS_REGION ?? "us-east-2";
|
|
10
|
+
let secretId = opts.secret;
|
|
11
|
+
if (secretId) {
|
|
12
|
+
const ensure = clients.ensureCredentials ?? ensureAwsCredentialsForStack;
|
|
13
|
+
await ensure(resolveExternalServiceScope(opts).stack);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
const { scope, services } = await discoverStackExternalServices(opts, clients.ssm ?? new SSMClient({ region }), clients.ensureCredentials ? { ensureCredentials: clients.ensureCredentials } : {});
|
|
17
|
+
const service = services.get(name);
|
|
18
|
+
if (!service?.meta?.secretPrefix) {
|
|
19
|
+
throw new Error(`No deployed ExternalService named "${name}" in ${scope.context.tenant}/${scope.platformEnv} (stack ${scope.stack ?? "unknown"}). Deploy the Integration first, or pass --secret <id>.`);
|
|
20
|
+
}
|
|
21
|
+
secretId = service.meta.secretPrefix;
|
|
22
|
+
}
|
|
23
|
+
const response = await (clients.secretsManager ?? new SecretsManagerClient({ region })).send(new GetSecretValueCommand({ SecretId: secretId }));
|
|
24
|
+
if (!response.SecretString) {
|
|
25
|
+
throw new Error(`The Integration credential at "${secretId}" is empty — populate it with \`mesh secrets set external/${name}\``);
|
|
26
|
+
}
|
|
27
|
+
return JSON.parse(response.SecretString);
|
|
28
|
+
}
|
|
29
|
+
export const fetchMcpToolsOverHttp = async ({ serverUrl, headers, timeoutMs }) => {
|
|
30
|
+
const { Client, StreamableHTTPClientTransport } = await import("@modelcontextprotocol/client");
|
|
31
|
+
const client = new Client({ name: "mesh-mcp-review", version: "1.0.0" }, { capabilities: {}, versionNegotiation: { mode: "auto" } });
|
|
32
|
+
const transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
|
|
33
|
+
requestInit: { headers, redirect: "error" },
|
|
34
|
+
});
|
|
35
|
+
try {
|
|
36
|
+
await client.connect(transport, { timeout: timeoutMs });
|
|
37
|
+
const result = await client.listTools(undefined, { timeout: timeoutMs });
|
|
38
|
+
return result.tools;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
if (transport.sessionId)
|
|
42
|
+
await transport.terminateSession().catch(() => { });
|
|
43
|
+
await client.close().catch(() => { });
|
|
44
|
+
await transport.close().catch(() => { });
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
export async function runMcpReviewCommand(manifestPath, opts) {
|
|
48
|
+
const { manifest } = readMcpManifestFile(manifestPath);
|
|
49
|
+
const credentials = manifest.auth.kind === "shared-header"
|
|
50
|
+
? await resolveIntegrationCredentials(manifest.name, opts)
|
|
51
|
+
: {};
|
|
52
|
+
if (manifest.auth.kind === "none") {
|
|
53
|
+
logInfo(`${manifest.name} declares no authentication; connecting unauthenticated.`);
|
|
54
|
+
}
|
|
55
|
+
const result = await reviewMcpCatalog({
|
|
56
|
+
manifestPath,
|
|
57
|
+
credentials,
|
|
58
|
+
fetchTools: fetchMcpToolsOverHttp,
|
|
59
|
+
...(opts.yes ? { yes: true } : {}),
|
|
60
|
+
...(opts.dryRun ? { dryRun: true } : {}),
|
|
61
|
+
confirm: (question) => confirm({ message: question, default: false }),
|
|
62
|
+
});
|
|
63
|
+
if (result.written) {
|
|
64
|
+
logInfo("Commit the snapshot with the regenerated worker package; McpIntegration refuses a package that does not match its manifest.");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type McpManifest, type McpTool, type McpToolAccess } from "@mesh-tech/api-registry/mcp-manifest";
|
|
2
|
+
export interface ToolDiffEntry {
|
|
3
|
+
name: string;
|
|
4
|
+
status: "added" | "removed" | "changed" | "unchanged";
|
|
5
|
+
access: McpToolAccess;
|
|
6
|
+
suggested?: "read";
|
|
7
|
+
detail?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SnapshotDiff {
|
|
10
|
+
entries: ToolDiffEntry[];
|
|
11
|
+
added: string[];
|
|
12
|
+
removed: string[];
|
|
13
|
+
changed: string[];
|
|
14
|
+
removedEnabled: string[];
|
|
15
|
+
clean: boolean;
|
|
16
|
+
next: McpTool[];
|
|
17
|
+
}
|
|
18
|
+
export declare function diffSnapshot(current: readonly McpTool[], fetched: readonly McpTool[], enabledOps?: readonly string[], classification?: Record<string, McpToolAccess>): SnapshotDiff;
|
|
19
|
+
export type FetchMcpTools = (input: {
|
|
20
|
+
serverUrl: string;
|
|
21
|
+
headers: Record<string, string>;
|
|
22
|
+
timeoutMs: number;
|
|
23
|
+
}) => Promise<unknown>;
|
|
24
|
+
export type ConfirmFn = (question: string) => Promise<boolean>;
|
|
25
|
+
export interface ReviewMcpCatalogOptions {
|
|
26
|
+
manifestPath: string;
|
|
27
|
+
credentials: Readonly<Record<string, string>>;
|
|
28
|
+
fetchTools: FetchMcpTools;
|
|
29
|
+
confirm?: ConfirmFn;
|
|
30
|
+
report?: (line: string) => void;
|
|
31
|
+
yes?: boolean;
|
|
32
|
+
dryRun?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface ReviewMcpCatalogResult {
|
|
35
|
+
diff: SnapshotDiff;
|
|
36
|
+
manifest: McpManifest;
|
|
37
|
+
snapshotPath: string;
|
|
38
|
+
written: boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare function reviewMcpCatalog(opts: ReviewMcpCatalogOptions): Promise<ReviewMcpCatalogResult>;
|
|
41
|
+
export declare function assertEnabledPresent(manifestName: string, enabledOps: readonly string[], catalog: readonly McpTool[]): void;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { classifyMcpTools, mcpAuthHeaders, parseMcpToolSnapshot, } from "@mesh-tech/api-registry/mcp-manifest";
|
|
3
|
+
import { defaultServeDir, generateMcpServeDir } from "./generate.js";
|
|
4
|
+
import { readMcpManifestFile } from "./manifest-file.js";
|
|
5
|
+
function canonical(tool) {
|
|
6
|
+
return JSON.stringify(tool, Object.keys(tool).sort());
|
|
7
|
+
}
|
|
8
|
+
function describeChange(before, after) {
|
|
9
|
+
const parts = [];
|
|
10
|
+
if (before.description !== after.description)
|
|
11
|
+
parts.push("description");
|
|
12
|
+
if (JSON.stringify(before.inputSchema) !== JSON.stringify(after.inputSchema))
|
|
13
|
+
parts.push("input schema");
|
|
14
|
+
if (JSON.stringify(before.outputSchema) !== JSON.stringify(after.outputSchema))
|
|
15
|
+
parts.push("output schema");
|
|
16
|
+
if (JSON.stringify(before.annotations) !== JSON.stringify(after.annotations))
|
|
17
|
+
parts.push("annotations (read/write classification)");
|
|
18
|
+
return parts.length > 0 ? parts.join(", ") : "other fields";
|
|
19
|
+
}
|
|
20
|
+
export function diffSnapshot(current, fetched, enabledOps = [], classification = {}) {
|
|
21
|
+
const before = new Map(current.map((t) => [t.name, t]));
|
|
22
|
+
const after = new Map(fetched.map((t) => [t.name, t]));
|
|
23
|
+
const classified = {
|
|
24
|
+
...classifyMcpTools(current, classification),
|
|
25
|
+
...classifyMcpTools(fetched, classification),
|
|
26
|
+
};
|
|
27
|
+
const accessOf = (name) => {
|
|
28
|
+
const entry = classified[name];
|
|
29
|
+
return { access: entry.access, ...(entry.suggested ? { suggested: entry.suggested } : {}) };
|
|
30
|
+
};
|
|
31
|
+
const entries = [];
|
|
32
|
+
for (const tool of fetched) {
|
|
33
|
+
const prior = before.get(tool.name);
|
|
34
|
+
if (!prior) {
|
|
35
|
+
entries.push({ name: tool.name, status: "added", ...accessOf(tool.name) });
|
|
36
|
+
}
|
|
37
|
+
else if (canonical(prior) !== canonical(tool)) {
|
|
38
|
+
entries.push({
|
|
39
|
+
name: tool.name,
|
|
40
|
+
status: "changed",
|
|
41
|
+
...accessOf(tool.name),
|
|
42
|
+
detail: describeChange(prior, tool),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
entries.push({ name: tool.name, status: "unchanged", ...accessOf(tool.name) });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (const tool of current) {
|
|
50
|
+
if (!after.has(tool.name)) {
|
|
51
|
+
entries.push({ name: tool.name, status: "removed", ...accessOf(tool.name) });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const pick = (status) => entries.filter((e) => e.status === status).map((e) => e.name);
|
|
55
|
+
const removed = pick("removed");
|
|
56
|
+
return {
|
|
57
|
+
entries,
|
|
58
|
+
added: pick("added"),
|
|
59
|
+
changed: pick("changed"),
|
|
60
|
+
removed,
|
|
61
|
+
removedEnabled: enabledOps.filter((op) => removed.includes(op)),
|
|
62
|
+
clean: pick("added").length === 0 && pick("changed").length === 0 && removed.length === 0,
|
|
63
|
+
next: [...fetched],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export async function reviewMcpCatalog(opts) {
|
|
67
|
+
const loaded = readMcpManifestFile(opts.manifestPath);
|
|
68
|
+
const { manifest } = loaded;
|
|
69
|
+
const report = opts.report ?? ((line) => console.log(line));
|
|
70
|
+
const snapshotPath = loaded.toolsPath;
|
|
71
|
+
const headers = mcpAuthHeaders(manifest, opts.credentials);
|
|
72
|
+
const fetched = parseMcpToolSnapshot(await opts.fetchTools({
|
|
73
|
+
serverUrl: manifest.serverUrl,
|
|
74
|
+
headers,
|
|
75
|
+
timeoutMs: manifest.timeoutMs ?? 30_000,
|
|
76
|
+
}), `${manifest.serverUrl} tools/list`);
|
|
77
|
+
if (fetched.length === 0) {
|
|
78
|
+
throw new Error(`${manifest.name}: the server answered tools/list with an empty catalog — refusing to replace a reviewed snapshot with nothing`);
|
|
79
|
+
}
|
|
80
|
+
const enabled = manifest.enabledOps ?? loaded.tools.map((t) => t.name);
|
|
81
|
+
const diff = diffSnapshot(loaded.tools, fetched, enabled, manifest.classification ?? {});
|
|
82
|
+
report(`${manifest.name} — ${manifest.serverUrl}`);
|
|
83
|
+
report(` snapshot: ${snapshotPath}`);
|
|
84
|
+
for (const entry of diff.entries) {
|
|
85
|
+
const mark = entry.status === "added"
|
|
86
|
+
? "+"
|
|
87
|
+
: entry.status === "removed"
|
|
88
|
+
? "-"
|
|
89
|
+
: entry.status === "changed"
|
|
90
|
+
? "~"
|
|
91
|
+
: " ";
|
|
92
|
+
const enabledMark = enabled.includes(entry.name) ? "enabled" : "not enabled";
|
|
93
|
+
const suggestion = entry.suggested ? ", vendor suggests read" : "";
|
|
94
|
+
report(` ${mark} ${entry.name} [${entry.access}${suggestion}, ${enabledMark}]${entry.detail ? ` — ${entry.detail}` : ""}`);
|
|
95
|
+
}
|
|
96
|
+
const suggestedReads = diff.entries
|
|
97
|
+
.filter((entry) => entry.suggested === "read" && entry.status !== "removed")
|
|
98
|
+
.map((entry) => entry.name);
|
|
99
|
+
if (suggestedReads.length > 0) {
|
|
100
|
+
report(` the vendor marks ${suggestedReads.join(", ")} read-only (readOnlyHint). They stay unknown and need approval until the manifest's "classification" names them "read".`);
|
|
101
|
+
}
|
|
102
|
+
if (diff.added.length > 0) {
|
|
103
|
+
report(` ${diff.added.length} new upstream tool(s) will be recorded but NOT enabled: add them to enabledOps deliberately.`);
|
|
104
|
+
}
|
|
105
|
+
if (diff.removedEnabled.length > 0) {
|
|
106
|
+
throw new Error(`${manifest.name}: the server no longer offers enabled operation(s) ${diff.removedEnabled.join(", ")} — remove them from enabledOps first, then re-review`);
|
|
107
|
+
}
|
|
108
|
+
if (diff.clean) {
|
|
109
|
+
report(" no changes — the reviewed snapshot still matches the server.");
|
|
110
|
+
return { diff, manifest, snapshotPath, written: false };
|
|
111
|
+
}
|
|
112
|
+
if (opts.dryRun) {
|
|
113
|
+
report(" --dry-run: nothing written.");
|
|
114
|
+
return { diff, manifest, snapshotPath, written: false };
|
|
115
|
+
}
|
|
116
|
+
const approved = opts.yes === true ||
|
|
117
|
+
(await (opts.confirm ?? (async () => false))(`Write the new snapshot to ${snapshotPath}?`));
|
|
118
|
+
if (!approved) {
|
|
119
|
+
report(" not written.");
|
|
120
|
+
return { diff, manifest, snapshotPath, written: false };
|
|
121
|
+
}
|
|
122
|
+
fs.writeFileSync(snapshotPath, `${JSON.stringify(diff.next, null, 2)}\n`);
|
|
123
|
+
report(` wrote ${diff.next.length} reviewed tool(s).`);
|
|
124
|
+
const serveDir = defaultServeDir(opts.manifestPath, manifest.name);
|
|
125
|
+
if (fs.existsSync(serveDir)) {
|
|
126
|
+
const generated = generateMcpServeDir(opts.manifestPath);
|
|
127
|
+
report(` regenerated ${generated.written.join(", ") || "nothing"} in ${generated.serveDir}.`);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
report(` run \`mesh mcp generate ${opts.manifestPath}\` to write the worker package.`);
|
|
131
|
+
}
|
|
132
|
+
return { diff, manifest, snapshotPath, written: true };
|
|
133
|
+
}
|
|
134
|
+
export function assertEnabledPresent(manifestName, enabledOps, catalog) {
|
|
135
|
+
const names = new Set(catalog.map((t) => t.name));
|
|
136
|
+
const missing = enabledOps.filter((op) => !names.has(op));
|
|
137
|
+
if (missing.length > 0) {
|
|
138
|
+
throw new Error(`${manifestName}: cannot enable ${missing.join(", ")} — absent from the reviewed snapshot`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface WorkspaceMembership {
|
|
2
|
+
workspaceFile?: string;
|
|
3
|
+
member: boolean;
|
|
4
|
+
suggestedGlob?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function matchesWorkspaceGlob(pattern: string, relDir: string): boolean;
|
|
7
|
+
export declare function workspaceMembership(dir: string): WorkspaceMembership;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
function findWorkspaceFile(from) {
|
|
5
|
+
let dir = from;
|
|
6
|
+
for (;;) {
|
|
7
|
+
const file = path.join(dir, "pnpm-workspace.yaml");
|
|
8
|
+
if (fs.existsSync(file))
|
|
9
|
+
return file;
|
|
10
|
+
const parent = path.dirname(dir);
|
|
11
|
+
if (parent === dir)
|
|
12
|
+
return undefined;
|
|
13
|
+
dir = parent;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function segmentPattern(segment) {
|
|
17
|
+
const body = segment
|
|
18
|
+
.split("")
|
|
19
|
+
.map((ch) => ch === "*" ? "[^/]*" : ch === "?" ? "[^/]" : ch.replace(/[.+^${}()|[\]\\]/g, "\\$&"))
|
|
20
|
+
.join("");
|
|
21
|
+
return new RegExp(segment.startsWith(".") ? `^${body}$` : `^(?!\\.)${body}$`);
|
|
22
|
+
}
|
|
23
|
+
function matchSegments(pattern, target) {
|
|
24
|
+
if (pattern.length === 0)
|
|
25
|
+
return target.length === 0;
|
|
26
|
+
const [head, ...rest] = pattern;
|
|
27
|
+
if (head === "**") {
|
|
28
|
+
for (let i = 0; i <= target.length; i++) {
|
|
29
|
+
if (target.slice(0, i).some((s) => s.startsWith(".")))
|
|
30
|
+
break;
|
|
31
|
+
if (matchSegments(rest, target.slice(i)))
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (target.length === 0)
|
|
37
|
+
return false;
|
|
38
|
+
return segmentPattern(head).test(target[0]) && matchSegments(rest, target.slice(1));
|
|
39
|
+
}
|
|
40
|
+
export function matchesWorkspaceGlob(pattern, relDir) {
|
|
41
|
+
const clean = (p) => p
|
|
42
|
+
.replace(/^\.\//, "")
|
|
43
|
+
.replace(/\/+$/, "")
|
|
44
|
+
.split("/")
|
|
45
|
+
.filter((s) => s.length > 0 && s !== ".");
|
|
46
|
+
return matchSegments(clean(pattern), clean(relDir.split(path.sep).join("/")));
|
|
47
|
+
}
|
|
48
|
+
export function workspaceMembership(dir) {
|
|
49
|
+
const abs = path.resolve(dir);
|
|
50
|
+
const workspaceFile = findWorkspaceFile(abs);
|
|
51
|
+
if (!workspaceFile)
|
|
52
|
+
return { member: false };
|
|
53
|
+
const root = path.dirname(workspaceFile);
|
|
54
|
+
const rel = path.relative(root, abs).split(path.sep).join("/");
|
|
55
|
+
const suggestedGlob = `${path.posix.dirname(rel)}/*`;
|
|
56
|
+
let globs;
|
|
57
|
+
try {
|
|
58
|
+
globs = parse(fs.readFileSync(workspaceFile, "utf8"))
|
|
59
|
+
?.packages;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return { workspaceFile, member: false, suggestedGlob };
|
|
63
|
+
}
|
|
64
|
+
let member = false;
|
|
65
|
+
for (const glob of Array.isArray(globs) ? globs : []) {
|
|
66
|
+
if (typeof glob !== "string")
|
|
67
|
+
continue;
|
|
68
|
+
if (glob.startsWith("!")) {
|
|
69
|
+
if (matchesWorkspaceGlob(glob.slice(1), rel))
|
|
70
|
+
member = false;
|
|
71
|
+
}
|
|
72
|
+
else if (matchesWorkspaceGlob(glob, rel)) {
|
|
73
|
+
member = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { workspaceFile, member, suggestedGlob };
|
|
77
|
+
}
|
|
@@ -2,9 +2,7 @@ import { SSMClient } from "@aws-sdk/client-ssm";
|
|
|
2
2
|
import { SecretsManagerClient, ListSecretsCommand, GetSecretValueCommand, PutSecretValueCommand, CreateSecretCommand, } from "@aws-sdk/client-secrets-manager";
|
|
3
3
|
import { buildInstanceIndexValue, instanceIndexSecretId, instanceKeyFromSecretName, parseInstanceIndex, } from "@mesh-tech/secrets";
|
|
4
4
|
import { logInfo, logSuccess, logError } from "../../utils/log.js";
|
|
5
|
-
import {
|
|
6
|
-
import { resolveSecretsContext } from "./context.js";
|
|
7
|
-
import { discoverExternalServices, ensureAwsCredentialsForStack, isResourceNotFound, } from "./set.js";
|
|
5
|
+
import { discoverStackExternalServices, isResourceNotFound } from "./set.js";
|
|
8
6
|
export async function scanInstanceKeys(client, secretPrefix) {
|
|
9
7
|
const keys = [];
|
|
10
8
|
let nextToken;
|
|
@@ -26,13 +24,10 @@ export function indexAlreadyMatches(missing, stale, indexExists, malformed) {
|
|
|
26
24
|
return missing.length === 0 && stale.length === 0 && indexExists && !malformed;
|
|
27
25
|
}
|
|
28
26
|
export async function reindexCommand(servicePath, opts) {
|
|
29
|
-
const stackOpt = resolveStackOption(opts);
|
|
30
|
-
const context = resolveSecretsContext(opts, stackOpt);
|
|
31
27
|
const region = opts.region ?? process.env.AWS_REGION ?? "us-east-2";
|
|
32
|
-
await ensureAwsCredentialsForStack(stackOpt ?? context.stage);
|
|
33
28
|
const ssmClient = new SSMClient({ region });
|
|
29
|
+
const { services } = await discoverStackExternalServices(opts, ssmClient);
|
|
34
30
|
const smClient = new SecretsManagerClient({ region });
|
|
35
|
-
const services = await discoverExternalServices(ssmClient, context.tenant, context.platformEnv);
|
|
36
31
|
const serviceName = servicePath?.replace(/^external\//, "");
|
|
37
32
|
if (!serviceName || !services.has(serviceName)) {
|
|
38
33
|
if (serviceName) {
|