@westbayberry/dg 1.3.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -201
- package/NOTICE +1 -4
- package/README.md +293 -0
- package/dist/api/analyze.js +210 -0
- package/dist/audit/deep.js +180 -0
- package/dist/audit/detectors.js +247 -0
- package/dist/audit/events.js +41 -0
- package/dist/audit/rules.js +426 -0
- package/dist/audit-ui/AuditApp.js +39 -0
- package/dist/audit-ui/components/AuditHeader.js +24 -0
- package/dist/audit-ui/components/AuditResultsView.js +307 -0
- package/dist/audit-ui/components/DeepStatusRow.js +11 -0
- package/dist/audit-ui/export.js +85 -0
- package/dist/audit-ui/format.js +34 -0
- package/dist/audit-ui/launch.js +34 -0
- package/dist/auth/device-login.js +271 -0
- package/dist/auth/env-token.js +6 -0
- package/dist/auth/login-app.js +156 -0
- package/dist/auth/store.js +147 -0
- package/dist/bin/dg.js +71 -0
- package/dist/commands/audit.js +357 -0
- package/dist/commands/completion.js +116 -0
- package/dist/commands/config.js +99 -0
- package/dist/commands/doctor.js +39 -0
- package/dist/commands/explain.js +100 -0
- package/dist/commands/guard-commit.js +158 -0
- package/dist/commands/help.js +74 -0
- package/dist/commands/licenses.js +435 -0
- package/dist/commands/login.js +81 -0
- package/dist/commands/logout.js +37 -0
- package/dist/commands/router.js +98 -0
- package/dist/commands/scan.js +18 -0
- package/dist/commands/service.js +475 -0
- package/dist/commands/setup.js +302 -0
- package/dist/commands/status.js +115 -0
- package/dist/commands/suggest.js +35 -0
- package/dist/commands/types.js +4 -0
- package/dist/commands/unavailable.js +11 -0
- package/dist/commands/uninstall.js +111 -0
- package/dist/commands/update.js +210 -0
- package/dist/commands/verify.js +151 -0
- package/dist/commands/version.js +22 -0
- package/dist/commands/wrap.js +55 -0
- package/dist/config/settings.js +302 -0
- package/dist/install-ui/LiveInstall.js +24 -0
- package/dist/install-ui/block-render.js +83 -0
- package/dist/install-ui/live-install-app.js +48 -0
- package/dist/install-ui/prompt.js +24 -0
- package/dist/launcher/classify.js +116 -0
- package/dist/launcher/env.js +53 -0
- package/dist/launcher/live-install.js +50 -0
- package/dist/launcher/output-redaction.js +77 -0
- package/dist/launcher/preflight-prompt.js +139 -0
- package/dist/launcher/resolve-real-binary.js +73 -0
- package/dist/launcher/run.js +417 -0
- package/dist/policy/evaluate.js +128 -0
- package/dist/presentation/mode.js +52 -0
- package/dist/presentation/theme.js +29 -0
- package/dist/proxy/buffer-budget.js +64 -0
- package/dist/proxy/ca.js +126 -0
- package/dist/proxy/classify-host.js +26 -0
- package/dist/proxy/enforcement.js +102 -0
- package/dist/proxy/metadata-map.js +336 -0
- package/dist/proxy/server.js +909 -0
- package/dist/proxy/upstream-proxy.js +102 -0
- package/dist/proxy/worker.js +39 -0
- package/dist/publish-set/collect.js +51 -0
- package/dist/publish-set/no-exec-shell.js +19 -0
- package/dist/publish-set/npm.js +109 -0
- package/dist/publish-set/pack.js +36 -0
- package/dist/publish-set/pypi.js +59 -0
- package/dist/runtime/cli.js +17 -0
- package/dist/runtime/first-run.js +60 -0
- package/dist/runtime/node-version.js +58 -0
- package/dist/runtime/nudges.js +105 -0
- package/dist/scan/analyze-worker.js +21 -0
- package/dist/scan/collect.js +153 -0
- package/dist/scan/command.js +159 -0
- package/dist/scan/discovery.js +209 -0
- package/dist/scan/render.js +240 -0
- package/dist/scan/scanner-report.js +82 -0
- package/dist/scan/staged.js +173 -0
- package/dist/scan/types.js +1 -0
- package/dist/scan-ui/LegacyApp.js +156 -0
- package/dist/scan-ui/alt-screen.js +84 -0
- package/dist/scan-ui/api-aliases.js +1 -0
- package/dist/scan-ui/components/ErrorView.js +23 -0
- package/dist/scan-ui/components/InteractiveResultsView.js +1166 -0
- package/dist/scan-ui/components/ProgressBar.js +89 -0
- package/dist/scan-ui/components/ProjectSelector.js +62 -0
- package/dist/scan-ui/components/ScoreHeader.js +20 -0
- package/dist/scan-ui/components/SetupBanner.js +13 -0
- package/dist/scan-ui/components/Spinner.js +4 -0
- package/dist/scan-ui/format-helpers.js +40 -0
- package/dist/scan-ui/hooks/useExpandAnimation.js +40 -0
- package/dist/scan-ui/hooks/useScan.js +113 -0
- package/dist/scan-ui/hooks/useTerminalSize.js +24 -0
- package/dist/scan-ui/launch.js +27 -0
- package/dist/scan-ui/logo.js +91 -0
- package/dist/scan-ui/shims.js +30 -0
- package/dist/security/sanitize.js +28 -0
- package/dist/service/state.js +837 -0
- package/dist/service/trust-store.js +234 -0
- package/dist/service/worker.js +88 -0
- package/dist/setup/git-hook.js +244 -0
- package/dist/setup/optional-support.js +58 -0
- package/dist/setup/plan.js +899 -0
- package/dist/state/cleanup-registry.js +60 -0
- package/dist/state/index.js +5 -0
- package/dist/state/locks.js +161 -0
- package/dist/state/paths.js +24 -0
- package/dist/state/sessions.js +170 -0
- package/dist/state/store.js +50 -0
- package/dist/telemetry/events.js +40 -0
- package/dist/util/git.js +20 -0
- package/dist/util/tty-prompt.js +43 -0
- package/dist/verify/local.js +400 -0
- package/dist/verify/package-check.js +240 -0
- package/dist/verify/preflight.js +698 -0
- package/dist/verify/render.js +184 -0
- package/dist/verify/types.js +1 -0
- package/package.json +33 -50
- package/dist/index.mjs +0 -54141
- package/dist/postinstall.mjs +0 -731
- package/dist/python-hook/dg_pip_hook.pth +0 -1
- package/dist/python-hook/dg_pip_hook.py +0 -130
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { loadUserConfig } from "../config/settings.js";
|
|
7
|
+
import { applyTrustInstall, applyTrustUninstall, readCertificateFingerprints, readServiceTrustRecord, renderTrustStorePlanLines, resolveTrustInstallPlan, TrustStoreError, writeServiceTrustRecord } from "./trust-store.js";
|
|
8
|
+
import { acquireLockSync, cleanupSessionSync, createSessionSync, resolveDgPaths, CLEANUP_REGISTRY_LOCK } from "../state/index.js";
|
|
9
|
+
export const SERVICE_SENTINEL = "dg-service-mode-v1";
|
|
10
|
+
export const TRUST_SENTINEL = "dg-service-trust-v1";
|
|
11
|
+
export const SERVICE_LOCK = "service-control";
|
|
12
|
+
export const SERVICE_LOCK_STALE_MS = 30 * 60 * 1000;
|
|
13
|
+
const EMPTY_SERVICE_STATE = {
|
|
14
|
+
version: 1,
|
|
15
|
+
configured: false,
|
|
16
|
+
running: false,
|
|
17
|
+
trustInstalled: false,
|
|
18
|
+
trust: undefined,
|
|
19
|
+
trustDrift: undefined,
|
|
20
|
+
proxy: undefined,
|
|
21
|
+
lastError: undefined,
|
|
22
|
+
policySyncedAt: undefined,
|
|
23
|
+
configuredAt: undefined,
|
|
24
|
+
startedAt: undefined,
|
|
25
|
+
stoppedAt: undefined,
|
|
26
|
+
trustInstalledAt: undefined,
|
|
27
|
+
trustUninstalledAt: undefined
|
|
28
|
+
};
|
|
29
|
+
export function resolveServicePaths(env = process.env) {
|
|
30
|
+
const paths = resolveDgPaths(env);
|
|
31
|
+
const serviceDir = join(paths.stateDir, "service");
|
|
32
|
+
return {
|
|
33
|
+
paths,
|
|
34
|
+
serviceDir,
|
|
35
|
+
statePath: join(serviceDir, "service.json"),
|
|
36
|
+
trustRecordPath: join(serviceDir, "trust-store.json"),
|
|
37
|
+
runtimePath: join(serviceDir, "runtime.json"),
|
|
38
|
+
logPath: join(serviceDir, "service.log.jsonl")
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function buildServiceSetupPlan(env) {
|
|
42
|
+
const paths = resolveServicePaths(env);
|
|
43
|
+
return {
|
|
44
|
+
kind: "setup",
|
|
45
|
+
writes: [
|
|
46
|
+
{
|
|
47
|
+
action: "create dg-owned service state directory",
|
|
48
|
+
path: paths.serviceDir
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
action: "record explicit service-mode configuration",
|
|
52
|
+
path: paths.statePath
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
action: "record dg-owned service writes for uninstall",
|
|
56
|
+
path: paths.paths.cleanupRegistryPath
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function buildTrustInstallPlan(env) {
|
|
62
|
+
const paths = resolveServicePaths(env);
|
|
63
|
+
const current = readState(paths);
|
|
64
|
+
const runtime = readRuntime(paths) ?? current.proxy;
|
|
65
|
+
const trustPlan = runtime?.caPath && existsSync(runtime.caPath) ? resolveTrustInstallPlan(runtime.caPath, env) : undefined;
|
|
66
|
+
return {
|
|
67
|
+
kind: "trust-install",
|
|
68
|
+
writes: [
|
|
69
|
+
...renderTrustStorePlanLines(trustPlan).map((line) => ({
|
|
70
|
+
action: line,
|
|
71
|
+
path: trustPlan?.target ?? paths.trustRecordPath
|
|
72
|
+
})),
|
|
73
|
+
{
|
|
74
|
+
action: "write dg-owned managed trust record after trust-store mutation succeeds",
|
|
75
|
+
path: paths.trustRecordPath
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
action: "record trust reversal in cleanup registry",
|
|
79
|
+
path: paths.paths.cleanupRegistryPath
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function buildTrustUninstallPlan(env) {
|
|
85
|
+
const paths = resolveServicePaths(env);
|
|
86
|
+
const record = readServiceTrustRecord(paths.trustRecordPath, TRUST_SENTINEL);
|
|
87
|
+
return {
|
|
88
|
+
kind: "trust-uninstall",
|
|
89
|
+
writes: [
|
|
90
|
+
...(record
|
|
91
|
+
? [
|
|
92
|
+
{
|
|
93
|
+
action: `remove ${record.provider} trust for certificate SHA-256 ${record.fingerprintSha256}`,
|
|
94
|
+
path: record.target
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
: []),
|
|
98
|
+
{
|
|
99
|
+
action: "remove dg-owned managed trust record",
|
|
100
|
+
path: paths.trustRecordPath
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
action: "remove trust cleanup registry entry",
|
|
104
|
+
path: paths.paths.cleanupRegistryPath
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export function buildServiceUninstallPlan(env) {
|
|
110
|
+
const paths = resolveServicePaths(env);
|
|
111
|
+
return {
|
|
112
|
+
kind: "uninstall",
|
|
113
|
+
writes: [
|
|
114
|
+
{
|
|
115
|
+
action: "stop explicit service mode if running",
|
|
116
|
+
path: paths.statePath
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
action: "remove dg-owned managed trust record if present",
|
|
120
|
+
path: paths.trustRecordPath
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
action: "remove dg-owned service state directory",
|
|
124
|
+
path: paths.serviceDir
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
action: "remove service and trust cleanup registry entries",
|
|
128
|
+
path: paths.paths.cleanupRegistryPath
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export function renderServicePlan(title, plan) {
|
|
134
|
+
const lines = [
|
|
135
|
+
title,
|
|
136
|
+
"",
|
|
137
|
+
"No service or trust-store state is changed until this plan is confirmed.",
|
|
138
|
+
...plan.writes.map((write) => `- ${write.action}: ${write.path}`)
|
|
139
|
+
];
|
|
140
|
+
return `${lines.join("\n")}\n`;
|
|
141
|
+
}
|
|
142
|
+
export function readServiceState(env) {
|
|
143
|
+
const paths = resolveServicePaths(env);
|
|
144
|
+
const state = applyRuntimeDiagnostics(paths, readState(paths));
|
|
145
|
+
return {
|
|
146
|
+
paths,
|
|
147
|
+
state,
|
|
148
|
+
changed: false
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export function configureService(env, now = new Date()) {
|
|
152
|
+
const paths = resolveServicePaths(env);
|
|
153
|
+
return withServiceLock(paths, () => {
|
|
154
|
+
const current = readState(paths);
|
|
155
|
+
const state = {
|
|
156
|
+
...current,
|
|
157
|
+
configured: true,
|
|
158
|
+
configuredAt: current.configuredAt ?? now.toISOString(),
|
|
159
|
+
policySyncedAt: now.toISOString()
|
|
160
|
+
};
|
|
161
|
+
writeState(paths, state);
|
|
162
|
+
writeLog(paths, "service.configured", now);
|
|
163
|
+
withRegistryLock(paths.paths, () => {
|
|
164
|
+
writeRegistry(paths.paths, mergeRegistry(readRegistry(paths.paths), [registryEntry("service", paths.statePath, SERVICE_SENTINEL, now)]));
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
paths,
|
|
168
|
+
state,
|
|
169
|
+
changed: !current.configured
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
export function startService(env, now = new Date()) {
|
|
174
|
+
const paths = resolveServicePaths(env);
|
|
175
|
+
return withServiceLock(paths, () => {
|
|
176
|
+
const current = readState(paths);
|
|
177
|
+
if (!current.configured) {
|
|
178
|
+
throw new ServiceNotConfiguredError();
|
|
179
|
+
}
|
|
180
|
+
const existingRuntime = readRuntime(paths);
|
|
181
|
+
if (current.running && existingRuntime && runtimeHealth(existingRuntime).healthy) {
|
|
182
|
+
const state = {
|
|
183
|
+
...current,
|
|
184
|
+
running: true,
|
|
185
|
+
proxy: existingRuntime,
|
|
186
|
+
trustDrift: detectTrustDrift(current.trust, existingRuntime),
|
|
187
|
+
lastError: undefined
|
|
188
|
+
};
|
|
189
|
+
writeState(paths, state);
|
|
190
|
+
writeLog(paths, "service.start.noop", now);
|
|
191
|
+
return {
|
|
192
|
+
paths,
|
|
193
|
+
state,
|
|
194
|
+
changed: false
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
cleanupRuntime(paths, existingRuntime ?? current.proxy);
|
|
198
|
+
const runtime = startServiceProxyRuntime(paths, env ?? process.env, now);
|
|
199
|
+
const trustRemoval = removeTrustRecordIfDrifted(paths, current.trust, runtime, now);
|
|
200
|
+
const state = {
|
|
201
|
+
...current,
|
|
202
|
+
running: true,
|
|
203
|
+
startedAt: current.running ? current.startedAt : now.toISOString(),
|
|
204
|
+
policySyncedAt: now.toISOString(),
|
|
205
|
+
proxy: runtime,
|
|
206
|
+
trustInstalled: trustRemoval.removed ? false : current.trustInstalled,
|
|
207
|
+
trust: trustRemoval.removed ? undefined : current.trust,
|
|
208
|
+
trustDrift: undefined,
|
|
209
|
+
trustUninstalledAt: trustRemoval.removed ? now.toISOString() : current.trustUninstalledAt,
|
|
210
|
+
lastError: trustRemoval.message
|
|
211
|
+
};
|
|
212
|
+
writeState(paths, state);
|
|
213
|
+
writeLog(paths, "service.started", now);
|
|
214
|
+
return {
|
|
215
|
+
paths,
|
|
216
|
+
state,
|
|
217
|
+
changed: !current.running
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
export function stopService(env, now = new Date()) {
|
|
222
|
+
const paths = resolveServicePaths(env);
|
|
223
|
+
return withServiceLock(paths, () => {
|
|
224
|
+
const current = readState(paths);
|
|
225
|
+
if (!current.configured) {
|
|
226
|
+
return {
|
|
227
|
+
paths,
|
|
228
|
+
state: current,
|
|
229
|
+
changed: false
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const state = {
|
|
233
|
+
...current,
|
|
234
|
+
running: false,
|
|
235
|
+
stoppedAt: current.running ? now.toISOString() : current.stoppedAt,
|
|
236
|
+
proxy: undefined,
|
|
237
|
+
lastError: undefined
|
|
238
|
+
};
|
|
239
|
+
cleanupRuntime(paths, readRuntime(paths) ?? current.proxy);
|
|
240
|
+
writeState(paths, state);
|
|
241
|
+
writeLog(paths, current.running ? "service.stopped" : "service.stop.noop", now);
|
|
242
|
+
return {
|
|
243
|
+
paths,
|
|
244
|
+
state,
|
|
245
|
+
changed: current.running
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
export function restartService(env, now = new Date()) {
|
|
250
|
+
const paths = resolveServicePaths(env);
|
|
251
|
+
return withServiceLock(paths, () => {
|
|
252
|
+
const current = readState(paths);
|
|
253
|
+
if (!current.configured) {
|
|
254
|
+
throw new ServiceNotConfiguredError();
|
|
255
|
+
}
|
|
256
|
+
cleanupRuntime(paths, readRuntime(paths) ?? current.proxy);
|
|
257
|
+
const runtime = startServiceProxyRuntime(paths, env ?? process.env, now);
|
|
258
|
+
const trustRemoval = removeTrustRecordIfDrifted(paths, current.trust, runtime, now);
|
|
259
|
+
const state = {
|
|
260
|
+
...current,
|
|
261
|
+
running: true,
|
|
262
|
+
startedAt: now.toISOString(),
|
|
263
|
+
stoppedAt: current.running ? now.toISOString() : current.stoppedAt,
|
|
264
|
+
policySyncedAt: now.toISOString(),
|
|
265
|
+
proxy: runtime,
|
|
266
|
+
trustInstalled: trustRemoval.removed ? false : current.trustInstalled,
|
|
267
|
+
trust: trustRemoval.removed ? undefined : current.trust,
|
|
268
|
+
trustDrift: undefined,
|
|
269
|
+
trustUninstalledAt: trustRemoval.removed ? now.toISOString() : current.trustUninstalledAt,
|
|
270
|
+
lastError: trustRemoval.message
|
|
271
|
+
};
|
|
272
|
+
writeState(paths, state);
|
|
273
|
+
writeLog(paths, "service.restarted", now);
|
|
274
|
+
return {
|
|
275
|
+
paths,
|
|
276
|
+
state,
|
|
277
|
+
changed: true
|
|
278
|
+
};
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
export function installServiceTrust(env, now = new Date()) {
|
|
282
|
+
const paths = resolveServicePaths(env);
|
|
283
|
+
return withServiceLock(paths, () => {
|
|
284
|
+
const current = readState(paths);
|
|
285
|
+
if (!current.configured) {
|
|
286
|
+
throw new ServiceNotConfiguredError();
|
|
287
|
+
}
|
|
288
|
+
const runtime = readRuntime(paths) ?? current.proxy;
|
|
289
|
+
if (!runtime?.caPath || !existsSync(runtime.caPath)) {
|
|
290
|
+
throw new ServiceTrustStoreError("dg service trust install requires a running service proxy with an active CA certificate. Run 'dg service start' first.");
|
|
291
|
+
}
|
|
292
|
+
const plan = trustStoreOperation(() => resolveTrustInstallPlan(runtime.caPath, env ?? process.env));
|
|
293
|
+
const existing = readServiceTrustRecord(paths.trustRecordPath, TRUST_SENTINEL);
|
|
294
|
+
if (existing && existing.fingerprintSha256 === plan.fingerprintSha256 && existing.provider === plan.provider && existing.target === plan.target) {
|
|
295
|
+
const state = {
|
|
296
|
+
...current,
|
|
297
|
+
trustInstalled: true,
|
|
298
|
+
trust: existing,
|
|
299
|
+
trustDrift: undefined,
|
|
300
|
+
trustInstalledAt: current.trustInstalledAt ?? existing.installedAt
|
|
301
|
+
};
|
|
302
|
+
writeState(paths, state);
|
|
303
|
+
writeLog(paths, "service.trust.install.noop", now);
|
|
304
|
+
return {
|
|
305
|
+
paths,
|
|
306
|
+
state,
|
|
307
|
+
changed: false
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (existing) {
|
|
311
|
+
trustStoreOperation(() => applyTrustUninstall(existing));
|
|
312
|
+
}
|
|
313
|
+
const record = trustStoreOperation(() => applyTrustInstall(plan, now, TRUST_SENTINEL));
|
|
314
|
+
const state = {
|
|
315
|
+
...current,
|
|
316
|
+
trustInstalled: true,
|
|
317
|
+
trust: record,
|
|
318
|
+
trustDrift: undefined,
|
|
319
|
+
trustInstalledAt: current.trustInstalledAt ?? now.toISOString()
|
|
320
|
+
};
|
|
321
|
+
writeServiceTrustRecord(paths.trustRecordPath, record);
|
|
322
|
+
writeState(paths, state);
|
|
323
|
+
writeLog(paths, "service.trust.installed", now);
|
|
324
|
+
withRegistryLock(paths.paths, () => {
|
|
325
|
+
writeRegistry(paths.paths, mergeRegistry(readRegistry(paths.paths), [registryEntry("trust-store", paths.trustRecordPath, TRUST_SENTINEL, now)]));
|
|
326
|
+
});
|
|
327
|
+
return {
|
|
328
|
+
paths,
|
|
329
|
+
state,
|
|
330
|
+
changed: !current.trustInstalled
|
|
331
|
+
};
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
export function uninstallServiceTrust(env, now = new Date()) {
|
|
335
|
+
const paths = resolveServicePaths(env);
|
|
336
|
+
return withServiceLock(paths, () => {
|
|
337
|
+
const current = readState(paths);
|
|
338
|
+
const record = readServiceTrustRecord(paths.trustRecordPath, TRUST_SENTINEL);
|
|
339
|
+
if (record) {
|
|
340
|
+
trustStoreOperation(() => applyTrustUninstall(record));
|
|
341
|
+
}
|
|
342
|
+
const removedTrust = removeFile(paths.trustRecordPath);
|
|
343
|
+
if (!current.configured && !removedTrust) {
|
|
344
|
+
return {
|
|
345
|
+
paths,
|
|
346
|
+
state: current,
|
|
347
|
+
changed: false
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
const state = {
|
|
351
|
+
...current,
|
|
352
|
+
trustInstalled: false,
|
|
353
|
+
trust: undefined,
|
|
354
|
+
trustDrift: undefined,
|
|
355
|
+
trustUninstalledAt: current.trustInstalled || removedTrust ? now.toISOString() : current.trustUninstalledAt
|
|
356
|
+
};
|
|
357
|
+
writeState(paths, state);
|
|
358
|
+
writeLog(paths, current.trustInstalled || removedTrust ? "service.trust.uninstalled" : "service.trust.uninstall.noop", now);
|
|
359
|
+
withRegistryLock(paths.paths, () => {
|
|
360
|
+
writeRegistryIfChanged(paths.paths, readRegistry(paths.paths), [{ kind: "trust-store", path: paths.trustRecordPath }]);
|
|
361
|
+
});
|
|
362
|
+
return {
|
|
363
|
+
paths,
|
|
364
|
+
state,
|
|
365
|
+
changed: current.trustInstalled || removedTrust
|
|
366
|
+
};
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
export function uninstallService(env, now = new Date()) {
|
|
370
|
+
const paths = resolveServicePaths(env);
|
|
371
|
+
return withServiceLock(paths, () => {
|
|
372
|
+
const current = readState(paths);
|
|
373
|
+
const removed = [];
|
|
374
|
+
const missing = [];
|
|
375
|
+
cleanupRuntime(paths, readRuntime(paths) ?? current.proxy);
|
|
376
|
+
const record = readServiceTrustRecord(paths.trustRecordPath, TRUST_SENTINEL);
|
|
377
|
+
if (record) {
|
|
378
|
+
trustStoreOperation(() => applyTrustUninstall(record));
|
|
379
|
+
}
|
|
380
|
+
if (existsSync(paths.trustRecordPath)) {
|
|
381
|
+
rmSync(paths.trustRecordPath, {
|
|
382
|
+
force: true
|
|
383
|
+
});
|
|
384
|
+
removed.push(paths.trustRecordPath);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
missing.push(paths.trustRecordPath);
|
|
388
|
+
}
|
|
389
|
+
if (existsSync(paths.serviceDir)) {
|
|
390
|
+
rmSync(paths.serviceDir, {
|
|
391
|
+
force: true,
|
|
392
|
+
recursive: true
|
|
393
|
+
});
|
|
394
|
+
removed.push(paths.serviceDir);
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
missing.push(paths.serviceDir);
|
|
398
|
+
}
|
|
399
|
+
withRegistryLock(paths.paths, () => {
|
|
400
|
+
writeRegistryIfChanged(paths.paths, readRegistry(paths.paths), [
|
|
401
|
+
{
|
|
402
|
+
kind: "service",
|
|
403
|
+
path: paths.statePath
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
kind: "trust-store",
|
|
407
|
+
path: paths.trustRecordPath
|
|
408
|
+
}
|
|
409
|
+
]);
|
|
410
|
+
});
|
|
411
|
+
return {
|
|
412
|
+
removed,
|
|
413
|
+
missing,
|
|
414
|
+
state: {
|
|
415
|
+
...current,
|
|
416
|
+
configured: false,
|
|
417
|
+
running: false,
|
|
418
|
+
trustInstalled: false,
|
|
419
|
+
trust: undefined,
|
|
420
|
+
trustDrift: undefined,
|
|
421
|
+
proxy: undefined,
|
|
422
|
+
lastError: undefined,
|
|
423
|
+
stoppedAt: current.running ? now.toISOString() : current.stoppedAt
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
export class ServiceNotConfiguredError extends Error {
|
|
429
|
+
constructor() {
|
|
430
|
+
super("Service mode is not configured");
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
export class ServiceTrustStoreError extends Error {
|
|
434
|
+
constructor(message) {
|
|
435
|
+
super(message);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
export class ServiceProxyError extends Error {
|
|
439
|
+
constructor(message) {
|
|
440
|
+
super(message);
|
|
441
|
+
this.name = "ServiceProxyError";
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function trustStoreOperation(operation) {
|
|
445
|
+
try {
|
|
446
|
+
return operation();
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
if (error instanceof TrustStoreError) {
|
|
450
|
+
throw new ServiceTrustStoreError(error.message);
|
|
451
|
+
}
|
|
452
|
+
throw error;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function withServiceLock(paths, run) {
|
|
456
|
+
const lock = acquireLockSync(paths.paths, SERVICE_LOCK, {
|
|
457
|
+
staleMs: SERVICE_LOCK_STALE_MS
|
|
458
|
+
});
|
|
459
|
+
try {
|
|
460
|
+
return run();
|
|
461
|
+
}
|
|
462
|
+
finally {
|
|
463
|
+
lock.release();
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function withRegistryLock(paths, run) {
|
|
467
|
+
const lock = acquireLockSync(paths, CLEANUP_REGISTRY_LOCK, {
|
|
468
|
+
staleMs: SERVICE_LOCK_STALE_MS
|
|
469
|
+
});
|
|
470
|
+
try {
|
|
471
|
+
return run();
|
|
472
|
+
}
|
|
473
|
+
finally {
|
|
474
|
+
lock.release();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
function readState(paths) {
|
|
478
|
+
try {
|
|
479
|
+
if (!existsSync(paths.statePath)) {
|
|
480
|
+
return EMPTY_SERVICE_STATE;
|
|
481
|
+
}
|
|
482
|
+
const parsed = JSON.parse(readFileSync(paths.statePath, "utf8"));
|
|
483
|
+
if (parsed.version !== 1) {
|
|
484
|
+
return EMPTY_SERVICE_STATE;
|
|
485
|
+
}
|
|
486
|
+
const trust = readServiceTrustRecord(paths.trustRecordPath, TRUST_SENTINEL);
|
|
487
|
+
return {
|
|
488
|
+
version: 1,
|
|
489
|
+
configured: parsed.configured === true,
|
|
490
|
+
running: parsed.running === true,
|
|
491
|
+
trustInstalled: parsed.trustInstalled === true && trust !== undefined,
|
|
492
|
+
trust,
|
|
493
|
+
trustDrift: undefined,
|
|
494
|
+
proxy: serviceProxyRuntime(parsed.proxy),
|
|
495
|
+
lastError: typeof parsed.lastError === "string" ? parsed.lastError : undefined,
|
|
496
|
+
policySyncedAt: parsed.policySyncedAt,
|
|
497
|
+
configuredAt: parsed.configuredAt,
|
|
498
|
+
startedAt: parsed.startedAt,
|
|
499
|
+
stoppedAt: parsed.stoppedAt,
|
|
500
|
+
trustInstalledAt: parsed.trustInstalledAt,
|
|
501
|
+
trustUninstalledAt: parsed.trustUninstalledAt
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
return EMPTY_SERVICE_STATE;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function applyRuntimeDiagnostics(paths, state) {
|
|
509
|
+
const runtime = readRuntime(paths) ?? state.proxy;
|
|
510
|
+
if (!runtime) {
|
|
511
|
+
return state;
|
|
512
|
+
}
|
|
513
|
+
const drift = detectTrustDrift(state.trust, runtime);
|
|
514
|
+
if (!state.running) {
|
|
515
|
+
return {
|
|
516
|
+
...state,
|
|
517
|
+
proxy: runtime,
|
|
518
|
+
trustDrift: drift,
|
|
519
|
+
lastError: "stale service runtime state: runtime file exists while service is stopped"
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
const health = runtimeHealth(runtime);
|
|
523
|
+
if (!health.healthy) {
|
|
524
|
+
return {
|
|
525
|
+
...state,
|
|
526
|
+
running: false,
|
|
527
|
+
proxy: runtime,
|
|
528
|
+
trustDrift: drift,
|
|
529
|
+
lastError: `stale service runtime state: ${health.reason}`
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
...state,
|
|
534
|
+
proxy: runtime,
|
|
535
|
+
trustDrift: drift
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function runtimeHealth(runtime) {
|
|
539
|
+
if (!processIsAlive(runtime.pid)) {
|
|
540
|
+
return {
|
|
541
|
+
healthy: false,
|
|
542
|
+
reason: `recorded service worker pid ${runtime.pid} is not running`
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
if (!healthEndpointReachable(runtime.healthUrl)) {
|
|
546
|
+
return {
|
|
547
|
+
healthy: false,
|
|
548
|
+
reason: `health endpoint is unreachable at ${runtime.healthUrl}`
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
healthy: true,
|
|
553
|
+
reason: undefined
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function healthEndpointReachable(healthUrl) {
|
|
557
|
+
const script = `
|
|
558
|
+
const http = require("node:http");
|
|
559
|
+
const https = require("node:https");
|
|
560
|
+
const target = process.argv[1];
|
|
561
|
+
const client = target.startsWith("https:") ? https : http;
|
|
562
|
+
const request = client.get(target, { timeout: 500 }, (response) => {
|
|
563
|
+
response.resume();
|
|
564
|
+
response.on("end", () => process.exit(response.statusCode === 200 ? 0 : 1));
|
|
565
|
+
});
|
|
566
|
+
request.on("timeout", () => request.destroy(new Error("timeout")));
|
|
567
|
+
request.on("error", () => process.exit(1));
|
|
568
|
+
`;
|
|
569
|
+
const result = spawnSync(process.execPath, ["-e", script, healthUrl], {
|
|
570
|
+
stdio: "ignore",
|
|
571
|
+
timeout: 1_000
|
|
572
|
+
});
|
|
573
|
+
return result.status === 0;
|
|
574
|
+
}
|
|
575
|
+
function detectTrustDrift(trust, runtime) {
|
|
576
|
+
if (!trust || !runtime?.caPath || !existsSync(runtime.caPath)) {
|
|
577
|
+
return undefined;
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
const active = readCertificateFingerprints(runtime.caPath);
|
|
581
|
+
if (active.fingerprintSha256 === trust.fingerprintSha256) {
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
584
|
+
return {
|
|
585
|
+
installedFingerprintSha256: trust.fingerprintSha256,
|
|
586
|
+
activeFingerprintSha256: active.fingerprintSha256,
|
|
587
|
+
message: `Installed service trust fingerprint ${trust.fingerprintSha256} does not match active service CA fingerprint ${active.fingerprintSha256}. Run dg service trust install --yes to trust the active CA.`
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
catch {
|
|
591
|
+
return {
|
|
592
|
+
installedFingerprintSha256: trust.fingerprintSha256,
|
|
593
|
+
activeFingerprintSha256: undefined,
|
|
594
|
+
message: `Installed service trust fingerprint ${trust.fingerprintSha256} cannot be compared because the active service CA is unreadable. Run dg service restart before reinstalling trust.`
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function removeTrustRecordIfDrifted(paths, trust, runtime, now) {
|
|
599
|
+
const drift = detectTrustDrift(trust, runtime);
|
|
600
|
+
if (!trust || !drift) {
|
|
601
|
+
return {
|
|
602
|
+
removed: false,
|
|
603
|
+
message: undefined
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
trustStoreOperation(() => applyTrustUninstall(trust));
|
|
607
|
+
removeFile(paths.trustRecordPath);
|
|
608
|
+
withRegistryLock(paths.paths, () => {
|
|
609
|
+
writeRegistryIfChanged(paths.paths, readRegistry(paths.paths), [{ kind: "trust-store", path: paths.trustRecordPath }]);
|
|
610
|
+
});
|
|
611
|
+
writeLog(paths, "service.trust.removed-after-ca-drift", now);
|
|
612
|
+
return {
|
|
613
|
+
removed: true,
|
|
614
|
+
message: `${drift.message} The stale dg-owned trust record was removed during service restart.`
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
function startServiceProxyRuntime(paths, env, now) {
|
|
618
|
+
const workerPath = env.DG_SERVICE_WORKER_PATH ?? fileURLToPath(new URL("./worker.js", import.meta.url));
|
|
619
|
+
if (!existsSync(workerPath)) {
|
|
620
|
+
throw new ServiceProxyError("service proxy worker is unavailable until the CLI package is built");
|
|
621
|
+
}
|
|
622
|
+
const session = createSessionSync(paths.paths);
|
|
623
|
+
const sessionBootstrapPath = join(session.dir, "session.json");
|
|
624
|
+
writeFileSync(sessionBootstrapPath, `${JSON.stringify(session)}\n`, {
|
|
625
|
+
encoding: "utf8",
|
|
626
|
+
mode: 0o600
|
|
627
|
+
});
|
|
628
|
+
const config = loadUserConfig(env);
|
|
629
|
+
const child = spawn(process.execPath, [workerPath, sessionBootstrapPath, config.api.baseUrl, paths.runtimePath], {
|
|
630
|
+
detached: true,
|
|
631
|
+
env: {
|
|
632
|
+
...env,
|
|
633
|
+
DG_SERVICE_CLASSIFICATION: JSON.stringify({
|
|
634
|
+
kind: "protected",
|
|
635
|
+
manager: "npm",
|
|
636
|
+
realBinaryName: "npm",
|
|
637
|
+
action: "service-proxy",
|
|
638
|
+
args: []
|
|
639
|
+
})
|
|
640
|
+
},
|
|
641
|
+
stdio: "ignore"
|
|
642
|
+
});
|
|
643
|
+
child.unref();
|
|
644
|
+
const runtime = waitForRuntime(paths, child.pid);
|
|
645
|
+
if (!runtime) {
|
|
646
|
+
if (child.pid) {
|
|
647
|
+
killProcess(child.pid);
|
|
648
|
+
}
|
|
649
|
+
cleanupSessionSync(session);
|
|
650
|
+
throw new ServiceProxyError("service proxy worker did not become healthy");
|
|
651
|
+
}
|
|
652
|
+
void now;
|
|
653
|
+
return runtime;
|
|
654
|
+
}
|
|
655
|
+
function waitForRuntime(paths, pid) {
|
|
656
|
+
const deadline = Date.now() + 2_000;
|
|
657
|
+
while (Date.now() < deadline) {
|
|
658
|
+
const runtime = readRuntime(paths);
|
|
659
|
+
if (runtime && (!pid || runtime.pid === pid) && runtime.proxyUrl && runtime.healthUrl) {
|
|
660
|
+
return runtime;
|
|
661
|
+
}
|
|
662
|
+
sleep(25);
|
|
663
|
+
}
|
|
664
|
+
return undefined;
|
|
665
|
+
}
|
|
666
|
+
function readRuntime(paths) {
|
|
667
|
+
try {
|
|
668
|
+
if (!existsSync(paths.runtimePath)) {
|
|
669
|
+
return undefined;
|
|
670
|
+
}
|
|
671
|
+
const parsed = JSON.parse(readFileSync(paths.runtimePath, "utf8"));
|
|
672
|
+
return serviceProxyRuntime(parsed);
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
return undefined;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function serviceProxyRuntime(value) {
|
|
679
|
+
if (typeof value !== "object" || value === null) {
|
|
680
|
+
return undefined;
|
|
681
|
+
}
|
|
682
|
+
const runtime = value;
|
|
683
|
+
if (typeof runtime.pid !== "number" ||
|
|
684
|
+
typeof runtime.proxyUrl !== "string" ||
|
|
685
|
+
typeof runtime.healthUrl !== "string" ||
|
|
686
|
+
typeof runtime.sessionDir !== "string" ||
|
|
687
|
+
typeof runtime.caPath !== "string" ||
|
|
688
|
+
typeof runtime.startedAt !== "string") {
|
|
689
|
+
return undefined;
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
pid: runtime.pid,
|
|
693
|
+
proxyUrl: runtime.proxyUrl,
|
|
694
|
+
healthUrl: runtime.healthUrl,
|
|
695
|
+
sessionDir: runtime.sessionDir,
|
|
696
|
+
caPath: runtime.caPath,
|
|
697
|
+
startedAt: runtime.startedAt
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function cleanupRuntime(paths, runtime) {
|
|
701
|
+
if (runtime?.pid && processIsAlive(runtime.pid)) {
|
|
702
|
+
killProcess(runtime.pid);
|
|
703
|
+
}
|
|
704
|
+
if (runtime?.sessionDir && runtime.sessionDir.startsWith(paths.paths.sessionsDir)) {
|
|
705
|
+
rmSync(runtime.sessionDir, {
|
|
706
|
+
force: true,
|
|
707
|
+
recursive: true
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
removeFile(paths.runtimePath);
|
|
711
|
+
}
|
|
712
|
+
function processIsAlive(pid) {
|
|
713
|
+
if (pid <= 0) {
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
try {
|
|
717
|
+
process.kill(pid, 0);
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function killProcess(pid) {
|
|
725
|
+
try {
|
|
726
|
+
process.kill(pid, "SIGTERM");
|
|
727
|
+
}
|
|
728
|
+
catch {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function sleep(ms) {
|
|
733
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
734
|
+
}
|
|
735
|
+
function writeJsonAtomicSync(path, value) {
|
|
736
|
+
mkdirSync(dirname(path), {
|
|
737
|
+
recursive: true,
|
|
738
|
+
mode: 0o700
|
|
739
|
+
});
|
|
740
|
+
const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
741
|
+
try {
|
|
742
|
+
writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, {
|
|
743
|
+
encoding: "utf8",
|
|
744
|
+
flag: "wx",
|
|
745
|
+
mode: 0o600
|
|
746
|
+
});
|
|
747
|
+
renameSync(tempPath, path);
|
|
748
|
+
}
|
|
749
|
+
catch (error) {
|
|
750
|
+
rmSync(tempPath, {
|
|
751
|
+
force: true
|
|
752
|
+
});
|
|
753
|
+
throw error;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function writeState(paths, state) {
|
|
757
|
+
writeJsonAtomicSync(paths.statePath, state);
|
|
758
|
+
}
|
|
759
|
+
function writeLog(paths, event, now) {
|
|
760
|
+
mkdirSync(dirname(paths.logPath), {
|
|
761
|
+
recursive: true,
|
|
762
|
+
mode: 0o700
|
|
763
|
+
});
|
|
764
|
+
writeFileSync(paths.logPath, `${JSON.stringify({ event, at: now.toISOString() })}\n`, {
|
|
765
|
+
encoding: "utf8",
|
|
766
|
+
flag: "a",
|
|
767
|
+
mode: 0o600
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
function readRegistry(paths) {
|
|
771
|
+
try {
|
|
772
|
+
if (!existsSync(paths.cleanupRegistryPath)) {
|
|
773
|
+
return {
|
|
774
|
+
version: 1,
|
|
775
|
+
entries: []
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
const parsed = JSON.parse(readFileSync(paths.cleanupRegistryPath, "utf8"));
|
|
779
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.entries)) {
|
|
780
|
+
throw new Error("unsupported cleanup registry");
|
|
781
|
+
}
|
|
782
|
+
return parsed;
|
|
783
|
+
}
|
|
784
|
+
catch {
|
|
785
|
+
return {
|
|
786
|
+
version: 1,
|
|
787
|
+
entries: []
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
function writeRegistry(paths, registry) {
|
|
792
|
+
writeJsonAtomicSync(paths.cleanupRegistryPath, registry);
|
|
793
|
+
}
|
|
794
|
+
function writeRegistryIfChanged(paths, registry, targets) {
|
|
795
|
+
const next = removeRegistryTargets(registry, targets);
|
|
796
|
+
if (!existsSync(paths.cleanupRegistryPath) && registry.entries.length === next.entries.length) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (registry.entries.length === next.entries.length) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
writeRegistry(paths, next);
|
|
803
|
+
}
|
|
804
|
+
function registryEntry(kind, path, sentinel, now) {
|
|
805
|
+
return {
|
|
806
|
+
kind,
|
|
807
|
+
path,
|
|
808
|
+
mode: "mode2",
|
|
809
|
+
sentinel,
|
|
810
|
+
installedAt: now.toISOString(),
|
|
811
|
+
owner: "dg"
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function mergeRegistry(registry, entries) {
|
|
815
|
+
return {
|
|
816
|
+
version: 1,
|
|
817
|
+
entries: [
|
|
818
|
+
...registry.entries.filter((entry) => !entries.some((next) => entry.kind === next.kind && entry.path === next.path && entry.sentinel === next.sentinel)),
|
|
819
|
+
...entries
|
|
820
|
+
]
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
function removeRegistryTargets(registry, targets) {
|
|
824
|
+
return {
|
|
825
|
+
version: 1,
|
|
826
|
+
entries: registry.entries.filter((entry) => !targets.some((target) => entry.kind === target.kind && entry.path === target.path))
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
function removeFile(path) {
|
|
830
|
+
if (!existsSync(path)) {
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
rmSync(path, {
|
|
834
|
+
force: true
|
|
835
|
+
});
|
|
836
|
+
return true;
|
|
837
|
+
}
|