pacman-debian 7.2.0 → 7.3.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.
- package/README.md +22 -2
- package/README_zh-CN.md +89 -19
- package/dist/cli/paclink.d.ts.map +1 -0
- package/dist/cli/paclink.js +261 -0
- package/dist/cli/paclink.js.map +1 -0
- package/dist/cli/pacman.d.ts.map +1 -1
- package/dist/cli/pacman.js +64 -38
- package/dist/cli/pacman.js.map +1 -1
- package/dist/core/compress.d.ts.map +1 -1
- package/dist/core/compress.js +29 -0
- package/dist/core/compress.js.map +1 -1
- package/dist/core/deps.d.ts.map +1 -1
- package/dist/core/deps.js +133 -68
- package/dist/core/deps.js.map +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/database.js +3 -1
- package/dist/db/database.js.map +1 -1
- package/dist/db/localdb.d.ts.map +1 -1
- package/dist/db/localdb.js +73 -4
- package/dist/db/localdb.js.map +1 -1
- package/dist/i18n/en.json +6 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +52 -6
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/paclink/en.d.ts.map +1 -0
- package/dist/i18n/paclink/en.js +62 -0
- package/dist/i18n/paclink/en.js.map +1 -0
- package/dist/i18n/paclink/zh-CN.d.ts.map +1 -0
- package/dist/i18n/paclink/zh-CN.js +62 -0
- package/dist/i18n/paclink/zh-CN.js.map +1 -0
- package/dist/i18n/setup/en.d.ts.map +1 -0
- package/dist/i18n/setup/en.js +49 -0
- package/dist/i18n/setup/en.js.map +1 -0
- package/dist/i18n/setup/zh-CN.d.ts.map +1 -0
- package/dist/i18n/setup/zh-CN.js +49 -0
- package/dist/i18n/setup/zh-CN.js.map +1 -0
- package/dist/i18n/zh-CN.json +6 -0
- package/dist/ops/install.d.ts.map +1 -1
- package/dist/ops/install.js +238 -45
- package/dist/ops/install.js.map +1 -1
- package/dist/ops/query.d.ts.map +1 -1
- package/dist/ops/query.js +25 -9
- package/dist/ops/query.js.map +1 -1
- package/dist/ops/remove.d.ts.map +1 -1
- package/dist/ops/remove.js +213 -42
- package/dist/ops/remove.js.map +1 -1
- package/dist/ops/upgrade.d.ts.map +1 -1
- package/dist/ops/upgrade.js +13 -15
- package/dist/ops/upgrade.js.map +1 -1
- package/dist/repo/repository.d.ts.map +1 -1
- package/dist/repo/repository.js +246 -90
- package/dist/repo/repository.js.map +1 -1
- package/dist/scripts/setup.js +119 -116
- package/dist/scripts/setup.js.map +1 -1
- package/package.json +18 -7
- package/README.zh.md +0 -388
- package/dist/ar.d.ts +0 -8
- package/dist/cli/pacman.d.ts +0 -2
- package/dist/compress.d.ts +0 -2
- package/dist/config.d.ts +0 -3
- package/dist/control.d.ts +0 -2
- package/dist/core/ar.d.ts +0 -7
- package/dist/core/compress.d.ts +0 -2
- package/dist/core/control.d.ts +0 -2
- package/dist/core/deb.d.ts +0 -4
- package/dist/core/deps.d.ts +0 -25
- package/dist/core/options.d.ts +0 -19
- package/dist/core/pkgfile.d.ts +0 -35
- package/dist/core/tar.d.ts +0 -13
- package/dist/core/types.d.ts +0 -83
- package/dist/database.d.ts +0 -20
- package/dist/db/database.d.ts +0 -17
- package/dist/db/dpkg-compat.d.ts +0 -19
- package/dist/db/localdb.d.ts +0 -9
- package/dist/db/sqlite.d.ts +0 -20
- package/dist/deb.d.ts +0 -5
- package/dist/dpkg-compat.d.ts +0 -18
- package/dist/i18n/index.d.ts +0 -3
- package/dist/index.d.ts +0 -3
- package/dist/install.d.ts +0 -2
- package/dist/makepkg/build.d.ts +0 -19
- package/dist/makepkg/index.d.ts +0 -3
- package/dist/makepkg/pkgbuild.d.ts +0 -36
- package/dist/ops/install.d.ts +0 -5
- package/dist/ops/query.d.ts +0 -9
- package/dist/ops/remove.d.ts +0 -3
- package/dist/ops/upgrade.d.ts +0 -4
- package/dist/pacman.d.ts +0 -2
- package/dist/query.d.ts +0 -5
- package/dist/remove.d.ts +0 -2
- package/dist/repo/config.d.ts +0 -3
- package/dist/repo/repository.d.ts +0 -10
- package/dist/repository.d.ts +0 -10
- package/dist/scripts/pacman-conf.d.ts +0 -3
- package/dist/scripts/setup.d.ts +0 -3
- package/dist/tar.d.ts +0 -17
- package/dist/types.d.ts +0 -80
- package/dist/ui/colors.d.ts +0 -13
- package/dist/ui/format.d.ts +0 -3
- package/dist/ui/progress.d.ts +0 -8
- package/dist/ui/prompt.d.ts +0 -4
- package/lib/pac4deb/Makefile +0 -26
- package/lib/pac4deb/README.md +0 -47
- package/lib/pac4deb/include/alpm.h +0 -166
- package/lib/pac4deb/include/alpm_list.h +0 -42
- package/lib/pac4deb/libalpm.so +0 -0
- package/lib/pac4deb/src/alpm_list.c +0 -102
- package/lib/pac4deb/src/genstubs.sh +0 -51
- package/lib/pac4deb/src/genstubs2.sh +0 -72
- package/lib/pac4deb/src/genstubs3.sh +0 -43
- package/lib/pac4deb/src/libalpm.c +0 -537
- package/lib/pac4deb/src/stubs_manual.c +0 -198
- package/lib/pac4deb/stubs.c +0 -6
- package/lib/pac4deb/update_header.sh +0 -15
- package/src/cli/pacman.ts +0 -308
- package/src/core/ar.ts +0 -54
- package/src/core/compress.ts +0 -27
- package/src/core/control.ts +0 -22
- package/src/core/deb.ts +0 -47
- package/src/core/deps.ts +0 -260
- package/src/core/options.ts +0 -20
- package/src/core/pkgfile.ts +0 -146
- package/src/core/tar.ts +0 -101
- package/src/core/types.ts +0 -89
- package/src/db/database.ts +0 -102
- package/src/db/dpkg-compat.ts +0 -129
- package/src/db/localdb.ts +0 -181
- package/src/i18n/en.json +0 -114
- package/src/i18n/index.ts +0 -32
- package/src/i18n/zh-CN.json +0 -114
- package/src/index.ts +0 -7
- package/src/makepkg/build.ts +0 -351
- package/src/makepkg/index.ts +0 -87
- package/src/makepkg/pkgbuild.ts +0 -146
- package/src/ops/install.ts +0 -260
- package/src/ops/query.ts +0 -117
- package/src/ops/remove.ts +0 -77
- package/src/ops/upgrade.ts +0 -87
- package/src/repo/config.ts +0 -96
- package/src/repo/repository.ts +0 -520
- package/src/scripts/pacman-conf.ts +0 -68
- package/src/scripts/setup.ts +0 -261
- package/src/ui/colors.ts +0 -40
- package/src/ui/format.ts +0 -9
- package/src/ui/progress.ts +0 -26
- package/src/ui/prompt.ts +0 -21
- package/tsconfig.json +0 -19
package/src/repo/repository.ts
DELETED
|
@@ -1,520 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import * as https from 'node:https';
|
|
4
|
-
import * as http from 'node:http';
|
|
5
|
-
import * as zlib from 'node:zlib';
|
|
6
|
-
|
|
7
|
-
import { loadConfig } from './config';
|
|
8
|
-
import { parseControlFile } from '../core/control';
|
|
9
|
-
import { decompress } from '../core/compress';
|
|
10
|
-
import { iterateTar, readFileFromTar } from '../core/tar';
|
|
11
|
-
import { parseDescFile } from '../core/pkgfile';
|
|
12
|
-
import type { RepoPkg, RepoConfig } from '../core/types';
|
|
13
|
-
import { color } from '../ui/colors';
|
|
14
|
-
import { t } from '../i18n';
|
|
15
|
-
import { humanSize, formatRate, formatETA, drawProgressBar } from '../ui/progress';
|
|
16
|
-
|
|
17
|
-
const CACHE_DIR = '/var/cache/pacman-debian';
|
|
18
|
-
const PKG_CACHE = path.join(CACHE_DIR, 'packages');
|
|
19
|
-
const DEB_CACHE = path.join(CACHE_DIR, 'pkg');
|
|
20
|
-
|
|
21
|
-
async function downloadFile(url: string, onProgress?: (received: number, total: number) => void, ifModifiedSince?: string): Promise<Buffer | null> {
|
|
22
|
-
const maxRedirects = 5;
|
|
23
|
-
const doRequest = (u: string, redirects: number): Promise<Buffer | null> => {
|
|
24
|
-
return new Promise((resolve, reject) => {
|
|
25
|
-
const mod = u.startsWith('https') ? https : http;
|
|
26
|
-
const headers: Record<string, string> = { 'User-Agent': 'Wget/1.21' };
|
|
27
|
-
if (ifModifiedSince) headers['If-Modified-Since'] = ifModifiedSince;
|
|
28
|
-
|
|
29
|
-
mod.get(u, { headers }, (res) => {
|
|
30
|
-
if (res.statusCode === 304) { resolve(null); return; }
|
|
31
|
-
if (res.statusCode && (res.statusCode >= 300 && res.statusCode < 400)) {
|
|
32
|
-
const loc = res.headers['location'];
|
|
33
|
-
if (!loc || redirects >= maxRedirects) { reject(new Error('redirect limit')); return; }
|
|
34
|
-
const next = loc.startsWith('http') ? loc : new URL(loc, u).href;
|
|
35
|
-
doRequest(next, redirects + 1).then(resolve, reject);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
|
|
39
|
-
reject(new Error(`HTTP ${res.statusCode} for ${u}`));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
const total = parseInt(res.headers['content-length'] || '0', 10);
|
|
43
|
-
let received = 0;
|
|
44
|
-
const chunks: Buffer[] = [];
|
|
45
|
-
res.on('data', (c: Buffer) => {
|
|
46
|
-
chunks.push(c);
|
|
47
|
-
received += c.length;
|
|
48
|
-
if (onProgress) onProgress(received, total);
|
|
49
|
-
});
|
|
50
|
-
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
51
|
-
}).on('error', reject).on('timeout', function (this: any) { this.destroy(); reject(new Error('timeout')); });
|
|
52
|
-
});
|
|
53
|
-
};
|
|
54
|
-
return doRequest(url, 0);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function parseDebianPackages(content: string, repo: string): RepoPkg[] {
|
|
58
|
-
const pkgs: RepoPkg[] = [];
|
|
59
|
-
for (const entry of content.split('\n\n').filter(Boolean)) {
|
|
60
|
-
const f = parseControlFile(entry);
|
|
61
|
-
if (!f['package']) continue;
|
|
62
|
-
pkgs.push({
|
|
63
|
-
package: f['package'], version: f['version'] || '0.0',
|
|
64
|
-
architecture: f['architecture'] || 'amd64',
|
|
65
|
-
description: f['description']?.split('\n')[0],
|
|
66
|
-
depends: f['depends'], conflicts: f['conflicts'], provides: f['provides'],
|
|
67
|
-
filename: f['filename'] || '',
|
|
68
|
-
size: f['size'] ? parseInt(f['size'], 10) : undefined,
|
|
69
|
-
installedSize: f['installed-size'] ? parseInt(f['installed-size'], 10) : undefined,
|
|
70
|
-
sha256: f['sha256'], repo, repoType: 'debian',
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
return pkgs;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function syncDebian(repo: RepoConfig, arch: string, ifModifiedSince?: string, onProgress?: (rec: number, tot: number) => void): Promise<{ pkgs: RepoPkg[]; size: number; notModified: boolean }> {
|
|
77
|
-
const all: RepoPkg[] = [];
|
|
78
|
-
let totalSize = 0;
|
|
79
|
-
const comps = repo.components || ['main'];
|
|
80
|
-
for (const comp of comps) {
|
|
81
|
-
const base = `${repo.server}/dists/${repo.dist}/${comp}/binary-${arch}/Packages`;
|
|
82
|
-
for (const ext of ['gz', 'xz']) {
|
|
83
|
-
let gotData = false;
|
|
84
|
-
try {
|
|
85
|
-
const url = `${base}.${ext}`;
|
|
86
|
-
const buf = await downloadFile(url, (rec, tot) => {
|
|
87
|
-
if (onProgress) onProgress(totalSize + rec, totalSize + (tot || 0));
|
|
88
|
-
}, ifModifiedSince);
|
|
89
|
-
if (buf === null) return { pkgs: [], size: 0, notModified: true }; // 304
|
|
90
|
-
gotData = true;
|
|
91
|
-
totalSize += buf.length;
|
|
92
|
-
const text = decompress(buf, `packages.${ext}`).toString('utf8');
|
|
93
|
-
all.push(...parseDebianPackages(text, repo.name));
|
|
94
|
-
break;
|
|
95
|
-
} catch (e: any) { if (gotData) throw e; }
|
|
96
|
-
}
|
|
97
|
-
ifModifiedSince = undefined;
|
|
98
|
-
}
|
|
99
|
-
return { pkgs: all, size: totalSize, notModified: false };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function parseArchDb(dbTar: Buffer, repo: string): RepoPkg[] {
|
|
103
|
-
const pkgs: RepoPkg[] = [];
|
|
104
|
-
const entries = new Map<string, Buffer[]>();
|
|
105
|
-
for (const entry of iterateTar(dbTar)) {
|
|
106
|
-
const parts = entry.name.split('/');
|
|
107
|
-
if (parts.length < 2) continue;
|
|
108
|
-
const pkgDir = parts[0];
|
|
109
|
-
const fileName = parts.slice(1).join('/');
|
|
110
|
-
if (!entries.has(pkgDir)) entries.set(pkgDir, []);
|
|
111
|
-
const data = entry.data || Buffer.alloc(0);
|
|
112
|
-
const nameBuf = Buffer.from(fileName + '\0');
|
|
113
|
-
const combined = Buffer.concat([nameBuf, data]);
|
|
114
|
-
entries.get(pkgDir)!.push(combined);
|
|
115
|
-
}
|
|
116
|
-
for (const [dir, files] of entries) {
|
|
117
|
-
let descContent = '', dependsContent = '';
|
|
118
|
-
for (const combined of files) {
|
|
119
|
-
const nullIdx = combined.indexOf(0);
|
|
120
|
-
if (nullIdx === -1) continue;
|
|
121
|
-
const name = combined.subarray(0, nullIdx).toString('utf8');
|
|
122
|
-
const content = combined.subarray(nullIdx + 1).toString('utf8');
|
|
123
|
-
if (name === 'desc') descContent = content;
|
|
124
|
-
else if (name === 'depends') dependsContent = content;
|
|
125
|
-
}
|
|
126
|
-
if (!descContent) continue;
|
|
127
|
-
const desc = parseDescFile(descContent);
|
|
128
|
-
const dependsParsed = parseDescFile(dependsContent);
|
|
129
|
-
const depends = (dependsParsed['depends'] || []).map(l => l.trim().split(/[<>=]/)[0].trim()).filter(Boolean);
|
|
130
|
-
const filename = (desc['filename'] || [''])[0];
|
|
131
|
-
const pkgName = (desc['name'] || [''])[0];
|
|
132
|
-
const version = (desc['version'] || [''])[0];
|
|
133
|
-
if (!pkgName || !version) continue;
|
|
134
|
-
const arch = (desc['arch'] || [''])[0];
|
|
135
|
-
const csize = (desc['csize'] || [''])[0];
|
|
136
|
-
const isize = (desc['isize'] || [''])[0];
|
|
137
|
-
const descText = (desc['desc'] || [''])[0];
|
|
138
|
-
pkgs.push({
|
|
139
|
-
package: pkgName, version, architecture: arch || 'any', description: descText,
|
|
140
|
-
depends: depends.join(', '), conflicts: (desc['conflicts'] || []).join(', '),
|
|
141
|
-
provides: (desc['provides'] || []).join(', '), filename,
|
|
142
|
-
size: csize ? parseInt(csize, 10) : undefined,
|
|
143
|
-
installedSize: isize ? Math.ceil(parseInt(isize, 10) / 1024) : undefined,
|
|
144
|
-
repo, repoType: 'arch',
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
return pkgs;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function resolveServer(server: string, repoName: string, arch: string): string {
|
|
151
|
-
return server.replace(/\$repo/g, repoName).replace(/\$arch/g, arch);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async function syncArch(repo: RepoConfig, globalArch: string, ifModifiedSince?: string, onProgress?: (rec: number, tot: number) => void): Promise<RepoPkg[]> {
|
|
155
|
-
const arch = repo.architecture || globalArch;
|
|
156
|
-
const baseUrl = resolveServer(repo.server, repo.name, arch);
|
|
157
|
-
const dbFile = repo.dbFile || `${repo.name}.db.tar.gz`;
|
|
158
|
-
const url = `${baseUrl}/${dbFile}`;
|
|
159
|
-
const buf = await downloadFile(url, onProgress, ifModifiedSince);
|
|
160
|
-
if (buf === null) return []; // 304 — up to date
|
|
161
|
-
const tar = decompress(buf, 'repo.tar.gz');
|
|
162
|
-
return parseArchDb(tar, repo.name);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ---- Multi-line progress: each repo gets its own row ----
|
|
166
|
-
// Uses ANSI cursor-up (ESC[A) to reach a specific row, writes in-place,
|
|
167
|
-
// then cursor-down (ESC[B) back to bottom. No absolute positioning.
|
|
168
|
-
class RepoProgress {
|
|
169
|
-
private rows: string[] = [];
|
|
170
|
-
private dirty: number[] = [];
|
|
171
|
-
private timer: ReturnType<typeof setInterval> | null = null;
|
|
172
|
-
private count = 0;
|
|
173
|
-
|
|
174
|
-
init(names: string[]) {
|
|
175
|
-
this.count = names.length;
|
|
176
|
-
// Initialize rows with just the repo name (shown before download starts)
|
|
177
|
-
this.rows = names.map(n => ` ${color.repo(n)}`);
|
|
178
|
-
if (!process.stdout.isTTY) return;
|
|
179
|
-
// Reserve lines for each repo (writes repo name immediately)
|
|
180
|
-
for (let i = 0; i < names.length; i++) process.stdout.write(` ${color.repo(names[i])}\n`);
|
|
181
|
-
// Mark all as dirty so first flush updates them
|
|
182
|
-
this.dirty = names.map((_, i) => i);
|
|
183
|
-
this.timer = setInterval(() => this.flush(), 200);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
setRow(idx: number, text: string) {
|
|
187
|
-
if (idx < 0 || idx >= this.count) return;
|
|
188
|
-
if (this.rows[idx] === text) return;
|
|
189
|
-
this.rows[idx] = text;
|
|
190
|
-
if (this.dirty.indexOf(idx) < 0) this.dirty.push(idx);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
finish() {
|
|
194
|
-
if (this.timer) { clearInterval(this.timer); this.timer = null; }
|
|
195
|
-
if (!process.stdout.isTTY) {
|
|
196
|
-
for (const r of this.rows) process.stdout.write(r + '\n');
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
// Flush ALL remaining dirty rows synchronously
|
|
200
|
-
const cols = process.stdout.columns || 80;
|
|
201
|
-
while (this.dirty.length) {
|
|
202
|
-
const idx = this.dirty.shift()!;
|
|
203
|
-
const n = this.count - idx;
|
|
204
|
-
if (n > 0) process.stdout.write(`\x1b[${n}A`);
|
|
205
|
-
process.stdout.write(`\r\x1b[2K${this.rows[idx]}`);
|
|
206
|
-
if (n > 0) process.stdout.write(`\x1b[${n}B`);
|
|
207
|
-
}
|
|
208
|
-
process.stdout.write('\n');
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
private flush() {
|
|
212
|
-
if (this.dirty.length === 0) return;
|
|
213
|
-
const idx = this.dirty.shift()!;
|
|
214
|
-
const n = this.count - idx;
|
|
215
|
-
const cols = process.stdout.columns || 80;
|
|
216
|
-
if (n > 0) process.stdout.write(`\x1b[${n}A`);
|
|
217
|
-
process.stdout.write(`\r\x1b[2K${this.rows[idx]}`);
|
|
218
|
-
if (n > 0) process.stdout.write(`\x1b[${n}B`);
|
|
219
|
-
setImmediate(() => this.flush());
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// ---- Write mutex ----
|
|
224
|
-
const writeQueue: string[] = [];
|
|
225
|
-
let writing = false;
|
|
226
|
-
|
|
227
|
-
function safeWrite(s: string) {
|
|
228
|
-
writeQueue.push(s);
|
|
229
|
-
if (!writing) flushWrite();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function flushWrite() {
|
|
233
|
-
if (writeQueue.length === 0) { writing = false; return; }
|
|
234
|
-
writing = true;
|
|
235
|
-
const s = writeQueue.shift()!;
|
|
236
|
-
if (process.stdout.write(s)) {
|
|
237
|
-
setImmediate(flushWrite);
|
|
238
|
-
} else {
|
|
239
|
-
process.stdout.once('drain', flushWrite);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// ---- Main sync ----
|
|
244
|
-
export async function syncRepos(force: boolean = false): Promise<void> {
|
|
245
|
-
const cfg = loadConfig();
|
|
246
|
-
if (!fs.existsSync(PKG_CACHE)) fs.mkdirSync(PKG_CACHE, { recursive: true });
|
|
247
|
-
const cols = process.stdout.columns || 80;
|
|
248
|
-
const progress = new RepoProgress();
|
|
249
|
-
const namePad = Math.max(...cfg.repos.map(r => r.name.length)) + 2;
|
|
250
|
-
progress.init(cfg.repos.map(r => r.name));
|
|
251
|
-
|
|
252
|
-
const tasks = cfg.repos.map(async (repo, idx) => {
|
|
253
|
-
const pname = color.repo(repo.name);
|
|
254
|
-
let ifModifiedSince: string | undefined;
|
|
255
|
-
|
|
256
|
-
if (!force) {
|
|
257
|
-
const infoFile = path.join(PKG_CACHE, repo.name, '.info');
|
|
258
|
-
if (fs.existsSync(infoFile)) {
|
|
259
|
-
try {
|
|
260
|
-
const st = fs.statSync(infoFile);
|
|
261
|
-
ifModifiedSince = st.mtime.toUTCString();
|
|
262
|
-
} catch {}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
let totalDownloaded = 0;
|
|
267
|
-
let totalExpected = 0;
|
|
268
|
-
const startTime = Date.now();
|
|
269
|
-
let prevTime = startTime;
|
|
270
|
-
let prevBytes = 0;
|
|
271
|
-
let smoothedRate = 0;
|
|
272
|
-
|
|
273
|
-
const fmtProgress = () => {
|
|
274
|
-
const dl = humanSize(totalDownloaded, 1);
|
|
275
|
-
const rateStr = formatRate(smoothedRate);
|
|
276
|
-
const eta = smoothedRate > 0 && totalExpected > 0 ? (totalExpected - totalDownloaded) / smoothedRate : 0;
|
|
277
|
-
const etaStr = formatETA(eta);
|
|
278
|
-
const pct = totalExpected > 0 ? Math.round(totalDownloaded / totalExpected * 100) : 0;
|
|
279
|
-
const bar = drawProgressBar(pct, cols);
|
|
280
|
-
return ` ${pname}${' '.repeat(namePad - repo.name.length)}${color.size(dl.val.padStart(7))} ${dl.unit.padEnd(3)} ${color.rate(rateStr)} ${etaStr} [${bar}] ${String(pct).padStart(3)}%`;
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
const updateProgress = () => {
|
|
284
|
-
const now = Date.now();
|
|
285
|
-
if (now - prevTime < 200) return;
|
|
286
|
-
const chunkTime = Math.max((now - prevTime) / 1000, 0.001);
|
|
287
|
-
smoothedRate = smoothedRate > 0
|
|
288
|
-
? ((totalDownloaded - prevBytes) / chunkTime + 2 * smoothedRate) / 3
|
|
289
|
-
: (totalDownloaded - prevBytes) / chunkTime;
|
|
290
|
-
prevTime = now;
|
|
291
|
-
prevBytes = totalDownloaded;
|
|
292
|
-
progress.setRow(idx, fmtProgress());
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
try {
|
|
296
|
-
let pkgs: RepoPkg[];
|
|
297
|
-
let notModified = false;
|
|
298
|
-
if (repo.type === 'arch') {
|
|
299
|
-
pkgs = await syncArch(repo, cfg.architecture, ifModifiedSince, (rec, tot) => {
|
|
300
|
-
totalDownloaded = rec; totalExpected = tot;
|
|
301
|
-
updateProgress();
|
|
302
|
-
});
|
|
303
|
-
notModified = ifModifiedSince !== undefined && pkgs.length === 0 && totalDownloaded === 0;
|
|
304
|
-
} else {
|
|
305
|
-
const result = await syncDebian(repo, cfg.architecture, ifModifiedSince, (rec, tot) => {
|
|
306
|
-
totalDownloaded = rec; totalExpected = tot;
|
|
307
|
-
updateProgress();
|
|
308
|
-
});
|
|
309
|
-
pkgs = result.pkgs;
|
|
310
|
-
notModified = result.notModified;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (notModified) {
|
|
314
|
-
progress.setRow(idx, ` ${pname}${' '.repeat(namePad - repo.name.length)}${color.ok(t('repo_already_uptodate'))}`);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Write JSON Lines chunks (parallel per chunk)
|
|
319
|
-
const pkgDir = path.join(PKG_CACHE, repo.name);
|
|
320
|
-
if (!fs.existsSync(pkgDir)) fs.mkdirSync(pkgDir, { recursive: true });
|
|
321
|
-
const CHUNK = 5000;
|
|
322
|
-
const chunks = Math.ceil(pkgs.length / CHUNK);
|
|
323
|
-
const writeTasks = [];
|
|
324
|
-
const idxLines: string[] = [];
|
|
325
|
-
for (let c = 0; c < chunks; c++) {
|
|
326
|
-
const chunk = pkgs.slice(c * CHUNK, (c + 1) * CHUNK);
|
|
327
|
-
const lines = chunk.map(p => JSON.stringify(p)).join('\n');
|
|
328
|
-
const fname = `${String(c).padStart(5, '0')}.jsonl`;
|
|
329
|
-
writeTasks.push(fs.promises.writeFile(path.join(pkgDir, fname), lines + '\n'));
|
|
330
|
-
// Build index with byte offsets (computed from previous chunk lengths)
|
|
331
|
-
let offset = 0;
|
|
332
|
-
for (let pi = 0; pi < chunk.length; pi++) {
|
|
333
|
-
const p = chunk[pi];
|
|
334
|
-
const json = JSON.stringify(p);
|
|
335
|
-
const desc = (p.description || '').replace(/\\/g, '\\\\').replace(/\t/g, '\\t').replace(/\n/g, '\\n');
|
|
336
|
-
const provides = (p.provides || '').replace(/\\/g, '\\\\').replace(/\t/g, '\\t').replace(/\n/g, '\\n');
|
|
337
|
-
idxLines.push(`${p.package} ${desc}\t${provides}\t${fname}\t${offset}`);
|
|
338
|
-
offset += Buffer.byteLength(json, 'utf8') + 1;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
await Promise.all(writeTasks);
|
|
342
|
-
await fs.promises.writeFile(path.join(pkgDir, '.info'), JSON.stringify({ total: pkgs.length, chunks, chunkSize: CHUNK }));
|
|
343
|
-
// Remove legacy all.json
|
|
344
|
-
try { fs.unlinkSync(path.join(pkgDir, 'all.json')); } catch {}
|
|
345
|
-
idxLines.sort(); // global sort so binary search works
|
|
346
|
-
await fs.promises.writeFile(path.join(pkgDir, 'packages.idx'), idxLines.join('\n') + '\n');
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// Final line
|
|
350
|
-
const elapsed = (Date.now() - startTime) / 1000;
|
|
351
|
-
const totalSec = Math.round(elapsed);
|
|
352
|
-
const finalRate = elapsed > 0 ? totalDownloaded / elapsed : 0;
|
|
353
|
-
const dl = humanSize(totalDownloaded, 1);
|
|
354
|
-
const rateStr = formatRate(finalRate);
|
|
355
|
-
const bar = drawProgressBar(100, cols);
|
|
356
|
-
progress.setRow(idx,
|
|
357
|
-
` ${pname}${' '.repeat(namePad - repo.name.length)}${color.size(dl.val.padStart(7))} ${dl.unit.padEnd(3)} ${color.rate(rateStr)} ${String(Math.floor(totalSec / 60)).padStart(2, '0')}:${String(totalSec % 60).padStart(2, '0')} [${bar}] ${color.ok('100%')}`
|
|
358
|
-
);
|
|
359
|
-
} catch (e: any) {
|
|
360
|
-
progress.setRow(idx, ` ${pname}${' '.repeat(namePad - repo.name.length)}${color.error(t('repo_sync_failed'))}: ${e.message}`);
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
await Promise.all(tasks);
|
|
365
|
-
progress.finish();
|
|
366
|
-
invalidateCache();
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/** Read a pkg from JSONL by byte offset (shared helper) */
|
|
370
|
-
export function readPkgAt(pkgDir: string, chunkFile: string, byteOff: number): RepoPkg | undefined {
|
|
371
|
-
const fd = fs.openSync(path.join(pkgDir, chunkFile), 'r');
|
|
372
|
-
const buf = Buffer.alloc(65536);
|
|
373
|
-
const bytes = fs.readSync(fd, buf, 0, 65536, byteOff);
|
|
374
|
-
fs.closeSync(fd);
|
|
375
|
-
const end = buf.indexOf(10);
|
|
376
|
-
const json = end >= 0 ? buf.toString('utf8', 0, end) : buf.toString('utf8', 0, bytes);
|
|
377
|
-
try { return JSON.parse(json) as RepoPkg; } catch { return undefined; }
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// ---- Cache ----
|
|
381
|
-
let _cache: RepoPkg[] | null = null;
|
|
382
|
-
|
|
383
|
-
export function getRepoCache(): RepoPkg[] {
|
|
384
|
-
if (_cache) return _cache;
|
|
385
|
-
if (!fs.existsSync(PKG_CACHE)) { _cache = []; return _cache; }
|
|
386
|
-
|
|
387
|
-
const cfg = loadConfig();
|
|
388
|
-
const seen = new Set<string>();
|
|
389
|
-
const all: RepoPkg[] = [];
|
|
390
|
-
|
|
391
|
-
for (const repo of cfg.repos) {
|
|
392
|
-
const pkgDir = path.join(PKG_CACHE, repo.name);
|
|
393
|
-
const idxPath = path.join(pkgDir, 'packages.idx');
|
|
394
|
-
if (!fs.existsSync(idxPath)) continue;
|
|
395
|
-
|
|
396
|
-
const idx = fs.readFileSync(idxPath, 'utf8').split('\n');
|
|
397
|
-
for (const line of idx) {
|
|
398
|
-
if (!line) continue;
|
|
399
|
-
// idx: pkgname desc\tprovides\tchunkFile\toffset
|
|
400
|
-
const lastTab = line.lastIndexOf('\t');
|
|
401
|
-
const byteOff = parseInt(line.slice(lastTab + 1), 10);
|
|
402
|
-
if (isNaN(byteOff)) continue;
|
|
403
|
-
const beforeOff = line.slice(0, lastTab);
|
|
404
|
-
const secondLastTab = beforeOff.lastIndexOf('\t');
|
|
405
|
-
const chunkFile = beforeOff.slice(secondLastTab + 1);
|
|
406
|
-
if (!chunkFile) continue;
|
|
407
|
-
|
|
408
|
-
const p = readPkgAt(pkgDir, chunkFile, byteOff);
|
|
409
|
-
if (p && !seen.has(p.package)) { seen.add(p.package); all.push(p); }
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
_cache = all;
|
|
414
|
-
return all;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
export function invalidateCache(): void { _cache = null; }
|
|
418
|
-
|
|
419
|
-
export function searchRepo(query: string): RepoPkg[] {
|
|
420
|
-
const lq = query.toLowerCase();
|
|
421
|
-
const results: RepoPkg[] = [];
|
|
422
|
-
const cfg = loadConfig();
|
|
423
|
-
const seen = new Set<string>();
|
|
424
|
-
|
|
425
|
-
for (const repo of cfg.repos) {
|
|
426
|
-
const pkgDir = path.join(PKG_CACHE, repo.name);
|
|
427
|
-
const idxPath = path.join(pkgDir, 'packages.idx');
|
|
428
|
-
if (!fs.existsSync(idxPath)) continue;
|
|
429
|
-
|
|
430
|
-
const idx = fs.readFileSync(idxPath, 'utf8').split('\n');
|
|
431
|
-
|
|
432
|
-
for (let i = 0; i < idx.length; i++) {
|
|
433
|
-
const line = idx[i];
|
|
434
|
-
if (!line) continue;
|
|
435
|
-
|
|
436
|
-
// idx line: pkgname desc\tprovides\tchunkFile\toffset
|
|
437
|
-
if (!line.toLowerCase().includes(lq)) continue;
|
|
438
|
-
|
|
439
|
-
const tab1 = line.indexOf('\t');
|
|
440
|
-
if (tab1 < 0) continue;
|
|
441
|
-
const pname = line.slice(0, tab1).split(' ')[0];
|
|
442
|
-
if (seen.has(pname)) continue;
|
|
443
|
-
seen.add(pname);
|
|
444
|
-
|
|
445
|
-
const lastTab = line.lastIndexOf('\t');
|
|
446
|
-
const byteOff = parseInt(line.slice(lastTab + 1), 10);
|
|
447
|
-
const beforeOff = line.slice(0, lastTab);
|
|
448
|
-
const secondLastTab = beforeOff.lastIndexOf('\t');
|
|
449
|
-
const chunkFile = beforeOff.slice(secondLastTab + 1);
|
|
450
|
-
if (!chunkFile || isNaN(byteOff)) continue;
|
|
451
|
-
|
|
452
|
-
const p = readPkgAt(pkgDir, chunkFile, byteOff);
|
|
453
|
-
if (p) results.push(p);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
return results;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
export function findInRepo(pkgName: string): RepoPkg | undefined {
|
|
461
|
-
const cfg = loadConfig();
|
|
462
|
-
for (const repo of cfg.repos) {
|
|
463
|
-
const pkgDir = path.join(PKG_CACHE, repo.name);
|
|
464
|
-
if (!fs.existsSync(pkgDir)) continue;
|
|
465
|
-
|
|
466
|
-
const idxPath = path.join(pkgDir, 'packages.idx');
|
|
467
|
-
if (!fs.existsSync(idxPath)) continue;
|
|
468
|
-
const idx = fs.readFileSync(idxPath, 'utf8').split('\n');
|
|
469
|
-
|
|
470
|
-
// Binary search: index is sorted "pkgname desc\tchunkFile\toffset"
|
|
471
|
-
// We compare against the first space/tab-delimited field (package name)
|
|
472
|
-
let lo = 0, hi = idx.length - 1;
|
|
473
|
-
while (lo <= hi) {
|
|
474
|
-
const mid = (lo + hi) >>> 1;
|
|
475
|
-
const line = idx[mid];
|
|
476
|
-
if (!line) { lo = mid + 1; continue; }
|
|
477
|
-
// Extract package name from start of line (up to first space)
|
|
478
|
-
const space = line.indexOf(' ');
|
|
479
|
-
const pname = space > 0 ? line.slice(0, space) : line;
|
|
480
|
-
if (pkgName < pname) hi = mid - 1;
|
|
481
|
-
else if (pkgName > pname) lo = mid + 1;
|
|
482
|
-
else {
|
|
483
|
-
const lastTab = line.lastIndexOf('\t');
|
|
484
|
-
const byteOff = parseInt(line.slice(lastTab + 1), 10);
|
|
485
|
-
const beforeOff = line.slice(0, lastTab);
|
|
486
|
-
const secondLastTab = beforeOff.lastIndexOf('\t');
|
|
487
|
-
const chunkFile = beforeOff.slice(secondLastTab + 1);
|
|
488
|
-
if (!chunkFile || isNaN(byteOff)) break;
|
|
489
|
-
return readPkgAt(pkgDir, chunkFile, byteOff);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
return undefined;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
export async function downloadPkg(rp: RepoPkg, dest?: string, onProgress?: (rec: number, tot: number) => void): Promise<string> {
|
|
497
|
-
if (!fs.existsSync(DEB_CACHE)) fs.mkdirSync(DEB_CACHE, { recursive: true });
|
|
498
|
-
const fn = path.basename(rp.filename);
|
|
499
|
-
const local = path.join(dest || DEB_CACHE, fn);
|
|
500
|
-
if (fs.existsSync(local)) return local;
|
|
501
|
-
|
|
502
|
-
let url: string;
|
|
503
|
-
if (rp.repoType === 'arch') {
|
|
504
|
-
const cfg = loadConfig();
|
|
505
|
-
const repo = cfg.repos.find(r => r.name === rp.repo);
|
|
506
|
-
if (!repo) throw new Error(`repo ${rp.repo} not found`);
|
|
507
|
-
const arch = repo.architecture || cfg.architecture;
|
|
508
|
-
url = `${resolveServer(repo.server, repo.name, arch)}/${rp.filename}`;
|
|
509
|
-
} else {
|
|
510
|
-
const cfg = loadConfig();
|
|
511
|
-
const repo = cfg.repos.find(r => r.name === rp.repo);
|
|
512
|
-
if (!repo) throw new Error(`repo ${rp.repo} not found`);
|
|
513
|
-
url = `${repo.server}/${rp.filename}`;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
const data = await downloadFile(url, onProgress);
|
|
517
|
-
if (!data) throw new Error('failed to download package');
|
|
518
|
-
fs.writeFileSync(local, data);
|
|
519
|
-
return local;
|
|
520
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { loadConfig } from '../repo/config';
|
|
3
|
-
|
|
4
|
-
function main(): void {
|
|
5
|
-
const cfg = loadConfig();
|
|
6
|
-
const args = process.argv.slice(2);
|
|
7
|
-
|
|
8
|
-
// Filter out --config if present (we ignore custom config paths for now)
|
|
9
|
-
const filtered: string[] = [];
|
|
10
|
-
for (let i = 0; i < args.length; i++) {
|
|
11
|
-
if (args[i] === '--config') { i++; continue; }
|
|
12
|
-
filtered.push(args[i]);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const [section, directive] = filtered;
|
|
16
|
-
|
|
17
|
-
if (!section) {
|
|
18
|
-
// Print entire config (simplified)
|
|
19
|
-
console.log('# pacman-debian configuration');
|
|
20
|
-
console.log(`[options]`);
|
|
21
|
-
console.log(`Architecture = ${cfg.architecture}`);
|
|
22
|
-
console.log('');
|
|
23
|
-
for (const repo of cfg.repos) {
|
|
24
|
-
console.log(`[${repo.name}]`);
|
|
25
|
-
console.log(`Server = ${repo.server}`);
|
|
26
|
-
if (repo.type) console.log(`Type = ${repo.type}`);
|
|
27
|
-
if (repo.dist) console.log(`Dist = ${repo.dist}`);
|
|
28
|
-
if (repo.components?.length) console.log(`Components = ${repo.components.join(' ')}`);
|
|
29
|
-
if (repo.dbFile) console.log(`DbFile = ${repo.dbFile}`);
|
|
30
|
-
if (repo.architecture) console.log(`Architecture = ${repo.architecture}`);
|
|
31
|
-
console.log('');
|
|
32
|
-
}
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (section === 'options') {
|
|
37
|
-
if (directive === 'architecture' || directive === 'Architecture') {
|
|
38
|
-
console.log(cfg.architecture);
|
|
39
|
-
}
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Look up a repo section
|
|
44
|
-
const repo = cfg.repos.find(r => r.name === section);
|
|
45
|
-
if (!repo) {
|
|
46
|
-
if (!directive) {
|
|
47
|
-
// Section not found, maybe it's a directive lookup in options
|
|
48
|
-
if (section === 'architecture' || section === 'Architecture') {
|
|
49
|
-
console.log(cfg.architecture);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!directive) {
|
|
56
|
-
// Print repo info (yay uses this to get rootdir, etc.)
|
|
57
|
-
console.log(`Server = ${repo.server}`);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const dl = directive.toLowerCase();
|
|
62
|
-
if (dl === 'server') console.log(repo.server);
|
|
63
|
-
else if (dl === 'type') console.log(repo.type || 'debian');
|
|
64
|
-
else if (dl === 'dist') console.log(repo.dist || '');
|
|
65
|
-
else if (dl === 'architecture') console.log(repo.architecture || cfg.architecture);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
main();
|