@westbayberry/dg 1.3.3 → 2.0.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.
Files changed (126) hide show
  1. package/LICENSE +1 -201
  2. package/NOTICE +1 -4
  3. package/README.md +293 -0
  4. package/dist/api/analyze.js +210 -0
  5. package/dist/audit/deep.js +180 -0
  6. package/dist/audit/detectors.js +247 -0
  7. package/dist/audit/events.js +41 -0
  8. package/dist/audit/rules.js +426 -0
  9. package/dist/audit-ui/AuditApp.js +39 -0
  10. package/dist/audit-ui/components/AuditHeader.js +24 -0
  11. package/dist/audit-ui/components/AuditResultsView.js +307 -0
  12. package/dist/audit-ui/components/DeepStatusRow.js +11 -0
  13. package/dist/audit-ui/export.js +85 -0
  14. package/dist/audit-ui/format.js +34 -0
  15. package/dist/audit-ui/launch.js +34 -0
  16. package/dist/auth/device-login.js +271 -0
  17. package/dist/auth/env-token.js +6 -0
  18. package/dist/auth/login-app.js +156 -0
  19. package/dist/auth/store.js +147 -0
  20. package/dist/bin/dg.js +71 -0
  21. package/dist/commands/audit.js +362 -0
  22. package/dist/commands/completion.js +116 -0
  23. package/dist/commands/config.js +99 -0
  24. package/dist/commands/doctor.js +39 -0
  25. package/dist/commands/explain.js +100 -0
  26. package/dist/commands/guard-commit.js +158 -0
  27. package/dist/commands/help.js +74 -0
  28. package/dist/commands/licenses.js +435 -0
  29. package/dist/commands/login.js +81 -0
  30. package/dist/commands/logout.js +37 -0
  31. package/dist/commands/router.js +98 -0
  32. package/dist/commands/scan.js +18 -0
  33. package/dist/commands/service.js +475 -0
  34. package/dist/commands/setup.js +302 -0
  35. package/dist/commands/status.js +115 -0
  36. package/dist/commands/suggest.js +35 -0
  37. package/dist/commands/types.js +4 -0
  38. package/dist/commands/unavailable.js +11 -0
  39. package/dist/commands/uninstall.js +111 -0
  40. package/dist/commands/update.js +210 -0
  41. package/dist/commands/verify.js +151 -0
  42. package/dist/commands/version.js +22 -0
  43. package/dist/commands/wrap.js +55 -0
  44. package/dist/config/settings.js +302 -0
  45. package/dist/install-ui/LiveInstall.js +24 -0
  46. package/dist/install-ui/block-render.js +85 -0
  47. package/dist/install-ui/live-install-app.js +48 -0
  48. package/dist/install-ui/prompt.js +24 -0
  49. package/dist/launcher/classify.js +116 -0
  50. package/dist/launcher/env.js +53 -0
  51. package/dist/launcher/live-install.js +50 -0
  52. package/dist/launcher/output-redaction.js +77 -0
  53. package/dist/launcher/preflight-prompt.js +139 -0
  54. package/dist/launcher/resolve-real-binary.js +73 -0
  55. package/dist/launcher/run.js +417 -0
  56. package/dist/policy/evaluate.js +128 -0
  57. package/dist/presentation/mode.js +52 -0
  58. package/dist/presentation/theme.js +29 -0
  59. package/dist/proxy/buffer-budget.js +64 -0
  60. package/dist/proxy/ca.js +126 -0
  61. package/dist/proxy/classify-host.js +26 -0
  62. package/dist/proxy/enforcement.js +102 -0
  63. package/dist/proxy/metadata-map.js +336 -0
  64. package/dist/proxy/server.js +919 -0
  65. package/dist/proxy/upstream-proxy.js +102 -0
  66. package/dist/proxy/worker.js +39 -0
  67. package/dist/publish-set/collect.js +51 -0
  68. package/dist/publish-set/no-exec-shell.js +19 -0
  69. package/dist/publish-set/npm.js +109 -0
  70. package/dist/publish-set/pack.js +36 -0
  71. package/dist/publish-set/pypi.js +59 -0
  72. package/dist/runtime/cli.js +17 -0
  73. package/dist/runtime/first-run.js +60 -0
  74. package/dist/runtime/node-version.js +58 -0
  75. package/dist/runtime/nudges.js +105 -0
  76. package/dist/scan/analyze-worker.js +21 -0
  77. package/dist/scan/collect.js +153 -0
  78. package/dist/scan/command.js +159 -0
  79. package/dist/scan/discovery.js +209 -0
  80. package/dist/scan/render.js +240 -0
  81. package/dist/scan/scanner-report.js +82 -0
  82. package/dist/scan/staged.js +173 -0
  83. package/dist/scan/types.js +1 -0
  84. package/dist/scan-ui/LegacyApp.js +156 -0
  85. package/dist/scan-ui/alt-screen.js +84 -0
  86. package/dist/scan-ui/api-aliases.js +1 -0
  87. package/dist/scan-ui/components/ErrorView.js +23 -0
  88. package/dist/scan-ui/components/InteractiveResultsView.js +1179 -0
  89. package/dist/scan-ui/components/ProgressBar.js +89 -0
  90. package/dist/scan-ui/components/ProjectSelector.js +62 -0
  91. package/dist/scan-ui/components/ScoreHeader.js +20 -0
  92. package/dist/scan-ui/components/SetupBanner.js +13 -0
  93. package/dist/scan-ui/components/Spinner.js +4 -0
  94. package/dist/scan-ui/format-helpers.js +40 -0
  95. package/dist/scan-ui/hooks/useExpandAnimation.js +40 -0
  96. package/dist/scan-ui/hooks/useScan.js +113 -0
  97. package/dist/scan-ui/hooks/useTerminalSize.js +24 -0
  98. package/dist/scan-ui/launch.js +27 -0
  99. package/dist/scan-ui/logo.js +91 -0
  100. package/dist/scan-ui/shims.js +30 -0
  101. package/dist/security/sanitize.js +28 -0
  102. package/dist/service/state.js +837 -0
  103. package/dist/service/trust-store.js +234 -0
  104. package/dist/service/worker.js +88 -0
  105. package/dist/setup/git-hook.js +244 -0
  106. package/dist/setup/optional-support.js +58 -0
  107. package/dist/setup/plan.js +935 -0
  108. package/dist/state/cleanup-registry.js +60 -0
  109. package/dist/state/index.js +5 -0
  110. package/dist/state/locks.js +161 -0
  111. package/dist/state/paths.js +24 -0
  112. package/dist/state/sessions.js +170 -0
  113. package/dist/state/store.js +50 -0
  114. package/dist/telemetry/events.js +40 -0
  115. package/dist/util/git.js +20 -0
  116. package/dist/util/tty-prompt.js +43 -0
  117. package/dist/verify/local.js +400 -0
  118. package/dist/verify/package-check.js +240 -0
  119. package/dist/verify/preflight.js +698 -0
  120. package/dist/verify/render.js +184 -0
  121. package/dist/verify/types.js +1 -0
  122. package/package.json +33 -50
  123. package/dist/index.mjs +0 -54116
  124. package/dist/postinstall.mjs +0 -731
  125. package/dist/python-hook/dg_pip_hook.pth +0 -1
  126. 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,5 @@
1
+ export * from "./cleanup-registry.js";
2
+ export * from "./locks.js";
3
+ export * from "./paths.js";
4
+ export * from "./sessions.js";
5
+ export * from "./store.js";
@@ -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
+ }
@@ -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
+ }