@1mcp/agent 0.34.3 → 0.35.0-beta.1
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/build/admin/assets/admin-console-CX5GzncA.css +1 -0
- package/build/admin/assets/admin-console-CkZ5lBKg.js +61 -0
- package/build/admin/index.html +13 -0
- package/build/commands/admin/admin.d.ts +90 -0
- package/build/commands/admin/admin.d.ts.map +1 -0
- package/build/commands/admin/admin.js +545 -0
- package/build/commands/admin/admin.js.map +1 -0
- package/build/commands/admin/index.d.ts +3 -0
- package/build/commands/admin/index.d.ts.map +1 -0
- package/build/commands/admin/index.js +99 -0
- package/build/commands/admin/index.js.map +1 -0
- package/build/commands/auth/index.d.ts.map +1 -1
- package/build/commands/auth/index.js +24 -5
- package/build/commands/auth/index.js.map +1 -1
- package/build/commands/auth/login.d.ts +1 -0
- package/build/commands/auth/login.d.ts.map +1 -1
- package/build/commands/auth/login.js +6 -10
- package/build/commands/auth/login.js.map +1 -1
- package/build/commands/auth/logout.d.ts +3 -0
- package/build/commands/auth/logout.d.ts.map +1 -1
- package/build/commands/auth/logout.js +19 -32
- package/build/commands/auth/logout.js.map +1 -1
- package/build/commands/auth/runtimeTargetContext.d.ts +25 -0
- package/build/commands/auth/runtimeTargetContext.d.ts.map +1 -0
- package/build/commands/auth/runtimeTargetContext.js +69 -0
- package/build/commands/auth/runtimeTargetContext.js.map +1 -0
- package/build/commands/auth/status.d.ts +1 -0
- package/build/commands/auth/status.d.ts.map +1 -1
- package/build/commands/auth/status.js +10 -33
- package/build/commands/auth/status.js.map +1 -1
- package/build/commands/inspect/index.d.ts.map +1 -1
- package/build/commands/inspect/index.js +4 -0
- package/build/commands/inspect/index.js.map +1 -1
- package/build/commands/inspect/inspect.d.ts +1 -0
- package/build/commands/inspect/inspect.d.ts.map +1 -1
- package/build/commands/inspect/inspect.js +2 -2
- package/build/commands/inspect/inspect.js.map +1 -1
- package/build/commands/instructions/index.d.ts.map +1 -1
- package/build/commands/instructions/index.js +4 -0
- package/build/commands/instructions/index.js.map +1 -1
- package/build/commands/instructions/instructions.d.ts +1 -0
- package/build/commands/instructions/instructions.d.ts.map +1 -1
- package/build/commands/instructions/instructions.js.map +1 -1
- package/build/commands/instructions/instructionsUtils.js +1 -1
- package/build/commands/instructions/instructionsUtils.js.map +1 -1
- package/build/commands/mcp/enable.d.ts +89 -2
- package/build/commands/mcp/enable.d.ts.map +1 -1
- package/build/commands/mcp/enable.js +555 -35
- package/build/commands/mcp/enable.js.map +1 -1
- package/build/commands/proxy/index.d.ts +1 -0
- package/build/commands/proxy/index.d.ts.map +1 -1
- package/build/commands/proxy/index.js +4 -0
- package/build/commands/proxy/index.js.map +1 -1
- package/build/commands/run/index.d.ts.map +1 -1
- package/build/commands/run/index.js +4 -0
- package/build/commands/run/index.js.map +1 -1
- package/build/commands/run/run.d.ts +1 -0
- package/build/commands/run/run.d.ts.map +1 -1
- package/build/commands/run/run.js +2 -2
- package/build/commands/run/run.js.map +1 -1
- package/build/commands/serve/serve.d.ts.map +1 -1
- package/build/commands/serve/serve.js +4 -0
- package/build/commands/serve/serve.js.map +1 -1
- package/build/commands/shared/apiClient.d.ts +5 -1
- package/build/commands/shared/apiClient.d.ts.map +1 -1
- package/build/commands/shared/apiClient.js +17 -9
- package/build/commands/shared/apiClient.js.map +1 -1
- package/build/commands/shared/clientSurfaceAttachment.d.ts +14 -0
- package/build/commands/shared/clientSurfaceAttachment.d.ts.map +1 -1
- package/build/commands/shared/clientSurfaceAttachment.js +51 -9
- package/build/commands/shared/clientSurfaceAttachment.js.map +1 -1
- package/build/commands/shared/commandRunner.d.ts +3 -1
- package/build/commands/shared/commandRunner.d.ts.map +1 -1
- package/build/commands/shared/commandRunner.js +59 -2
- package/build/commands/shared/commandRunner.js.map +1 -1
- package/build/commands/shared/serveTargetResolver.d.ts +14 -1
- package/build/commands/shared/serveTargetResolver.d.ts.map +1 -1
- package/build/commands/shared/serveTargetResolver.js +145 -3
- package/build/commands/shared/serveTargetResolver.js.map +1 -1
- package/build/commands/target/index.d.ts +3 -0
- package/build/commands/target/index.d.ts.map +1 -0
- package/build/commands/target/index.js +171 -0
- package/build/commands/target/index.js.map +1 -0
- package/build/commands/target/target.d.ts +78 -0
- package/build/commands/target/target.d.ts.map +1 -0
- package/build/commands/target/target.js +471 -0
- package/build/commands/target/target.js.map +1 -0
- package/build/constants/mcp.d.ts +1 -1
- package/build/constants/mcp.d.ts.map +1 -1
- package/build/constants/mcp.js +1 -1
- package/build/constants/mcp.js.map +1 -1
- package/build/core/capabilities/asyncLoadingOrchestrator.d.ts +6 -14
- package/build/core/capabilities/asyncLoadingOrchestrator.d.ts.map +1 -1
- package/build/core/capabilities/asyncLoadingOrchestrator.js +24 -17
- package/build/core/capabilities/asyncLoadingOrchestrator.js.map +1 -1
- package/build/core/capabilities/asyncLoadingOrchestratorEvent.d.ts +6 -0
- package/build/core/capabilities/asyncLoadingOrchestratorEvent.d.ts.map +1 -0
- package/build/core/capabilities/asyncLoadingOrchestratorEvent.js +7 -0
- package/build/core/capabilities/asyncLoadingOrchestratorEvent.js.map +1 -0
- package/build/core/capabilities/lazyLoadingOrchestrator.d.ts.map +1 -1
- package/build/core/capabilities/lazyLoadingOrchestrator.js +6 -5
- package/build/core/capabilities/lazyLoadingOrchestrator.js.map +1 -1
- package/build/core/client/clientManager.d.ts +2 -0
- package/build/core/client/clientManager.d.ts.map +1 -1
- package/build/core/client/clientManager.js +27 -24
- package/build/core/client/clientManager.js.map +1 -1
- package/build/core/client/connectedClient.d.ts +7 -0
- package/build/core/client/connectedClient.d.ts.map +1 -0
- package/build/core/client/connectedClient.js +2 -0
- package/build/core/client/connectedClient.js.map +1 -0
- package/build/core/client/connectionHandler.d.ts +2 -1
- package/build/core/client/connectionHandler.d.ts.map +1 -1
- package/build/core/client/connectionHandler.js +2 -5
- package/build/core/client/connectionHandler.js.map +1 -1
- package/build/core/client/transportRecreationState.d.ts +11 -0
- package/build/core/client/transportRecreationState.d.ts.map +1 -0
- package/build/core/client/transportRecreationState.js +2 -0
- package/build/core/client/transportRecreationState.js.map +1 -0
- package/build/core/client/transportRecreator.d.ts +2 -0
- package/build/core/client/transportRecreator.d.ts.map +1 -1
- package/build/core/client/transportRecreator.js +24 -4
- package/build/core/client/transportRecreator.js.map +1 -1
- package/build/core/loading/parallelExecutor.d.ts +5 -5
- package/build/core/loading/parallelExecutor.d.ts.map +1 -1
- package/build/core/loading/parallelExecutor.js +31 -33
- package/build/core/loading/parallelExecutor.js.map +1 -1
- package/build/core/runtime/runtimeIdentityService.d.ts +30 -0
- package/build/core/runtime/runtimeIdentityService.d.ts.map +1 -0
- package/build/core/runtime/runtimeIdentityService.js +63 -0
- package/build/core/runtime/runtimeIdentityService.js.map +1 -0
- package/build/core/server/agentConfig.d.ts +4 -0
- package/build/core/server/agentConfig.d.ts.map +1 -1
- package/build/core/server/agentConfig.js +3 -0
- package/build/core/server/agentConfig.js.map +1 -1
- package/build/core/tools/handlers/serverManagementHandler.d.ts +7 -7
- package/build/core/tools/internal/adapters/helpers/configTransformers.d.ts +2 -2
- package/build/core/tools/internal/schemas/discovery.d.ts +1 -1
- package/build/core/tools/internal/schemas/index.d.ts +13 -13
- package/build/core/tools/internal/schemas/installation.d.ts +7 -7
- package/build/core/tools/internal/schemas/management.d.ts +5 -5
- package/build/core/types/transport.d.ts +24 -3
- package/build/core/types/transport.d.ts.map +1 -1
- package/build/core/types/transport.js +7 -2
- package/build/core/types/transport.js.map +1 -1
- package/build/domains/admin/adminConfiguredServerService.d.ts +275 -0
- package/build/domains/admin/adminConfiguredServerService.d.ts.map +1 -0
- package/build/domains/admin/adminConfiguredServerService.js +1926 -0
- package/build/domains/admin/adminConfiguredServerService.js.map +1 -0
- package/build/domains/admin/adminConnectivityChecker.d.ts +7 -0
- package/build/domains/admin/adminConnectivityChecker.d.ts.map +1 -0
- package/build/domains/admin/adminConnectivityChecker.js +42 -0
- package/build/domains/admin/adminConnectivityChecker.js.map +1 -0
- package/build/domains/admin/adminDomain.d.ts +29 -0
- package/build/domains/admin/adminDomain.d.ts.map +1 -0
- package/build/domains/admin/adminDomain.js +39 -0
- package/build/domains/admin/adminDomain.js.map +1 -0
- package/build/domains/admin/adminIdentityService.d.ts +75 -0
- package/build/domains/admin/adminIdentityService.d.ts.map +1 -0
- package/build/domains/admin/adminIdentityService.js +321 -0
- package/build/domains/admin/adminIdentityService.js.map +1 -0
- package/build/domains/admin/adminOperationService.d.ts +202 -0
- package/build/domains/admin/adminOperationService.d.ts.map +1 -0
- package/build/domains/admin/adminOperationService.js +724 -0
- package/build/domains/admin/adminOperationService.js.map +1 -0
- package/build/domains/admin/adminPresetService.d.ts +140 -0
- package/build/domains/admin/adminPresetService.d.ts.map +1 -0
- package/build/domains/admin/adminPresetService.js +248 -0
- package/build/domains/admin/adminPresetService.js.map +1 -0
- package/build/domains/admin/runtimeScopeAdminLock.d.ts +22 -0
- package/build/domains/admin/runtimeScopeAdminLock.d.ts.map +1 -0
- package/build/domains/admin/runtimeScopeAdminLock.js +121 -0
- package/build/domains/admin/runtimeScopeAdminLock.js.map +1 -0
- package/build/domains/config-change/configChange.d.ts +6 -1
- package/build/domains/config-change/configChange.d.ts.map +1 -1
- package/build/domains/config-change/configChange.js +344 -0
- package/build/domains/config-change/configChange.js.map +1 -1
- package/build/domains/config-change/types.d.ts +18 -3
- package/build/domains/config-change/types.d.ts.map +1 -1
- package/build/domains/preset/manager/presetManager.d.ts +1 -0
- package/build/domains/preset/manager/presetManager.d.ts.map +1 -1
- package/build/domains/preset/manager/presetManager.js +3 -0
- package/build/domains/preset/manager/presetManager.js.map +1 -1
- package/build/domains/preset/tagAuthoring.d.ts +10 -0
- package/build/domains/preset/tagAuthoring.d.ts.map +1 -0
- package/build/domains/preset/tagAuthoring.js +94 -0
- package/build/domains/preset/tagAuthoring.js.map +1 -0
- package/build/domains/runtime-targets/runtimeIdentityVerification.d.ts +51 -0
- package/build/domains/runtime-targets/runtimeIdentityVerification.d.ts.map +1 -0
- package/build/domains/runtime-targets/runtimeIdentityVerification.js +258 -0
- package/build/domains/runtime-targets/runtimeIdentityVerification.js.map +1 -0
- package/build/domains/runtime-targets/runtimeTargetStore.d.ts +222 -0
- package/build/domains/runtime-targets/runtimeTargetStore.d.ts.map +1 -0
- package/build/domains/runtime-targets/runtimeTargetStore.js +1456 -0
- package/build/domains/runtime-targets/runtimeTargetStore.js.map +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +7 -1
- package/build/index.js.map +1 -1
- package/build/transport/http/routes/adminRoutes.d.ts +34 -0
- package/build/transport/http/routes/adminRoutes.d.ts.map +1 -0
- package/build/transport/http/routes/adminRoutes.js +1217 -0
- package/build/transport/http/routes/adminRoutes.js.map +1 -0
- package/build/transport/http/routes/oauthRoutes.d.ts +2 -0
- package/build/transport/http/routes/oauthRoutes.d.ts.map +1 -1
- package/build/transport/http/routes/oauthRoutes.js +5 -185
- package/build/transport/http/routes/oauthRoutes.js.map +1 -1
- package/build/transport/http/routes/runtimeIdentityRoutes.d.ts +7 -0
- package/build/transport/http/routes/runtimeIdentityRoutes.d.ts.map +1 -0
- package/build/transport/http/routes/runtimeIdentityRoutes.js +9 -0
- package/build/transport/http/routes/runtimeIdentityRoutes.js.map +1 -0
- package/build/transport/http/routes/toolRoutes.d.ts.map +1 -1
- package/build/transport/http/routes/toolRoutes.js +1 -7
- package/build/transport/http/routes/toolRoutes.js.map +1 -1
- package/build/transport/http/server.d.ts +3 -0
- package/build/transport/http/server.d.ts.map +1 -1
- package/build/transport/http/server.js +92 -3
- package/build/transport/http/server.js.map +1 -1
- package/build/transport/managedStdioStderr.d.ts +40 -0
- package/build/transport/managedStdioStderr.d.ts.map +1 -0
- package/build/transport/managedStdioStderr.js +198 -0
- package/build/transport/managedStdioStderr.js.map +1 -0
- package/build/transport/managedStdioStderrEvent.d.ts +6 -0
- package/build/transport/managedStdioStderrEvent.d.ts.map +1 -0
- package/build/transport/managedStdioStderrEvent.js +7 -0
- package/build/transport/managedStdioStderrEvent.js.map +1 -0
- package/build/transport/managedStdioStderrMetadata.d.ts +9 -0
- package/build/transport/managedStdioStderrMetadata.d.ts.map +1 -0
- package/build/transport/managedStdioStderrMetadata.js +2 -0
- package/build/transport/managedStdioStderrMetadata.js.map +1 -0
- package/build/transport/managedStdioStderrOptions.d.ts +10 -0
- package/build/transport/managedStdioStderrOptions.d.ts.map +1 -0
- package/build/transport/managedStdioStderrOptions.js +2 -0
- package/build/transport/managedStdioStderrOptions.js.map +1 -0
- package/build/transport/restartableStdioTransport.d.ts +3 -1
- package/build/transport/restartableStdioTransport.d.ts.map +1 -1
- package/build/transport/restartableStdioTransport.js +5 -1
- package/build/transport/restartableStdioTransport.js.map +1 -1
- package/build/transport/transportFactory.d.ts.map +1 -1
- package/build/transport/transportFactory.js +21 -3
- package/build/transport/transportFactory.js.map +1 -1
- package/build/utils/validation/urlDetection.d.ts +13 -2
- package/build/utils/validation/urlDetection.d.ts.map +1 -1
- package/build/utils/validation/urlDetection.js +37 -6
- package/build/utils/validation/urlDetection.js.map +1 -1
- package/package.json +22 -9
|
@@ -0,0 +1,1456 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { getGlobalConfigDir } from '../../constants.js';
|
|
5
|
+
const METADATA_FILE = 'runtime-targets.json';
|
|
6
|
+
const SECRETS_FILE = 'runtime-target-secrets.json';
|
|
7
|
+
const LOCK_FILE = '.runtime-targets.lock';
|
|
8
|
+
const STORE_SCHEMA_VERSION = 1;
|
|
9
|
+
const TARGET_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,62}$/;
|
|
10
|
+
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
|
|
11
|
+
export class RuntimeTargetStoreError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
details;
|
|
14
|
+
recoveryCommand;
|
|
15
|
+
constructor(code, message, details, recoveryCommand) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.details = details;
|
|
19
|
+
this.recoveryCommand = recoveryCommand;
|
|
20
|
+
this.name = 'RuntimeTargetStoreError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class RuntimeTargetStore {
|
|
24
|
+
static activeLocks = new Set();
|
|
25
|
+
storeDir;
|
|
26
|
+
now;
|
|
27
|
+
static defaultStoreDir() {
|
|
28
|
+
return getGlobalConfigDir();
|
|
29
|
+
}
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
this.storeDir = options.storeDir ?? RuntimeTargetStore.defaultStoreDir();
|
|
32
|
+
this.now = options.now ?? (() => new Date());
|
|
33
|
+
}
|
|
34
|
+
current() {
|
|
35
|
+
const metadata = this.readMetadata();
|
|
36
|
+
const currentName = metadata.current && metadata.targets[metadata.current] ? metadata.current : 'local';
|
|
37
|
+
return currentName === 'local' ? this.localEntry(true) : this.toListEntry(metadata.targets[currentName], true);
|
|
38
|
+
}
|
|
39
|
+
resolveForConnection(name) {
|
|
40
|
+
const metadata = this.readMetadata();
|
|
41
|
+
const currentName = metadata.current && metadata.targets[metadata.current] ? metadata.current : 'local';
|
|
42
|
+
const targetName = name ?? currentName;
|
|
43
|
+
if (targetName === 'local') {
|
|
44
|
+
return this.localEntry(targetName === currentName);
|
|
45
|
+
}
|
|
46
|
+
const target = metadata.targets[targetName];
|
|
47
|
+
if (!target) {
|
|
48
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${targetName}" was not found`);
|
|
49
|
+
}
|
|
50
|
+
return this.toListEntry(target, metadata.current === targetName, undefined, true);
|
|
51
|
+
}
|
|
52
|
+
list() {
|
|
53
|
+
const metadata = this.readMetadata();
|
|
54
|
+
const secrets = this.readSecretsForMetadataOnly();
|
|
55
|
+
const currentName = metadata.current && metadata.targets[metadata.current] ? metadata.current : 'local';
|
|
56
|
+
const local = this.localEntry(currentName === 'local', secrets);
|
|
57
|
+
const remotes = Object.values(metadata.targets)
|
|
58
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
59
|
+
.map((target) => this.toListEntry(target, target.name === currentName, secrets));
|
|
60
|
+
return [local, ...remotes];
|
|
61
|
+
}
|
|
62
|
+
inspect(name) {
|
|
63
|
+
if (name === 'local') {
|
|
64
|
+
return this.localEntry(this.current().name === 'local');
|
|
65
|
+
}
|
|
66
|
+
const metadata = this.readMetadata();
|
|
67
|
+
const target = metadata.targets[name];
|
|
68
|
+
if (!target) {
|
|
69
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${name}" was not found`);
|
|
70
|
+
}
|
|
71
|
+
return this.toListEntry(target, metadata.current === name);
|
|
72
|
+
}
|
|
73
|
+
useTarget(name, options = {}) {
|
|
74
|
+
if (name === 'local') {
|
|
75
|
+
return this.withLock(() => {
|
|
76
|
+
const metadata = this.readMetadata();
|
|
77
|
+
this.writeMetadata({ ...metadata, current: undefined });
|
|
78
|
+
return { target: this.localEntry(true), warnings: [] };
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return this.withLock(() => {
|
|
82
|
+
const metadata = this.readMetadata();
|
|
83
|
+
const target = metadata.targets[name];
|
|
84
|
+
if (!target) {
|
|
85
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${name}" was not found`);
|
|
86
|
+
}
|
|
87
|
+
const nextTarget = this.confirmInsecureTlsIfRequired(target, 'use', options.acceptInsecureTls);
|
|
88
|
+
this.writeMetadata({
|
|
89
|
+
...metadata,
|
|
90
|
+
current: name,
|
|
91
|
+
targets: {
|
|
92
|
+
...metadata.targets,
|
|
93
|
+
[name]: nextTarget,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
const entry = this.toListEntry(nextTarget, true);
|
|
97
|
+
return {
|
|
98
|
+
target: entry,
|
|
99
|
+
warnings: verificationWarnings(entry),
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
requireInsecureTlsConfirmation(input) {
|
|
104
|
+
if (input.name === 'local') {
|
|
105
|
+
return this.localEntry(this.current().name === 'local');
|
|
106
|
+
}
|
|
107
|
+
return this.withLock(() => {
|
|
108
|
+
const metadata = this.readMetadata();
|
|
109
|
+
const target = metadata.targets[input.name];
|
|
110
|
+
if (!target) {
|
|
111
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${input.name}" was not found`);
|
|
112
|
+
}
|
|
113
|
+
const nextTarget = this.confirmInsecureTlsIfRequired(target, input.operation, input.acceptInsecureTls);
|
|
114
|
+
if (nextTarget !== target) {
|
|
115
|
+
this.writeMetadata({
|
|
116
|
+
...metadata,
|
|
117
|
+
targets: {
|
|
118
|
+
...metadata.targets,
|
|
119
|
+
[input.name]: nextTarget,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return this.toListEntry(nextTarget, metadata.current === input.name);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
deleteTarget(name, options = {}) {
|
|
127
|
+
if (name === 'local') {
|
|
128
|
+
throw new RuntimeTargetStoreError('target_local_reserved', 'The built-in local target cannot be deleted');
|
|
129
|
+
}
|
|
130
|
+
this.withLock(() => {
|
|
131
|
+
const metadata = this.readMetadata();
|
|
132
|
+
if (!metadata.targets[name]) {
|
|
133
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${name}" was not found`);
|
|
134
|
+
}
|
|
135
|
+
if (metadata.current === name && !options.force) {
|
|
136
|
+
throw new RuntimeTargetStoreError('target_is_current', 'Cannot delete the current runtime target without force');
|
|
137
|
+
}
|
|
138
|
+
const { [name]: _deleted, ...targets } = metadata.targets;
|
|
139
|
+
const secrets = this.readSecretsForCredentialUse();
|
|
140
|
+
const hadSecrets = secrets.credentials[name] !== undefined;
|
|
141
|
+
const { [name]: _deletedSecrets, ...credentials } = secrets.credentials;
|
|
142
|
+
const nextMetadata = {
|
|
143
|
+
...metadata,
|
|
144
|
+
current: metadata.current === name ? undefined : metadata.current,
|
|
145
|
+
targets,
|
|
146
|
+
};
|
|
147
|
+
if (hadSecrets) {
|
|
148
|
+
this.writeSecretsThenMetadata(secrets, { ...secrets, credentials }, nextMetadata);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
this.writeMetadata(nextMetadata);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
renameTarget(sourceName, destinationName) {
|
|
155
|
+
if (sourceName === 'local') {
|
|
156
|
+
throw new RuntimeTargetStoreError('target_local_reserved', 'The built-in local target cannot be renamed');
|
|
157
|
+
}
|
|
158
|
+
const normalizedDestinationName = validateRuntimeTargetName(destinationName);
|
|
159
|
+
return this.withLock(() => {
|
|
160
|
+
const metadata = this.readMetadata();
|
|
161
|
+
const source = metadata.targets[sourceName];
|
|
162
|
+
if (!source) {
|
|
163
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${sourceName}" was not found`);
|
|
164
|
+
}
|
|
165
|
+
if (metadata.targets[normalizedDestinationName]) {
|
|
166
|
+
throw new RuntimeTargetStoreError('target_already_exists', `Runtime target "${normalizedDestinationName}" already exists`);
|
|
167
|
+
}
|
|
168
|
+
const renamed = {
|
|
169
|
+
...source,
|
|
170
|
+
name: normalizedDestinationName,
|
|
171
|
+
updatedAt: this.now().toISOString(),
|
|
172
|
+
};
|
|
173
|
+
const { [sourceName]: _removed, ...remainingTargets } = metadata.targets;
|
|
174
|
+
const secrets = this.readSecretsForCredentialUse();
|
|
175
|
+
const sourceSecrets = secrets.credentials[sourceName];
|
|
176
|
+
const { [sourceName]: _removedSecrets, ...remainingCredentials } = secrets.credentials;
|
|
177
|
+
const nextMetadata = {
|
|
178
|
+
...metadata,
|
|
179
|
+
current: metadata.current === sourceName ? normalizedDestinationName : metadata.current,
|
|
180
|
+
targets: {
|
|
181
|
+
...remainingTargets,
|
|
182
|
+
[normalizedDestinationName]: renamed,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
if (sourceSecrets !== undefined) {
|
|
186
|
+
this.writeSecretsThenMetadata(secrets, {
|
|
187
|
+
...secrets,
|
|
188
|
+
credentials: {
|
|
189
|
+
...remainingCredentials,
|
|
190
|
+
[normalizedDestinationName]: sourceSecrets,
|
|
191
|
+
},
|
|
192
|
+
}, nextMetadata);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
this.writeMetadata(nextMetadata);
|
|
196
|
+
}
|
|
197
|
+
return this.toListEntry(renamed, metadata.current === sourceName);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
setCredentialReferences(name, runtimeScopeId, references) {
|
|
201
|
+
if (!runtimeScopeId) {
|
|
202
|
+
throw new RuntimeTargetStoreError('identity_invalid', 'Runtime scope id is required for credential references');
|
|
203
|
+
}
|
|
204
|
+
this.withLock(() => {
|
|
205
|
+
const metadata = this.readMetadata();
|
|
206
|
+
if (name !== 'local' && !metadata.targets[name]) {
|
|
207
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${name}" was not found`);
|
|
208
|
+
}
|
|
209
|
+
const secrets = this.readSecretsForCredentialUse();
|
|
210
|
+
const byScope = secrets.credentials[name] ?? {};
|
|
211
|
+
this.writeSecrets({
|
|
212
|
+
...secrets,
|
|
213
|
+
credentials: {
|
|
214
|
+
...secrets.credentials,
|
|
215
|
+
[name]: {
|
|
216
|
+
...byScope,
|
|
217
|
+
[runtimeScopeId]: compactCredentialReferences(references),
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
setOAuthTokenReference(name, runtimeScopeId, oauth) {
|
|
224
|
+
if (!runtimeScopeId) {
|
|
225
|
+
throw new RuntimeTargetStoreError('identity_invalid', 'Runtime scope id is required for credential references');
|
|
226
|
+
}
|
|
227
|
+
this.withLock(() => {
|
|
228
|
+
const metadata = this.readMetadata();
|
|
229
|
+
if (name !== 'local' && !metadata.targets[name]) {
|
|
230
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${name}" was not found`);
|
|
231
|
+
}
|
|
232
|
+
const secrets = this.readSecretsForCredentialUse();
|
|
233
|
+
const byScope = secrets.credentials[name] ?? {};
|
|
234
|
+
const bucket = byScope[runtimeScopeId] ?? {};
|
|
235
|
+
this.writeSecrets({
|
|
236
|
+
...secrets,
|
|
237
|
+
credentials: {
|
|
238
|
+
...secrets.credentials,
|
|
239
|
+
[name]: {
|
|
240
|
+
...byScope,
|
|
241
|
+
[runtimeScopeId]: {
|
|
242
|
+
...bucket,
|
|
243
|
+
oauth,
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
getOAuthTokenReference(name, runtimeScopeId) {
|
|
251
|
+
if (!runtimeScopeId) {
|
|
252
|
+
throw new RuntimeTargetStoreError('identity_invalid', 'Runtime scope id is required for credential references');
|
|
253
|
+
}
|
|
254
|
+
const metadata = this.readMetadata();
|
|
255
|
+
if (name !== 'local' && !metadata.targets[name]) {
|
|
256
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${name}" was not found`);
|
|
257
|
+
}
|
|
258
|
+
const secrets = this.readSecretsForCredentialUse();
|
|
259
|
+
return secrets.credentials[name]?.[runtimeScopeId]?.oauth;
|
|
260
|
+
}
|
|
261
|
+
clearOAuthTokenReference(name, runtimeScopeId) {
|
|
262
|
+
if (!runtimeScopeId) {
|
|
263
|
+
throw new RuntimeTargetStoreError('identity_invalid', 'Runtime scope id is required for credential references');
|
|
264
|
+
}
|
|
265
|
+
this.withLock(() => {
|
|
266
|
+
const metadata = this.readMetadata();
|
|
267
|
+
if (name !== 'local' && !metadata.targets[name]) {
|
|
268
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${name}" was not found`);
|
|
269
|
+
}
|
|
270
|
+
const secrets = this.readSecretsForCredentialUse();
|
|
271
|
+
const byScope = secrets.credentials[name];
|
|
272
|
+
const bucket = byScope?.[runtimeScopeId];
|
|
273
|
+
if (!bucket || bucket.oauth === undefined) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const { oauth: _oauth, ...nextBucket } = bucket;
|
|
277
|
+
const nextScopes = { ...byScope };
|
|
278
|
+
if (Object.keys(nextBucket).length === 0) {
|
|
279
|
+
delete nextScopes[runtimeScopeId];
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
nextScopes[runtimeScopeId] = nextBucket;
|
|
283
|
+
}
|
|
284
|
+
const nextCredentials = { ...secrets.credentials };
|
|
285
|
+
if (Object.keys(nextScopes).length === 0) {
|
|
286
|
+
delete nextCredentials[name];
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
nextCredentials[name] = nextScopes;
|
|
290
|
+
}
|
|
291
|
+
this.writeSecrets({ ...secrets, credentials: nextCredentials });
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
clearLocalOAuthTokenReferences() {
|
|
295
|
+
return this.clearLocalCredentialReferences('oauth');
|
|
296
|
+
}
|
|
297
|
+
clearLocalAdminSessionReferences() {
|
|
298
|
+
return this.clearLocalCredentialReferences('adminSession');
|
|
299
|
+
}
|
|
300
|
+
setAdminSessionReference(name, runtimeScopeId, adminSession) {
|
|
301
|
+
if (!runtimeScopeId) {
|
|
302
|
+
throw new RuntimeTargetStoreError('identity_invalid', 'Runtime scope id is required for credential references');
|
|
303
|
+
}
|
|
304
|
+
this.withLock(() => {
|
|
305
|
+
const metadata = this.readMetadata();
|
|
306
|
+
if (name !== 'local' && !metadata.targets[name]) {
|
|
307
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${name}" was not found`);
|
|
308
|
+
}
|
|
309
|
+
const secrets = this.readSecretsForCredentialUse();
|
|
310
|
+
const byScope = secrets.credentials[name] ?? {};
|
|
311
|
+
const bucket = byScope[runtimeScopeId] ?? {};
|
|
312
|
+
this.writeSecrets({
|
|
313
|
+
...secrets,
|
|
314
|
+
credentials: {
|
|
315
|
+
...secrets.credentials,
|
|
316
|
+
[name]: {
|
|
317
|
+
...byScope,
|
|
318
|
+
[runtimeScopeId]: {
|
|
319
|
+
...bucket,
|
|
320
|
+
adminSession,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
getAdminSessionReference(name, runtimeScopeId) {
|
|
328
|
+
if (!runtimeScopeId) {
|
|
329
|
+
throw new RuntimeTargetStoreError('identity_invalid', 'Runtime scope id is required for credential references');
|
|
330
|
+
}
|
|
331
|
+
const metadata = this.readMetadata();
|
|
332
|
+
if (name !== 'local' && !metadata.targets[name]) {
|
|
333
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${name}" was not found`);
|
|
334
|
+
}
|
|
335
|
+
const secrets = this.readSecretsForCredentialUse();
|
|
336
|
+
return secrets.credentials[name]?.[runtimeScopeId]?.adminSession;
|
|
337
|
+
}
|
|
338
|
+
clearAdminSessionReference(name, runtimeScopeId) {
|
|
339
|
+
if (!runtimeScopeId) {
|
|
340
|
+
throw new RuntimeTargetStoreError('identity_invalid', 'Runtime scope id is required for credential references');
|
|
341
|
+
}
|
|
342
|
+
this.withLock(() => {
|
|
343
|
+
const metadata = this.readMetadata();
|
|
344
|
+
if (name !== 'local' && !metadata.targets[name]) {
|
|
345
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${name}" was not found`);
|
|
346
|
+
}
|
|
347
|
+
const secrets = this.readSecretsForCredentialUse();
|
|
348
|
+
const byScope = secrets.credentials[name];
|
|
349
|
+
const bucket = byScope?.[runtimeScopeId];
|
|
350
|
+
if (!bucket || bucket.adminSession === undefined) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const { adminSession: _adminSession, ...nextBucket } = bucket;
|
|
354
|
+
const nextScopes = { ...byScope };
|
|
355
|
+
if (Object.keys(nextBucket).length === 0) {
|
|
356
|
+
delete nextScopes[runtimeScopeId];
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
nextScopes[runtimeScopeId] = nextBucket;
|
|
360
|
+
}
|
|
361
|
+
const nextCredentials = { ...secrets.credentials };
|
|
362
|
+
if (Object.keys(nextScopes).length === 0) {
|
|
363
|
+
delete nextCredentials[name];
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
nextCredentials[name] = nextScopes;
|
|
367
|
+
}
|
|
368
|
+
this.writeSecrets({ ...secrets, credentials: nextCredentials });
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
replaceVerifiedTarget(input) {
|
|
372
|
+
const name = validateRuntimeTargetName(input.name);
|
|
373
|
+
const hasDisplayNameInput = Object.prototype.hasOwnProperty.call(input, 'displayName');
|
|
374
|
+
const nextDisplayName = hasDisplayNameInput ? normalizeDisplayName(input.displayName) : undefined;
|
|
375
|
+
const tlsMetadata = normalizeTlsMetadata({
|
|
376
|
+
caFile: input.caFile,
|
|
377
|
+
insecureSkipVerify: input.insecureSkipVerify,
|
|
378
|
+
});
|
|
379
|
+
const url = normalizeRuntimeTargetUrl(input.url, {
|
|
380
|
+
allowInsecureHttp: tlsMetadata.insecureSkipVerify === true,
|
|
381
|
+
});
|
|
382
|
+
validateRuntimeIdentity(input.observedIdentity);
|
|
383
|
+
return this.withLock(() => {
|
|
384
|
+
const metadata = this.readMetadata();
|
|
385
|
+
const existing = metadata.targets[name];
|
|
386
|
+
if (!existing) {
|
|
387
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${name}" was not found`);
|
|
388
|
+
}
|
|
389
|
+
const previousRuntimeScopeId = existing.observedIdentity?.runtimeScopeId;
|
|
390
|
+
const nextRuntimeScopeId = input.observedIdentity.runtimeScopeId;
|
|
391
|
+
const identityChanged = previousRuntimeScopeId !== undefined && previousRuntimeScopeId !== nextRuntimeScopeId;
|
|
392
|
+
if (identityChanged && !input.acceptNewIdentity) {
|
|
393
|
+
throw new RuntimeTargetStoreError('identity_runtime_scope_mismatch', 'Runtime target identity changed; accept the new identity before replacing metadata');
|
|
394
|
+
}
|
|
395
|
+
const now = this.now().toISOString();
|
|
396
|
+
const updated = {
|
|
397
|
+
...existing,
|
|
398
|
+
url,
|
|
399
|
+
displayName: hasDisplayNameInput ? nextDisplayName : existing.displayName,
|
|
400
|
+
caFile: tlsMetadata.caFile,
|
|
401
|
+
insecureSkipVerify: tlsMetadata.insecureSkipVerify,
|
|
402
|
+
insecureTlsConfirmationRequired: undefined,
|
|
403
|
+
observedIdentity: input.observedIdentity,
|
|
404
|
+
lastVerifiedAt: now,
|
|
405
|
+
updatedAt: now,
|
|
406
|
+
};
|
|
407
|
+
const secrets = this.readSecretsForCredentialUse();
|
|
408
|
+
const shouldClearCredentials = identityChanged || existing.url !== url;
|
|
409
|
+
const { [name]: _removedSecrets, ...credentials } = secrets.credentials;
|
|
410
|
+
const nextSecrets = shouldClearCredentials ? { ...secrets, credentials } : secrets;
|
|
411
|
+
const nextMetadata = {
|
|
412
|
+
...metadata,
|
|
413
|
+
targets: {
|
|
414
|
+
...metadata.targets,
|
|
415
|
+
[name]: updated,
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
if (shouldClearCredentials && secrets.credentials[name] !== undefined) {
|
|
419
|
+
this.writeSecretsThenMetadata(secrets, nextSecrets, nextMetadata);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
this.writeMetadata(nextMetadata);
|
|
423
|
+
}
|
|
424
|
+
return this.toListEntry(updated, metadata.current === name, nextSecrets);
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
updateObservedIdentityMetadata(name, observedIdentity) {
|
|
428
|
+
if (name === 'local') {
|
|
429
|
+
throw new RuntimeTargetStoreError('target_local_reserved', 'The built-in local target has no stored metadata');
|
|
430
|
+
}
|
|
431
|
+
validateRuntimeIdentity(observedIdentity);
|
|
432
|
+
return this.withLock(() => {
|
|
433
|
+
const metadata = this.readMetadata();
|
|
434
|
+
const existing = metadata.targets[name];
|
|
435
|
+
if (!existing) {
|
|
436
|
+
throw new RuntimeTargetStoreError('target_not_found', `Runtime target "${name}" was not found`);
|
|
437
|
+
}
|
|
438
|
+
if (existing.observedIdentity?.runtimeScopeId &&
|
|
439
|
+
existing.observedIdentity.runtimeScopeId !== observedIdentity.runtimeScopeId) {
|
|
440
|
+
throw new RuntimeTargetStoreError('identity_runtime_scope_mismatch', 'Runtime target identity changed; stored credentials cannot be used until identity replacement is accepted');
|
|
441
|
+
}
|
|
442
|
+
const now = this.now().toISOString();
|
|
443
|
+
const updated = {
|
|
444
|
+
...existing,
|
|
445
|
+
observedIdentity,
|
|
446
|
+
lastVerifiedAt: now,
|
|
447
|
+
updatedAt: now,
|
|
448
|
+
};
|
|
449
|
+
this.writeMetadata({
|
|
450
|
+
...metadata,
|
|
451
|
+
targets: {
|
|
452
|
+
...metadata.targets,
|
|
453
|
+
[name]: updated,
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
const entry = this.toListEntry(updated, metadata.current === name);
|
|
457
|
+
return {
|
|
458
|
+
target: entry,
|
|
459
|
+
warnings: externalUrlWarnings(existing.url, observedIdentity),
|
|
460
|
+
};
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
prepareAddTarget(input) {
|
|
464
|
+
const name = validateRuntimeTargetName(input.name);
|
|
465
|
+
const displayName = normalizeDisplayName(input.displayName);
|
|
466
|
+
const tlsMetadata = normalizeTlsMetadata({
|
|
467
|
+
caFile: input.caFile,
|
|
468
|
+
insecureSkipVerify: input.insecureSkipVerify,
|
|
469
|
+
});
|
|
470
|
+
const url = normalizeRuntimeTargetUrl(input.url, {
|
|
471
|
+
allowInsecureHttp: tlsMetadata.insecureSkipVerify === true,
|
|
472
|
+
});
|
|
473
|
+
this.withLock(() => {
|
|
474
|
+
const metadata = this.readMetadata();
|
|
475
|
+
if (metadata.targets[name]) {
|
|
476
|
+
throw new RuntimeTargetStoreError('target_already_exists', `Runtime target "${name}" already exists`);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
return {
|
|
480
|
+
name,
|
|
481
|
+
url,
|
|
482
|
+
displayName,
|
|
483
|
+
caFile: tlsMetadata.caFile,
|
|
484
|
+
insecureSkipVerify: tlsMetadata.insecureSkipVerify,
|
|
485
|
+
use: input.use ?? false,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
commitVerifiedAdd(prepared, observedIdentity) {
|
|
489
|
+
validateRuntimeIdentity(observedIdentity);
|
|
490
|
+
return this.withLock(() => {
|
|
491
|
+
const metadata = this.readMetadata();
|
|
492
|
+
if (metadata.targets[prepared.name]) {
|
|
493
|
+
throw new RuntimeTargetStoreError('target_already_exists', `Runtime target "${prepared.name}" already exists`);
|
|
494
|
+
}
|
|
495
|
+
const now = this.now().toISOString();
|
|
496
|
+
const target = {
|
|
497
|
+
name: prepared.name,
|
|
498
|
+
url: prepared.url,
|
|
499
|
+
displayName: prepared.displayName,
|
|
500
|
+
caFile: prepared.caFile,
|
|
501
|
+
insecureSkipVerify: prepared.insecureSkipVerify,
|
|
502
|
+
observedIdentity,
|
|
503
|
+
lastVerifiedAt: now,
|
|
504
|
+
createdAt: now,
|
|
505
|
+
updatedAt: now,
|
|
506
|
+
};
|
|
507
|
+
this.writeMetadata({
|
|
508
|
+
...metadata,
|
|
509
|
+
current: prepared.use ? prepared.name : metadata.current,
|
|
510
|
+
targets: {
|
|
511
|
+
...metadata.targets,
|
|
512
|
+
[prepared.name]: target,
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
return this.toListEntry(target, prepared.use);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
async addVerifiedTarget(input, verifier) {
|
|
519
|
+
const prepared = this.prepareAddTarget(input);
|
|
520
|
+
const observedIdentity = await verifier({
|
|
521
|
+
targetName: prepared.name,
|
|
522
|
+
url: prepared.url,
|
|
523
|
+
caFile: prepared.caFile,
|
|
524
|
+
insecureSkipVerify: prepared.insecureSkipVerify,
|
|
525
|
+
storeLocked: this.isLockedInThisProcess(),
|
|
526
|
+
});
|
|
527
|
+
return this.commitVerifiedAdd(prepared, observedIdentity);
|
|
528
|
+
}
|
|
529
|
+
exportTargetBundle() {
|
|
530
|
+
const metadata = this.readMetadata();
|
|
531
|
+
return {
|
|
532
|
+
targetBundleVersion: 1,
|
|
533
|
+
targets: Object.values(metadata.targets)
|
|
534
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
535
|
+
.map((target) => targetToBundleEntry(target)),
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
previewImportTargetBundle(bundle, options = {}) {
|
|
539
|
+
const metadata = this.readMetadata();
|
|
540
|
+
return this.validateImportBundle(bundle, metadata, options);
|
|
541
|
+
}
|
|
542
|
+
importTargetBundle(bundle, options = {}) {
|
|
543
|
+
return this.withLock(() => {
|
|
544
|
+
const metadata = this.readMetadata();
|
|
545
|
+
const result = this.validateImportBundle(bundle, metadata, options);
|
|
546
|
+
const now = this.now().toISOString();
|
|
547
|
+
const importedTargets = Object.fromEntries(result.additions.map((addition) => [
|
|
548
|
+
addition.name,
|
|
549
|
+
{
|
|
550
|
+
name: addition.name,
|
|
551
|
+
url: addition.url,
|
|
552
|
+
displayName: addition.displayName,
|
|
553
|
+
caFile: addition.caFile,
|
|
554
|
+
insecureSkipVerify: addition.insecureSkipVerify,
|
|
555
|
+
insecureTlsConfirmationRequired: addition.insecureSkipVerify === true ? true : undefined,
|
|
556
|
+
observedIdentity: addition.observedIdentity,
|
|
557
|
+
lastVerifiedAt: addition.lastVerifiedAt,
|
|
558
|
+
createdAt: now,
|
|
559
|
+
updatedAt: now,
|
|
560
|
+
},
|
|
561
|
+
]));
|
|
562
|
+
this.writeMetadata({
|
|
563
|
+
...metadata,
|
|
564
|
+
targets: {
|
|
565
|
+
...metadata.targets,
|
|
566
|
+
...importedTargets,
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
return result;
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
doctor(options = {}) {
|
|
573
|
+
return this.withLock(() => {
|
|
574
|
+
const issues = [];
|
|
575
|
+
const repairs = [];
|
|
576
|
+
const metadataInspection = this.inspectMetadataFileForDoctor(issues);
|
|
577
|
+
const secretsInspection = this.inspectSecretsFileForDoctor(issues);
|
|
578
|
+
const hasInsecureSecretPermissions = secretsInspection.fileExists && !isOwnerOnly(this.secretsPath());
|
|
579
|
+
if (hasInsecureSecretPermissions) {
|
|
580
|
+
issues.push({
|
|
581
|
+
code: 'secret_store_insecure_permissions',
|
|
582
|
+
message: 'Runtime target secret store is readable or writable by non-owners',
|
|
583
|
+
});
|
|
584
|
+
if (options.fixSecrets) {
|
|
585
|
+
fs.chmodSync(this.secretsPath(), 0o600);
|
|
586
|
+
repairs.push({
|
|
587
|
+
code: 'fixed_secret_store_permissions',
|
|
588
|
+
message: 'Runtime target secret store permissions were repaired',
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (metadataInspection.metadata) {
|
|
593
|
+
const currentName = metadataInspection.metadata.current;
|
|
594
|
+
if (currentName && currentName !== 'local' && !metadataInspection.metadata.targets[currentName]) {
|
|
595
|
+
issues.push({
|
|
596
|
+
code: 'current_context_missing',
|
|
597
|
+
targetName: currentName,
|
|
598
|
+
message: `Current runtime target "${currentName}" does not exist`,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
if (metadataInspection.metadata.targets.local) {
|
|
602
|
+
issues.push({
|
|
603
|
+
code: 'reserved_local_stored_target',
|
|
604
|
+
targetName: 'local',
|
|
605
|
+
message: 'The reserved local target is stored as remote metadata',
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (metadataInspection.metadata && secretsInspection.secrets) {
|
|
610
|
+
const orphanNames = Object.keys(secretsInspection.secrets.credentials).filter((targetName) => targetName !== 'local' && !metadataInspection.metadata?.targets[targetName]);
|
|
611
|
+
for (const targetName of orphanNames) {
|
|
612
|
+
issues.push({
|
|
613
|
+
code: 'orphaned_credentials',
|
|
614
|
+
targetName,
|
|
615
|
+
message: `Credential references exist for missing runtime target "${targetName}"`,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
if (options.pruneOrphans && orphanNames.length > 0 && (!hasInsecureSecretPermissions || options.fixSecrets)) {
|
|
619
|
+
const credentials = { ...secretsInspection.secrets.credentials };
|
|
620
|
+
for (const targetName of orphanNames) {
|
|
621
|
+
delete credentials[targetName];
|
|
622
|
+
repairs.push({
|
|
623
|
+
code: 'pruned_orphaned_credentials',
|
|
624
|
+
targetName,
|
|
625
|
+
message: `Removed orphaned credential references for "${targetName}"`,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
this.writeSecrets({ ...secretsInspection.secrets, credentials });
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return { issues, repairs };
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
validateImportBundle(bundle, metadata, options) {
|
|
635
|
+
const validationFacts = [];
|
|
636
|
+
const warnings = [];
|
|
637
|
+
const additions = [];
|
|
638
|
+
const caFileExists = options.caFileExists ?? ((caFile) => fs.existsSync(caFile));
|
|
639
|
+
if (!isRecord(bundle)) {
|
|
640
|
+
validationFacts.push({
|
|
641
|
+
code: 'invalid_bundle_schema',
|
|
642
|
+
message: 'Runtime target import bundle must be an object',
|
|
643
|
+
});
|
|
644
|
+
throwImportValidationFailed(validationFacts);
|
|
645
|
+
}
|
|
646
|
+
if (bundle.targetBundleVersion !== 1) {
|
|
647
|
+
validationFacts.push({
|
|
648
|
+
code: 'invalid_bundle_version',
|
|
649
|
+
message: 'Runtime target import bundle version must be 1',
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
if (!Array.isArray(bundle.targets)) {
|
|
653
|
+
validationFacts.push({
|
|
654
|
+
code: 'invalid_bundle_schema',
|
|
655
|
+
message: 'Runtime target import bundle targets must be an array',
|
|
656
|
+
});
|
|
657
|
+
throwImportValidationFailed(validationFacts);
|
|
658
|
+
}
|
|
659
|
+
const seenNames = new Set();
|
|
660
|
+
for (const rawEntry of bundle.targets) {
|
|
661
|
+
if (!isRecord(rawEntry)) {
|
|
662
|
+
validationFacts.push({
|
|
663
|
+
code: 'invalid_target_entry',
|
|
664
|
+
message: 'Runtime target import entry must be an object',
|
|
665
|
+
});
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
const rawName = rawEntry.name;
|
|
669
|
+
const targetName = typeof rawName === 'string' ? rawName : undefined;
|
|
670
|
+
let normalizedName;
|
|
671
|
+
if (!targetName) {
|
|
672
|
+
validationFacts.push({
|
|
673
|
+
code: 'invalid_target_name',
|
|
674
|
+
message: 'Runtime target import entry name must be a string',
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
else if (seenNames.has(targetName)) {
|
|
678
|
+
validationFacts.push({
|
|
679
|
+
code: 'duplicate_bundle_entry',
|
|
680
|
+
targetName,
|
|
681
|
+
message: `Runtime target "${targetName}" is duplicated in the import bundle`,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
seenNames.add(targetName);
|
|
686
|
+
}
|
|
687
|
+
if (targetName) {
|
|
688
|
+
try {
|
|
689
|
+
normalizedName = validateRuntimeTargetName(targetName);
|
|
690
|
+
}
|
|
691
|
+
catch (error) {
|
|
692
|
+
const code = error instanceof RuntimeTargetStoreError && error.code === 'target_name_reserved'
|
|
693
|
+
? 'reserved_local_target'
|
|
694
|
+
: 'invalid_target_name';
|
|
695
|
+
validationFacts.push({
|
|
696
|
+
code,
|
|
697
|
+
targetName,
|
|
698
|
+
message: code === 'reserved_local_target'
|
|
699
|
+
? 'The built-in local target cannot be imported as stored metadata'
|
|
700
|
+
: 'Runtime target import entry name is invalid',
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (normalizedName && metadata.targets[normalizedName]) {
|
|
705
|
+
validationFacts.push({
|
|
706
|
+
code: 'target_name_conflict',
|
|
707
|
+
targetName: normalizedName,
|
|
708
|
+
message: `Runtime target "${normalizedName}" already exists`,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
let displayName;
|
|
712
|
+
if (Object.prototype.hasOwnProperty.call(rawEntry, 'displayName')) {
|
|
713
|
+
if (typeof rawEntry.displayName !== 'string' && rawEntry.displayName !== undefined) {
|
|
714
|
+
validationFacts.push({
|
|
715
|
+
code: 'invalid_display_name',
|
|
716
|
+
targetName,
|
|
717
|
+
message: 'Runtime target import displayName must be a string',
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
try {
|
|
722
|
+
displayName = normalizeDisplayName(rawEntry.displayName);
|
|
723
|
+
}
|
|
724
|
+
catch {
|
|
725
|
+
validationFacts.push({
|
|
726
|
+
code: 'invalid_display_name',
|
|
727
|
+
targetName,
|
|
728
|
+
message: 'Runtime target import displayName is invalid',
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
let tlsMetadata;
|
|
734
|
+
try {
|
|
735
|
+
tlsMetadata = normalizeTlsMetadata({
|
|
736
|
+
caFile: rawEntry.caFile,
|
|
737
|
+
insecureSkipVerify: rawEntry.insecureSkipVerify,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
catch {
|
|
741
|
+
validationFacts.push({
|
|
742
|
+
code: 'invalid_tls_metadata',
|
|
743
|
+
targetName,
|
|
744
|
+
message: 'Runtime target import TLS metadata is invalid',
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
let normalizedUrl;
|
|
748
|
+
if (typeof rawEntry.url !== 'string') {
|
|
749
|
+
validationFacts.push({
|
|
750
|
+
code: 'invalid_url',
|
|
751
|
+
targetName,
|
|
752
|
+
message: 'Runtime target import entry URL must be a string',
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
try {
|
|
757
|
+
normalizedUrl = normalizeRuntimeTargetUrl(rawEntry.url, {
|
|
758
|
+
allowInsecureHttp: tlsMetadata?.insecureSkipVerify === true,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
catch {
|
|
762
|
+
validationFacts.push({
|
|
763
|
+
code: 'invalid_url',
|
|
764
|
+
targetName,
|
|
765
|
+
message: 'Runtime target import entry URL is invalid',
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
let observedIdentity;
|
|
770
|
+
if (Object.prototype.hasOwnProperty.call(rawEntry, 'observedIdentity')) {
|
|
771
|
+
if (!isRecord(rawEntry.observedIdentity)) {
|
|
772
|
+
validationFacts.push({
|
|
773
|
+
code: 'invalid_observed_identity',
|
|
774
|
+
targetName,
|
|
775
|
+
message: 'Runtime target import observedIdentity must be an object',
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
observedIdentity = {
|
|
780
|
+
identityProtocolVersion: rawEntry.observedIdentity.identityProtocolVersion,
|
|
781
|
+
runtimeScopeId: rawEntry.observedIdentity.runtimeScopeId,
|
|
782
|
+
externalUrl: rawEntry.observedIdentity.externalUrl,
|
|
783
|
+
runtimeVersion: rawEntry.observedIdentity.runtimeVersion,
|
|
784
|
+
serverTime: rawEntry.observedIdentity.serverTime,
|
|
785
|
+
};
|
|
786
|
+
try {
|
|
787
|
+
validateRuntimeIdentity(observedIdentity);
|
|
788
|
+
}
|
|
789
|
+
catch {
|
|
790
|
+
validationFacts.push({
|
|
791
|
+
code: 'invalid_observed_identity',
|
|
792
|
+
targetName,
|
|
793
|
+
message: 'Runtime target import observedIdentity is invalid',
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
let lastVerifiedAt;
|
|
799
|
+
if (Object.prototype.hasOwnProperty.call(rawEntry, 'lastVerifiedAt')) {
|
|
800
|
+
if (typeof rawEntry.lastVerifiedAt !== 'string' || Number.isNaN(Date.parse(rawEntry.lastVerifiedAt))) {
|
|
801
|
+
validationFacts.push({
|
|
802
|
+
code: 'invalid_last_verified_at',
|
|
803
|
+
targetName,
|
|
804
|
+
message: 'Runtime target import lastVerifiedAt must be an ISO-like timestamp string',
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
lastVerifiedAt = rawEntry.lastVerifiedAt;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (normalizedName && normalizedUrl && tlsMetadata) {
|
|
812
|
+
const addition = omitUndefined({
|
|
813
|
+
name: normalizedName,
|
|
814
|
+
url: normalizedUrl,
|
|
815
|
+
displayName,
|
|
816
|
+
caFile: tlsMetadata.caFile,
|
|
817
|
+
insecureSkipVerify: tlsMetadata.insecureSkipVerify,
|
|
818
|
+
observedIdentity,
|
|
819
|
+
lastVerifiedAt,
|
|
820
|
+
});
|
|
821
|
+
additions.push(addition);
|
|
822
|
+
if (addition.caFile && !caFileExists(addition.caFile)) {
|
|
823
|
+
warnings.push({
|
|
824
|
+
code: 'warning_missing_ca_file',
|
|
825
|
+
targetName: addition.name,
|
|
826
|
+
message: `CA bundle path "${addition.caFile}" does not exist on this machine`,
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
if (addition.insecureSkipVerify) {
|
|
830
|
+
warnings.push({
|
|
831
|
+
code: 'warning_insecure_tls_confirmation_required',
|
|
832
|
+
targetName: addition.name,
|
|
833
|
+
message: `Runtime target "${addition.name}" will require insecure TLS confirmation before first use`,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (validationFacts.length > 0) {
|
|
839
|
+
throwImportValidationFailed(validationFacts);
|
|
840
|
+
}
|
|
841
|
+
return {
|
|
842
|
+
additions: additions.sort((left, right) => left.name.localeCompare(right.name)),
|
|
843
|
+
validationFacts,
|
|
844
|
+
warnings: warnings.sort((left, right) => left.targetName.localeCompare(right.targetName)),
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
confirmInsecureTlsIfRequired(target, operation, acceptInsecureTls) {
|
|
848
|
+
if (!target.insecureTlsConfirmationRequired) {
|
|
849
|
+
return target;
|
|
850
|
+
}
|
|
851
|
+
if (!acceptInsecureTls) {
|
|
852
|
+
throw new RuntimeTargetStoreError('target_insecure_tls_confirmation_required', `Runtime target "${target.name}" uses imported insecure TLS metadata and requires confirmation`, { operation, targetName: target.name }, insecureTlsRecoveryCommand(target.name, operation));
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
...target,
|
|
856
|
+
insecureTlsConfirmationRequired: false,
|
|
857
|
+
updatedAt: this.now().toISOString(),
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
inspectMetadataFileForDoctor(issues) {
|
|
861
|
+
const filePath = this.metadataPath();
|
|
862
|
+
if (!fs.existsSync(filePath)) {
|
|
863
|
+
return { metadata: { schemaVersion: STORE_SCHEMA_VERSION, targets: {} } };
|
|
864
|
+
}
|
|
865
|
+
let parsed;
|
|
866
|
+
try {
|
|
867
|
+
parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
868
|
+
}
|
|
869
|
+
catch {
|
|
870
|
+
issues.push({
|
|
871
|
+
code: 'metadata_parse_failed',
|
|
872
|
+
message: 'Runtime target metadata file is not valid JSON',
|
|
873
|
+
});
|
|
874
|
+
return {};
|
|
875
|
+
}
|
|
876
|
+
if (!isRecord(parsed) || parsed.schemaVersion !== STORE_SCHEMA_VERSION || !isRecord(parsed.targets)) {
|
|
877
|
+
issues.push({
|
|
878
|
+
code: 'metadata_schema_invalid',
|
|
879
|
+
message: 'Runtime target metadata file does not match schema version 1',
|
|
880
|
+
});
|
|
881
|
+
return {};
|
|
882
|
+
}
|
|
883
|
+
if (parsed.current !== undefined && typeof parsed.current !== 'string') {
|
|
884
|
+
issues.push({
|
|
885
|
+
code: 'metadata_schema_invalid',
|
|
886
|
+
message: 'Runtime target metadata current pointer must be a string',
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
for (const [targetName, rawTarget] of Object.entries(parsed.targets)) {
|
|
890
|
+
inspectStoredTargetForDoctor(targetName, rawTarget, issues);
|
|
891
|
+
}
|
|
892
|
+
return {
|
|
893
|
+
metadata: {
|
|
894
|
+
schemaVersion: STORE_SCHEMA_VERSION,
|
|
895
|
+
current: typeof parsed.current === 'string' ? parsed.current : undefined,
|
|
896
|
+
targets: parsed.targets,
|
|
897
|
+
},
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
inspectSecretsFileForDoctor(issues) {
|
|
901
|
+
const filePath = this.secretsPath();
|
|
902
|
+
if (!fs.existsSync(filePath)) {
|
|
903
|
+
return { fileExists: false, secrets: { schemaVersion: STORE_SCHEMA_VERSION, credentials: {} } };
|
|
904
|
+
}
|
|
905
|
+
let parsed;
|
|
906
|
+
try {
|
|
907
|
+
parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
908
|
+
}
|
|
909
|
+
catch {
|
|
910
|
+
issues.push({
|
|
911
|
+
code: 'secrets_parse_failed',
|
|
912
|
+
message: 'Runtime target secrets file is not valid JSON',
|
|
913
|
+
});
|
|
914
|
+
return { fileExists: true };
|
|
915
|
+
}
|
|
916
|
+
if (!isRecord(parsed) || parsed.schemaVersion !== STORE_SCHEMA_VERSION || !isRecord(parsed.credentials)) {
|
|
917
|
+
issues.push({
|
|
918
|
+
code: 'secrets_schema_invalid',
|
|
919
|
+
message: 'Runtime target secrets file does not match schema version 1',
|
|
920
|
+
});
|
|
921
|
+
return { fileExists: true };
|
|
922
|
+
}
|
|
923
|
+
for (const [targetName, rawScopes] of Object.entries(parsed.credentials)) {
|
|
924
|
+
inspectCredentialScopesForDoctor(targetName, rawScopes, issues);
|
|
925
|
+
}
|
|
926
|
+
return {
|
|
927
|
+
fileExists: true,
|
|
928
|
+
secrets: {
|
|
929
|
+
schemaVersion: STORE_SCHEMA_VERSION,
|
|
930
|
+
credentials: parsed.credentials,
|
|
931
|
+
},
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
localEntry(current, secrets = this.readSecretsForMetadataOnly()) {
|
|
935
|
+
return {
|
|
936
|
+
name: 'local',
|
|
937
|
+
kind: 'local',
|
|
938
|
+
synthetic: true,
|
|
939
|
+
current,
|
|
940
|
+
verificationStatus: 'synthetic',
|
|
941
|
+
credentialReferences: credentialReferencePresence(secrets.credentials.local),
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
toListEntry(target, current, secrets = this.readSecretsForMetadataOnly(), includeEmbeddedCredentials = false) {
|
|
945
|
+
return {
|
|
946
|
+
name: target.name,
|
|
947
|
+
kind: 'remote',
|
|
948
|
+
synthetic: false,
|
|
949
|
+
current,
|
|
950
|
+
url: includeEmbeddedCredentials ? target.url : redactRuntimeTargetUrlUserinfo(target.url),
|
|
951
|
+
displayName: target.displayName,
|
|
952
|
+
caFile: target.caFile,
|
|
953
|
+
insecureSkipVerify: target.insecureSkipVerify,
|
|
954
|
+
insecureTlsConfirmationRequired: target.insecureTlsConfirmationRequired ?? false,
|
|
955
|
+
observedIdentity: target.observedIdentity
|
|
956
|
+
? {
|
|
957
|
+
...target.observedIdentity,
|
|
958
|
+
externalUrl: redactRuntimeTargetUrlUserinfo(target.observedIdentity.externalUrl),
|
|
959
|
+
}
|
|
960
|
+
: undefined,
|
|
961
|
+
lastVerifiedAt: target.lastVerifiedAt,
|
|
962
|
+
verificationStatus: this.verificationStatus(target),
|
|
963
|
+
credentialReferences: credentialReferencePresence(secrets.credentials[target.name]),
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
verificationStatus(target) {
|
|
967
|
+
if (!target.lastVerifiedAt) {
|
|
968
|
+
return 'never-verified';
|
|
969
|
+
}
|
|
970
|
+
const ageMs = this.now().getTime() - new Date(target.lastVerifiedAt).getTime();
|
|
971
|
+
return ageMs > THIRTY_DAYS_MS ? 'stale' : 'verified';
|
|
972
|
+
}
|
|
973
|
+
readMetadata() {
|
|
974
|
+
const filePath = this.metadataPath();
|
|
975
|
+
if (!fs.existsSync(filePath)) {
|
|
976
|
+
return { schemaVersion: STORE_SCHEMA_VERSION, targets: {} };
|
|
977
|
+
}
|
|
978
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
979
|
+
return {
|
|
980
|
+
schemaVersion: STORE_SCHEMA_VERSION,
|
|
981
|
+
current: typeof parsed.current === 'string' ? parsed.current : undefined,
|
|
982
|
+
targets: isRecord(parsed.targets) ? parsed.targets : {},
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
readSecretsForMetadataOnly() {
|
|
986
|
+
const filePath = this.secretsPath();
|
|
987
|
+
if (!fs.existsSync(filePath)) {
|
|
988
|
+
return { schemaVersion: STORE_SCHEMA_VERSION, credentials: {} };
|
|
989
|
+
}
|
|
990
|
+
if (!isOwnerOnly(filePath)) {
|
|
991
|
+
return { schemaVersion: STORE_SCHEMA_VERSION, credentials: {} };
|
|
992
|
+
}
|
|
993
|
+
return this.readSecretsUnchecked();
|
|
994
|
+
}
|
|
995
|
+
readSecretsForCredentialUse() {
|
|
996
|
+
const filePath = this.secretsPath();
|
|
997
|
+
if (!fs.existsSync(filePath)) {
|
|
998
|
+
return { schemaVersion: STORE_SCHEMA_VERSION, credentials: {} };
|
|
999
|
+
}
|
|
1000
|
+
if (!isOwnerOnly(filePath)) {
|
|
1001
|
+
throw new RuntimeTargetStoreError('target_secret_store_insecure', 'Runtime target secret store is too permissive');
|
|
1002
|
+
}
|
|
1003
|
+
return this.readSecretsUnchecked();
|
|
1004
|
+
}
|
|
1005
|
+
readSecretsUnchecked() {
|
|
1006
|
+
const filePath = this.secretsPath();
|
|
1007
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
1008
|
+
return {
|
|
1009
|
+
schemaVersion: STORE_SCHEMA_VERSION,
|
|
1010
|
+
credentials: isRecord(parsed.credentials)
|
|
1011
|
+
? parsed.credentials
|
|
1012
|
+
: {},
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
writeMetadata(metadata) {
|
|
1016
|
+
writeJsonAtomic(this.metadataPath(), metadata);
|
|
1017
|
+
}
|
|
1018
|
+
writeSecrets(secrets) {
|
|
1019
|
+
writeJsonAtomic(this.secretsPath(), secrets, 0o600);
|
|
1020
|
+
}
|
|
1021
|
+
writeSecretsThenMetadata(previousSecrets, nextSecrets, nextMetadata) {
|
|
1022
|
+
this.writeSecrets(nextSecrets);
|
|
1023
|
+
try {
|
|
1024
|
+
this.writeMetadata(nextMetadata);
|
|
1025
|
+
}
|
|
1026
|
+
catch (error) {
|
|
1027
|
+
try {
|
|
1028
|
+
this.writeSecrets(previousSecrets);
|
|
1029
|
+
}
|
|
1030
|
+
catch {
|
|
1031
|
+
// Keep the original metadata-write error as the reported failure.
|
|
1032
|
+
}
|
|
1033
|
+
throw error;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
withLock(fn) {
|
|
1037
|
+
const release = this.acquireLock();
|
|
1038
|
+
try {
|
|
1039
|
+
return fn();
|
|
1040
|
+
}
|
|
1041
|
+
finally {
|
|
1042
|
+
release();
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
acquireLock() {
|
|
1046
|
+
fs.mkdirSync(this.storeDir, { recursive: true });
|
|
1047
|
+
const lockPath = this.lockPath();
|
|
1048
|
+
if (RuntimeTargetStore.activeLocks.has(lockPath)) {
|
|
1049
|
+
throw new RuntimeTargetStoreError('lock_unavailable', 'Another 1mcp command is editing runtime targets');
|
|
1050
|
+
}
|
|
1051
|
+
RuntimeTargetStore.activeLocks.add(lockPath);
|
|
1052
|
+
let fd = null;
|
|
1053
|
+
try {
|
|
1054
|
+
fd = fs.openSync(lockPath, 'wx', 0o600);
|
|
1055
|
+
}
|
|
1056
|
+
catch (error) {
|
|
1057
|
+
RuntimeTargetStore.activeLocks.delete(lockPath);
|
|
1058
|
+
if (isFileExistsError(error)) {
|
|
1059
|
+
throw new RuntimeTargetStoreError('lock_unavailable', 'Another 1mcp command is editing runtime targets');
|
|
1060
|
+
}
|
|
1061
|
+
throw error;
|
|
1062
|
+
}
|
|
1063
|
+
return () => {
|
|
1064
|
+
if (fd !== null) {
|
|
1065
|
+
fs.closeSync(fd);
|
|
1066
|
+
}
|
|
1067
|
+
fs.rmSync(lockPath, { force: true });
|
|
1068
|
+
RuntimeTargetStore.activeLocks.delete(lockPath);
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
metadataPath() {
|
|
1072
|
+
return path.join(this.storeDir, METADATA_FILE);
|
|
1073
|
+
}
|
|
1074
|
+
secretsPath() {
|
|
1075
|
+
return path.join(this.storeDir, SECRETS_FILE);
|
|
1076
|
+
}
|
|
1077
|
+
lockPath() {
|
|
1078
|
+
return path.join(this.storeDir, LOCK_FILE);
|
|
1079
|
+
}
|
|
1080
|
+
isLockedInThisProcess() {
|
|
1081
|
+
return RuntimeTargetStore.activeLocks.has(this.lockPath());
|
|
1082
|
+
}
|
|
1083
|
+
clearLocalCredentialReferences(kind) {
|
|
1084
|
+
return this.withLock(() => {
|
|
1085
|
+
const secrets = this.readSecretsForCredentialUse();
|
|
1086
|
+
const localScopes = secrets.credentials.local;
|
|
1087
|
+
if (!localScopes) {
|
|
1088
|
+
return 0;
|
|
1089
|
+
}
|
|
1090
|
+
let cleared = 0;
|
|
1091
|
+
const nextLocalScopes = {};
|
|
1092
|
+
for (const [runtimeScopeId, bucket] of Object.entries(localScopes)) {
|
|
1093
|
+
if (bucket[kind] === undefined) {
|
|
1094
|
+
nextLocalScopes[runtimeScopeId] = bucket;
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
cleared += 1;
|
|
1098
|
+
const nextBucket = { ...bucket };
|
|
1099
|
+
delete nextBucket[kind];
|
|
1100
|
+
if (Object.keys(nextBucket).length > 0) {
|
|
1101
|
+
nextLocalScopes[runtimeScopeId] = nextBucket;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (cleared === 0) {
|
|
1105
|
+
return 0;
|
|
1106
|
+
}
|
|
1107
|
+
const nextCredentials = { ...secrets.credentials };
|
|
1108
|
+
if (Object.keys(nextLocalScopes).length === 0) {
|
|
1109
|
+
delete nextCredentials.local;
|
|
1110
|
+
}
|
|
1111
|
+
else {
|
|
1112
|
+
nextCredentials.local = nextLocalScopes;
|
|
1113
|
+
}
|
|
1114
|
+
this.writeSecrets({ ...secrets, credentials: nextCredentials });
|
|
1115
|
+
return cleared;
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
export function validateRuntimeTargetName(name) {
|
|
1120
|
+
if (!TARGET_NAME_PATTERN.test(name) || name.includes('/') || name.includes('\\')) {
|
|
1121
|
+
throw new RuntimeTargetStoreError('target_name_invalid', 'Runtime target name is invalid');
|
|
1122
|
+
}
|
|
1123
|
+
if (name === 'local') {
|
|
1124
|
+
throw new RuntimeTargetStoreError('target_name_reserved', 'Runtime target name "local" is reserved');
|
|
1125
|
+
}
|
|
1126
|
+
return name;
|
|
1127
|
+
}
|
|
1128
|
+
export function normalizeDisplayName(displayName) {
|
|
1129
|
+
if (displayName === undefined) {
|
|
1130
|
+
return undefined;
|
|
1131
|
+
}
|
|
1132
|
+
const normalized = displayName.trim();
|
|
1133
|
+
if (normalized === '') {
|
|
1134
|
+
return undefined;
|
|
1135
|
+
}
|
|
1136
|
+
const codePoints = Array.from(normalized);
|
|
1137
|
+
if (codePoints.length > 80) {
|
|
1138
|
+
throw new RuntimeTargetStoreError('target_display_name_invalid', 'Runtime target display name is too long');
|
|
1139
|
+
}
|
|
1140
|
+
for (const char of codePoints) {
|
|
1141
|
+
const codePoint = char.codePointAt(0);
|
|
1142
|
+
if (codePoint === undefined || isControlCodePoint(codePoint) || isSurrogateCodePoint(codePoint)) {
|
|
1143
|
+
throw new RuntimeTargetStoreError('target_display_name_invalid', 'Runtime target display name is invalid');
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return normalized;
|
|
1147
|
+
}
|
|
1148
|
+
export function assertRuntimeTargetConfigDirAllowed(input) {
|
|
1149
|
+
if (!input.configDir) {
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
if (input.command === 'verify' && input.targetName === 'local') {
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
if (input.command === 'verify') {
|
|
1156
|
+
throw new RuntimeTargetStoreError('target_config_dir_remote_unsupported', '--config-dir selects only a local Runtime Scope and cannot scope a remote target');
|
|
1157
|
+
}
|
|
1158
|
+
throw new RuntimeTargetStoreError('target_store_config_dir_unsupported', 'Runtime target store commands are user-global and do not accept --config-dir');
|
|
1159
|
+
}
|
|
1160
|
+
export function normalizeRuntimeTargetUrl(url, options = {}) {
|
|
1161
|
+
let parsed;
|
|
1162
|
+
try {
|
|
1163
|
+
parsed = new URL(url);
|
|
1164
|
+
}
|
|
1165
|
+
catch {
|
|
1166
|
+
throw new RuntimeTargetStoreError('target_url_invalid', 'Runtime target URL is invalid');
|
|
1167
|
+
}
|
|
1168
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
1169
|
+
throw new RuntimeTargetStoreError('target_url_invalid', 'Runtime target URL must use http or https');
|
|
1170
|
+
}
|
|
1171
|
+
if (parsed.username || parsed.password) {
|
|
1172
|
+
throw new RuntimeTargetStoreError('target_url_credentials_unsupported', 'Runtime target URLs must not contain embedded credentials');
|
|
1173
|
+
}
|
|
1174
|
+
if (parsed.protocol === 'http:' && !isLoopbackHostname(parsed.hostname) && !options.allowInsecureHttp) {
|
|
1175
|
+
throw new RuntimeTargetStoreError('target_url_invalid', 'Runtime target URL must use https unless non-loopback HTTP is explicitly accepted with --insecure-skip-verify');
|
|
1176
|
+
}
|
|
1177
|
+
parsed.search = '';
|
|
1178
|
+
parsed.hash = '';
|
|
1179
|
+
return parsed.toString().replace(/\/$/, '');
|
|
1180
|
+
}
|
|
1181
|
+
function isLoopbackHostname(hostname) {
|
|
1182
|
+
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
1183
|
+
if (normalized === 'localhost' || normalized === '::1' || normalized === '0:0:0:0:0:0:0:1') {
|
|
1184
|
+
return true;
|
|
1185
|
+
}
|
|
1186
|
+
if (net.isIP(normalized) === 4) {
|
|
1187
|
+
return normalized.split('.')[0] === '127';
|
|
1188
|
+
}
|
|
1189
|
+
return false;
|
|
1190
|
+
}
|
|
1191
|
+
function normalizeTlsMetadata(input) {
|
|
1192
|
+
let caFile;
|
|
1193
|
+
if (input.caFile !== undefined) {
|
|
1194
|
+
if (typeof input.caFile !== 'string') {
|
|
1195
|
+
throw new RuntimeTargetStoreError('target_tls_metadata_invalid', 'Runtime target CA file must be a string');
|
|
1196
|
+
}
|
|
1197
|
+
caFile = input.caFile.trim();
|
|
1198
|
+
if (caFile === '' || hasControlCodePoint(caFile)) {
|
|
1199
|
+
throw new RuntimeTargetStoreError('target_tls_metadata_invalid', 'Runtime target CA file is invalid');
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
let insecureSkipVerify;
|
|
1203
|
+
if (input.insecureSkipVerify !== undefined) {
|
|
1204
|
+
if (typeof input.insecureSkipVerify !== 'boolean') {
|
|
1205
|
+
throw new RuntimeTargetStoreError('target_tls_metadata_invalid', 'Runtime target insecureSkipVerify must be a boolean');
|
|
1206
|
+
}
|
|
1207
|
+
insecureSkipVerify = input.insecureSkipVerify === true ? true : undefined;
|
|
1208
|
+
}
|
|
1209
|
+
return omitUndefined({ caFile, insecureSkipVerify });
|
|
1210
|
+
}
|
|
1211
|
+
function targetToBundleEntry(target) {
|
|
1212
|
+
return omitUndefined({
|
|
1213
|
+
name: target.name,
|
|
1214
|
+
url: redactRuntimeTargetUrlUserinfo(target.url),
|
|
1215
|
+
displayName: target.displayName,
|
|
1216
|
+
caFile: target.caFile,
|
|
1217
|
+
insecureSkipVerify: target.insecureSkipVerify,
|
|
1218
|
+
observedIdentity: target.observedIdentity
|
|
1219
|
+
? {
|
|
1220
|
+
...target.observedIdentity,
|
|
1221
|
+
externalUrl: redactRuntimeTargetUrlUserinfo(target.observedIdentity.externalUrl),
|
|
1222
|
+
}
|
|
1223
|
+
: undefined,
|
|
1224
|
+
lastVerifiedAt: target.lastVerifiedAt,
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
function redactRuntimeTargetUrlUserinfo(url) {
|
|
1228
|
+
try {
|
|
1229
|
+
const parsed = new URL(url);
|
|
1230
|
+
parsed.username = '';
|
|
1231
|
+
parsed.password = '';
|
|
1232
|
+
return parsed.toString().replace(/\/$/, '');
|
|
1233
|
+
}
|
|
1234
|
+
catch {
|
|
1235
|
+
return '';
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
function throwImportValidationFailed(validationFacts) {
|
|
1239
|
+
throw new RuntimeTargetStoreError('target_import_validation_failed', 'Runtime target import bundle failed validation', { validationFacts });
|
|
1240
|
+
}
|
|
1241
|
+
function inspectStoredTargetForDoctor(targetName, rawTarget, issues) {
|
|
1242
|
+
const addIssue = (message) => {
|
|
1243
|
+
issues.push({ code: 'metadata_schema_invalid', targetName, message });
|
|
1244
|
+
};
|
|
1245
|
+
if (!isRecord(rawTarget)) {
|
|
1246
|
+
addIssue(`Runtime target "${targetName}" metadata must be an object`);
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
if (rawTarget.name !== targetName) {
|
|
1250
|
+
addIssue(`Runtime target "${targetName}" metadata name does not match its key`);
|
|
1251
|
+
}
|
|
1252
|
+
try {
|
|
1253
|
+
validateRuntimeTargetName(targetName);
|
|
1254
|
+
}
|
|
1255
|
+
catch {
|
|
1256
|
+
addIssue(`Runtime target "${targetName}" has an invalid or reserved name`);
|
|
1257
|
+
}
|
|
1258
|
+
let tlsMetadata;
|
|
1259
|
+
try {
|
|
1260
|
+
tlsMetadata = normalizeTlsMetadata({
|
|
1261
|
+
caFile: rawTarget.caFile,
|
|
1262
|
+
insecureSkipVerify: rawTarget.insecureSkipVerify,
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
catch {
|
|
1266
|
+
addIssue(`Runtime target "${targetName}" TLS metadata is invalid`);
|
|
1267
|
+
}
|
|
1268
|
+
if (typeof rawTarget.url !== 'string') {
|
|
1269
|
+
addIssue(`Runtime target "${targetName}" URL must be a string`);
|
|
1270
|
+
}
|
|
1271
|
+
else {
|
|
1272
|
+
try {
|
|
1273
|
+
normalizeRuntimeTargetUrl(rawTarget.url, {
|
|
1274
|
+
allowInsecureHttp: tlsMetadata?.insecureSkipVerify === true,
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
catch {
|
|
1278
|
+
addIssue(`Runtime target "${targetName}" URL is invalid`);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
if (Object.prototype.hasOwnProperty.call(rawTarget, 'displayName')) {
|
|
1282
|
+
if (typeof rawTarget.displayName !== 'string' && rawTarget.displayName !== undefined) {
|
|
1283
|
+
addIssue(`Runtime target "${targetName}" displayName must be a string`);
|
|
1284
|
+
}
|
|
1285
|
+
else {
|
|
1286
|
+
try {
|
|
1287
|
+
normalizeDisplayName(rawTarget.displayName);
|
|
1288
|
+
}
|
|
1289
|
+
catch {
|
|
1290
|
+
addIssue(`Runtime target "${targetName}" displayName is invalid`);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
if (Object.prototype.hasOwnProperty.call(rawTarget, 'insecureTlsConfirmationRequired')) {
|
|
1295
|
+
if (typeof rawTarget.insecureTlsConfirmationRequired !== 'boolean') {
|
|
1296
|
+
addIssue(`Runtime target "${targetName}" insecure TLS confirmation marker must be a boolean`);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
if (Object.prototype.hasOwnProperty.call(rawTarget, 'observedIdentity')) {
|
|
1300
|
+
if (!isRecord(rawTarget.observedIdentity)) {
|
|
1301
|
+
addIssue(`Runtime target "${targetName}" observed identity must be an object`);
|
|
1302
|
+
}
|
|
1303
|
+
else {
|
|
1304
|
+
try {
|
|
1305
|
+
validateRuntimeIdentity(rawTarget.observedIdentity);
|
|
1306
|
+
}
|
|
1307
|
+
catch {
|
|
1308
|
+
addIssue(`Runtime target "${targetName}" observed identity is invalid`);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
inspectTimestampForDoctor(targetName, rawTarget, 'lastVerifiedAt', issues);
|
|
1313
|
+
inspectTimestampForDoctor(targetName, rawTarget, 'createdAt', issues);
|
|
1314
|
+
inspectTimestampForDoctor(targetName, rawTarget, 'updatedAt', issues);
|
|
1315
|
+
}
|
|
1316
|
+
function inspectTimestampForDoctor(targetName, target, field, issues) {
|
|
1317
|
+
if (!Object.prototype.hasOwnProperty.call(target, field)) {
|
|
1318
|
+
if (field === 'createdAt' || field === 'updatedAt') {
|
|
1319
|
+
issues.push({
|
|
1320
|
+
code: 'metadata_schema_invalid',
|
|
1321
|
+
targetName,
|
|
1322
|
+
message: `Runtime target "${targetName}" ${field} timestamp is missing`,
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
if (typeof target[field] !== 'string' || Number.isNaN(Date.parse(target[field]))) {
|
|
1328
|
+
issues.push({
|
|
1329
|
+
code: 'metadata_schema_invalid',
|
|
1330
|
+
targetName,
|
|
1331
|
+
message: `Runtime target "${targetName}" ${field} timestamp is invalid`,
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
function inspectCredentialScopesForDoctor(targetName, rawScopes, issues) {
|
|
1336
|
+
if (!isRecord(rawScopes)) {
|
|
1337
|
+
issues.push({
|
|
1338
|
+
code: 'secrets_schema_invalid',
|
|
1339
|
+
targetName,
|
|
1340
|
+
message: `Runtime target "${targetName}" credential scopes must be an object`,
|
|
1341
|
+
});
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
for (const [runtimeScopeId, rawBucket] of Object.entries(rawScopes)) {
|
|
1345
|
+
if (!runtimeScopeId) {
|
|
1346
|
+
issues.push({
|
|
1347
|
+
code: 'secrets_schema_invalid',
|
|
1348
|
+
targetName,
|
|
1349
|
+
message: `Runtime target "${targetName}" credential scope id must be non-empty`,
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
if (!isRecord(rawBucket)) {
|
|
1353
|
+
issues.push({
|
|
1354
|
+
code: 'secrets_schema_invalid',
|
|
1355
|
+
targetName,
|
|
1356
|
+
message: `Runtime target "${targetName}" credential bucket for scope "${runtimeScopeId}" must be an object`,
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
function insecureTlsRecoveryCommand(name, operation) {
|
|
1362
|
+
if (operation === 'use') {
|
|
1363
|
+
return `1mcp target use ${name} --accept-insecure-tls`;
|
|
1364
|
+
}
|
|
1365
|
+
return `1mcp target verify ${name} --accept-insecure-tls`;
|
|
1366
|
+
}
|
|
1367
|
+
function omitUndefined(value) {
|
|
1368
|
+
return Object.fromEntries(Object.entries(value).filter(([, entryValue]) => entryValue !== undefined));
|
|
1369
|
+
}
|
|
1370
|
+
function compactCredentialReferences(references) {
|
|
1371
|
+
const bucket = {};
|
|
1372
|
+
if (references.oauth !== undefined) {
|
|
1373
|
+
bucket.oauth = references.oauth;
|
|
1374
|
+
}
|
|
1375
|
+
if (references.adminSession !== undefined) {
|
|
1376
|
+
bucket.adminSession = references.adminSession;
|
|
1377
|
+
}
|
|
1378
|
+
return bucket;
|
|
1379
|
+
}
|
|
1380
|
+
function externalUrlWarnings(configuredUrl, observedIdentity) {
|
|
1381
|
+
return normalizeUrlForComparison(configuredUrl) === normalizeUrlForComparison(observedIdentity.externalUrl)
|
|
1382
|
+
? []
|
|
1383
|
+
: [
|
|
1384
|
+
{
|
|
1385
|
+
code: 'warning_external_url_mismatch',
|
|
1386
|
+
message: `Runtime target externalUrl differs from configured URL for "${configuredUrl}"`,
|
|
1387
|
+
},
|
|
1388
|
+
];
|
|
1389
|
+
}
|
|
1390
|
+
function normalizeUrlForComparison(url) {
|
|
1391
|
+
return url.replace(/\/$/, '');
|
|
1392
|
+
}
|
|
1393
|
+
function validateRuntimeIdentity(identity) {
|
|
1394
|
+
if (identity.identityProtocolVersion !== '1' ||
|
|
1395
|
+
typeof identity.runtimeScopeId !== 'string' ||
|
|
1396
|
+
identity.runtimeScopeId.length === 0 ||
|
|
1397
|
+
typeof identity.externalUrl !== 'string' ||
|
|
1398
|
+
identity.externalUrl.length === 0 ||
|
|
1399
|
+
typeof identity.runtimeVersion !== 'string' ||
|
|
1400
|
+
identity.runtimeVersion.length === 0 ||
|
|
1401
|
+
(identity.serverTime !== undefined && typeof identity.serverTime !== 'string')) {
|
|
1402
|
+
throw new RuntimeTargetStoreError('identity_invalid', 'Runtime identity response is missing required fields');
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
function credentialReferencePresence(byScope) {
|
|
1406
|
+
const buckets = Object.values(byScope ?? {});
|
|
1407
|
+
return {
|
|
1408
|
+
oauth: buckets.some((bucket) => bucket.oauth !== undefined),
|
|
1409
|
+
adminSession: buckets.some((bucket) => bucket.adminSession !== undefined),
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
function verificationWarnings(entry) {
|
|
1413
|
+
if (entry.verificationStatus === 'stale') {
|
|
1414
|
+
return [{ code: 'warning_target_stale', message: `Runtime target "${entry.name}" has stale verification` }];
|
|
1415
|
+
}
|
|
1416
|
+
if (entry.verificationStatus === 'never-verified') {
|
|
1417
|
+
return [
|
|
1418
|
+
{ code: 'warning_target_never_verified', message: `Runtime target "${entry.name}" has never been verified` },
|
|
1419
|
+
];
|
|
1420
|
+
}
|
|
1421
|
+
return [];
|
|
1422
|
+
}
|
|
1423
|
+
function writeJsonAtomic(filePath, value, mode) {
|
|
1424
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1425
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
1426
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode });
|
|
1427
|
+
fs.renameSync(tempPath, filePath);
|
|
1428
|
+
if (mode !== undefined) {
|
|
1429
|
+
fs.chmodSync(filePath, mode);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
function isRecord(value) {
|
|
1433
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1434
|
+
}
|
|
1435
|
+
function isFileExistsError(error) {
|
|
1436
|
+
return typeof error === 'object' && error !== null && 'code' in error && error.code === 'EEXIST';
|
|
1437
|
+
}
|
|
1438
|
+
function isOwnerOnly(filePath) {
|
|
1439
|
+
if (process.platform === 'win32') {
|
|
1440
|
+
return true;
|
|
1441
|
+
}
|
|
1442
|
+
return (fs.statSync(filePath).mode & 0o077) === 0;
|
|
1443
|
+
}
|
|
1444
|
+
function isControlCodePoint(codePoint) {
|
|
1445
|
+
return codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f);
|
|
1446
|
+
}
|
|
1447
|
+
function hasControlCodePoint(value) {
|
|
1448
|
+
return Array.from(value).some((char) => {
|
|
1449
|
+
const codePoint = char.codePointAt(0);
|
|
1450
|
+
return codePoint === undefined || isControlCodePoint(codePoint);
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
function isSurrogateCodePoint(codePoint) {
|
|
1454
|
+
return codePoint >= 0xd800 && codePoint <= 0xdfff;
|
|
1455
|
+
}
|
|
1456
|
+
//# sourceMappingURL=runtimeTargetStore.js.map
|