@saluzi/saluzi-edu 0.2.7 → 0.2.9
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/cli.js +62 -60
- package/package.json +2 -4
- package/scripts/postinstall.cjs +0 -580
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saluzi/saluzi-edu",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Saluzi CLI - interactive AI coding assistant in the terminal",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
"files": [
|
|
30
30
|
"dist",
|
|
31
31
|
"scripts/acp-link.mjs",
|
|
32
|
-
"scripts/postinstall.cjs",
|
|
33
32
|
"scripts/run-parallel.mjs",
|
|
34
33
|
"packages/remote-control-server/src",
|
|
35
34
|
"packages/remote-control-server/web/dist",
|
|
@@ -58,7 +57,6 @@
|
|
|
58
57
|
"check:bundle": "bun run scripts/check-bundle-integrity.ts",
|
|
59
58
|
"check:unused": "knip-bun",
|
|
60
59
|
"health": "bun run scripts/health-check.ts",
|
|
61
|
-
"postinstall": "node scripts/postinstall.cjs",
|
|
62
60
|
"docs:dev": "npx mintlify dev",
|
|
63
61
|
"typecheck": "tsc --noEmit",
|
|
64
62
|
"precheck": "bun run typecheck && bun run check:fix && bun test --isolate",
|
|
@@ -91,7 +89,7 @@
|
|
|
91
89
|
"@anthropic-ai/sandbox-runtime": "^0.0.44",
|
|
92
90
|
"@anthropic-ai/sdk": "^0.81.0",
|
|
93
91
|
"@anthropic-ai/vertex-sdk": "^0.16.0",
|
|
94
|
-
"@saluzi/sa-ink": "^0.1.
|
|
92
|
+
"@saluzi/sa-ink": "^0.1.11",
|
|
95
93
|
"@aws-sdk/client-bedrock": "^3.1037.0",
|
|
96
94
|
"@aws-sdk/client-bedrock-runtime": "^3.1037.0",
|
|
97
95
|
"@aws-sdk/client-sts": "^3.1037.0",
|
package/scripts/postinstall.cjs
DELETED
|
@@ -1,580 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Postinstall script — runs automatically after `bun install` or `npm install`.
|
|
4
|
-
*
|
|
5
|
-
* Downloads ripgrep binary (idempotent, skips if exists).
|
|
6
|
-
* Works in dev mode (src/ exists), published mode (dist/ exists), with bun or node.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* node scripts/postinstall.js
|
|
10
|
-
* node scripts/postinstall.js --force
|
|
11
|
-
* bun run scripts/postinstall.js
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const {
|
|
15
|
-
existsSync,
|
|
16
|
-
mkdirSync,
|
|
17
|
-
readFileSync,
|
|
18
|
-
renameSync,
|
|
19
|
-
rmSync,
|
|
20
|
-
statSync,
|
|
21
|
-
writeFileSync,
|
|
22
|
-
chmodSync,
|
|
23
|
-
} = require('fs')
|
|
24
|
-
const { spawnSync } = require('child_process')
|
|
25
|
-
const { setDefaultResultOrder } = require('node:dns')
|
|
26
|
-
const path = require('path')
|
|
27
|
-
const os = require('os')
|
|
28
|
-
|
|
29
|
-
// Prefer IPv4 first — Bun on Windows sometimes fails GitHub over broken IPv6 paths.
|
|
30
|
-
try {
|
|
31
|
-
setDefaultResultOrder('ipv4first')
|
|
32
|
-
} catch {
|
|
33
|
-
/* ignore */
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// --- Config ---
|
|
37
|
-
|
|
38
|
-
const RG_VERSION = '15.0.1'
|
|
39
|
-
const DEFAULT_RELEASE_BASE = `https://github.com/microsoft/ripgrep-prebuilt/releases/download/v${RG_VERSION}`
|
|
40
|
-
const MIRROR_RELEASE_BASE = `https://ghproxy.net/https://github.com/microsoft/ripgrep-prebuilt/releases/download/v${RG_VERSION}`
|
|
41
|
-
const RELEASE_BASE = (
|
|
42
|
-
process.env.RIPGREP_DOWNLOAD_BASE ?? DEFAULT_RELEASE_BASE
|
|
43
|
-
).replace(/\/$/, '')
|
|
44
|
-
|
|
45
|
-
const scriptDir = path.dirname(__filename)
|
|
46
|
-
const projectRoot = path.resolve(scriptDir, '..')
|
|
47
|
-
|
|
48
|
-
// --- Platform mapping ---
|
|
49
|
-
|
|
50
|
-
function getPlatformMapping() {
|
|
51
|
-
const arch = process.arch
|
|
52
|
-
const platform = process.platform
|
|
53
|
-
|
|
54
|
-
if (platform === 'darwin') {
|
|
55
|
-
if (arch === 'arm64')
|
|
56
|
-
return { target: 'aarch64-apple-darwin', ext: 'tar.gz' }
|
|
57
|
-
if (arch === 'x64') return { target: 'x86_64-apple-darwin', ext: 'tar.gz' }
|
|
58
|
-
throw new Error(`Unsupported macOS arch: ${arch}`)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (platform === 'win32') {
|
|
62
|
-
if (arch === 'x64') return { target: 'x86_64-pc-windows-msvc', ext: 'zip' }
|
|
63
|
-
if (arch === 'arm64')
|
|
64
|
-
return { target: 'aarch64-pc-windows-msvc', ext: 'zip' }
|
|
65
|
-
throw new Error(`Unsupported Windows arch: ${arch}`)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (platform === 'linux') {
|
|
69
|
-
const isMusl = detectMusl()
|
|
70
|
-
if (arch === 'x64') {
|
|
71
|
-
return { target: 'x86_64-unknown-linux-musl', ext: 'tar.gz' }
|
|
72
|
-
}
|
|
73
|
-
if (arch === 'arm64') {
|
|
74
|
-
return isMusl
|
|
75
|
-
? { target: 'aarch64-unknown-linux-musl', ext: 'tar.gz' }
|
|
76
|
-
: { target: 'aarch64-unknown-linux-gnu', ext: 'tar.gz' }
|
|
77
|
-
}
|
|
78
|
-
throw new Error(`Unsupported Linux arch: ${arch}`)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
throw new Error(`Unsupported platform: ${platform}`)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function detectMusl() {
|
|
85
|
-
const muslArch = process.arch === 'x64' ? 'x86_64' : 'aarch64'
|
|
86
|
-
try {
|
|
87
|
-
statSync(`/lib/libc.musl-${muslArch}.so.1`)
|
|
88
|
-
return true
|
|
89
|
-
} catch {
|
|
90
|
-
return false
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// --- Paths ---
|
|
95
|
-
|
|
96
|
-
function getVendorDir() {
|
|
97
|
-
if (existsSync(path.join(projectRoot, 'src'))) {
|
|
98
|
-
return path.resolve(projectRoot, 'src', 'utils', 'vendor', 'ripgrep')
|
|
99
|
-
}
|
|
100
|
-
return path.resolve(projectRoot, 'dist', 'vendor', 'ripgrep')
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function getBinaryPath() {
|
|
104
|
-
const dir = getVendorDir()
|
|
105
|
-
const subdir = `${process.arch}-${process.platform}`
|
|
106
|
-
const binary = process.platform === 'win32' ? 'rg.exe' : 'rg'
|
|
107
|
-
return path.resolve(dir, subdir, binary)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// --- Download helpers ---
|
|
111
|
-
|
|
112
|
-
function proxyEnvSet() {
|
|
113
|
-
const v = s => (s ?? '').trim()
|
|
114
|
-
return !!(
|
|
115
|
-
v(process.env.HTTPS_PROXY) ||
|
|
116
|
-
v(process.env.HTTP_PROXY) ||
|
|
117
|
-
v(process.env.ALL_PROXY) ||
|
|
118
|
-
v(process.env.https_proxy) ||
|
|
119
|
-
v(process.env.http_proxy)
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function tryPowerShellDownload(url, dest) {
|
|
124
|
-
const u = url.replace(/'/g, "''")
|
|
125
|
-
const d = dest.replace(/'/g, "''")
|
|
126
|
-
const cmd = `Invoke-WebRequest -Uri '${u}' -OutFile '${d}' -UseBasicParsing`
|
|
127
|
-
const result = spawnSync(
|
|
128
|
-
'powershell.exe',
|
|
129
|
-
[
|
|
130
|
-
'-NoProfile',
|
|
131
|
-
'-NonInteractive',
|
|
132
|
-
'-ExecutionPolicy',
|
|
133
|
-
'Bypass',
|
|
134
|
-
'-Command',
|
|
135
|
-
cmd,
|
|
136
|
-
],
|
|
137
|
-
{ stdio: 'pipe', windowsHide: true },
|
|
138
|
-
)
|
|
139
|
-
return result.status === 0 && existsSync(dest) && statSync(dest).size > 0
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function tryCurlDownload(url, dest) {
|
|
143
|
-
const curl = process.platform === 'win32' ? 'curl.exe' : 'curl'
|
|
144
|
-
const result = spawnSync(curl, ['-fsSL', '-L', '--fail', '-o', dest, url], {
|
|
145
|
-
stdio: 'pipe',
|
|
146
|
-
windowsHide: true,
|
|
147
|
-
})
|
|
148
|
-
return result.status === 0 && existsSync(dest) && statSync(dest).size > 0
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async function fetchRelease(url) {
|
|
152
|
-
// Prefer undici when available (installed as optionalDep) — its
|
|
153
|
-
// EnvHttpProxyAgent honors HTTPS_PROXY/HTTP_PROXY/NO_PROXY, which Node's
|
|
154
|
-
// built-in fetch does NOT. On published installs where undici may be absent
|
|
155
|
-
// (--omit=optional, pnpm strict), fall back gracefully.
|
|
156
|
-
let undici
|
|
157
|
-
try {
|
|
158
|
-
undici = require('undici')
|
|
159
|
-
} catch {
|
|
160
|
-
// undici unavailable — fall through to global fetch + curl/PowerShell
|
|
161
|
-
}
|
|
162
|
-
if (proxyEnvSet() && undici) {
|
|
163
|
-
try {
|
|
164
|
-
return await undici.fetch(url, {
|
|
165
|
-
redirect: 'follow',
|
|
166
|
-
dispatcher: new undici.EnvHttpProxyAgent(),
|
|
167
|
-
})
|
|
168
|
-
} catch (e) {
|
|
169
|
-
// undici fetch failed — fall through to downloadUrlToBufferWithFallback
|
|
170
|
-
// which tries curl/PowerShell (those honor system proxy config).
|
|
171
|
-
const msg = e instanceof Error ? e.message : String(e)
|
|
172
|
-
console.warn(
|
|
173
|
-
`[postinstall] undici fetch failed (${msg}); trying curl/PowerShell fallback`,
|
|
174
|
-
)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// Node 18+ has global fetch, Bun has it too. NOTE: global fetch does NOT
|
|
178
|
-
// honor HTTPS_PROXY — proxy users without undici must rely on the
|
|
179
|
-
// curl/PowerShell fallback in downloadUrlToBufferWithFallback, which DOES
|
|
180
|
-
// honor system proxy config (curl on Linux/macOS, winhttp on Windows).
|
|
181
|
-
return await fetch(url, { redirect: 'follow' })
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async function downloadUrlToBuffer(url) {
|
|
185
|
-
const response = await fetchRelease(url)
|
|
186
|
-
if (!response.ok) {
|
|
187
|
-
throw new Error(
|
|
188
|
-
`Download failed: ${response.status} ${response.statusText}`,
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
|
-
return Buffer.from(await response.arrayBuffer())
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async function downloadUrlToBufferWithFallback(url) {
|
|
195
|
-
let firstError
|
|
196
|
-
try {
|
|
197
|
-
return await downloadUrlToBuffer(url)
|
|
198
|
-
} catch (e) {
|
|
199
|
-
firstError = e
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const tmpRoot = path.join(
|
|
203
|
-
os.tmpdir(),
|
|
204
|
-
`ripgrep-dl-${process.pid}-${Date.now()}`,
|
|
205
|
-
)
|
|
206
|
-
const tmpFile = path.join(tmpRoot, 'archive')
|
|
207
|
-
mkdirSync(tmpRoot, { recursive: true })
|
|
208
|
-
try {
|
|
209
|
-
if (process.platform === 'win32' && tryPowerShellDownload(url, tmpFile)) {
|
|
210
|
-
return readFileSync(tmpFile)
|
|
211
|
-
}
|
|
212
|
-
if (tryCurlDownload(url, tmpFile)) {
|
|
213
|
-
return readFileSync(tmpFile)
|
|
214
|
-
}
|
|
215
|
-
} finally {
|
|
216
|
-
rmSync(tmpRoot, { recursive: true, force: true })
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
throw firstError
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// --- Extract ---
|
|
223
|
-
|
|
224
|
-
function findZipEntryKey(files, want) {
|
|
225
|
-
return Object.keys(files).find(k => {
|
|
226
|
-
const norm = k.replace(/\\/g, '/')
|
|
227
|
-
return norm === want || norm.endsWith(`/${want}`)
|
|
228
|
-
})
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async function extractZip(buffer, binaryPath, extractedBinary) {
|
|
232
|
-
const binaryDir = path.dirname(binaryPath)
|
|
233
|
-
// Try fflate first (bundled dep)
|
|
234
|
-
let fflateError
|
|
235
|
-
try {
|
|
236
|
-
const { unzipSync } = require('fflate')
|
|
237
|
-
const unzipped = unzipSync(new Uint8Array(buffer))
|
|
238
|
-
const key = findZipEntryKey(unzipped, extractedBinary)
|
|
239
|
-
if (!key) {
|
|
240
|
-
throw new Error(`Binary ${extractedBinary} not found in zip`)
|
|
241
|
-
}
|
|
242
|
-
writeFileSync(binaryPath, Buffer.from(unzipped[key]))
|
|
243
|
-
return
|
|
244
|
-
} catch (e) {
|
|
245
|
-
fflateError = e
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Fallback: PowerShell Expand-Archive or unzip CLI
|
|
249
|
-
const tmpDir = path.join(binaryDir, '.tmp-download')
|
|
250
|
-
rmSync(tmpDir, { recursive: true, force: true })
|
|
251
|
-
mkdirSync(tmpDir, { recursive: true })
|
|
252
|
-
try {
|
|
253
|
-
const assetName = `archive.zip`
|
|
254
|
-
const archivePath = path.join(tmpDir, assetName)
|
|
255
|
-
writeFileSync(archivePath, buffer)
|
|
256
|
-
|
|
257
|
-
let extracted = false
|
|
258
|
-
if (process.platform === 'win32') {
|
|
259
|
-
const psCmd = `Expand-Archive -Path '${archivePath.replace(/'/g, "''")}' -DestinationPath '${tmpDir.replace(/'/g, "''")}' -Force`
|
|
260
|
-
const psResult = spawnSync(
|
|
261
|
-
'powershell.exe',
|
|
262
|
-
[
|
|
263
|
-
'-NoProfile',
|
|
264
|
-
'-NonInteractive',
|
|
265
|
-
'-ExecutionPolicy',
|
|
266
|
-
'Bypass',
|
|
267
|
-
'-Command',
|
|
268
|
-
psCmd,
|
|
269
|
-
],
|
|
270
|
-
{ stdio: 'pipe', windowsHide: true },
|
|
271
|
-
)
|
|
272
|
-
if (psResult.status === 0) {
|
|
273
|
-
extracted = true
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (!extracted) {
|
|
278
|
-
const result = spawnSync('unzip', ['-o', archivePath, '-d', tmpDir], {
|
|
279
|
-
stdio: 'pipe',
|
|
280
|
-
})
|
|
281
|
-
if (result.status !== 0) {
|
|
282
|
-
const unzipErr = result.stderr?.toString().trim() || 'command not found'
|
|
283
|
-
const fflateMsg =
|
|
284
|
-
fflateError instanceof Error
|
|
285
|
-
? fflateError.message
|
|
286
|
-
: String(fflateError)
|
|
287
|
-
throw new Error(
|
|
288
|
-
`zip extraction failed (fflate: ${fflateMsg}; unzip: ${unzipErr})`,
|
|
289
|
-
)
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const srcBinary = path.join(tmpDir, extractedBinary)
|
|
294
|
-
if (!existsSync(srcBinary)) {
|
|
295
|
-
throw new Error(`Binary not found at expected path: ${srcBinary}`)
|
|
296
|
-
}
|
|
297
|
-
renameSync(srcBinary, binaryPath)
|
|
298
|
-
} finally {
|
|
299
|
-
rmSync(tmpDir, { recursive: true, force: true })
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
async function extractTarGz(buffer, binaryPath, extractedBinary, assetName) {
|
|
304
|
-
const binaryDir = path.dirname(binaryPath)
|
|
305
|
-
const tmpDir = path.join(binaryDir, '.tmp-download')
|
|
306
|
-
rmSync(tmpDir, { recursive: true, force: true })
|
|
307
|
-
mkdirSync(tmpDir, { recursive: true })
|
|
308
|
-
try {
|
|
309
|
-
const archivePath = path.join(tmpDir, assetName)
|
|
310
|
-
writeFileSync(archivePath, buffer)
|
|
311
|
-
const result = spawnSync('tar', ['xzf', archivePath, '-C', tmpDir], {
|
|
312
|
-
stdio: 'pipe',
|
|
313
|
-
})
|
|
314
|
-
if (result.status !== 0) {
|
|
315
|
-
throw new Error(`tar extract failed: ${result.stderr?.toString()}`)
|
|
316
|
-
}
|
|
317
|
-
const srcBinary = path.join(tmpDir, extractedBinary)
|
|
318
|
-
if (!existsSync(srcBinary)) {
|
|
319
|
-
throw new Error(`Binary not found at expected path: ${srcBinary}`)
|
|
320
|
-
}
|
|
321
|
-
renameSync(srcBinary, binaryPath)
|
|
322
|
-
} finally {
|
|
323
|
-
rmSync(tmpDir, { recursive: true, force: true })
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// --- Main ---
|
|
328
|
-
|
|
329
|
-
async function downloadAndExtract() {
|
|
330
|
-
const { target, ext } = getPlatformMapping()
|
|
331
|
-
const assetName = `ripgrep-v${RG_VERSION}-${target}.${ext}`
|
|
332
|
-
|
|
333
|
-
const binaryPath = getBinaryPath()
|
|
334
|
-
const binaryDir = path.dirname(binaryPath)
|
|
335
|
-
|
|
336
|
-
const force = process.argv.includes('--force')
|
|
337
|
-
if (!force && existsSync(binaryPath)) {
|
|
338
|
-
const stat = statSync(binaryPath)
|
|
339
|
-
if (stat.size > 0) {
|
|
340
|
-
console.log(`[ripgrep] Binary already exists at ${binaryPath}, skipping.`)
|
|
341
|
-
return
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
console.log(`[ripgrep] Downloading v${RG_VERSION} for ${target}...`)
|
|
346
|
-
|
|
347
|
-
const extractedBinary = process.platform === 'win32' ? 'rg.exe' : 'rg'
|
|
348
|
-
|
|
349
|
-
const mirrors = [RELEASE_BASE]
|
|
350
|
-
if (RELEASE_BASE === DEFAULT_RELEASE_BASE.replace(/\/$/, '')) {
|
|
351
|
-
mirrors.push(MIRROR_RELEASE_BASE.replace(/\/$/, ''))
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
let buffer
|
|
355
|
-
let lastError
|
|
356
|
-
for (const base of mirrors) {
|
|
357
|
-
const url = `${base}/${assetName}`
|
|
358
|
-
try {
|
|
359
|
-
console.log(`[ripgrep] Trying ${url}`)
|
|
360
|
-
buffer = await downloadUrlToBufferWithFallback(url)
|
|
361
|
-
break
|
|
362
|
-
} catch (e) {
|
|
363
|
-
console.warn(
|
|
364
|
-
`[ripgrep] Download from ${base} failed: ${e instanceof Error ? e.message : e}`,
|
|
365
|
-
)
|
|
366
|
-
lastError = e
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
if (!buffer) {
|
|
370
|
-
throw lastError
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
try {
|
|
374
|
-
console.log(`[ripgrep] Downloaded ${Math.round(buffer.length / 1024)} KB`)
|
|
375
|
-
|
|
376
|
-
mkdirSync(binaryDir, { recursive: true })
|
|
377
|
-
|
|
378
|
-
if (ext === 'tar.gz') {
|
|
379
|
-
await extractTarGz(buffer, binaryPath, extractedBinary, assetName)
|
|
380
|
-
} else {
|
|
381
|
-
await extractZip(buffer, binaryPath, extractedBinary)
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (process.platform !== 'win32') {
|
|
385
|
-
chmodSync(binaryPath, 0o755)
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
console.log(`[ripgrep] Installed to ${binaryPath}`)
|
|
389
|
-
} catch (e) {
|
|
390
|
-
const msg = e instanceof Error ? e.message : String(e)
|
|
391
|
-
const hint =
|
|
392
|
-
'Check network or set HTTPS_PROXY. If GitHub is blocked, set RIPGREP_DOWNLOAD_BASE to a mirror (see script header).'
|
|
393
|
-
throw new Error(`${msg} ${hint}`)
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
async function main() {
|
|
398
|
-
await downloadAndExtract()
|
|
399
|
-
await setupCodegraph()
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// --- Codegraph dist/ setup ---
|
|
403
|
-
// The workspace package packages/codegraph/ is a stub (only package.json).
|
|
404
|
-
// The published @saluzi/codegraph on npm has a complete dist/ with bun:sqlite
|
|
405
|
-
// compatibility. This step downloads it so require('@saluzi/codegraph') works.
|
|
406
|
-
|
|
407
|
-
const CODEGRAPH_PKG = '@saluzi/codegraph'
|
|
408
|
-
const CODEGRAPH_METADATA_FILE = '.dist-metadata.json'
|
|
409
|
-
|
|
410
|
-
function getCodegraphVersion() {
|
|
411
|
-
// Read from root package.json dependencies, strip semver range prefix (^~>=)
|
|
412
|
-
try {
|
|
413
|
-
const pkgPath = path.join(projectRoot, 'package.json')
|
|
414
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
415
|
-
const version = pkg.dependencies?.[CODEGRAPH_PKG]
|
|
416
|
-
if (version) {
|
|
417
|
-
return version.replace(/^[\^~>=<]+/, '')
|
|
418
|
-
}
|
|
419
|
-
} catch {
|
|
420
|
-
// fallback
|
|
421
|
-
}
|
|
422
|
-
return '0.1.0'
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function getCodegraphDistIndex() {
|
|
426
|
-
return path.join(projectRoot, 'packages', 'codegraph', 'dist', 'index.js')
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function getCodegraphMetadataPath() {
|
|
430
|
-
return path.join(
|
|
431
|
-
projectRoot,
|
|
432
|
-
'packages',
|
|
433
|
-
'codegraph',
|
|
434
|
-
CODEGRAPH_METADATA_FILE,
|
|
435
|
-
)
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function readCodegraphMetadata() {
|
|
439
|
-
try {
|
|
440
|
-
const metaPath = getCodegraphMetadataPath()
|
|
441
|
-
if (existsSync(metaPath)) {
|
|
442
|
-
return JSON.parse(readFileSync(metaPath, 'utf8'))
|
|
443
|
-
}
|
|
444
|
-
} catch {
|
|
445
|
-
// corrupted or unreadable
|
|
446
|
-
}
|
|
447
|
-
return null
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function writeCodegraphMetadata(metadata) {
|
|
451
|
-
const metaPath = getCodegraphMetadataPath()
|
|
452
|
-
writeFileSync(metaPath, JSON.stringify(metadata, null, 2) + '\n')
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
async function setupCodegraph() {
|
|
456
|
-
const distIndex = getCodegraphDistIndex()
|
|
457
|
-
const force = process.argv.includes('--force')
|
|
458
|
-
const requiredVersion = getCodegraphVersion()
|
|
459
|
-
|
|
460
|
-
// Check if dist/ exists
|
|
461
|
-
if (!force && existsSync(distIndex)) {
|
|
462
|
-
// Read metadata to check version
|
|
463
|
-
const metadata = readCodegraphMetadata()
|
|
464
|
-
|
|
465
|
-
// If no metadata, assume it's a local build — don't overwrite
|
|
466
|
-
if (!metadata) {
|
|
467
|
-
console.log(
|
|
468
|
-
`[codegraph] dist/ exists without metadata (local build?), skipping.`,
|
|
469
|
-
)
|
|
470
|
-
return
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// If metadata indicates local build, don't overwrite
|
|
474
|
-
if (metadata.source === 'local') {
|
|
475
|
-
console.log(`[codegraph] dist/ is a local build, skipping.`)
|
|
476
|
-
return
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// If version matches, skip
|
|
480
|
-
if (metadata.version === requiredVersion) {
|
|
481
|
-
console.log(`[codegraph] dist/ already at v${requiredVersion}, skipping.`)
|
|
482
|
-
return
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// Version mismatch — need to update
|
|
486
|
-
console.log(
|
|
487
|
-
`[codegraph] dist/ version mismatch: installed ${metadata.version}, required ${requiredVersion}`,
|
|
488
|
-
)
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
console.log(
|
|
492
|
-
`[codegraph] Fetching ${CODEGRAPH_PKG}@${requiredVersion} from npm registry...`,
|
|
493
|
-
)
|
|
494
|
-
|
|
495
|
-
// 1. Resolve tarball URL from npm registry
|
|
496
|
-
const registryUrl = `https://registry.npmjs.org/${CODEGRAPH_PKG}/${requiredVersion}`
|
|
497
|
-
let tarballUrl
|
|
498
|
-
try {
|
|
499
|
-
const resp = await fetchRelease(registryUrl)
|
|
500
|
-
if (!resp.ok) {
|
|
501
|
-
throw new Error(`${resp.status} ${resp.statusText}`)
|
|
502
|
-
}
|
|
503
|
-
const meta = await resp.json()
|
|
504
|
-
tarballUrl = meta.dist?.tarball
|
|
505
|
-
if (!tarballUrl) {
|
|
506
|
-
throw new Error('no tarball URL in registry response')
|
|
507
|
-
}
|
|
508
|
-
} catch (e) {
|
|
509
|
-
const msg = e instanceof Error ? e.message : String(e)
|
|
510
|
-
console.warn(
|
|
511
|
-
`[codegraph] Failed to resolve tarball URL: ${msg} (non-fatal, codegraph graph features will be unavailable)`,
|
|
512
|
-
)
|
|
513
|
-
return
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// 2. Download tarball
|
|
517
|
-
let buffer
|
|
518
|
-
try {
|
|
519
|
-
buffer = await downloadUrlToBufferWithFallback(tarballUrl)
|
|
520
|
-
} catch (e) {
|
|
521
|
-
const msg = e instanceof Error ? e.message : String(e)
|
|
522
|
-
console.warn(
|
|
523
|
-
`[codegraph] Download failed: ${msg} (non-fatal, codegraph graph features will be unavailable)`,
|
|
524
|
-
)
|
|
525
|
-
return
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// 3. Extract package/dist/ → packages/codegraph/dist/
|
|
529
|
-
const tmpDir = path.join(os.tmpdir(), `codegraph-setup-${process.pid}`)
|
|
530
|
-
mkdirSync(tmpDir, { recursive: true })
|
|
531
|
-
try {
|
|
532
|
-
const tgzPath = path.join(tmpDir, 'package.tgz')
|
|
533
|
-
writeFileSync(tgzPath, buffer)
|
|
534
|
-
const result = spawnSync('tar', ['xzf', tgzPath, '-C', tmpDir], {
|
|
535
|
-
stdio: 'pipe',
|
|
536
|
-
})
|
|
537
|
-
if (result.status !== 0) {
|
|
538
|
-
throw new Error(
|
|
539
|
-
`tar extract failed: ${result.stderr?.toString() || 'unknown error'}`,
|
|
540
|
-
)
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const extractedDist = path.join(tmpDir, 'package', 'dist')
|
|
544
|
-
if (!existsSync(extractedDist)) {
|
|
545
|
-
throw new Error('extracted package has no dist/ directory')
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const targetDist = path.join(projectRoot, 'packages', 'codegraph', 'dist')
|
|
549
|
-
rmSync(targetDist, { recursive: true, force: true })
|
|
550
|
-
renameSync(extractedDist, targetDist)
|
|
551
|
-
|
|
552
|
-
// Write metadata to track installed version
|
|
553
|
-
writeCodegraphMetadata({
|
|
554
|
-
version: requiredVersion,
|
|
555
|
-
source: 'npm',
|
|
556
|
-
installedAt: new Date().toISOString(),
|
|
557
|
-
})
|
|
558
|
-
|
|
559
|
-
console.log(
|
|
560
|
-
`[codegraph] Installed dist/ to ${targetDist} (${CODEGRAPH_PKG}@${requiredVersion})`,
|
|
561
|
-
)
|
|
562
|
-
} catch (e) {
|
|
563
|
-
const msg = e instanceof Error ? e.message : String(e)
|
|
564
|
-
console.warn(
|
|
565
|
-
`[codegraph] Setup failed: ${msg} (non-fatal, codegraph graph features will be unavailable)`,
|
|
566
|
-
)
|
|
567
|
-
} finally {
|
|
568
|
-
rmSync(tmpDir, { recursive: true, force: true })
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
main().catch(error => {
|
|
573
|
-
const msg = error instanceof Error ? error.message : String(error)
|
|
574
|
-
console.error(`[postinstall] ripgrep download failed (non-fatal): ${msg}`)
|
|
575
|
-
console.error(
|
|
576
|
-
`[postinstall] You can install ripgrep manually: https://github.com/BurntSushi/ripgrep#installation`,
|
|
577
|
-
)
|
|
578
|
-
// Never exit with error code — postinstall must not break install
|
|
579
|
-
process.exit(0)
|
|
580
|
-
})
|