pacman-debian 7.3.36 → 7.5.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.
Files changed (99) hide show
  1. package/README.md +12 -0
  2. package/README_zh-CN.md +11 -0
  3. package/dist/cli/archlinux-java.js +36 -12
  4. package/dist/cli/archlinux-java.js.map +1 -1
  5. package/dist/cli/paclink.js +84 -0
  6. package/dist/cli/paclink.js.map +1 -1
  7. package/dist/cli/pacman.d.ts.map +1 -1
  8. package/dist/cli/pacman.js +66 -11
  9. package/dist/cli/pacman.js.map +1 -1
  10. package/dist/cli/pactree.d.ts +3 -0
  11. package/dist/cli/pactree.d.ts.map +1 -0
  12. package/dist/cli/pactree.js +216 -0
  13. package/dist/cli/pactree.js.map +1 -0
  14. package/dist/core/deps.d.ts +7 -0
  15. package/dist/core/deps.d.ts.map +1 -1
  16. package/dist/core/deps.js +104 -24
  17. package/dist/core/deps.js.map +1 -1
  18. package/dist/core/logger.d.ts +7 -0
  19. package/dist/core/logger.d.ts.map +1 -0
  20. package/dist/core/logger.js +93 -0
  21. package/dist/core/logger.js.map +1 -0
  22. package/dist/core/options.d.ts +2 -0
  23. package/dist/core/options.d.ts.map +1 -1
  24. package/dist/core/paclinks.d.ts +9 -0
  25. package/dist/core/paclinks.d.ts.map +1 -0
  26. package/dist/core/paclinks.js +69 -0
  27. package/dist/core/paclinks.js.map +1 -0
  28. package/dist/core/pkgfile.d.ts +2 -0
  29. package/dist/core/pkgfile.d.ts.map +1 -1
  30. package/dist/core/pkgfile.js +6 -0
  31. package/dist/core/pkgfile.js.map +1 -1
  32. package/dist/core/tar.d.ts +3 -1
  33. package/dist/core/tar.d.ts.map +1 -1
  34. package/dist/core/tar.js +87 -11
  35. package/dist/core/tar.js.map +1 -1
  36. package/dist/core/types.d.ts +17 -0
  37. package/dist/core/types.d.ts.map +1 -1
  38. package/dist/db/dpkg-compat.d.ts +5 -2
  39. package/dist/db/dpkg-compat.d.ts.map +1 -1
  40. package/dist/db/dpkg-compat.js +130 -57
  41. package/dist/db/dpkg-compat.js.map +1 -1
  42. package/dist/db/localdb.d.ts.map +1 -1
  43. package/dist/db/localdb.js +21 -2
  44. package/dist/db/localdb.js.map +1 -1
  45. package/dist/debug-upgrade.d.ts +2 -0
  46. package/dist/debug-upgrade.d.ts.map +1 -0
  47. package/dist/debug-upgrade.js +140 -0
  48. package/dist/debug-upgrade.js.map +1 -0
  49. package/dist/i18n/en.json +2 -0
  50. package/dist/i18n/paclink/en.d.ts.map +1 -1
  51. package/dist/i18n/paclink/en.js +7 -0
  52. package/dist/i18n/paclink/en.js.map +1 -1
  53. package/dist/i18n/paclink/zh-CN.d.ts.map +1 -1
  54. package/dist/i18n/paclink/zh-CN.js +7 -0
  55. package/dist/i18n/paclink/zh-CN.js.map +1 -1
  56. package/dist/i18n/zh-CN.json +2 -0
  57. package/dist/lock/dpkg-helper +0 -0
  58. package/dist/lock/dpkg-lock.d.ts +5 -0
  59. package/dist/lock/dpkg-lock.d.ts.map +1 -0
  60. package/dist/lock/dpkg-lock.js +113 -0
  61. package/dist/lock/dpkg-lock.js.map +1 -0
  62. package/dist/makepkg/build.d.ts.map +1 -1
  63. package/dist/makepkg/build.js +3 -5
  64. package/dist/makepkg/build.js.map +1 -1
  65. package/dist/makepkg/pkgbuild.d.ts.map +1 -1
  66. package/dist/makepkg/pkgbuild.js +109 -53
  67. package/dist/makepkg/pkgbuild.js.map +1 -1
  68. package/dist/ops/install.d.ts +2 -1
  69. package/dist/ops/install.d.ts.map +1 -1
  70. package/dist/ops/install.js +290 -157
  71. package/dist/ops/install.js.map +1 -1
  72. package/dist/ops/query.d.ts.map +1 -1
  73. package/dist/ops/query.js +62 -35
  74. package/dist/ops/query.js.map +1 -1
  75. package/dist/ops/remove.d.ts.map +1 -1
  76. package/dist/ops/remove.js +106 -60
  77. package/dist/ops/remove.js.map +1 -1
  78. package/dist/ops/upgrade.d.ts.map +1 -1
  79. package/dist/ops/upgrade.js +71 -13
  80. package/dist/ops/upgrade.js.map +1 -1
  81. package/dist/repo/config.d.ts.map +1 -1
  82. package/dist/repo/config.js +60 -6
  83. package/dist/repo/config.js.map +1 -1
  84. package/dist/repo/repository.d.ts +6 -0
  85. package/dist/repo/repository.d.ts.map +1 -1
  86. package/dist/repo/repository.js +348 -49
  87. package/dist/repo/repository.js.map +1 -1
  88. package/dist/scripts/setup.js +27 -77
  89. package/dist/scripts/setup.js.map +1 -1
  90. package/docs/en/architecture.md +109 -45
  91. package/docs/en/pactree.md +42 -0
  92. package/docs/en/yay-aur.md +10 -6
  93. package/docs/zh-CN/pactree.md +41 -0
  94. package/lib/pac4deb/include/alpm.h +2 -1
  95. package/lib/pac4deb/src/libalpm.c +116 -54
  96. package/lib/pac4deb/src/stubs_manual.c +11 -1
  97. package/package.json +4 -3
  98. package/resources/paclinks.conf +64 -0
  99. package/resources/pacman.conf.template +16 -2
@@ -38,16 +38,21 @@ exports.syncRepos = syncRepos;
38
38
  exports.readPkgAt = readPkgAt;
39
39
  exports.batchFindInRepo = batchFindInRepo;
40
40
  exports.findProvides = findProvides;
41
+ exports.findProvidesScoped = findProvidesScoped;
41
42
  exports.getRepoCache = getRepoCache;
42
43
  exports.invalidateCache = invalidateCache;
43
44
  exports.searchRepo = searchRepo;
44
45
  exports.findInRepo = findInRepo;
46
+ exports.findInRepoScoped = findInRepoScoped;
47
+ exports.findInRepoVersioned = findInRepoVersioned;
45
48
  exports.getPkgUrl = getPkgUrl;
46
49
  exports.downloadPkg = downloadPkg;
47
50
  const fs = __importStar(require("node:fs"));
48
51
  const path = __importStar(require("node:path"));
49
52
  const https = __importStar(require("node:https"));
50
53
  const http = __importStar(require("node:http"));
54
+ const crypto = __importStar(require("node:crypto"));
55
+ const node_child_process_1 = require("node:child_process");
51
56
  const config_1 = require("./config");
52
57
  const control_1 = require("../core/control");
53
58
  const compress_1 = require("../core/compress");
@@ -56,15 +61,48 @@ const pkgfile_1 = require("../core/pkgfile");
56
61
  const colors_1 = require("../ui/colors");
57
62
  const i18n_1 = require("../i18n");
58
63
  const progress_1 = require("../ui/progress");
64
+ const logger_1 = require("../core/logger");
59
65
  const CACHE_DIR = '/var/cache/pacman-debian';
60
66
  const PKG_CACHE = path.join(CACHE_DIR, 'packages');
61
67
  const DEB_CACHE = path.join(CACHE_DIR, 'pkg');
68
+ const activePartials = new Set();
69
+ let partialCleanupInstalled = false;
70
+ function installPartialCleanup() {
71
+ if (partialCleanupInstalled)
72
+ return;
73
+ partialCleanupInstalled = true;
74
+ process.once('SIGINT', () => {
75
+ for (const file of activePartials) {
76
+ try {
77
+ fs.unlinkSync(file);
78
+ }
79
+ catch { }
80
+ }
81
+ process.exit(130);
82
+ });
83
+ }
84
+ async function atomicWrite(filePath, content) {
85
+ const tmp = `${filePath}.tmp-${process.pid}`;
86
+ try {
87
+ await fs.promises.writeFile(tmp, content, 'utf8');
88
+ await fs.promises.rename(tmp, filePath);
89
+ }
90
+ catch (error) {
91
+ try {
92
+ await fs.promises.unlink(tmp);
93
+ }
94
+ catch { }
95
+ throw error;
96
+ }
97
+ }
62
98
  /* ---- Shared keep-alive HTTP agents ---- */
63
99
  const httpAgent = new http.Agent({ keepAlive: true, maxSockets: 8 });
64
100
  const httpsAgent = new https.Agent({ keepAlive: true, maxSockets: 8 });
65
101
  let _idxCache = new Map();
102
+ let _pkgCache = new Map(); // LRU: key = chunkFile:offset
66
103
  function invalidateIdxCache() {
67
104
  _idxCache.clear();
105
+ _pkgCache.clear();
68
106
  }
69
107
  function parseIdxLine(line) {
70
108
  const lastTab = line.lastIndexOf('\t');
@@ -81,20 +119,24 @@ function parseIdxLine(line) {
81
119
  if (!chunkFile)
82
120
  return null;
83
121
  const rest = beforeOff.slice(0, secondLastTab);
84
- const firstTab = rest.indexOf('\t');
85
- const pkgName = rest.slice(0, rest.indexOf(' '));
86
- const provides = firstTab >= 0 ? rest.slice(firstTab + 1) : '';
87
- return { pkgName, provides, chunkFile, offset };
122
+ const thirdLastTab = rest.lastIndexOf('\t');
123
+ const provides = thirdLastTab >= 0 ? rest.slice(thirdLastTab + 1) : '';
124
+ const beforeProv = thirdLastTab >= 0 ? rest.slice(0, thirdLastTab) : rest;
125
+ const firstSpace = beforeProv.indexOf(' ');
126
+ const pkgName = beforeProv.slice(0, firstSpace);
127
+ const versionText = firstSpace >= 0 ? beforeProv.slice(firstSpace + 1) : '';
128
+ const firstTab = versionText.indexOf('\t');
129
+ const version = (firstTab >= 0 ? versionText.slice(0, firstTab) : versionText).trim();
130
+ return { pkgName, version, provides, chunkFile, offset };
88
131
  }
89
132
  function getIdx(repoName) {
133
+ const cached = _idxCache.get(repoName);
134
+ if (cached)
135
+ return cached;
90
136
  const pkgDir = path.join(PKG_CACHE, repoName);
91
137
  const idxPath = path.join(pkgDir, 'packages.idx');
92
138
  if (!fs.existsSync(idxPath))
93
139
  return null;
94
- const st = fs.statSync(idxPath);
95
- const cached = _idxCache.get(repoName);
96
- if (cached && cached.mtime === st.mtimeMs)
97
- return cached;
98
140
  const lines = fs.readFileSync(idxPath, 'utf8').split('\n').filter(l => l.length > 0);
99
141
  const providesIndex = new Map();
100
142
  for (const line of lines) {
@@ -110,11 +152,28 @@ function getIdx(repoName) {
110
152
  }
111
153
  }
112
154
  }
113
- const entry = { lines, mtime: st.mtimeMs, providesIndex };
155
+ const entry = { lines, providesIndex };
114
156
  _idxCache.set(repoName, entry);
115
157
  return entry;
116
158
  }
117
- async function downloadFile(url, onProgress, ifModifiedSince) {
159
+ async function downloadFile(url, onProgress, ifModifiedSince, dest) {
160
+ const cfg = (0, config_1.loadConfig)();
161
+ // XferCommand support
162
+ if (cfg.xferCommand) {
163
+ const tmpDest = dest || path.join('/tmp', `download-${Date.now()}`);
164
+ const cmd = cfg.xferCommand.replace(/%u/g, url).replace(/%o/g, tmpDest);
165
+ const result = (0, node_child_process_1.spawnSync)('/bin/sh', ['-c', cmd], { stdio: 'pipe', timeout: 300000 });
166
+ if (result.status !== 0) {
167
+ const out = result.stdout?.toString() || '';
168
+ const err = result.stderr?.toString() || '';
169
+ (0, logger_1.logError)(`XferCommand exit ${result.status} for ${url}: ${err || out}`);
170
+ throw new Error(`XferCommand failed (exit ${result.status}): ${err || out}`);
171
+ }
172
+ if (dest)
173
+ return null;
174
+ (0, logger_1.log)(`download: ${url} -> ${tmpDest}`);
175
+ return fs.readFileSync(tmpDest);
176
+ }
118
177
  const maxRedirects = 5;
119
178
  const doRequest = (u, redirects) => {
120
179
  return new Promise((resolve, reject) => {
@@ -129,7 +188,6 @@ async function downloadFile(url, onProgress, ifModifiedSince) {
129
188
  resolve(null);
130
189
  return;
131
190
  }
132
- // Handle redirects (301, 302, 303, 307, 308)
133
191
  if (res.statusCode && (res.statusCode >= 300 && res.statusCode < 400)) {
134
192
  const loc = res.headers['location'];
135
193
  res.destroy();
@@ -148,14 +206,29 @@ async function downloadFile(url, onProgress, ifModifiedSince) {
148
206
  }
149
207
  const total = parseInt(res.headers['content-length'] || '0', 10);
150
208
  let received = 0;
151
- const chunks = [];
152
- res.on('data', (c) => {
153
- chunks.push(c);
154
- received += c.length;
155
- if (onProgress)
156
- onProgress(received, total);
157
- });
158
- res.on('end', () => resolve(Buffer.concat(chunks)));
209
+ if (dest) {
210
+ // Stream directly to file (for package downloads)
211
+ const ws = fs.createWriteStream(dest);
212
+ res.on('data', (c) => {
213
+ received += c.length;
214
+ if (onProgress)
215
+ onProgress(received, total);
216
+ });
217
+ res.pipe(ws);
218
+ ws.on('finish', () => resolve(null));
219
+ ws.on('error', reject);
220
+ }
221
+ else {
222
+ // Buffer in memory (for metadata)
223
+ const chunks = [];
224
+ res.on('data', (c) => {
225
+ chunks.push(c);
226
+ received += c.length;
227
+ if (onProgress)
228
+ onProgress(received, total);
229
+ });
230
+ res.on('end', () => resolve(Buffer.concat(chunks)));
231
+ }
159
232
  });
160
233
  req.on('error', reject);
161
234
  req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
@@ -182,36 +255,124 @@ function parseDebianPackages(content, repo) {
182
255
  }
183
256
  return pkgs;
184
257
  }
258
+ function parseReleaseSha256(text) {
259
+ const map = new Map();
260
+ let inSha256 = false;
261
+ for (const line of text.split('\n')) {
262
+ const t = line.trim();
263
+ if (t === 'SHA256:') {
264
+ inSha256 = true;
265
+ continue;
266
+ }
267
+ if (inSha256) {
268
+ if (!t || t === '') {
269
+ inSha256 = false;
270
+ continue;
271
+ }
272
+ const m = t.match(/^([a-f0-9]{64})\s+\d+\s+(.+)$/);
273
+ if (m)
274
+ map.set(m[2], m[1]);
275
+ }
276
+ }
277
+ return map;
278
+ }
279
+ function debArch(arch) {
280
+ const map = { aarch64: 'arm64', x86_64: 'amd64', x64: 'amd64', i686: 'i386' };
281
+ return map[arch] || arch;
282
+ }
185
283
  async function syncDebian(repo, arch, ifModifiedSince, onProgress) {
284
+ arch = debArch(arch);
186
285
  const comps = repo.components || ['main'];
286
+ const pkgDir = path.join(PKG_CACHE, repo.name);
287
+ let info = {};
288
+ const infoFile = path.join(pkgDir, '.info');
289
+ if (fs.existsSync(infoFile)) {
290
+ try {
291
+ info = JSON.parse(fs.readFileSync(infoFile, 'utf8'));
292
+ }
293
+ catch {
294
+ info = {};
295
+ }
296
+ }
297
+ const oldSha256 = info.sha256 || {};
298
+ // Download Release file first (like apt does)
299
+ const releaseBase = `${repo.server}/dists/${repo.dist}`;
300
+ let releaseText = null;
301
+ let releaseSize = 0;
302
+ for (const rfile of ['InRelease', 'Release']) {
303
+ try {
304
+ const releaseBuf = await downloadFile(`${releaseBase}/${rfile}`, (rec, tot) => {
305
+ if (onProgress)
306
+ onProgress(rec, tot);
307
+ }, ifModifiedSince);
308
+ if (releaseBuf === null)
309
+ return { pkgs: [], size: 0, notModified: true };
310
+ releaseText = releaseBuf.toString('utf8');
311
+ releaseSize = releaseBuf.length;
312
+ break;
313
+ }
314
+ catch { }
315
+ }
316
+ if (!releaseText)
317
+ return { pkgs: [], size: 0, notModified: true };
318
+ const sha256Map = parseReleaseSha256(releaseText);
187
319
  const all = [];
188
- let totalSize = 0;
320
+ let totalSize = releaseSize;
321
+ let anyData = false;
322
+ const newSha256 = {};
189
323
  for (const comp of comps) {
190
- const base = `${repo.server}/dists/${repo.dist}/${comp}/binary-${arch}/Packages`;
191
- for (const ext of ['gz', 'xz']) {
192
- let gotData = false;
324
+ const prefix = `${comp}/binary-${arch}/Packages`;
325
+ let downloaded = false;
326
+ for (const ext of ['xz', 'gz']) {
327
+ const key = `${prefix}.${ext}`;
328
+ const expectedHash = sha256Map.get(key);
329
+ if (!expectedHash)
330
+ continue;
331
+ newSha256[key] = expectedHash;
332
+ if (oldSha256[key] === expectedHash)
333
+ continue; // SHA256 matches, skip download
193
334
  try {
194
- const url = `${base}.${ext}`;
335
+ const url = `${releaseBase}/${key}`;
195
336
  const buf = await downloadFile(url, (rec, tot) => {
196
337
  if (onProgress)
197
338
  onProgress(totalSize + rec, totalSize + (tot || 0));
198
- }, ifModifiedSince);
199
- if (buf === null)
200
- return { pkgs: [], size: 0, notModified: true }; // 304
201
- gotData = true;
339
+ }, undefined);
340
+ if (!buf)
341
+ continue;
342
+ downloaded = true;
343
+ anyData = true;
202
344
  totalSize += buf.length;
203
- const text = (await (0, compress_1.decompressAsync)(buf, `packages.${ext}`)).toString('utf8');
345
+ const text = (await (0, compress_1.decompressAsync)(buf, key)).toString('utf8');
204
346
  all.push(...parseDebianPackages(text, repo.name));
205
347
  break;
206
348
  }
207
- catch (e) {
208
- if (gotData)
209
- throw e;
210
- }
349
+ catch { }
211
350
  }
212
351
  ifModifiedSince = undefined;
213
352
  }
214
- return { pkgs: all, size: totalSize, notModified: false };
353
+ // Save SHA256 info only if anything was downloaded
354
+ if (anyData) {
355
+ info.sha256 = newSha256;
356
+ info.total = all.length;
357
+ try {
358
+ fs.mkdirSync(pkgDir, { recursive: true });
359
+ fs.writeFileSync(infoFile, JSON.stringify(info));
360
+ }
361
+ catch { }
362
+ }
363
+ // If idx is empty but Release was fresh → force re-download next time
364
+ if (!anyData && all.length === 0) {
365
+ const idxPath = path.join(pkgDir, 'packages.idx');
366
+ if (!fs.existsSync(idxPath) || fs.statSync(idxPath).size < 10) {
367
+ // Clear cached SHA256 so next sync forces Packages download
368
+ delete info.sha256;
369
+ try {
370
+ fs.writeFileSync(infoFile, JSON.stringify(info));
371
+ }
372
+ catch { }
373
+ }
374
+ }
375
+ return { pkgs: all, size: totalSize, notModified: !anyData };
215
376
  }
216
377
  function parseArchDb(dbTar, repo) {
217
378
  const pkgs = [];
@@ -449,6 +610,11 @@ async function syncRepos(force = false) {
449
610
  progress.setRow(idx, `\x1b[K ${pname}${' '.repeat(fixedNameWidth - repo.name.length)}${colors_1.color.ok((0, i18n_1.t)('repo_already_uptodate'))}`);
450
611
  return;
451
612
  }
613
+ // Show "up to date" when 0 bytes downloaded (304 or empty sync)
614
+ if (totalDownloaded === 0 && totalExpected === 0 && ifModifiedSince) {
615
+ progress.setRow(idx, `\x1b[K ${pname}${' '.repeat(fixedNameWidth - repo.name.length)}${colors_1.color.ok((0, i18n_1.t)('repo_already_uptodate'))}`);
616
+ return;
617
+ }
452
618
  // Write JSON Lines chunks (parallel per chunk)
453
619
  const pkgDir = path.join(PKG_CACHE, repo.name);
454
620
  if (!fs.existsSync(pkgDir))
@@ -461,7 +627,7 @@ async function syncRepos(force = false) {
461
627
  const chunk = pkgs.slice(c * CHUNK, (c + 1) * CHUNK);
462
628
  const lines = chunk.map(p => JSON.stringify(p)).join('\n');
463
629
  const fname = `${String(c).padStart(5, '0')}.jsonl`;
464
- writeTasks.push(fs.promises.writeFile(path.join(pkgDir, fname), lines + '\n'));
630
+ writeTasks.push(atomicWrite(path.join(pkgDir, fname), lines + '\n'));
465
631
  // Build index with byte offsets (computed from previous chunk lengths)
466
632
  let offset = 0;
467
633
  for (let pi = 0; pi < chunk.length; pi++) {
@@ -469,19 +635,20 @@ async function syncRepos(force = false) {
469
635
  const json = JSON.stringify(p);
470
636
  const desc = (p.description || '').replace(/\\/g, '\\\\').replace(/\t/g, '\\t').replace(/\n/g, '\\n');
471
637
  const provides = (p.provides || '').replace(/\\/g, '\\\\').replace(/\t/g, '\\t').replace(/\n/g, '\\n');
472
- idxLines.push(`${p.package} ${desc}\t${provides}\t${fname}\t${offset}`);
638
+ const version = (p.version || '').replace(/\\/g, '\\\\').replace(/\t/g, '\\t').replace(/\n/g, '\\n');
639
+ idxLines.push(`${p.package} ${version}\t${desc}\t${provides}\t${fname}\t${offset}`);
473
640
  offset += Buffer.byteLength(json, 'utf8') + 1;
474
641
  }
475
642
  }
476
643
  await Promise.all(writeTasks);
477
- await fs.promises.writeFile(path.join(pkgDir, '.info'), JSON.stringify({ total: pkgs.length, chunks, chunkSize: CHUNK }));
644
+ await atomicWrite(path.join(pkgDir, '.info'), JSON.stringify({ total: pkgs.length, chunks, chunkSize: CHUNK }));
478
645
  // Remove legacy all.json
479
646
  try {
480
647
  fs.unlinkSync(path.join(pkgDir, 'all.json'));
481
648
  }
482
649
  catch { }
483
650
  idxLines.sort(); // global sort so binary search works
484
- await fs.promises.writeFile(path.join(pkgDir, 'packages.idx'), idxLines.join('\n') + '\n');
651
+ await atomicWrite(path.join(pkgDir, 'packages.idx'), idxLines.join('\n') + '\n');
485
652
  // Final line
486
653
  const elapsed = (Date.now() - startTime) / 1000;
487
654
  const totalSec = Math.round(elapsed);
@@ -636,6 +803,26 @@ function findProvides(name) {
636
803
  }
637
804
  return undefined;
638
805
  }
806
+ /** Look up a virtual provider in one repository only. */
807
+ function findProvidesScoped(repoName, name) {
808
+ const cfg = (0, config_1.loadConfig)();
809
+ const repo = cfg.repos.find(r => r.name === repoName);
810
+ if (!repo)
811
+ return undefined;
812
+ const idxEntry = getIdx(repo.name);
813
+ if (!idxEntry)
814
+ return undefined;
815
+ const entries = idxEntry.providesIndex.get(name);
816
+ if (!entries)
817
+ return undefined;
818
+ const pkgDir = path.join(PKG_CACHE, repo.name);
819
+ for (const e of entries) {
820
+ const p = readPkgAt(pkgDir, e.chunkFile, e.offset);
821
+ if (p)
822
+ return p;
823
+ }
824
+ return undefined;
825
+ }
639
826
  // ---- Cache ----
640
827
  let _cache = null;
641
828
  function getRepoCache() {
@@ -676,7 +863,8 @@ function invalidateCache() {
676
863
  invalidateIdxCache();
677
864
  }
678
865
  function searchRepo(query) {
679
- const lq = query.toLowerCase();
866
+ const sl = query.indexOf('/');
867
+ const lq = (sl > 0 ? query.slice(sl + 1) : query).toLowerCase();
680
868
  const results = [];
681
869
  const cfg = (0, config_1.loadConfig)();
682
870
  const seen = new Set();
@@ -693,14 +881,24 @@ function searchRepo(query) {
693
881
  if (!info || seen.has(info.pkgName))
694
882
  continue;
695
883
  seen.add(info.pkgName);
696
- const p = readPkgAt(pkgDir, info.chunkFile, info.offset);
697
- if (p)
884
+ // Read from cache first, fallback to JSONL
885
+ const cacheKey = `${info.chunkFile}:${info.offset}`;
886
+ let p = _pkgCache.get(cacheKey);
887
+ if (!p) {
888
+ p = readPkgAt(pkgDir, info.chunkFile, info.offset);
889
+ if (p)
890
+ _pkgCache.set(cacheKey, p);
891
+ }
892
+ if (p && results.length < 50)
698
893
  results.push(p);
699
894
  }
700
895
  }
701
896
  return results;
702
897
  }
703
898
  function findInRepo(pkgName) {
899
+ const sl = pkgName.indexOf('/');
900
+ if (sl > 0)
901
+ return findInRepoScoped(pkgName.slice(0, sl), pkgName.slice(sl + 1));
704
902
  const cfg = (0, config_1.loadConfig)();
705
903
  for (const repo of cfg.repos) {
706
904
  const idxEntry = getIdx(repo.name);
@@ -719,10 +917,79 @@ function findInRepo(pkgName) {
719
917
  lo = mid + 1;
720
918
  else {
721
919
  const info = parseIdxLine(line);
722
- if (info)
723
- return readPkgAt(pkgDir, info.chunkFile, info.offset);
920
+ if (!info)
921
+ return undefined;
922
+ const cacheKey = `${info.chunkFile}:${info.offset}`;
923
+ let p = _pkgCache.get(cacheKey);
924
+ if (!p) {
925
+ p = readPkgAt(pkgDir, info.chunkFile, info.offset);
926
+ if (p)
927
+ _pkgCache.set(cacheKey, p);
928
+ }
929
+ return p;
930
+ }
931
+ }
932
+ }
933
+ return undefined;
934
+ }
935
+ /** Search only in a specific repo by name (e.g. "extra", "trixie"). */
936
+ function findInRepoScoped(repoName, pkgName) {
937
+ const cfg = (0, config_1.loadConfig)();
938
+ const repo = cfg.repos.find(r => r.name === repoName);
939
+ if (!repo)
940
+ return undefined;
941
+ const idxEntry = getIdx(repo.name);
942
+ if (!idxEntry)
943
+ return undefined;
944
+ const { lines } = idxEntry;
945
+ const pkgDir = path.join(PKG_CACHE, repo.name);
946
+ let lo = 0, hi = lines.length - 1;
947
+ while (lo <= hi) {
948
+ const mid = (lo + hi) >>> 1;
949
+ const line = lines[mid];
950
+ const pname = line.slice(0, line.indexOf(' '));
951
+ if (pkgName < pname)
952
+ hi = mid - 1;
953
+ else if (pkgName > pname)
954
+ lo = mid + 1;
955
+ else {
956
+ const info = parseIdxLine(line);
957
+ if (!info)
724
958
  return undefined;
959
+ const cacheKey = `${info.chunkFile}:${info.offset}`;
960
+ let p = _pkgCache.get(cacheKey);
961
+ if (!p) {
962
+ p = readPkgAt(pkgDir, info.chunkFile, info.offset);
963
+ if (p)
964
+ _pkgCache.set(cacheKey, p);
725
965
  }
966
+ return p;
967
+ }
968
+ }
969
+ return undefined;
970
+ }
971
+ /** Find an exact package version, respecting repository order or a scope. */
972
+ function findInRepoVersioned(pkgName, version, repoName) {
973
+ const cfg = (0, config_1.loadConfig)();
974
+ const repos = repoName ? cfg.repos.filter(r => r.name === repoName) : cfg.repos;
975
+ for (const repo of repos) {
976
+ const idxEntry = getIdx(repo.name);
977
+ if (!idxEntry)
978
+ continue;
979
+ const pkgDir = path.join(PKG_CACHE, repo.name);
980
+ for (const line of idxEntry.lines) {
981
+ const info = parseIdxLine(line);
982
+ if (!info || info.pkgName !== pkgName)
983
+ continue;
984
+ const cacheKey = `${info.chunkFile}:${info.offset}`;
985
+ let p = _pkgCache.get(cacheKey);
986
+ if (!p) {
987
+ p = readPkgAt(pkgDir, info.chunkFile, info.offset);
988
+ if (p)
989
+ _pkgCache.set(cacheKey, p);
990
+ }
991
+ if (p && p.package === pkgName && p.version === version)
992
+ return p;
726
993
  }
727
994
  }
728
995
  return undefined;
@@ -747,13 +1014,45 @@ async function downloadPkg(rp, dest, onProgress) {
747
1014
  fs.mkdirSync(DEB_CACHE, { recursive: true });
748
1015
  const fn = path.basename(rp.filename);
749
1016
  const local = path.join(dest || DEB_CACHE, fn);
750
- if (fs.existsSync(local))
751
- return local;
1017
+ if (fs.existsSync(local)) {
1018
+ if (!rp.sha256)
1019
+ return local;
1020
+ try {
1021
+ const hash = crypto.createHash('sha256').update(fs.readFileSync(local)).digest('hex');
1022
+ if (hash === rp.sha256)
1023
+ return local;
1024
+ fs.unlinkSync(local);
1025
+ }
1026
+ catch { }
1027
+ }
752
1028
  const url = getPkgUrl(rp);
753
- const data = await downloadFile(url, onProgress);
754
- if (!data)
755
- throw new Error('failed to download package');
756
- fs.writeFileSync(local, data);
1029
+ const partial = `${local}.part`;
1030
+ try {
1031
+ fs.unlinkSync(partial);
1032
+ }
1033
+ catch { }
1034
+ installPartialCleanup();
1035
+ activePartials.add(partial);
1036
+ // Stream directly to file instead of buffering in memory
1037
+ try {
1038
+ const data = await downloadFile(url, onProgress, undefined, partial);
1039
+ if (data === null)
1040
+ fs.renameSync(partial, local);
1041
+ else if (data)
1042
+ fs.writeFileSync(partial, data);
1043
+ if (data)
1044
+ fs.renameSync(partial, local);
1045
+ }
1046
+ catch (error) {
1047
+ try {
1048
+ fs.unlinkSync(partial);
1049
+ }
1050
+ catch { }
1051
+ throw error;
1052
+ }
1053
+ finally {
1054
+ activePartials.delete(partial);
1055
+ }
757
1056
  return local;
758
1057
  }
759
1058
  //# sourceMappingURL=repository.js.map