@westbayberry/dg 1.3.3 → 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 -54116
- 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,60 @@
|
|
|
1
|
+
import { readJsonFile, writeJsonFileAtomic } from "./store.js";
|
|
2
|
+
import { acquireLock, CLEANUP_REGISTRY_LOCK } from "./locks.js";
|
|
3
|
+
export function emptyCleanupRegistry() {
|
|
4
|
+
return {
|
|
5
|
+
version: 1,
|
|
6
|
+
entries: []
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export async function readCleanupRegistry(paths) {
|
|
10
|
+
const registry = await readJsonFile(paths.cleanupRegistryPath, emptyCleanupRegistry());
|
|
11
|
+
if (registry.version !== 1 || !Array.isArray(registry.entries)) {
|
|
12
|
+
throw new Error(`Unsupported cleanup registry at ${paths.cleanupRegistryPath}`);
|
|
13
|
+
}
|
|
14
|
+
return registry;
|
|
15
|
+
}
|
|
16
|
+
export async function writeCleanupRegistry(paths, registry) {
|
|
17
|
+
await writeJsonFileAtomic(paths.cleanupRegistryPath, registry);
|
|
18
|
+
}
|
|
19
|
+
export async function recordCleanupEntry(paths, entry) {
|
|
20
|
+
const lock = await acquireLock(paths, CLEANUP_REGISTRY_LOCK);
|
|
21
|
+
try {
|
|
22
|
+
const registry = await readCleanupRegistry(paths);
|
|
23
|
+
const nextEntry = {
|
|
24
|
+
...entry,
|
|
25
|
+
installedAt: entry.installedAt ?? new Date().toISOString(),
|
|
26
|
+
owner: "dg"
|
|
27
|
+
};
|
|
28
|
+
const entries = registry.entries.filter((candidate) => !sameRegistryTarget(candidate, nextEntry));
|
|
29
|
+
const next = {
|
|
30
|
+
version: 1,
|
|
31
|
+
entries: [...entries, nextEntry]
|
|
32
|
+
};
|
|
33
|
+
await writeCleanupRegistry(paths, next);
|
|
34
|
+
return next;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await lock.release();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function removeCleanupEntry(paths, target) {
|
|
41
|
+
const lock = await acquireLock(paths, CLEANUP_REGISTRY_LOCK);
|
|
42
|
+
try {
|
|
43
|
+
const registry = await readCleanupRegistry(paths);
|
|
44
|
+
const next = {
|
|
45
|
+
version: 1,
|
|
46
|
+
entries: registry.entries.filter((candidate) => !sameRegistryTarget(candidate, target))
|
|
47
|
+
};
|
|
48
|
+
await writeCleanupRegistry(paths, next);
|
|
49
|
+
return next;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
await lock.release();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function ownedCleanupEntries(registry) {
|
|
56
|
+
return registry.entries.filter((entry) => entry.owner === "dg");
|
|
57
|
+
}
|
|
58
|
+
function sameRegistryTarget(left, right) {
|
|
59
|
+
return left.kind === right.kind && left.path === right.path && left.sentinel === right.sentinel;
|
|
60
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { closeSync, mkdirSync, openSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdir, open, readFile, rename, rm, stat } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export const CLEANUP_REGISTRY_LOCK = "cleanup-registry";
|
|
5
|
+
let takeoverCounter = 0;
|
|
6
|
+
function isErrno(error, code) {
|
|
7
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
8
|
+
}
|
|
9
|
+
export class LockBusyError extends Error {
|
|
10
|
+
path;
|
|
11
|
+
constructor(path) {
|
|
12
|
+
super(`dg lock is already held: ${path}`);
|
|
13
|
+
this.path = path;
|
|
14
|
+
this.name = "LockBusyError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function acquireLock(paths, name, options = {}) {
|
|
18
|
+
assertLockName(name);
|
|
19
|
+
await mkdir(paths.locksDir, {
|
|
20
|
+
recursive: true
|
|
21
|
+
});
|
|
22
|
+
const path = join(paths.locksDir, `${name}.lock`);
|
|
23
|
+
await removeStaleLock(path, options);
|
|
24
|
+
let handle;
|
|
25
|
+
try {
|
|
26
|
+
handle = await open(path, "wx", 0o600);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (isErrno(error, "EEXIST")) {
|
|
30
|
+
throw new LockBusyError(path);
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
const metadata = {
|
|
35
|
+
pid: process.pid,
|
|
36
|
+
acquiredAt: (options.now ?? new Date()).toISOString()
|
|
37
|
+
};
|
|
38
|
+
try {
|
|
39
|
+
await handle.writeFile(`${JSON.stringify(metadata)}\n`, "utf8");
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await handle.close();
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
name,
|
|
46
|
+
path,
|
|
47
|
+
release: async () => {
|
|
48
|
+
await rm(path, {
|
|
49
|
+
force: true
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function acquireLockSync(paths, name, options = {}) {
|
|
55
|
+
assertLockName(name);
|
|
56
|
+
mkdirSync(paths.locksDir, {
|
|
57
|
+
recursive: true
|
|
58
|
+
});
|
|
59
|
+
const path = join(paths.locksDir, `${name}.lock`);
|
|
60
|
+
removeStaleLockSync(path, options);
|
|
61
|
+
let fd;
|
|
62
|
+
try {
|
|
63
|
+
fd = openSync(path, "wx", 0o600);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (isErrno(error, "EEXIST")) {
|
|
67
|
+
throw new LockBusyError(path);
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
const metadata = {
|
|
72
|
+
pid: process.pid,
|
|
73
|
+
acquiredAt: (options.now ?? new Date()).toISOString()
|
|
74
|
+
};
|
|
75
|
+
try {
|
|
76
|
+
writeFileSync(fd, `${JSON.stringify(metadata)}\n`, "utf8");
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
closeSync(fd);
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
name,
|
|
83
|
+
path,
|
|
84
|
+
release: () => {
|
|
85
|
+
rmSync(path, {
|
|
86
|
+
force: true
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export async function readLockMetadata(path) {
|
|
92
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
93
|
+
}
|
|
94
|
+
async function removeStaleLock(path, options) {
|
|
95
|
+
if (!options.staleMs) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const details = await stat(path).catch((error) => {
|
|
99
|
+
if (isErrno(error, "ENOENT")) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
});
|
|
104
|
+
if (!details) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const now = options.now?.getTime() ?? Date.now();
|
|
108
|
+
if (now - details.mtimeMs < options.staleMs) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const takeoverPath = `${path}.stale-${process.pid}-${++takeoverCounter}`;
|
|
112
|
+
try {
|
|
113
|
+
await rename(path, takeoverPath);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (isErrno(error, "ENOENT")) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
await rm(takeoverPath, {
|
|
122
|
+
force: true
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function removeStaleLockSync(path, options) {
|
|
126
|
+
if (!options.staleMs) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
let details;
|
|
130
|
+
try {
|
|
131
|
+
details = statSync(path);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (isErrno(error, "ENOENT")) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
const now = options.now?.getTime() ?? Date.now();
|
|
140
|
+
if (now - details.mtimeMs < options.staleMs) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const takeoverPath = `${path}.stale-${process.pid}-${++takeoverCounter}`;
|
|
144
|
+
try {
|
|
145
|
+
renameSync(path, takeoverPath);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
if (isErrno(error, "ENOENT")) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
rmSync(takeoverPath, {
|
|
154
|
+
force: true
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function assertLockName(name) {
|
|
158
|
+
if (!/^[a-z][a-z0-9-]{0,63}$/.test(name)) {
|
|
159
|
+
throw new Error(`Invalid dg lock name: ${name}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
export function resolveDgPaths(env = process.env) {
|
|
4
|
+
const homeDir = env.HOME && isAbsolute(env.HOME) ? env.HOME : homedir();
|
|
5
|
+
const fallbackRoot = join(homeDir, ".dg");
|
|
6
|
+
const configDir = xdgPath(env.XDG_CONFIG_HOME, fallbackRoot);
|
|
7
|
+
const stateDir = xdgPath(env.XDG_STATE_HOME, join(fallbackRoot, "state"));
|
|
8
|
+
const cacheDir = xdgPath(env.XDG_CACHE_HOME, join(fallbackRoot, "cache"));
|
|
9
|
+
return {
|
|
10
|
+
homeDir,
|
|
11
|
+
configDir,
|
|
12
|
+
stateDir,
|
|
13
|
+
cacheDir,
|
|
14
|
+
sessionsDir: join(stateDir, "sessions"),
|
|
15
|
+
cleanupRegistryPath: join(stateDir, "cleanup-registry.json"),
|
|
16
|
+
locksDir: join(stateDir, "locks")
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function xdgPath(value, fallback) {
|
|
20
|
+
if (!value || !isAbsolute(value)) {
|
|
21
|
+
return fallback;
|
|
22
|
+
}
|
|
23
|
+
return join(value, "dg");
|
|
24
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
2
|
+
import { appendFile, mkdir, readdir, rm, stat } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { readJsonFile, writeJsonFileAtomic } from "./store.js";
|
|
6
|
+
export async function createSession(paths, id = randomUUID()) {
|
|
7
|
+
assertSessionId(id);
|
|
8
|
+
const dir = join(paths.sessionsDir, id);
|
|
9
|
+
await mkdir(paths.sessionsDir, {
|
|
10
|
+
recursive: true,
|
|
11
|
+
mode: 0o700
|
|
12
|
+
});
|
|
13
|
+
await mkdir(dir, {
|
|
14
|
+
recursive: false,
|
|
15
|
+
mode: 0o700
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
id,
|
|
19
|
+
dir,
|
|
20
|
+
files: sessionFiles(dir)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function createSessionSync(paths, id = randomUUID()) {
|
|
24
|
+
assertSessionId(id);
|
|
25
|
+
const dir = join(paths.sessionsDir, id);
|
|
26
|
+
mkdirSync(paths.sessionsDir, {
|
|
27
|
+
recursive: true,
|
|
28
|
+
mode: 0o700
|
|
29
|
+
});
|
|
30
|
+
mkdirSync(dir, {
|
|
31
|
+
recursive: false,
|
|
32
|
+
mode: 0o700
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
id,
|
|
36
|
+
dir,
|
|
37
|
+
files: sessionFiles(dir)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export async function writeSessionJson(session, name, value) {
|
|
41
|
+
await writeJsonFileAtomic(session.files[name], value);
|
|
42
|
+
}
|
|
43
|
+
export async function readSessionJson(session, name, fallback) {
|
|
44
|
+
return readJsonFile(session.files[name], fallback);
|
|
45
|
+
}
|
|
46
|
+
export async function appendSessionLog(session, value) {
|
|
47
|
+
await appendFile(session.files.log, `${JSON.stringify(value)}\n`, {
|
|
48
|
+
encoding: "utf8",
|
|
49
|
+
mode: 0o600
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export async function cleanupSession(session) {
|
|
53
|
+
await rm(session.dir, {
|
|
54
|
+
force: true,
|
|
55
|
+
recursive: true
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export function cleanupSessionSync(session) {
|
|
59
|
+
rmSync(session.dir, {
|
|
60
|
+
force: true,
|
|
61
|
+
recursive: true
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export async function sweepStaleSessions(paths, options) {
|
|
65
|
+
const now = options.now?.getTime() ?? Date.now();
|
|
66
|
+
const entries = await readdir(paths.sessionsDir, {
|
|
67
|
+
withFileTypes: true
|
|
68
|
+
}).catch((error) => {
|
|
69
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
});
|
|
74
|
+
const removed = [];
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
if (!entry.isDirectory() || !isValidSessionId(entry.name)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const dir = join(paths.sessionsDir, entry.name);
|
|
80
|
+
const details = await stat(dir).catch((error) => {
|
|
81
|
+
if (isEnoent(error)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
});
|
|
86
|
+
if (!details || now - details.mtimeMs < options.olderThanMs) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
await rm(dir, {
|
|
90
|
+
force: true,
|
|
91
|
+
recursive: true
|
|
92
|
+
});
|
|
93
|
+
removed.push(entry.name);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
removed
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export function findStaleSessionsSync(paths, options) {
|
|
100
|
+
const now = options.now?.getTime() ?? Date.now();
|
|
101
|
+
let entries;
|
|
102
|
+
try {
|
|
103
|
+
entries = readdirSync(paths.sessionsDir, {
|
|
104
|
+
withFileTypes: true
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
109
|
+
return {
|
|
110
|
+
stale: []
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
const stale = [];
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
if (!entry.isDirectory() || !isValidSessionId(entry.name)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const dir = join(paths.sessionsDir, entry.name);
|
|
121
|
+
let details;
|
|
122
|
+
try {
|
|
123
|
+
details = statSync(dir);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (isEnoent(error)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
if (now - details.mtimeMs >= options.olderThanMs) {
|
|
132
|
+
stale.push(entry.name);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
stale
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export function sweepStaleSessionsSync(paths, options) {
|
|
140
|
+
const report = findStaleSessionsSync(paths, options);
|
|
141
|
+
for (const id of report.stale) {
|
|
142
|
+
rmSync(join(paths.sessionsDir, id), {
|
|
143
|
+
force: true,
|
|
144
|
+
recursive: true
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
removed: report.stale
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function sessionFiles(dir) {
|
|
152
|
+
return {
|
|
153
|
+
proxy: join(dir, "proxy.json"),
|
|
154
|
+
ca: join(dir, "ca.pem"),
|
|
155
|
+
block: join(dir, "block.json"),
|
|
156
|
+
hash: join(dir, "hash.json"),
|
|
157
|
+
log: join(dir, "log.jsonl")
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function assertSessionId(id) {
|
|
161
|
+
if (!isValidSessionId(id)) {
|
|
162
|
+
throw new Error(`Invalid dg session id: ${id}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function isValidSessionId(id) {
|
|
166
|
+
return /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(id);
|
|
167
|
+
}
|
|
168
|
+
function isEnoent(error) {
|
|
169
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
170
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
export class JsonStoreError extends Error {
|
|
5
|
+
cause;
|
|
6
|
+
constructor(message, cause) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.cause = cause;
|
|
9
|
+
this.name = "JsonStoreError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function readJsonFile(path, fallback) {
|
|
13
|
+
try {
|
|
14
|
+
const content = await readFile(path, "utf8");
|
|
15
|
+
return JSON.parse(content);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (isNotFound(error)) {
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
21
|
+
if (error instanceof SyntaxError) {
|
|
22
|
+
throw new JsonStoreError(`Malformed JSON store at ${path}`, error);
|
|
23
|
+
}
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function writeJsonFileAtomic(path, value) {
|
|
28
|
+
await mkdir(dirname(path), {
|
|
29
|
+
recursive: true
|
|
30
|
+
});
|
|
31
|
+
const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
32
|
+
const payload = `${JSON.stringify(value, null, 2)}\n`;
|
|
33
|
+
try {
|
|
34
|
+
await writeFile(tempPath, payload, {
|
|
35
|
+
encoding: "utf8",
|
|
36
|
+
flag: "wx",
|
|
37
|
+
mode: 0o600
|
|
38
|
+
});
|
|
39
|
+
await rename(tempPath, path);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
await rm(tempPath, {
|
|
43
|
+
force: true
|
|
44
|
+
});
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function isNotFound(error) {
|
|
49
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
50
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { loadUserConfig } from "../config/settings.js";
|
|
4
|
+
import { resolveDgPaths } from "../state/index.js";
|
|
5
|
+
const ALLOWED_ATTRIBUTE_KEYS = new Set(["command", "exitCode", "policyMode", "decision", "packageCount", "authenticated"]);
|
|
6
|
+
export function telemetryLogPath(paths) {
|
|
7
|
+
return join(paths.stateDir, "telemetry.jsonl");
|
|
8
|
+
}
|
|
9
|
+
export function buildTelemetryEvent(type, attributes, now = new Date()) {
|
|
10
|
+
return {
|
|
11
|
+
type,
|
|
12
|
+
createdAt: now.toISOString(),
|
|
13
|
+
attributes: sanitizeTelemetryAttributes(attributes)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function recordTelemetryEvent(event, env = process.env) {
|
|
17
|
+
const config = loadUserConfig(env);
|
|
18
|
+
if (!config.telemetry.enabled) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const path = telemetryLogPath(resolveDgPaths(env));
|
|
22
|
+
mkdirSync(dirname(path), {
|
|
23
|
+
recursive: true,
|
|
24
|
+
mode: 0o700
|
|
25
|
+
});
|
|
26
|
+
appendFileSync(path, `${JSON.stringify(event)}\n`, {
|
|
27
|
+
encoding: "utf8",
|
|
28
|
+
mode: 0o600
|
|
29
|
+
});
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
function sanitizeTelemetryAttributes(attributes) {
|
|
33
|
+
const sanitized = {};
|
|
34
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
35
|
+
if (ALLOWED_ATTRIBUTE_KEYS.has(key)) {
|
|
36
|
+
sanitized[key] = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return sanitized;
|
|
40
|
+
}
|
package/dist/util/git.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
const DEFAULT_MAX_BUFFER = 256 * 1024 * 1024;
|
|
3
|
+
export function gitSync(args, options) {
|
|
4
|
+
const result = spawnSync("git", [...args], {
|
|
5
|
+
cwd: options.cwd,
|
|
6
|
+
env: options.env ?? process.env,
|
|
7
|
+
encoding: "utf8",
|
|
8
|
+
maxBuffer: options.maxBuffer ?? DEFAULT_MAX_BUFFER
|
|
9
|
+
});
|
|
10
|
+
return {
|
|
11
|
+
ok: result.status === 0,
|
|
12
|
+
code: result.status,
|
|
13
|
+
stdout: result.stdout ?? "",
|
|
14
|
+
stderr: (result.stderr ?? "").trim()
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function gitTrimmed(args, options) {
|
|
18
|
+
const result = gitSync(args, options);
|
|
19
|
+
return result.ok ? result.stdout.trim() : null;
|
|
20
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { closeSync, openSync, readSync } from "node:fs";
|
|
2
|
+
export function promptYesNo(question, defaultYes, out = process.stderr) {
|
|
3
|
+
let tty;
|
|
4
|
+
try {
|
|
5
|
+
tty = openSync("/dev/tty", "rs");
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
out.write(`${question} ${defaultYes ? "[Y/n]" : "[y/N]"} `);
|
|
12
|
+
const byte = Buffer.alloc(1);
|
|
13
|
+
let answer = "";
|
|
14
|
+
for (;;) {
|
|
15
|
+
let read = 0;
|
|
16
|
+
try {
|
|
17
|
+
read = readSync(tty, byte, 0, 1, null);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error.code === "EAGAIN") {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
if (read === 0) {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
const char = byte.toString("utf8");
|
|
29
|
+
if (char === "\n" || char === "\r") {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
answer += char;
|
|
33
|
+
}
|
|
34
|
+
const normalized = answer.trim().toLowerCase();
|
|
35
|
+
if (normalized === "") {
|
|
36
|
+
return defaultYes;
|
|
37
|
+
}
|
|
38
|
+
return normalized === "y" || normalized === "yes";
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
closeSync(tty);
|
|
42
|
+
}
|
|
43
|
+
}
|