log-llm-config 1.3.22 → 1.3.24
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/dist/endpoint_client/index.js +1 -1
- package/dist/endpoint_client/registry_api.js +6 -2
- package/dist/log_config_files/readers/vscdb_reader.js +12 -8
- package/dist/log_config_files/runtime/remediation_config_path.js +3 -1
- package/dist/log_config_files/runtime/remediation_sync.js +111 -41
- package/dist/log_config_files/runtime/sqlite_binary.js +92 -0
- package/package.json +1 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { FILE_PATH_REGISTRY_FILE_PATTERNS_PATH, FILE_PATH_REGISTRY_SENSITIVE_PATHS_PATH, } from './types.js';
|
|
2
|
-
export { getFileCollectionPatterns, getSensitivePathsAuditCandidates } from './registry_api.js';
|
|
2
|
+
export { buildApiUrl, getFileCollectionPatterns, getSensitivePathsAuditCandidates, } from './registry_api.js';
|
|
3
3
|
export { postStartupPayload, patchPayload, classifyEndpointResponse } from './startup_api.js';
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { URL } from 'node:url';
|
|
1
2
|
import { executeGet } from './http_transport.js';
|
|
2
3
|
import { FILE_PATH_REGISTRY_FILE_PATTERNS_PATH, FILE_PATH_REGISTRY_SENSITIVE_PATHS_PATH, } from './types.js';
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
/** Join API base (may include a path prefix, e.g. https://host/optimus/) with an absolute API path. */
|
|
5
|
+
export function buildApiUrl(base, path) {
|
|
6
|
+
const normalizedBase = `${base.replace(/\/+$/, '')}/`;
|
|
7
|
+
const relativePath = path.replace(/^\/+/, '');
|
|
8
|
+
return new URL(relativePath, normalizedBase).href;
|
|
5
9
|
}
|
|
6
10
|
/**
|
|
7
11
|
* GET file collection patterns from the backend (what to look for).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { resolveSqlite3Binary } from '../runtime/sqlite_binary.js';
|
|
3
4
|
import { readFileCollectionVscdbContract, writeFileCollectionVscdbContract, } from '../runtime/management_storage.js';
|
|
4
5
|
/**
|
|
5
6
|
* ItemTable keys that store a bare JSON boolean; compliance paths use `${key}.${field}`.
|
|
@@ -11,10 +12,15 @@ export const CURSOR_SCALAR_ITEMTABLE_FIELDS = {
|
|
|
11
12
|
'cursor/autoOpenLocalhostUrls': 'autoOpenLocalhostUrls',
|
|
12
13
|
};
|
|
13
14
|
function querySqlite(dbPath, key) {
|
|
15
|
+
const bin = resolveSqlite3Binary();
|
|
16
|
+
if (!bin)
|
|
17
|
+
throw new Error('sqlite3 not found');
|
|
14
18
|
const safe = key.replace(/'/g, "''");
|
|
15
|
-
|
|
19
|
+
const script = `.timeout 60000\nSELECT value FROM ItemTable WHERE key='${safe}';\n`;
|
|
20
|
+
return execFileSync(bin, ['-noheader', dbPath], {
|
|
21
|
+
input: script,
|
|
16
22
|
encoding: 'utf8',
|
|
17
|
-
stdio: ['
|
|
23
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
18
24
|
}).trim();
|
|
19
25
|
}
|
|
20
26
|
/**
|
|
@@ -73,7 +79,8 @@ export function readVscdbItemTableJson(dbPath, itemKey) {
|
|
|
73
79
|
try {
|
|
74
80
|
if (!dbPath || !existsSync(dbPath))
|
|
75
81
|
return null;
|
|
76
|
-
|
|
82
|
+
if (!resolveSqlite3Binary())
|
|
83
|
+
return null;
|
|
77
84
|
}
|
|
78
85
|
catch {
|
|
79
86
|
return null;
|
|
@@ -218,10 +225,7 @@ export function readVSCDBState(dbPath, readQueries, mergeFromComposerStateKeys)
|
|
|
218
225
|
try {
|
|
219
226
|
if (!dbPath || !existsSync(dbPath))
|
|
220
227
|
return null;
|
|
221
|
-
|
|
222
|
-
execSync('which sqlite3', { stdio: 'ignore' });
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
228
|
+
if (!resolveSqlite3Binary()) {
|
|
225
229
|
console.warn('sqlite3 command not found; skipping vscdb reading');
|
|
226
230
|
return null;
|
|
227
231
|
}
|
|
@@ -52,7 +52,9 @@ export function canonicalCursorUserStateVscdbPath(filePath) {
|
|
|
52
52
|
function cursorStateVscdbAbsoluteBasePaths() {
|
|
53
53
|
const h = homedir();
|
|
54
54
|
if (process.platform === 'darwin') {
|
|
55
|
-
|
|
55
|
+
const support = join(h, 'Library', 'Application Support');
|
|
56
|
+
const variants = ['Cursor', 'Cursor - Insiders', 'Cursor Nightly', 'Cursor Next'];
|
|
57
|
+
return variants.map((name) => join(support, name, 'User', 'globalStorage', 'state.vscdb'));
|
|
56
58
|
}
|
|
57
59
|
if (process.platform === 'win32') {
|
|
58
60
|
const appData = process.env.APPDATA;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from 'node:fs';
|
|
2
|
-
import { dirname } from 'node:path';
|
|
2
|
+
import { delimiter, dirname } from 'node:path';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
4
|
import { executeGet, executeBody } from '../../endpoint_client/http_transport.js';
|
|
5
5
|
import { complianceRunnerDiag, hookRunLog, logRemediationApplyFailure } from './hook_logger.js';
|
|
@@ -10,8 +10,31 @@ import { loadEndpointBase } from '../sender/endpoint_config.js';
|
|
|
10
10
|
import { tryResolveHardwareUuid } from './hardware_uuid.js';
|
|
11
11
|
import { CURSOR_SCALAR_ITEMTABLE_FIELDS, persistVscdbComposerContractFromPatternsResponse, readVscdbItemTableJson, } from '../readers/vscdb_reader.js';
|
|
12
12
|
import { sendConfigFile } from '../sender/batch_sender.js';
|
|
13
|
-
import { getFileCollectionPatterns } from '../../endpoint_client/registry_api.js';
|
|
13
|
+
import { buildApiUrl, getFileCollectionPatterns } from '../../endpoint_client/registry_api.js';
|
|
14
14
|
import { resolveRemediationConfigPath } from './remediation_config_path.js';
|
|
15
|
+
import { resolveSqlite3Binary } from './sqlite_binary.js';
|
|
16
|
+
/** Best-effort detail from execFileSync failures (stderr, exit code, errno). */
|
|
17
|
+
function formatNodeChildException(err) {
|
|
18
|
+
if (!(err instanceof Error)) {
|
|
19
|
+
const s = String(err);
|
|
20
|
+
return { short: s, long: s };
|
|
21
|
+
}
|
|
22
|
+
const e = err;
|
|
23
|
+
const bits = [e.message];
|
|
24
|
+
if (e.code)
|
|
25
|
+
bits.push(`errno_code=${e.code}`);
|
|
26
|
+
if (typeof e.status === 'number')
|
|
27
|
+
bits.push(`exit_status=${e.status}`);
|
|
28
|
+
let stderr = '';
|
|
29
|
+
if (e.stderr != null) {
|
|
30
|
+
stderr = typeof e.stderr === 'string' ? e.stderr : e.stderr.toString('utf8');
|
|
31
|
+
const t = stderr.trim();
|
|
32
|
+
if (t)
|
|
33
|
+
bits.push(`stderr=${t.slice(0, 1200)}`);
|
|
34
|
+
}
|
|
35
|
+
const long = bits.join(' | ');
|
|
36
|
+
return { short: bits.slice(0, 2).join(' | '), long };
|
|
37
|
+
}
|
|
15
38
|
function reactiveStorageItemKeyFromContract() {
|
|
16
39
|
const k = readFileCollectionVscdbContract()?.reactive_storage_item_key;
|
|
17
40
|
return typeof k === 'string' && k.trim() !== '' ? k.trim() : undefined;
|
|
@@ -96,17 +119,9 @@ async function ensureInstructionsForUuids(endpointBase, machineUuid, want, overl
|
|
|
96
119
|
// ---------------------------------------------------------------------------
|
|
97
120
|
// API helpers
|
|
98
121
|
// ---------------------------------------------------------------------------
|
|
99
|
-
function resolveOrigin(endpointBase) {
|
|
100
|
-
try {
|
|
101
|
-
return new URL(endpointBase).origin;
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return endpointBase.replace(/\/+$/, '');
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
122
|
export async function fetchSync(endpointBase, machineUuid, activeUuids, timeoutMs = 8000) {
|
|
108
123
|
const uuidsParam = activeUuids.join(',');
|
|
109
|
-
const url = `${
|
|
124
|
+
const url = `${buildApiUrl(endpointBase, '/api/findings/remediations/sync/')}?machine_uuid=${encodeURIComponent(machineUuid)}&active_uuids=${encodeURIComponent(uuidsParam)}`;
|
|
110
125
|
const { statusCode, body } = await executeGet(url, timeoutMs);
|
|
111
126
|
if (statusCode !== 200 || !body) {
|
|
112
127
|
const line = `remediation_sync_get: url=${url} status=${statusCode} bytes=${body?.length ?? 0}`;
|
|
@@ -124,7 +139,7 @@ export async function fetchSync(endpointBase, machineUuid, activeUuids, timeoutM
|
|
|
124
139
|
}
|
|
125
140
|
}
|
|
126
141
|
async function fetchManifest(endpointBase, machineUuid, uuids, timeoutMs = 8000) {
|
|
127
|
-
const url = `${
|
|
142
|
+
const url = `${buildApiUrl(endpointBase, '/api/findings/remediations/manifest/')}?machine_uuid=${encodeURIComponent(machineUuid)}&uuids=${encodeURIComponent(uuids.join(','))}`;
|
|
128
143
|
const { statusCode, body } = await executeGet(url, timeoutMs);
|
|
129
144
|
if (statusCode !== 200 || !body) {
|
|
130
145
|
const line = `remediation_manifest_get: url=${url} status=${statusCode} bytes=${body?.length ?? 0}`;
|
|
@@ -309,16 +324,19 @@ function mergePostApplyUploadIntoPayload(payload, hint) {
|
|
|
309
324
|
payload.post_apply_uploads.push({ file_path: hint.file_path, file_type: ft });
|
|
310
325
|
}
|
|
311
326
|
function assertSqlite3Available() {
|
|
312
|
-
|
|
313
|
-
|
|
327
|
+
const bin = resolveSqlite3Binary();
|
|
328
|
+
if (bin) {
|
|
329
|
+
complianceRunnerDiag(`sqlite_update: using sqlite3 binary ${bin}`);
|
|
314
330
|
return true;
|
|
315
331
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
332
|
+
const pathEnv = process.env.PATH ?? '';
|
|
333
|
+
const n = pathEnv ? pathEnv.split(delimiter).filter(Boolean).length : 0;
|
|
334
|
+
const line = 'sqlite_update: sqlite3 command not found';
|
|
335
|
+
hookRunLog(line);
|
|
336
|
+
hookRunLog(`sqlite_update: no sqlite3 resolved after checking env OPTIMUS_SQLITE3/SQLITE3_PATH, common paths, and PATH (${n} entries)`);
|
|
337
|
+
hookRunLog('sqlite_update: hint set OPTIMUS_SQLITE3 to the full path (e.g. /usr/bin/sqlite3 on macOS) if sqlite3 is outside PATH');
|
|
338
|
+
complianceRunnerDiag(`${line} PATH_entry_count=${n}`);
|
|
339
|
+
return false;
|
|
322
340
|
}
|
|
323
341
|
/**
|
|
324
342
|
* Unquoted SQLite identifiers must be [A-Za-z_][A-Za-z0-9_]* so values read from
|
|
@@ -610,30 +628,68 @@ function sqliteSelectValueCell(dbPath, table, key_column, value_column, target_k
|
|
|
610
628
|
if (!assertSafeSqliteIdentifiersForItemTable(table, key_column, value_column)) {
|
|
611
629
|
return '';
|
|
612
630
|
}
|
|
631
|
+
const sqlite3 = resolveSqlite3Binary();
|
|
632
|
+
if (!sqlite3) {
|
|
633
|
+
const line = 'sqlite_update: sqliteSelectValueCell called with no resolved sqlite3 binary (logic error: assertSqlite3Available should run first)';
|
|
634
|
+
hookRunLog(line);
|
|
635
|
+
complianceRunnerDiag(line);
|
|
636
|
+
return '';
|
|
637
|
+
}
|
|
613
638
|
const safeName = target_key.replace(/'/g, "''");
|
|
614
639
|
const script = `.timeout 60000\nSELECT ${value_column} FROM ${table} WHERE ${key_column}='${safeName}';\n`;
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
640
|
+
try {
|
|
641
|
+
return execFileSync(sqlite3, ['-noheader', dbPath], {
|
|
642
|
+
input: script,
|
|
643
|
+
encoding: 'utf8',
|
|
644
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
645
|
+
}).trim();
|
|
646
|
+
}
|
|
647
|
+
catch (err) {
|
|
648
|
+
const { short, long } = formatNodeChildException(err);
|
|
649
|
+
hookRunLog(`sqlite_update: SELECT failed binary=${sqlite3} db=${dbPath} table=${table} target_key=${target_key} ${long}`);
|
|
650
|
+
complianceRunnerDiag(`sqlite_update: SELECT failed target_key=${target_key} ${long.slice(0, 1800)}`);
|
|
651
|
+
throw new Error(`sqlite3 SELECT failed: ${short}`);
|
|
652
|
+
}
|
|
620
653
|
}
|
|
621
654
|
function sqliteExecWithTimeout(dbPath, sqlBody) {
|
|
655
|
+
const sqlite3 = resolveSqlite3Binary();
|
|
656
|
+
if (!sqlite3)
|
|
657
|
+
throw new Error('sqlite3 not found');
|
|
622
658
|
const script = `.timeout 60000\n${sqlBody}\n`;
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
659
|
+
try {
|
|
660
|
+
execFileSync(sqlite3, [dbPath], {
|
|
661
|
+
input: script,
|
|
662
|
+
encoding: 'utf8',
|
|
663
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
const { long } = formatNodeChildException(err);
|
|
668
|
+
hookRunLog(`sqlite_update: UPDATE/exec failed binary=${sqlite3} db=${dbPath} ${long}`);
|
|
669
|
+
complianceRunnerDiag(`sqlite_update: UPDATE/exec failed ${long.slice(0, 1800)}`);
|
|
670
|
+
throw err;
|
|
671
|
+
}
|
|
628
672
|
}
|
|
629
673
|
/** Runs a single UPDATE (or other SQL) and returns sqlite `changes()` for the last statement. */
|
|
630
674
|
function sqliteRunUpdateReturningChanges(dbPath, updateSql) {
|
|
675
|
+
const sqlite3 = resolveSqlite3Binary();
|
|
676
|
+
if (!sqlite3)
|
|
677
|
+
throw new Error('sqlite3 not found');
|
|
631
678
|
const script = `.timeout 60000\n${updateSql}\nSELECT changes();\n`;
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
679
|
+
let out;
|
|
680
|
+
try {
|
|
681
|
+
out = execFileSync(sqlite3, [dbPath], {
|
|
682
|
+
input: script,
|
|
683
|
+
encoding: 'utf8',
|
|
684
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
685
|
+
}).trim();
|
|
686
|
+
}
|
|
687
|
+
catch (err) {
|
|
688
|
+
const { long } = formatNodeChildException(err);
|
|
689
|
+
hookRunLog(`sqlite_update: batch UPDATE/changes failed binary=${sqlite3} db=${dbPath} ${long}`);
|
|
690
|
+
complianceRunnerDiag(`sqlite_update: batch UPDATE failed ${long.slice(0, 1800)}`);
|
|
691
|
+
throw err;
|
|
692
|
+
}
|
|
637
693
|
const lines = out.split(/\r?\n/).filter((l) => l.length > 0);
|
|
638
694
|
const last = lines[lines.length - 1] ?? '0';
|
|
639
695
|
return parseInt(last, 10) || 0;
|
|
@@ -770,9 +826,12 @@ function assertSafeDeferredItemTableKey(targetKey) {
|
|
|
770
826
|
}
|
|
771
827
|
function queueDeferredSqliteOpsMerged(configPath, sqliteOps, postApplyUpload) {
|
|
772
828
|
const dbPath = configPath.split('#')[0];
|
|
829
|
+
hookRunLog(`sqlite_update: deferred_queue begin configPath=${configPath} dbPath=${dbPath} dbExists=${existsSync(dbPath)} sqliteOps=${sqliteOps.length}`);
|
|
830
|
+
complianceRunnerDiag(`sqlite_update: deferred_queue begin dbExists=${existsSync(dbPath)} ops=${sqliteOps.length}`);
|
|
773
831
|
if (!existsSync(dbPath)) {
|
|
774
832
|
const line = `sqlite_update: database not found at ${dbPath}`;
|
|
775
833
|
hookRunLog(line);
|
|
834
|
+
hookRunLog(`sqlite_update: parent dir exists=${existsSync(dirname(dbPath))} (if false, wrong Cursor profile path or never launched Cursor)`);
|
|
776
835
|
complianceRunnerDiag(line);
|
|
777
836
|
return {
|
|
778
837
|
ok: false,
|
|
@@ -782,7 +841,7 @@ function queueDeferredSqliteOpsMerged(configPath, sqliteOps, postApplyUpload) {
|
|
|
782
841
|
if (!assertSqlite3Available()) {
|
|
783
842
|
return {
|
|
784
843
|
ok: false,
|
|
785
|
-
reason: 'sqlite3 CLI not found
|
|
844
|
+
reason: 'sqlite3 CLI not found (IDE hooks often have a minimal PATH). On macOS try OPTIMUS_SQLITE3=/usr/bin/sqlite3 or install sqlite3 and fix PATH.',
|
|
786
845
|
};
|
|
787
846
|
}
|
|
788
847
|
const resolvedOps = sqliteOps.map((op) => resolveCursorComposerSqliteOp(dbPath, op));
|
|
@@ -806,8 +865,10 @@ function queueDeferredSqliteOpsMerged(configPath, sqliteOps, postApplyUpload) {
|
|
|
806
865
|
}
|
|
807
866
|
complianceRunnerDiag(`sqlite_update: deferred merge db=${dbPath} target_key=${first.target_key} operations=${ops.length}`);
|
|
808
867
|
let currentJson = {};
|
|
868
|
+
let rawSelectLength = 0;
|
|
809
869
|
try {
|
|
810
870
|
const result = sqliteSelectValueCell(dbPath, first.table, first.key_column, first.value_column, first.target_key);
|
|
871
|
+
rawSelectLength = result.length;
|
|
811
872
|
if (result) {
|
|
812
873
|
const parsed = JSON.parse(result);
|
|
813
874
|
currentJson = coerceItemTableValueToObjectRoot(first.target_key, parsed);
|
|
@@ -828,12 +889,16 @@ function queueDeferredSqliteOpsMerged(configPath, sqliteOps, postApplyUpload) {
|
|
|
828
889
|
repairComposerStateEmptySegmentBug(currentJson);
|
|
829
890
|
const updatedJson = serializeItemTableValueForWrite(first.target_key, currentJson);
|
|
830
891
|
if (updatedJson === '{}' && Object.keys(currentJson).length === 0) {
|
|
831
|
-
const
|
|
892
|
+
const opSummary = ops
|
|
893
|
+
.map((o) => `path=${o.json_path ?? ''} updates=${JSON.stringify(o.updates ?? {})}`)
|
|
894
|
+
.join(' || ')
|
|
895
|
+
.slice(0, 900);
|
|
896
|
+
const line = `sqlite_update: deferred merge produced empty JSON — refusing to queue (would wipe ItemTable row) target_key=${first.target_key} raw_cell_len=${rawSelectLength} ops=${opSummary}`;
|
|
832
897
|
hookRunLog(line);
|
|
833
|
-
complianceRunnerDiag(line);
|
|
898
|
+
complianceRunnerDiag(line.slice(0, 2000));
|
|
834
899
|
return {
|
|
835
900
|
ok: false,
|
|
836
|
-
reason:
|
|
901
|
+
reason: `merge produced empty JSON after ops (target_key=${first.target_key}, raw_cell_len=${rawSelectLength}). Check sqlite_update lines above for SELECT errors or bad sqlite_op merge.`,
|
|
837
902
|
};
|
|
838
903
|
}
|
|
839
904
|
queueDeferredVscdbItem({
|
|
@@ -974,7 +1039,12 @@ export function enforceRemediation(instruction) {
|
|
|
974
1039
|
: undefined;
|
|
975
1040
|
const q = queueDeferredSqliteOpsMerged(inst.config_file_path, ops, postApplyUpload);
|
|
976
1041
|
if (!q.ok) {
|
|
977
|
-
|
|
1042
|
+
const dbOnly = inst.config_file_path.split('#')[0];
|
|
1043
|
+
return fail(`deferred state.vscdb queue failed — ${q.reason} (see sqlite_update lines above in hook_log for SELECT/exec details)`, {
|
|
1044
|
+
config_file_path: inst.config_file_path,
|
|
1045
|
+
resolved_db_path: dbOnly,
|
|
1046
|
+
sqlite3_binary: resolveSqlite3Binary() ?? 'unresolved',
|
|
1047
|
+
});
|
|
978
1048
|
}
|
|
979
1049
|
return { ok: true, deferredSqlite: true };
|
|
980
1050
|
}
|
|
@@ -1052,7 +1122,7 @@ export function reportAutofixApplied(remediationUuid, result) {
|
|
|
1052
1122
|
return Promise.resolve();
|
|
1053
1123
|
}
|
|
1054
1124
|
const endpointBase = loadEndpointBase();
|
|
1055
|
-
const url =
|
|
1125
|
+
const url = buildApiUrl(endpointBase, '/endpoint_security/api/autofix-applied/');
|
|
1056
1126
|
const payload = { hardware_uuid: hardwareUuid, remediation_uuid: remediationUuid, result };
|
|
1057
1127
|
const signature = createSignature(payload, authKey.key);
|
|
1058
1128
|
const body = JSON.stringify({ ...payload, signature });
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
let cached;
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the sqlite3 CLI for remediations and vscdb reads.
|
|
7
|
+
*
|
|
8
|
+
* Cursor/IDE-launched hooks often inherit a minimal PATH (no Homebrew), while macOS still ships
|
|
9
|
+
* `/usr/bin/sqlite3`. Prefer explicit paths before `which` / bare `sqlite3`.
|
|
10
|
+
*
|
|
11
|
+
* Override: `OPTIMUS_SQLITE3`, `SQLITE3_PATH`, or `SQLITE3_BIN` = absolute path to the binary.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveSqlite3Binary() {
|
|
14
|
+
if (cached !== undefined)
|
|
15
|
+
return cached;
|
|
16
|
+
for (const envName of ['OPTIMUS_SQLITE3', 'SQLITE3_PATH', 'SQLITE3_BIN']) {
|
|
17
|
+
const v = process.env[envName]?.trim();
|
|
18
|
+
if (v && existsSync(v)) {
|
|
19
|
+
cached = v;
|
|
20
|
+
return cached;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const candidates = [];
|
|
24
|
+
if (process.platform === 'darwin') {
|
|
25
|
+
candidates.push('/usr/bin/sqlite3', '/opt/homebrew/bin/sqlite3', '/usr/local/bin/sqlite3', '/opt/local/bin/sqlite3');
|
|
26
|
+
}
|
|
27
|
+
else if (process.platform === 'linux') {
|
|
28
|
+
candidates.push('/usr/bin/sqlite3', '/usr/local/bin/sqlite3');
|
|
29
|
+
}
|
|
30
|
+
else if (process.platform === 'win32') {
|
|
31
|
+
const pf = process.env.ProgramFiles;
|
|
32
|
+
if (pf) {
|
|
33
|
+
candidates.push(join(pf, 'Git', 'usr', 'bin', 'sqlite3.exe'));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const p of candidates) {
|
|
37
|
+
if (p && existsSync(p)) {
|
|
38
|
+
cached = p;
|
|
39
|
+
return cached;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
if (process.platform === 'win32') {
|
|
44
|
+
const out = execFileSync('where.exe', ['sqlite3'], {
|
|
45
|
+
encoding: 'utf8',
|
|
46
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
47
|
+
}).trim();
|
|
48
|
+
const first = out.split(/\r?\n/).find((line) => {
|
|
49
|
+
const t = line.trim();
|
|
50
|
+
return t.length > 0 && existsSync(t);
|
|
51
|
+
});
|
|
52
|
+
if (first) {
|
|
53
|
+
cached = first.trim();
|
|
54
|
+
return cached;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
for (const whichBin of ['which', '/usr/bin/which']) {
|
|
59
|
+
try {
|
|
60
|
+
const out = execFileSync(whichBin, ['sqlite3'], {
|
|
61
|
+
encoding: 'utf8',
|
|
62
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
63
|
+
}).trim();
|
|
64
|
+
const first = out.split(/\n/)[0]?.trim();
|
|
65
|
+
if (first && existsSync(first)) {
|
|
66
|
+
cached = first;
|
|
67
|
+
return cached;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
/* try next */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
/* fall through */
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
execFileSync('sqlite3', ['--version'], { stdio: 'ignore' });
|
|
81
|
+
cached = 'sqlite3';
|
|
82
|
+
return cached;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
cached = null;
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Test-only: reset memoized path after env/path changes. */
|
|
90
|
+
export function clearSqlite3BinaryCacheForTests() {
|
|
91
|
+
cached = undefined;
|
|
92
|
+
}
|