@evantahler/mcpx 0.18.3 → 0.18.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/package.json +63 -63
- package/src/cli.ts +46 -54
- package/src/client/browser.ts +36 -15
- package/src/client/debug-fetch.ts +64 -56
- package/src/client/elicitation.ts +279 -291
- package/src/client/http.ts +1 -1
- package/src/client/manager.ts +481 -514
- package/src/client/oauth.ts +272 -282
- package/src/client/sse.ts +1 -1
- package/src/client/stdio.ts +7 -7
- package/src/client/trace.ts +146 -152
- package/src/client/transport-options.ts +20 -20
- package/src/commands/add.ts +160 -165
- package/src/commands/allow.ts +141 -142
- package/src/commands/auth.ts +86 -90
- package/src/commands/check-update.ts +49 -53
- package/src/commands/deny.ts +114 -117
- package/src/commands/exec.ts +218 -222
- package/src/commands/index.ts +41 -41
- package/src/commands/info.ts +48 -50
- package/src/commands/list.ts +49 -49
- package/src/commands/ping.ts +47 -50
- package/src/commands/prompt.ts +40 -50
- package/src/commands/remove.ts +54 -56
- package/src/commands/resource.ts +31 -36
- package/src/commands/search.ts +35 -39
- package/src/commands/servers.ts +44 -48
- package/src/commands/skill.ts +89 -95
- package/src/commands/task.ts +50 -60
- package/src/commands/upgrade.ts +191 -208
- package/src/commands/with-command.ts +27 -29
- package/src/config/env.ts +26 -28
- package/src/config/loader.ts +103 -103
- package/src/config/schemas.ts +78 -87
- package/src/constants.ts +17 -17
- package/src/context.ts +51 -51
- package/src/lib/client-settings.ts +127 -140
- package/src/lib/input.ts +23 -26
- package/src/output/format-output.ts +12 -16
- package/src/output/format-table.ts +39 -42
- package/src/output/formatter.ts +794 -815
- package/src/output/logger.ts +140 -152
- package/src/sdk.ts +283 -291
- package/src/search/index.ts +50 -54
- package/src/search/indexer.ts +65 -65
- package/src/search/keyword.ts +54 -54
- package/src/search/semantic.ts +39 -39
- package/src/search/staleness.ts +3 -3
- package/src/search/types.ts +4 -4
- package/src/update/background.ts +51 -51
- package/src/update/cache.ts +21 -21
- package/src/update/checker.ts +81 -86
- package/src/validation/schema.ts +53 -58
package/src/commands/upgrade.ts
CHANGED
|
@@ -1,226 +1,209 @@
|
|
|
1
|
+
import { tmpdir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { dim, green, red, yellow } from "ansis";
|
|
1
4
|
import { $ } from "bun";
|
|
2
|
-
import { green, yellow, red, cyan, dim } from "ansis";
|
|
3
5
|
import type { Command } from "commander";
|
|
4
6
|
import { createSpinner } from "nanospinner";
|
|
5
|
-
import { tmpdir } from "os";
|
|
6
|
-
import { join } from "path";
|
|
7
7
|
import pkg from "../../package.json";
|
|
8
|
-
import {
|
|
9
|
-
checkForUpdate,
|
|
10
|
-
detectInstallMethod,
|
|
11
|
-
needsCheck,
|
|
12
|
-
type InstallMethod,
|
|
13
|
-
} from "../update/checker.ts";
|
|
14
|
-
import { loadUpdateCache, saveUpdateCache, clearUpdateCache } from "../update/cache.ts";
|
|
15
|
-
import type { UpdateCache } from "../update/checker.ts";
|
|
16
8
|
import pkgMeta from "../../package.json";
|
|
9
|
+
import { clearUpdateCache, loadUpdateCache, saveUpdateCache } from "../update/cache.ts";
|
|
10
|
+
import type { UpdateCache } from "../update/checker.ts";
|
|
11
|
+
import { checkForUpdate, detectInstallMethod, type InstallMethod, needsCheck } from "../update/checker.ts";
|
|
17
12
|
|
|
18
|
-
const GITHUB_REPO = pkgMeta.repository.url
|
|
19
|
-
.replace(/^https:\/\/github\.com\//, "")
|
|
20
|
-
.replace(/\.git$/, "");
|
|
13
|
+
const GITHUB_REPO = pkgMeta.repository.url.replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
|
|
21
14
|
|
|
22
15
|
function platformArtifactName(): string {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
16
|
+
let os: string;
|
|
17
|
+
let ext = "";
|
|
18
|
+
switch (process.platform) {
|
|
19
|
+
case "darwin":
|
|
20
|
+
os = "darwin";
|
|
21
|
+
break;
|
|
22
|
+
case "win32":
|
|
23
|
+
os = "windows";
|
|
24
|
+
ext = ".exe";
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
os = "linux";
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
const arch = process.arch === "arm64" ? "arm64" : "x64";
|
|
31
|
+
return `mcpx-${os}-${arch}${ext}`;
|
|
39
32
|
}
|
|
40
33
|
|
|
41
34
|
async function upgradeWithPackageManager(command: string, args: string[]): Promise<boolean> {
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
const result = await $`${command} ${args}`.nothrow();
|
|
36
|
+
return result.exitCode === 0;
|
|
44
37
|
}
|
|
45
38
|
|
|
46
39
|
async function upgradeFromBinary(latestVersion: string): Promise<boolean> {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
40
|
+
const artifact = platformArtifactName();
|
|
41
|
+
const tag = `v${latestVersion}`;
|
|
42
|
+
const url = `https://github.com/${GITHUB_REPO}/releases/download/${tag}/${artifact}`;
|
|
43
|
+
|
|
44
|
+
const tmpPath = join(tmpdir(), `mcpx-upgrade-${Date.now()}`);
|
|
45
|
+
const targetPath = process.execPath;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const res = await fetch(url);
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
console.error(red(`Failed to download binary: HTTP ${res.status}`));
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const bytes = await res.arrayBuffer();
|
|
55
|
+
await Bun.write(tmpPath, bytes);
|
|
56
|
+
|
|
57
|
+
await $`chmod +x ${tmpPath}`.quiet();
|
|
58
|
+
|
|
59
|
+
// Try to move into place
|
|
60
|
+
const mv = await $`mv ${tmpPath} ${targetPath}`.quiet().nothrow();
|
|
61
|
+
|
|
62
|
+
if (mv.exitCode !== 0) {
|
|
63
|
+
// Try with sudo
|
|
64
|
+
console.log(dim("Requires elevated permissions..."));
|
|
65
|
+
const sudo = await $`sudo mv ${tmpPath} ${targetPath}`.nothrow();
|
|
66
|
+
if (sudo.exitCode !== 0) {
|
|
67
|
+
console.error(red("Failed to install binary. Try running with sudo."));
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return true;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error(red(`Failed to upgrade binary: ${err}`));
|
|
75
|
+
// Clean up temp file
|
|
76
|
+
await $`rm -f ${tmpPath}`.quiet().nothrow();
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
86
79
|
}
|
|
87
80
|
|
|
88
81
|
export function registerUpgradeCommand(program: Command) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
console.error(red("Upgrade failed. See errors above."));
|
|
217
|
-
}
|
|
218
|
-
process.exit(1);
|
|
219
|
-
}
|
|
220
|
-
} catch (err) {
|
|
221
|
-
spinner?.error({ text: "Upgrade failed" });
|
|
222
|
-
console.error(String(err));
|
|
223
|
-
process.exit(1);
|
|
224
|
-
}
|
|
225
|
-
});
|
|
82
|
+
program
|
|
83
|
+
.command("upgrade")
|
|
84
|
+
.description("Upgrade mcpx to the latest version")
|
|
85
|
+
.action(async () => {
|
|
86
|
+
const opts = program.opts();
|
|
87
|
+
const json = !!(opts.json as boolean | undefined);
|
|
88
|
+
const isTTY = process.stderr.isTTY ?? false;
|
|
89
|
+
|
|
90
|
+
const spinner =
|
|
91
|
+
!json && isTTY ? createSpinner("Checking for updates...", { stream: process.stderr }).start() : null;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// Check for update (use cache if fresh)
|
|
95
|
+
const cache = await loadUpdateCache();
|
|
96
|
+
let latestVersion: string;
|
|
97
|
+
let hasUpdate: boolean;
|
|
98
|
+
|
|
99
|
+
if (!needsCheck(cache) && cache) {
|
|
100
|
+
latestVersion = cache.latestVersion;
|
|
101
|
+
hasUpdate = cache.hasUpdate;
|
|
102
|
+
} else {
|
|
103
|
+
const info = await checkForUpdate(pkg.version);
|
|
104
|
+
latestVersion = info.latestVersion;
|
|
105
|
+
hasUpdate = info.hasUpdate;
|
|
106
|
+
|
|
107
|
+
const newCache: UpdateCache = {
|
|
108
|
+
lastCheckAt: new Date().toISOString(),
|
|
109
|
+
latestVersion,
|
|
110
|
+
hasUpdate,
|
|
111
|
+
changelog: info.changelog,
|
|
112
|
+
};
|
|
113
|
+
await saveUpdateCache(newCache);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!hasUpdate) {
|
|
117
|
+
spinner?.stop();
|
|
118
|
+
if (json) {
|
|
119
|
+
console.log(
|
|
120
|
+
JSON.stringify({
|
|
121
|
+
upgraded: false,
|
|
122
|
+
currentVersion: pkg.version,
|
|
123
|
+
message: "Already up to date",
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
126
|
+
} else {
|
|
127
|
+
console.log(green(`mcpx is already up to date (v${pkg.version})`));
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const method: InstallMethod = detectInstallMethod();
|
|
133
|
+
spinner?.update({
|
|
134
|
+
text: `Upgrading from v${pkg.version} to v${latestVersion} (${method})...`,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
let success = false;
|
|
138
|
+
|
|
139
|
+
switch (method) {
|
|
140
|
+
case "bun":
|
|
141
|
+
spinner?.stop();
|
|
142
|
+
success = await upgradeWithPackageManager("bun", ["install", "-g", `@evantahler/mcpx@${latestVersion}`]);
|
|
143
|
+
break;
|
|
144
|
+
|
|
145
|
+
case "npm":
|
|
146
|
+
spinner?.stop();
|
|
147
|
+
success = await upgradeWithPackageManager("npm", ["install", "-g", `@evantahler/mcpx@${latestVersion}`]);
|
|
148
|
+
break;
|
|
149
|
+
|
|
150
|
+
case "binary":
|
|
151
|
+
spinner?.stop();
|
|
152
|
+
success = await upgradeFromBinary(latestVersion);
|
|
153
|
+
break;
|
|
154
|
+
|
|
155
|
+
case "local-dev":
|
|
156
|
+
spinner?.stop();
|
|
157
|
+
if (json) {
|
|
158
|
+
console.log(
|
|
159
|
+
JSON.stringify({
|
|
160
|
+
upgraded: false,
|
|
161
|
+
currentVersion: pkg.version,
|
|
162
|
+
latestVersion,
|
|
163
|
+
installMethod: "local-dev",
|
|
164
|
+
message: "Running from source. Use `git pull && bun install` to update.",
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
167
|
+
} else {
|
|
168
|
+
console.log(yellow("Running from source. Use `git pull && bun install` to update."));
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (success) {
|
|
174
|
+
await clearUpdateCache();
|
|
175
|
+
if (json) {
|
|
176
|
+
console.log(
|
|
177
|
+
JSON.stringify({
|
|
178
|
+
upgraded: true,
|
|
179
|
+
previousVersion: pkg.version,
|
|
180
|
+
newVersion: latestVersion,
|
|
181
|
+
installMethod: method,
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
} else {
|
|
185
|
+
console.log(green(`Successfully upgraded mcpx: v${pkg.version} → v${latestVersion}`));
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
if (json) {
|
|
189
|
+
console.log(
|
|
190
|
+
JSON.stringify({
|
|
191
|
+
upgraded: false,
|
|
192
|
+
currentVersion: pkg.version,
|
|
193
|
+
latestVersion,
|
|
194
|
+
installMethod: method,
|
|
195
|
+
message: "Upgrade failed",
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
console.error(red("Upgrade failed. See errors above."));
|
|
200
|
+
}
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
spinner?.error({ text: "Upgrade failed" });
|
|
205
|
+
console.error(String(err));
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
226
209
|
}
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
-
import {
|
|
2
|
+
import { type AppContext, getContext } from "../context.ts";
|
|
3
3
|
import { formatError } from "../output/formatter.ts";
|
|
4
4
|
import { logger, type Spinner } from "../output/logger.ts";
|
|
5
5
|
|
|
6
6
|
export interface CommandContext extends AppContext {
|
|
7
|
-
|
|
7
|
+
spinner: Spinner;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
interface WithCommandOptions {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
/** Spinner text shown during execution. If omitted, no spinner is started. */
|
|
12
|
+
spinnerText?: string;
|
|
13
|
+
/** Error message for spinner.error(). Defaults to "Failed". */
|
|
14
|
+
errorLabel?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const noopSpinner: Spinner = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
update() {},
|
|
19
|
+
success() {},
|
|
20
|
+
error() {},
|
|
21
|
+
stop() {},
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -34,26 +34,24 @@ const noopSpinner: Spinner = {
|
|
|
34
34
|
* manager.close() is always called in finally.
|
|
35
35
|
*/
|
|
36
36
|
export function withCommand<TArgs extends unknown[]>(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
program: Command,
|
|
38
|
+
options: WithCommandOptions,
|
|
39
|
+
handler: (ctx: CommandContext, ...args: TArgs) => Promise<void>,
|
|
40
40
|
): (...args: TArgs) => Promise<void> {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
return async (...args: TArgs) => {
|
|
42
|
+
const appCtx = await getContext(program);
|
|
43
|
+
const { manager, formatOptions } = appCtx;
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
? logger.startSpinner(options.spinnerText, formatOptions)
|
|
47
|
-
: noopSpinner;
|
|
45
|
+
const spinner = options.spinnerText ? logger.startSpinner(options.spinnerText, formatOptions) : noopSpinner;
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
try {
|
|
48
|
+
await handler({ ...appCtx, spinner }, ...args);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
spinner.error(options.errorLabel ?? "Failed");
|
|
51
|
+
console.error(formatError(String(err), formatOptions));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
} finally {
|
|
54
|
+
await manager.close();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
59
57
|
}
|
package/src/config/env.ts
CHANGED
|
@@ -4,40 +4,38 @@ const ENV_VAR_PATTERN = /\$\{([^}]+)\}/g;
|
|
|
4
4
|
|
|
5
5
|
/** Whether to throw on missing env vars (default: true) */
|
|
6
6
|
function isStrictEnv(): boolean {
|
|
7
|
-
|
|
7
|
+
return process.env[ENV.STRICT_ENV] !== "false";
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/** Replace ${VAR_NAME} in a string with the corresponding env var value */
|
|
11
11
|
export function interpolateEnvString(value: string): string {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return envValue;
|
|
24
|
-
});
|
|
12
|
+
return value.replace(ENV_VAR_PATTERN, (_match, varName: string) => {
|
|
13
|
+
const envValue = process.env[varName];
|
|
14
|
+
if (envValue === undefined) {
|
|
15
|
+
if (isStrictEnv()) {
|
|
16
|
+
throw new Error(`Environment variable "${varName}" is not set (set ${ENV.STRICT_ENV}=false to warn instead)`);
|
|
17
|
+
}
|
|
18
|
+
console.warn(`Warning: environment variable "${varName}" is not set`);
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
return envValue;
|
|
22
|
+
});
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
/** Recursively interpolate env vars in all string values of an object */
|
|
28
26
|
export function interpolateEnv<T>(obj: T): T {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
if (typeof obj === "string") {
|
|
28
|
+
return interpolateEnvString(obj) as T;
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(obj)) {
|
|
31
|
+
return obj.map((item) => interpolateEnv(item)) as T;
|
|
32
|
+
}
|
|
33
|
+
if (typeof obj === "object" && obj !== null) {
|
|
34
|
+
const result: Record<string, unknown> = {};
|
|
35
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
36
|
+
result[key] = interpolateEnv(value);
|
|
37
|
+
}
|
|
38
|
+
return result as T;
|
|
39
|
+
}
|
|
40
|
+
return obj;
|
|
43
41
|
}
|