pacman-debian 7.2.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 (233) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +440 -0
  3. package/README.zh.md +388 -0
  4. package/README_zh-CN.md +421 -0
  5. package/dist/ar.d.ts +8 -0
  6. package/dist/ar.d.ts.map +1 -0
  7. package/dist/ar.js +88 -0
  8. package/dist/ar.js.map +1 -0
  9. package/dist/cli/pacman.d.ts +2 -0
  10. package/dist/cli/pacman.d.ts.map +1 -0
  11. package/dist/cli/pacman.js +512 -0
  12. package/dist/cli/pacman.js.map +1 -0
  13. package/dist/compress.d.ts +2 -0
  14. package/dist/compress.d.ts.map +1 -0
  15. package/dist/compress.js +78 -0
  16. package/dist/compress.js.map +1 -0
  17. package/dist/config.d.ts +3 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/config.js +90 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/control.d.ts +2 -0
  22. package/dist/control.d.ts.map +1 -0
  23. package/dist/control.js +31 -0
  24. package/dist/control.js.map +1 -0
  25. package/dist/core/ar.d.ts +7 -0
  26. package/dist/core/ar.d.ts.map +1 -0
  27. package/dist/core/ar.js +45 -0
  28. package/dist/core/ar.js.map +1 -0
  29. package/dist/core/compress.d.ts +2 -0
  30. package/dist/core/compress.d.ts.map +1 -0
  31. package/dist/core/compress.js +75 -0
  32. package/dist/core/compress.js.map +1 -0
  33. package/dist/core/control.d.ts +2 -0
  34. package/dist/core/control.d.ts.map +1 -0
  35. package/dist/core/control.js +29 -0
  36. package/dist/core/control.js.map +1 -0
  37. package/dist/core/deb.d.ts +4 -0
  38. package/dist/core/deb.d.ts.map +1 -0
  39. package/dist/core/deb.js +89 -0
  40. package/dist/core/deb.js.map +1 -0
  41. package/dist/core/deps.d.ts +25 -0
  42. package/dist/core/deps.d.ts.map +1 -0
  43. package/dist/core/deps.js +230 -0
  44. package/dist/core/deps.js.map +1 -0
  45. package/dist/core/options.d.ts +19 -0
  46. package/dist/core/options.d.ts.map +1 -0
  47. package/dist/core/options.js +3 -0
  48. package/dist/core/options.js.map +1 -0
  49. package/dist/core/pkgfile.d.ts +35 -0
  50. package/dist/core/pkgfile.d.ts.map +1 -0
  51. package/dist/core/pkgfile.js +162 -0
  52. package/dist/core/pkgfile.js.map +1 -0
  53. package/dist/core/tar.d.ts +13 -0
  54. package/dist/core/tar.d.ts.map +1 -0
  55. package/dist/core/tar.js +132 -0
  56. package/dist/core/tar.js.map +1 -0
  57. package/dist/core/types.d.ts +83 -0
  58. package/dist/core/types.d.ts.map +1 -0
  59. package/dist/core/types.js +3 -0
  60. package/dist/core/types.js.map +1 -0
  61. package/dist/database.d.ts +20 -0
  62. package/dist/database.d.ts.map +1 -0
  63. package/dist/database.js +181 -0
  64. package/dist/database.js.map +1 -0
  65. package/dist/db/database.d.ts +17 -0
  66. package/dist/db/database.d.ts.map +1 -0
  67. package/dist/db/database.js +145 -0
  68. package/dist/db/database.js.map +1 -0
  69. package/dist/db/dpkg-compat.d.ts +19 -0
  70. package/dist/db/dpkg-compat.d.ts.map +1 -0
  71. package/dist/db/dpkg-compat.js +150 -0
  72. package/dist/db/dpkg-compat.js.map +1 -0
  73. package/dist/db/localdb.d.ts +9 -0
  74. package/dist/db/localdb.d.ts.map +1 -0
  75. package/dist/db/localdb.js +246 -0
  76. package/dist/db/localdb.js.map +1 -0
  77. package/dist/db/sqlite.d.ts +20 -0
  78. package/dist/db/sqlite.d.ts.map +1 -0
  79. package/dist/db/sqlite.js +309 -0
  80. package/dist/db/sqlite.js.map +1 -0
  81. package/dist/deb.d.ts +5 -0
  82. package/dist/deb.d.ts.map +1 -0
  83. package/dist/deb.js +105 -0
  84. package/dist/deb.js.map +1 -0
  85. package/dist/dpkg-compat.d.ts +18 -0
  86. package/dist/dpkg-compat.d.ts.map +1 -0
  87. package/dist/dpkg-compat.js +137 -0
  88. package/dist/dpkg-compat.js.map +1 -0
  89. package/dist/i18n/en.json +109 -0
  90. package/dist/i18n/index.d.ts +3 -0
  91. package/dist/i18n/index.d.ts.map +1 -0
  92. package/dist/i18n/index.js +36 -0
  93. package/dist/i18n/index.js.map +1 -0
  94. package/dist/i18n/zh-CN.json +109 -0
  95. package/dist/index.d.ts +3 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +9 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/install.d.ts +2 -0
  100. package/dist/install.d.ts.map +1 -0
  101. package/dist/install.js +126 -0
  102. package/dist/install.js.map +1 -0
  103. package/dist/makepkg/build.d.ts +19 -0
  104. package/dist/makepkg/build.d.ts.map +1 -0
  105. package/dist/makepkg/build.js +370 -0
  106. package/dist/makepkg/build.js.map +1 -0
  107. package/dist/makepkg/index.d.ts +3 -0
  108. package/dist/makepkg/index.d.ts.map +1 -0
  109. package/dist/makepkg/index.js +84 -0
  110. package/dist/makepkg/index.js.map +1 -0
  111. package/dist/makepkg/pkgbuild.d.ts +36 -0
  112. package/dist/makepkg/pkgbuild.d.ts.map +1 -0
  113. package/dist/makepkg/pkgbuild.js +165 -0
  114. package/dist/makepkg/pkgbuild.js.map +1 -0
  115. package/dist/ops/install.d.ts +5 -0
  116. package/dist/ops/install.d.ts.map +1 -0
  117. package/dist/ops/install.js +299 -0
  118. package/dist/ops/install.js.map +1 -0
  119. package/dist/ops/query.d.ts +9 -0
  120. package/dist/ops/query.d.ts.map +1 -0
  121. package/dist/ops/query.js +189 -0
  122. package/dist/ops/query.js.map +1 -0
  123. package/dist/ops/remove.d.ts +3 -0
  124. package/dist/ops/remove.d.ts.map +1 -0
  125. package/dist/ops/remove.js +121 -0
  126. package/dist/ops/remove.js.map +1 -0
  127. package/dist/ops/upgrade.d.ts +4 -0
  128. package/dist/ops/upgrade.d.ts.map +1 -0
  129. package/dist/ops/upgrade.js +122 -0
  130. package/dist/ops/upgrade.js.map +1 -0
  131. package/dist/pacman.d.ts +2 -0
  132. package/dist/pacman.d.ts.map +1 -0
  133. package/dist/pacman.js +165 -0
  134. package/dist/pacman.js.map +1 -0
  135. package/dist/query.d.ts +5 -0
  136. package/dist/query.d.ts.map +1 -0
  137. package/dist/query.js +143 -0
  138. package/dist/query.js.map +1 -0
  139. package/dist/remove.d.ts +2 -0
  140. package/dist/remove.d.ts.map +1 -0
  141. package/dist/remove.js +82 -0
  142. package/dist/remove.js.map +1 -0
  143. package/dist/repo/config.d.ts +3 -0
  144. package/dist/repo/config.d.ts.map +1 -0
  145. package/dist/repo/config.js +146 -0
  146. package/dist/repo/config.js.map +1 -0
  147. package/dist/repo/repository.d.ts +10 -0
  148. package/dist/repo/repository.d.ts.map +1 -0
  149. package/dist/repo/repository.js +596 -0
  150. package/dist/repo/repository.js.map +1 -0
  151. package/dist/repository.d.ts +10 -0
  152. package/dist/repository.d.ts.map +1 -0
  153. package/dist/repository.js +175 -0
  154. package/dist/repository.js.map +1 -0
  155. package/dist/scripts/pacman-conf.d.ts +3 -0
  156. package/dist/scripts/pacman-conf.d.ts.map +1 -0
  157. package/dist/scripts/pacman-conf.js +74 -0
  158. package/dist/scripts/pacman-conf.js.map +1 -0
  159. package/dist/scripts/setup.d.ts +3 -0
  160. package/dist/scripts/setup.d.ts.map +1 -0
  161. package/dist/scripts/setup.js +289 -0
  162. package/dist/scripts/setup.js.map +1 -0
  163. package/dist/tar.d.ts +17 -0
  164. package/dist/tar.d.ts.map +1 -0
  165. package/dist/tar.js +148 -0
  166. package/dist/tar.js.map +1 -0
  167. package/dist/types.d.ts +80 -0
  168. package/dist/types.d.ts.map +1 -0
  169. package/dist/types.js +3 -0
  170. package/dist/types.js.map +1 -0
  171. package/dist/ui/colors.d.ts +13 -0
  172. package/dist/ui/colors.d.ts.map +1 -0
  173. package/dist/ui/colors.js +38 -0
  174. package/dist/ui/colors.js.map +1 -0
  175. package/dist/ui/format.d.ts +3 -0
  176. package/dist/ui/format.d.ts.map +1 -0
  177. package/dist/ui/format.js +15 -0
  178. package/dist/ui/format.js.map +1 -0
  179. package/dist/ui/progress.d.ts +8 -0
  180. package/dist/ui/progress.d.ts.map +1 -0
  181. package/dist/ui/progress.js +50 -0
  182. package/dist/ui/progress.js.map +1 -0
  183. package/dist/ui/prompt.d.ts +4 -0
  184. package/dist/ui/prompt.d.ts.map +1 -0
  185. package/dist/ui/prompt.js +60 -0
  186. package/dist/ui/prompt.js.map +1 -0
  187. package/lib/pac4deb/Makefile +26 -0
  188. package/lib/pac4deb/README.md +47 -0
  189. package/lib/pac4deb/include/alpm.h +166 -0
  190. package/lib/pac4deb/include/alpm_list.h +42 -0
  191. package/lib/pac4deb/libalpm.so +0 -0
  192. package/lib/pac4deb/src/alpm_list.c +102 -0
  193. package/lib/pac4deb/src/genstubs.sh +51 -0
  194. package/lib/pac4deb/src/genstubs2.sh +72 -0
  195. package/lib/pac4deb/src/genstubs3.sh +43 -0
  196. package/lib/pac4deb/src/libalpm.c +537 -0
  197. package/lib/pac4deb/src/stubs_manual.c +198 -0
  198. package/lib/pac4deb/stubs.c +6 -0
  199. package/lib/pac4deb/update_header.sh +15 -0
  200. package/package.json +41 -0
  201. package/src/cli/pacman.ts +308 -0
  202. package/src/core/ar.ts +54 -0
  203. package/src/core/compress.ts +27 -0
  204. package/src/core/control.ts +22 -0
  205. package/src/core/deb.ts +47 -0
  206. package/src/core/deps.ts +260 -0
  207. package/src/core/options.ts +20 -0
  208. package/src/core/pkgfile.ts +146 -0
  209. package/src/core/tar.ts +101 -0
  210. package/src/core/types.ts +89 -0
  211. package/src/db/database.ts +102 -0
  212. package/src/db/dpkg-compat.ts +129 -0
  213. package/src/db/localdb.ts +181 -0
  214. package/src/i18n/en.json +114 -0
  215. package/src/i18n/index.ts +32 -0
  216. package/src/i18n/zh-CN.json +114 -0
  217. package/src/index.ts +7 -0
  218. package/src/makepkg/build.ts +351 -0
  219. package/src/makepkg/index.ts +87 -0
  220. package/src/makepkg/pkgbuild.ts +146 -0
  221. package/src/ops/install.ts +260 -0
  222. package/src/ops/query.ts +117 -0
  223. package/src/ops/remove.ts +77 -0
  224. package/src/ops/upgrade.ts +87 -0
  225. package/src/repo/config.ts +96 -0
  226. package/src/repo/repository.ts +520 -0
  227. package/src/scripts/pacman-conf.ts +68 -0
  228. package/src/scripts/setup.ts +261 -0
  229. package/src/ui/colors.ts +40 -0
  230. package/src/ui/format.ts +9 -0
  231. package/src/ui/progress.ts +26 -0
  232. package/src/ui/prompt.ts +21 -0
  233. package/tsconfig.json +19 -0
@@ -0,0 +1,22 @@
1
+ export function parseControlFile(content: string): Record<string, string> {
2
+ const fields: Record<string, string> = {};
3
+ let key = '';
4
+ let val = '';
5
+
6
+ for (const line of content.split('\n')) {
7
+ if (line.startsWith(' ') || line.startsWith('\t')) {
8
+ val += '\n' + line.trimEnd();
9
+ } else if (line.trim() === '') {
10
+ continue;
11
+ } else {
12
+ if (key) fields[key.toLowerCase()] = val.trimEnd();
13
+ const ci = line.indexOf(':');
14
+ if (ci === -1) continue;
15
+ key = line.slice(0, ci).trim();
16
+ val = line.slice(ci + 1).trim();
17
+ }
18
+ }
19
+
20
+ if (key) fields[key.toLowerCase()] = val.trimEnd();
21
+ return fields;
22
+ }
@@ -0,0 +1,47 @@
1
+ import * as fs from 'node:fs';
2
+ import { parseAr } from './ar';
3
+ import { decompress } from './compress';
4
+ import { readFileFromTar, listTarEntries } from './tar';
5
+ import { parseControlFile } from './control';
6
+ import type { DebPackage, DebControl } from './types';
7
+
8
+ export function parseDeb(filePath: string): DebPackage {
9
+ const rawMembers = parseAr(fs.readFileSync(filePath));
10
+ let debianBinary: Buffer | null = null;
11
+ let controlTarRaw: Buffer | null = null;
12
+ let controlTarName = '';
13
+ let dataTarRaw: Buffer | null = null;
14
+ let dataTarName = '';
15
+
16
+ for (const m of rawMembers) {
17
+ if (m.name === 'debian-binary') debianBinary = m.data;
18
+ else if (m.name.startsWith('control.tar')) { controlTarRaw = m.data; controlTarName = m.name; }
19
+ else if (m.name.startsWith('data.tar')) { dataTarRaw = m.data; dataTarName = m.name; }
20
+ }
21
+
22
+ if (!debianBinary) throw new Error(`Missing debian-binary in ${filePath}`);
23
+ if (!controlTarRaw || !dataTarRaw) throw new Error(`Malformed .deb: missing tar members`);
24
+
25
+ const controlTar = decompress(controlTarRaw, controlTarName);
26
+ const dataTar = decompress(dataTarRaw, dataTarName);
27
+
28
+ const controlFileBuf = readFileFromTar(controlTar, './control')
29
+ ?? readFileFromTar(controlTar, 'control');
30
+ if (!controlFileBuf) throw new Error(`Missing control file in ${filePath}`);
31
+
32
+ const fields = parseControlFile(controlFileBuf.toString('utf8'));
33
+ if (!fields['package']) throw new Error(`control file missing Package field`);
34
+
35
+ const control: DebControl = { package: fields['package'], version: fields['version'] || '0.0', architecture: fields['architecture'] || 'amd64' };
36
+ for (const [k, v] of Object.entries(fields)) if (!(k in control)) (control as any)[k] = v;
37
+
38
+ return { path: filePath, control, controlTar, dataTar };
39
+ }
40
+
41
+ export function readScript(pkg: DebPackage, name: string): string | null {
42
+ for (const prefix of ['', './']) {
43
+ const buf = readFileFromTar(pkg.controlTar, prefix + name);
44
+ if (buf) return buf.toString('utf8');
45
+ }
46
+ return null;
47
+ }
@@ -0,0 +1,260 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { findInRepo, getRepoCache } from '../repo/repository';
3
+ import { loadDatabase } from '../db/database';
4
+ import { readDpkgStatus, dpkgHasPackage } from '../db/dpkg-compat';
5
+ import type { RepoPkg } from './types';
6
+
7
+ export interface Dep {
8
+ name: string;
9
+ version?: string;
10
+ operator?: string;
11
+ arch?: string;
12
+ }
13
+
14
+ export interface DepResult {
15
+ pkg: RepoPkg;
16
+ needed: boolean;
17
+ reason: string;
18
+ }
19
+
20
+ /* ---- Dependency string parser ---- */
21
+ export function parseDep(s: string): Dep[] {
22
+ const alternatives = s.split('|').map(a => a.trim());
23
+ return alternatives.map(a => {
24
+ let name = a;
25
+ let operator: string | undefined;
26
+ let version: string | undefined;
27
+
28
+ // Debian: "pkg (>= 1.0)" or Arch: "pkg>=1.0"
29
+ const parenMatch = a.match(/\(?\s*([<>=!]+)\s*([^)]+)\s*\)?\s*$/);
30
+ if (parenMatch) {
31
+ operator = parenMatch[1].trim();
32
+ version = parenMatch[2].trim().replace(/\)$/, '').trim();
33
+ name = a.slice(0, a.indexOf(parenMatch[1])).trim();
34
+ // Clean trailing paren/space from name
35
+ name = name.replace(/\(\s*$/, '').trim();
36
+ }
37
+
38
+ // Architecture qualifier: "libc6:arm64"
39
+ const archSep = name.lastIndexOf(':');
40
+ let arch: string | undefined;
41
+ if (archSep > 0 && name.length - archSep <= 8 && !name.includes('/')) {
42
+ arch = name.slice(archSep + 1);
43
+ name = name.slice(0, archSep);
44
+ }
45
+
46
+ return { name, version, operator, arch };
47
+ });
48
+ }
49
+
50
+ /* ---- Version comparison ---- */
51
+ function verCmp(a: string, b: string): number {
52
+ // Try dpkg compare first
53
+ try {
54
+ const out = execSync(`dpkg --compare-versions "${a}" gt "${b}" 2>/dev/null && echo gt || (dpkg --compare-versions "${a}" eq "${b}" 2>/dev/null && echo eq) || echo lt`, { encoding: 'utf8', timeout: 5000 }).trim();
55
+ if (out === 'gt') return 1;
56
+ if (out === 'eq') return 0;
57
+ if (out === 'lt') return -1;
58
+ } catch {}
59
+
60
+ // Fallback: simple numeric/string comparison
61
+ const aParts = a.replace(/[^\d.]/g, '').split('.').map(Number);
62
+ const bParts = b.replace(/[^\d.]/g, '').split('.').map(Number);
63
+ for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
64
+ const an = aParts[i] || 0, bn = bParts[i] || 0;
65
+ if (an !== bn) return an - bn;
66
+ }
67
+ return a.localeCompare(b);
68
+ }
69
+
70
+ function checkVersion(installed: string, operator: string, required: string): boolean {
71
+ const cmp = verCmp(installed, required);
72
+ switch (operator) {
73
+ case '>=': return cmp >= 0;
74
+ case '<=': return cmp <= 0;
75
+ case '>': return cmp > 0;
76
+ case '<': return cmp < 0;
77
+ case '=': case '==': return cmp === 0;
78
+ default: return true; // no constraint
79
+ }
80
+ }
81
+
82
+ /* ---- Fast path: pre-load DBs once ---- */
83
+ interface DepState {
84
+ localPkgs: Map<string, string>;
85
+ dpkgPkgs: Map<string, string>;
86
+ repoCache: RepoPkg[] | null;
87
+ }
88
+
89
+ // Packages that MUST come from dpkg - never install from Arch repos
90
+ const SYSTEM_PKGS = new Set([
91
+ 'glibc', 'libc6', 'linux-api-headers', 'filesystem', 'iana-etc',
92
+ 'bash', 'coreutils', 'systemd', 'dbus', 'util-linux', 'shadow',
93
+ 'pam', 'libcap', 'libseccomp', 'zlib', 'libzstd', 'libarchive',
94
+ ]);
95
+
96
+ let _state: DepState | null = null;
97
+
98
+ function getState(): DepState {
99
+ if (_state) return _state;
100
+ const local = loadDatabase();
101
+ const localMap = new Map<string, string>();
102
+ for (const [n, p] of local.packages) localMap.set(n, p.version);
103
+
104
+ const dpkg = readDpkgStatus();
105
+ const dpkgMap = new Map<string, string>();
106
+ for (const [n, p] of dpkg) dpkgMap.set(n, p.version);
107
+
108
+ _state = { localPkgs: localMap, dpkgPkgs: dpkgMap, repoCache: null };
109
+ return _state;
110
+ }
111
+
112
+ /* ---- Check if dep is satisfied ---- */
113
+ function isDepSatisfied(dep: Dep, state: DepState): boolean {
114
+ const installedVer = state.localPkgs.get(dep.name) || state.dpkgPkgs.get(dep.name);
115
+ if (!installedVer) return false;
116
+ if (dep.operator && dep.version) {
117
+ return checkVersion(installedVer, dep.operator, dep.version);
118
+ }
119
+ return true;
120
+ }
121
+
122
+ /* ---- Find provider in repo ---- */
123
+ function findProvider(name: string, state: DepState): RepoPkg | undefined {
124
+ const direct = findInRepo(name);
125
+ if (direct) return direct;
126
+
127
+ // Search provides via packages.idx (index has provides field)
128
+ const { loadConfig } = require('../repo/config');
129
+ const { readPkgAt } = require('../repo/repository');
130
+ const fs = require('node:fs');
131
+ const path = require('node:path');
132
+
133
+ const cfg = loadConfig();
134
+ const PKG_CACHE = '/var/cache/pacman-debian/packages';
135
+ for (const repo of cfg.repos) {
136
+ const pkgDir = path.join(PKG_CACHE, repo.name);
137
+ const idxPath = path.join(pkgDir, 'packages.idx');
138
+ if (!fs.existsSync(idxPath)) continue;
139
+ const idx = fs.readFileSync(idxPath, 'utf8').split('\n');
140
+ for (const line of idx) {
141
+ if (!line) continue;
142
+ // idx: pkgname desc\tprovides\tchunkFile\toffset
143
+ const firstTab = line.indexOf('\t');
144
+ if (firstTab < 0) continue;
145
+ const rest = line.slice(firstTab + 1);
146
+ const secondTab = rest.indexOf('\t');
147
+ if (secondTab < 0) continue;
148
+ const provides = rest.slice(0, secondTab);
149
+ if (!provides) continue;
150
+ if (provides.split(',').some((pr: string) => {
151
+ const pn = pr.trim().split(/[<>=]/)[0].trim();
152
+ return pn === name;
153
+ })) {
154
+ const lastTab = line.lastIndexOf('\t');
155
+ const byteOff = parseInt(line.slice(lastTab + 1), 10);
156
+ const beforeOff = line.slice(0, lastTab);
157
+ const thirdLastTab = beforeOff.lastIndexOf('\t');
158
+ const chunkFile = beforeOff.slice(thirdLastTab + 1);
159
+ if (chunkFile && !isNaN(byteOff)) {
160
+ return readPkgAt(pkgDir, chunkFile, byteOff);
161
+ }
162
+ }
163
+ }
164
+ }
165
+ return undefined;
166
+ }
167
+
168
+ /* ---- Full dependency resolution ---- */
169
+ export function resolveDeps(targets: string[]): { install: DepResult[]; errors: string[] } {
170
+ const state = getState();
171
+ const install: DepResult[] = [];
172
+ const errors: string[] = [];
173
+ const seen = new Set<string>();
174
+ const toProcess: string[] = [...targets];
175
+
176
+ while (toProcess.length > 0) {
177
+ const name = toProcess.shift()!;
178
+ if (seen.has(name)) continue;
179
+ seen.add(name);
180
+
181
+ if (isDepSatisfied({ name }, state)) continue;
182
+
183
+ // System packages must come from dpkg - skip Arch versions
184
+ if (SYSTEM_PKGS.has(name)) {
185
+ if (!state.dpkgPkgs.has(name)) {
186
+ errors.push(`'${name}' is a system package not available via dpkg`);
187
+ }
188
+ continue;
189
+ }
190
+
191
+ const rp = findProvider(name, state);
192
+ if (!rp) {
193
+ errors.push(`'${name}' not found`);
194
+ continue;
195
+ }
196
+
197
+ const alreadyResolved = install.some(i => i.pkg.package === rp.package);
198
+ if (!alreadyResolved) {
199
+ install.push({ pkg: rp, needed: true, reason: 'target' });
200
+ }
201
+
202
+ const deps = parseDepList(rp.depends);
203
+ for (const d of deps) {
204
+ if (!seen.has(d.name) && !isDepSatisfied(d, state)) {
205
+ toProcess.push(d.name);
206
+ }
207
+ }
208
+ }
209
+
210
+ return { install, errors };
211
+ }
212
+
213
+ export function invalidateDepCache(): void { _state = null; }
214
+
215
+ function parseDepList(s?: string): Dep[] {
216
+ if (!s) return [];
217
+ const result: Dep[] = [];
218
+ // Arch format: space-separated (glibc>=2.35 yyjson)
219
+ // Debian format: comma-separated (libc6 (>= 2.34), libyyjson)
220
+ const parts = s.includes(',') ? s.split(',') : s.split(/\s+/);
221
+ for (const part of parts) {
222
+ const trimmed = part.trim();
223
+ if (!trimmed) continue;
224
+ const parsed = parseDep(trimmed);
225
+ if (parsed.length > 0) result.push(parsed[0]);
226
+ }
227
+ return result;
228
+ }
229
+
230
+ /* ---- Conflict detection ---- */
231
+ export interface Conflict {
232
+ a: string;
233
+ b: string;
234
+ reason: string;
235
+ }
236
+
237
+ export function detectConflicts(packages: RepoPkg[]): Conflict[] {
238
+ const conflicts: Conflict[] = [];
239
+ const names = new Set(packages.map(p => p.package));
240
+
241
+ for (const pkg of packages) {
242
+ const pkgConflicts = pkg.conflicts || '';
243
+ const conflictNames = pkgConflicts.split(',').map(s => s.trim().split(/[<>=]/)[0].trim()).filter(Boolean);
244
+
245
+ for (const c of conflictNames) {
246
+ // Check against other to-be-installed packages
247
+ for (const other of packages) {
248
+ if (other.package !== pkg.package && other.package === c) {
249
+ conflicts.push({ a: pkg.package, b: c, reason: `${pkg.package} conflicts with ${c}` });
250
+ }
251
+ }
252
+ // Check against installed packages
253
+ if (dpkgHasPackage(c) || loadDatabase().packages.has(c)) {
254
+ conflicts.push({ a: pkg.package, b: c, reason: `${pkg.package} conflicts with installed ${c}` });
255
+ }
256
+ }
257
+ }
258
+
259
+ return conflicts;
260
+ }
@@ -0,0 +1,20 @@
1
+ export interface InstallOptions {
2
+ needed?: boolean;
3
+ noscriptlet?: boolean;
4
+ asdeps?: boolean;
5
+ print?: boolean;
6
+ }
7
+
8
+ export interface RemoveOptions {
9
+ recursive?: boolean;
10
+ noscriptlet?: boolean;
11
+ cascade?: boolean;
12
+ nodeps?: boolean;
13
+ nosave?: boolean;
14
+ print?: boolean;
15
+ }
16
+
17
+ export interface DbOptions {
18
+ asdeps?: boolean;
19
+ asexplicit?: boolean;
20
+ }
@@ -0,0 +1,146 @@
1
+ import * as zlib from 'node:zlib';
2
+ import { iterateTar } from './tar';
3
+
4
+ export interface PkgInfo {
5
+ name: string;
6
+ version: string;
7
+ description?: string;
8
+ depends?: string[];
9
+ conflicts?: string[];
10
+ provides?: string[];
11
+ url?: string;
12
+ license?: string[];
13
+ arch?: string;
14
+ installedSize?: number;
15
+ size?: number;
16
+ packager?: string;
17
+ }
18
+
19
+ export interface InstallScript {
20
+ pre_install?: string;
21
+ post_install?: string;
22
+ pre_remove?: string;
23
+ post_remove?: string;
24
+ }
25
+
26
+ function parsePKGINFO(content: string): PkgInfo {
27
+ const info: PkgInfo = { name: '', version: '' };
28
+ const deps: string[] = [];
29
+ const conflicts: string[] = [];
30
+ const provides: string[] = [];
31
+ const licenses: string[] = [];
32
+
33
+ for (const line of content.split('\n')) {
34
+ const trimmed = line.trim();
35
+ if (!trimmed || trimmed.startsWith('#')) continue;
36
+ const eq = trimmed.indexOf(' = ');
37
+ if (eq === -1) continue;
38
+ const key = trimmed.slice(0, eq).trim();
39
+ const val = trimmed.slice(eq + 3).trim();
40
+
41
+ switch (key) {
42
+ case 'pkgname': info.name = val; break;
43
+ case 'pkgver': info.version = val; break;
44
+ case 'pkgdesc': info.description = val; break;
45
+ case 'url': info.url = val; break;
46
+ case 'arch': info.arch = val; break;
47
+ case 'packager': info.packager = val; break;
48
+ case 'size': info.size = parseInt(val, 10) || undefined; break;
49
+ case 'installed_size': info.installedSize = parseInt(val, 10) || undefined; break;
50
+ case 'depend': deps.push(val.split(/[<>=]/)[0].trim()); break;
51
+ case 'conflict': conflicts.push(val.split(/[<>=]/)[0].trim()); break;
52
+ case 'provides': provides.push(val.split(/[<>=]/)[0].trim()); break;
53
+ case 'license': licenses.push(val); break;
54
+ }
55
+ }
56
+
57
+ if (deps.length > 0) info.depends = deps;
58
+ if (conflicts.length > 0) info.conflicts = conflicts;
59
+ if (provides.length > 0) info.provides = provides;
60
+ if (licenses.length > 0) info.license = licenses;
61
+ return info;
62
+ }
63
+
64
+ function parseInstallScript(content: string): InstallScript {
65
+ const script: InstallScript = {};
66
+ const funcs = ['pre_install', 'post_install', 'pre_remove', 'post_remove'];
67
+ for (const fn of funcs) {
68
+ const re = new RegExp(`${fn}\\s*\\(\\)\\s*\\{([\\s\\S]*?)\\n\\}`, 'm');
69
+ const m = content.match(re);
70
+ if (m) (script as any)[fn] = m[1].trim();
71
+ }
72
+ return script;
73
+ }
74
+
75
+ export function parsePkgTarZst(data: Buffer): { info: PkgInfo; install?: InstallScript; files: string[]; dataBlocks: { name: string; data: Buffer | null }[] } {
76
+ // decompress zstd
77
+ const { execSync } = require('node:child_process');
78
+ const fs = require('node:fs');
79
+ const path = require('node:path');
80
+ const os = require('node:os');
81
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archpkg-'));
82
+ const inFile = path.join(tmp, 'input.zst');
83
+ const outFile = path.join(tmp, 'output.tar');
84
+ try {
85
+ fs.writeFileSync(inFile, data);
86
+ execSync(`zstd -d -f "${inFile}" --stdout > "${outFile}"`, { stdio: 'pipe' });
87
+ const tarData = fs.readFileSync(outFile);
88
+ return parseArchTar(tarData);
89
+ } finally {
90
+ try { fs.unlinkSync(inFile); } catch {}
91
+ try { fs.unlinkSync(outFile); } catch {}
92
+ try { fs.rmdirSync(tmp); } catch {}
93
+ }
94
+ }
95
+
96
+ function parseArchTar(tarData: Buffer): { info: PkgInfo; install?: InstallScript; files: string[]; dataBlocks: { name: string; data: Buffer | null }[] } {
97
+ let info: PkgInfo = { name: '', version: '' };
98
+ let install: InstallScript | undefined;
99
+ const files: string[] = [];
100
+ const dataBlocks: { name: string; data: Buffer | null }[] = [];
101
+
102
+ for (const entry of iterateTar(tarData)) {
103
+ const name = entry.name.replace(/^\.\//, '');
104
+ if (name === '.PKGINFO' && entry.data) {
105
+ info = parsePKGINFO(entry.data.toString('utf8'));
106
+ continue;
107
+ }
108
+ if (name === '.INSTALL' && entry.data) {
109
+ install = parseInstallScript(entry.data.toString('utf8'));
110
+ continue;
111
+ }
112
+ if (name === '.MTREE' || name === '' || name.startsWith('.')) continue;
113
+ files.push('/' + name);
114
+ dataBlocks.push({ name, data: entry.data });
115
+ }
116
+
117
+ return { info, install, files, dataBlocks };
118
+ }
119
+
120
+ export function extractArchTarEntry(name: string, dest: string, entries: { name: string; data: Buffer | null }[]): void {
121
+ const entry = entries.find(e => e.name === name);
122
+ if (!entry || !entry.data) return;
123
+ const targetPath = require('node:path').resolve(dest, name);
124
+ require('node:fs').mkdirSync(require('node:path').dirname(targetPath), { recursive: true });
125
+ require('node:fs').writeFileSync(targetPath, entry.data, { mode: 0o755 });
126
+ }
127
+
128
+ export function parseDescFile(content: string): Record<string, string[]> {
129
+ const result: Record<string, string[]> = {};
130
+ let currentKey = '';
131
+ const values: string[] = [];
132
+
133
+ for (const line of content.split('\n')) {
134
+ const keyMatch = line.match(/^%([A-Z_]+)%$/);
135
+ if (keyMatch) {
136
+ if (currentKey && values.length > 0) result[currentKey] = [...values];
137
+ currentKey = keyMatch[1].toLowerCase();
138
+ values.length = 0;
139
+ } else if (line.trim()) {
140
+ values.push(line.trim());
141
+ }
142
+ }
143
+ if (currentKey && values.length > 0) result[currentKey] = [...values];
144
+
145
+ return result;
146
+ }
@@ -0,0 +1,101 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+
4
+ interface TarHeader {
5
+ name: string;
6
+ mode: number;
7
+ size: number;
8
+ type: 'file' | 'directory' | 'symlink' | 'unknown';
9
+ linkname: string;
10
+ }
11
+
12
+ function parseOctal(buf: Buffer, start: number, len: number): number {
13
+ const str = buf.subarray(start, start + len).toString('utf8').replace(/\0/g, '').trim();
14
+ return parseInt(str, 8) || 0;
15
+ }
16
+
17
+ function trim(s: string): string {
18
+ return s.replace(/\0/g, '').trim();
19
+ }
20
+
21
+ function readHeader(buf: Buffer): TarHeader | null {
22
+ if (buf.length < 512) return null;
23
+ if (buf.subarray(0, 512).every(b => b === 0)) return null;
24
+
25
+ const name = trim(buf.subarray(0, 100).toString('utf8'));
26
+ const prefix = trim(buf.subarray(345, 500).toString('utf8'));
27
+ const mode = parseOctal(buf, 100, 8);
28
+ const size = parseOctal(buf, 124, 12);
29
+ const typeflag = String.fromCharCode(buf[156]);
30
+ const linkname = trim(buf.subarray(157, 257).toString('utf8'));
31
+ const fullName = prefix ? `${prefix}/${name}` : name;
32
+
33
+ let type: 'file' | 'directory' | 'symlink' | 'unknown' = 'unknown';
34
+ if (typeflag === '0' || typeflag === '\0') type = 'file';
35
+ else if (typeflag === '5') type = 'directory';
36
+ else if (typeflag === '2') type = 'symlink';
37
+
38
+ return { name: fullName, mode, size, type, linkname };
39
+ }
40
+
41
+ export interface TarEntry {
42
+ name: string;
43
+ type: string;
44
+ size: number;
45
+ data: Buffer | null;
46
+ linkname: string;
47
+ mode: number;
48
+ }
49
+
50
+ export function* iterateTar(buf: Buffer): Generator<TarEntry> {
51
+ let offset = 0;
52
+ while (offset < buf.length) {
53
+ if (offset + 512 > buf.length) break;
54
+ const hdr = readHeader(buf.subarray(offset));
55
+ if (!hdr) break;
56
+ offset += 512;
57
+ const paddedSize = Math.ceil(hdr.size / 512) * 512;
58
+ const data = hdr.type === 'file' && hdr.size > 0
59
+ ? buf.subarray(offset, offset + hdr.size) : null;
60
+ yield { name: hdr.name, type: hdr.type, size: hdr.size, data, linkname: hdr.linkname, mode: hdr.mode };
61
+ offset += paddedSize;
62
+ }
63
+ }
64
+
65
+ export function readFileFromTar(buf: Buffer, filePath: string): Buffer | null {
66
+ for (const entry of iterateTar(buf)) {
67
+ if (entry.name === filePath && entry.data) return entry.data;
68
+ }
69
+ return null;
70
+ }
71
+
72
+ export function extractTar(buf: Buffer, dest: string): string[] {
73
+ const extracted: string[] = [];
74
+ for (const entry of iterateTar(buf)) {
75
+ let targetPath = entry.name;
76
+ if (targetPath.startsWith('./')) targetPath = targetPath.slice(2);
77
+ if (targetPath.startsWith('/')) targetPath = targetPath.slice(1);
78
+ if (!targetPath) continue;
79
+ const fullPath = path.resolve(dest, targetPath);
80
+ if (!fullPath.startsWith(path.resolve(dest))) continue;
81
+ extracted.push(`/${targetPath}`);
82
+
83
+ if (entry.type === 'directory') {
84
+ fs.mkdirSync(fullPath, { recursive: true, mode: entry.mode || 0o755 });
85
+ } else if (entry.type === 'file') {
86
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
87
+ if (entry.data) fs.writeFileSync(fullPath, entry.data, { mode: entry.mode || 0o644 });
88
+ } else if (entry.type === 'symlink') {
89
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
90
+ try { fs.unlinkSync(fullPath); } catch {}
91
+ fs.symlinkSync(entry.linkname, fullPath);
92
+ }
93
+ }
94
+ return extracted;
95
+ }
96
+
97
+ export function listTarEntries(buf: Buffer): string[] {
98
+ const names: string[] = [];
99
+ for (const entry of iterateTar(buf)) names.push(entry.name);
100
+ return names;
101
+ }
@@ -0,0 +1,89 @@
1
+ export interface DebControl {
2
+ package: string;
3
+ version: string;
4
+ architecture: string;
5
+ maintainer?: string;
6
+ description?: string;
7
+ depends?: string;
8
+ 'pre-depends'?: string;
9
+ conflicts?: string;
10
+ provides?: string;
11
+ 'installed-size'?: string;
12
+ section?: string;
13
+ priority?: string;
14
+ homepage?: string;
15
+ [key: string]: string | undefined;
16
+ }
17
+
18
+ export interface DebPackage {
19
+ path: string;
20
+ control: DebControl;
21
+ controlTar: Buffer;
22
+ dataTar: Buffer;
23
+ }
24
+
25
+ export interface InstalledPackage {
26
+ name: string;
27
+ version: string;
28
+ architecture: string;
29
+ description: string;
30
+ depends?: string;
31
+ 'pre-depends'?: string;
32
+ conflicts?: string;
33
+ provides?: string;
34
+ maintainer?: string;
35
+ homepage?: string;
36
+ controlSection?: string;
37
+ controlPriority?: string;
38
+ installedSize?: number;
39
+ installTime: number;
40
+ reason: 'explicit' | 'dependency';
41
+ files: string[];
42
+ repoType?: 'debian' | 'arch';
43
+ }
44
+
45
+ export interface RepoPkg {
46
+ package: string;
47
+ version: string;
48
+ architecture: string;
49
+ description?: string;
50
+ depends?: string;
51
+ conflicts?: string;
52
+ provides?: string;
53
+ filename: string;
54
+ size?: number;
55
+ installedSize?: number;
56
+ sha256?: string;
57
+ repo: string;
58
+ repoType: 'debian' | 'arch';
59
+ }
60
+
61
+ export interface RepoConfig {
62
+ name: string;
63
+ type?: 'debian' | 'arch';
64
+ server: string;
65
+ dist?: string;
66
+ components?: string[];
67
+ dbFile?: string;
68
+ architecture?: string;
69
+ }
70
+
71
+ export interface Config {
72
+ architecture: string;
73
+ color: boolean;
74
+ repos: RepoConfig[];
75
+ }
76
+
77
+ export interface Database {
78
+ packages: Map<string, InstalledPackage>;
79
+ fileIndex: Map<string, string>;
80
+ }
81
+
82
+ export interface Transaction {
83
+ id: string;
84
+ timestamp: number;
85
+ action: 'install' | 'remove';
86
+ package: string;
87
+ version: string;
88
+ completed: boolean;
89
+ }