@nordbyte/nordrelay-auto-updater 0.1.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.
@@ -0,0 +1,92 @@
1
+ import { runCommand } from "./subprocess.js";
2
+
3
+ export function collectNpmPackages(settings = {}) {
4
+ if (settings.enableNpmPackages === false) {
5
+ return { supported: false, packages: [], errors: [], globalRoot: "", disabled: true };
6
+ }
7
+ const timeoutMs = settings.commandTimeoutMs;
8
+ const registryArgs = settings.npmRegistry ? ["--registry", String(settings.npmRegistry)] : [];
9
+ const root = runCommand("npm", ["root", "-g"], { timeoutMs });
10
+ const listed = runCommand("npm", ["ls", "-g", "--depth=0", "--json", "--long", ...registryArgs], { timeoutMs, maxBuffer: 16 * 1024 * 1024 });
11
+ const outdated = runCommand("npm", ["outdated", "-g", "--json", "--long", ...registryArgs], { timeoutMs, maxBuffer: 16 * 1024 * 1024 });
12
+ const packages = mergeNpmPackageData(parseNpmList(listed.stdout), parseNpmOutdated(outdated.stdout));
13
+ const errors = [];
14
+ if (listed.missing) {
15
+ errors.push({ command: "npm", error: "npm not found" });
16
+ } else if (!listed.ok && !listed.stdout.trim()) {
17
+ errors.push({ command: "npm ls -g", error: listed.stderr || listed.error || `Exit ${listed.status}` });
18
+ }
19
+ if (!outdated.ok && ![0, 1].includes(Number(outdated.status)) && !outdated.stdout.trim()) {
20
+ errors.push({ command: "npm outdated -g", error: outdated.stderr || outdated.error || `Exit ${outdated.status}` });
21
+ }
22
+ return {
23
+ supported: !listed.missing,
24
+ globalRoot: root.stdout.trim(),
25
+ packages,
26
+ errors,
27
+ diagnostics: {
28
+ listDurationMs: listed.durationMs,
29
+ outdatedDurationMs: outdated.durationMs,
30
+ listStatus: listed.status,
31
+ outdatedStatus: outdated.status,
32
+ },
33
+ };
34
+ }
35
+
36
+ export function parseNpmList(stdout) {
37
+ try {
38
+ const parsed = JSON.parse(String(stdout || "{}"));
39
+ const dependencies = parsed.dependencies && typeof parsed.dependencies === "object" ? parsed.dependencies : {};
40
+ return Object.entries(dependencies).map(([name, item]) => ({
41
+ name,
42
+ current: String(item?.version || ""),
43
+ wanted: String(item?.version || ""),
44
+ latest: String(item?.version || ""),
45
+ location: String(item?.path || item?.location || ""),
46
+ type: String(item?.dev ? "dev" : item?.optional ? "optional" : "prod"),
47
+ homepage: String(item?.homepage || ""),
48
+ status: "latest",
49
+ }));
50
+ } catch {
51
+ return [];
52
+ }
53
+ }
54
+
55
+ export function parseNpmOutdated(stdout) {
56
+ try {
57
+ const parsed = JSON.parse(String(stdout || "{}"));
58
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return [];
59
+ return Object.entries(parsed).map(([name, item]) => ({
60
+ name,
61
+ current: String(item?.current || ""),
62
+ wanted: String(item?.wanted || ""),
63
+ latest: String(item?.latest || ""),
64
+ location: String(item?.location || ""),
65
+ type: String(item?.type || ""),
66
+ homepage: String(item?.homepage || ""),
67
+ status: "outdated",
68
+ }));
69
+ } catch {
70
+ return [];
71
+ }
72
+ }
73
+
74
+ export function mergeNpmPackageData(installed, outdated) {
75
+ const map = new Map();
76
+ for (const item of installed) {
77
+ map.set(item.name, { ...item });
78
+ }
79
+ for (const item of outdated) {
80
+ const existing = map.get(item.name) || {};
81
+ map.set(item.name, {
82
+ ...existing,
83
+ ...item,
84
+ status: item.latest && item.current && item.latest !== item.current ? "outdated" : "latest",
85
+ });
86
+ }
87
+ return [...map.values()].sort((left, right) => {
88
+ const status = Number(right.status === "outdated") - Number(left.status === "outdated");
89
+ return status || String(left.name).localeCompare(String(right.name));
90
+ });
91
+ }
92
+
@@ -0,0 +1,19 @@
1
+ import { managersForPlatform, runManager } from "./package-managers.js";
2
+
3
+ export function collectOsUpdates(settings = {}) {
4
+ if (settings.enableOsUpdates === false) {
5
+ return { supported: false, managers: [], updates: [], errors: [], disabled: true };
6
+ }
7
+ const managers = managersForPlatform(process.platform, settings);
8
+ if (!managers.length) {
9
+ return { supported: false, managers: [], updates: [], errors: [], disabled: false };
10
+ }
11
+ const results = managers.map((name) => runManager(name, settings));
12
+ return {
13
+ supported: true,
14
+ managers: results,
15
+ updates: results.flatMap((result) => result.updates.map((update) => ({ ...update, manager: result.name }))),
16
+ errors: results.filter((result) => !result.ok && !result.missing).map((result) => ({ manager: result.name, error: result.error, status: result.status })),
17
+ };
18
+ }
19
+
@@ -0,0 +1,175 @@
1
+ import { runCommand } from "./subprocess.js";
2
+
3
+ export const OS_MANAGER_COMMANDS = {
4
+ apt: { command: "apt", args: ["list", "--upgradable"] },
5
+ dnf: { command: "dnf", args: ["check-update"] },
6
+ pacman: { command: "pacman", args: ["-Qu"] },
7
+ zypper: { command: "zypper", args: ["list-updates"] },
8
+ snap: { command: "snap", args: ["refresh", "--list"] },
9
+ flatpak: { command: "flatpak", args: ["remote-ls", "--updates"] },
10
+ softwareupdate: { command: "softwareupdate", args: ["-l"] },
11
+ brew: { command: "brew", args: ["outdated", "--json=v2"] },
12
+ };
13
+
14
+ export function managersForPlatform(platform = process.platform, settings = {}) {
15
+ if (settings.enableOsUpdates === false) return [];
16
+ const disabled = new Set(String(settings.disabledManagers || "").split(",").map((item) => item.trim()).filter(Boolean));
17
+ const enabled = [];
18
+ if (platform === "linux") {
19
+ enabled.push("apt", "dnf", "pacman", "zypper", "snap", "flatpak");
20
+ } else if (platform === "darwin") {
21
+ enabled.push("softwareupdate", "brew");
22
+ }
23
+ return enabled.filter((name) => !disabled.has(name));
24
+ }
25
+
26
+ export function runManager(name, settings = {}) {
27
+ const spec = OS_MANAGER_COMMANDS[name];
28
+ if (!spec) {
29
+ return { name, ok: false, updates: [], error: `Unknown package manager: ${name}`, durationMs: 0 };
30
+ }
31
+ const result = runCommand(spec.command, spec.args, { timeoutMs: settings.commandTimeoutMs });
32
+ const parser = MANAGER_PARSERS[name];
33
+ const updates = parser ? parser(result.stdout, result.stderr) : [];
34
+ const okStatus = result.ok || (name === "dnf" && result.status === 100);
35
+ return {
36
+ name,
37
+ command: [spec.command, ...spec.args].join(" "),
38
+ ok: okStatus && !result.timedOut && !result.missing,
39
+ missing: result.missing,
40
+ status: result.status,
41
+ durationMs: result.durationMs,
42
+ updates,
43
+ error: okStatus || result.missing ? "" : result.stderr || result.error || `Exit ${result.status}`,
44
+ };
45
+ }
46
+
47
+ export function parseAptList(stdout) {
48
+ return String(stdout || "")
49
+ .split(/\r?\n/)
50
+ .map((line) => line.trim())
51
+ .filter((line) => line && !/^listing/i.test(line))
52
+ .map((line) => {
53
+ const match = /^([^/\s]+)\/([^\s]+)\s+([^\s]+)\s+([^\s]+)(?:\s+\[upgradable from:\s*([^\]]+)\])?/i.exec(line);
54
+ if (!match) return null;
55
+ return {
56
+ manager: "apt",
57
+ name: match[1],
58
+ source: match[2],
59
+ latest: match[3],
60
+ arch: match[4],
61
+ current: match[5] || "",
62
+ };
63
+ })
64
+ .filter(Boolean);
65
+ }
66
+
67
+ export function parseDnfCheckUpdate(stdout) {
68
+ return String(stdout || "")
69
+ .split(/\r?\n/)
70
+ .map((line) => line.trim())
71
+ .filter((line) => line && !/^(last metadata|obsoleting|security:)/i.test(line))
72
+ .map((line) => {
73
+ const parts = line.split(/\s+/);
74
+ if (parts.length < 3 || !parts[0].includes(".")) return null;
75
+ return { manager: "dnf", name: parts[0], current: "", latest: parts[1], source: parts.slice(2).join(" ") };
76
+ })
77
+ .filter(Boolean);
78
+ }
79
+
80
+ export function parsePacmanQu(stdout) {
81
+ return String(stdout || "")
82
+ .split(/\r?\n/)
83
+ .map((line) => line.trim())
84
+ .filter(Boolean)
85
+ .map((line) => {
86
+ const match = /^(\S+)\s+(\S+)\s+->\s+(\S+)/.exec(line);
87
+ return match ? { manager: "pacman", name: match[1], current: match[2], latest: match[3], source: "" } : null;
88
+ })
89
+ .filter(Boolean);
90
+ }
91
+
92
+ export function parseZypperListUpdates(stdout) {
93
+ return String(stdout || "")
94
+ .split(/\r?\n/)
95
+ .map((line) => line.trim())
96
+ .filter((line) => line.includes("|") && !/^[-+|]+$/.test(line))
97
+ .map((line) => line.split("|").map((part) => part.trim()))
98
+ .filter((parts) => parts.length >= 5 && !/^repository$/i.test(parts[0]))
99
+ .map((parts) => ({ manager: "zypper", source: parts[0], name: parts[2], current: parts[3], latest: parts[4] }));
100
+ }
101
+
102
+ export function parseSnapRefreshList(stdout) {
103
+ return String(stdout || "")
104
+ .split(/\r?\n/)
105
+ .map((line) => line.trim())
106
+ .filter((line) => line && !/^name\s+/i.test(line))
107
+ .map((line) => {
108
+ const parts = line.split(/\s+/);
109
+ return parts.length >= 4 ? { manager: "snap", name: parts[0], current: parts[1], latest: parts[2], source: parts.slice(3).join(" ") } : null;
110
+ })
111
+ .filter(Boolean);
112
+ }
113
+
114
+ export function parseFlatpakUpdates(stdout) {
115
+ return String(stdout || "")
116
+ .split(/\r?\n/)
117
+ .map((line) => line.trim())
118
+ .filter(Boolean)
119
+ .map((line) => {
120
+ const parts = line.split(/\t+/);
121
+ return { manager: "flatpak", name: parts[0] || line, current: "", latest: parts[1] || "", source: parts[2] || "" };
122
+ });
123
+ }
124
+
125
+ export function parseSoftwareUpdate(stdout) {
126
+ const updates = [];
127
+ const lines = String(stdout || "").split(/\r?\n/);
128
+ for (let index = 0; index < lines.length; index += 1) {
129
+ const line = lines[index].trim();
130
+ const match = /^\*\s+Label:\s*(.+)$/i.exec(line);
131
+ if (!match) continue;
132
+ const title = String(lines[index + 1] || "").replace(/^Title:\s*/i, "").trim();
133
+ const version = /Version:\s*([^,]+)/i.exec(title)?.[1]?.trim() || "";
134
+ updates.push({ manager: "softwareupdate", name: match[1].trim(), current: "", latest: version, source: title });
135
+ }
136
+ return updates;
137
+ }
138
+
139
+ export function parseBrewOutdated(stdout) {
140
+ try {
141
+ const parsed = JSON.parse(String(stdout || "{}"));
142
+ const formulae = Array.isArray(parsed.formulae) ? parsed.formulae : [];
143
+ const casks = Array.isArray(parsed.casks) ? parsed.casks : [];
144
+ return [
145
+ ...formulae.map((item) => ({
146
+ manager: "brew",
147
+ name: item.name,
148
+ current: Array.isArray(item.installed_versions) ? item.installed_versions.join(", ") : "",
149
+ latest: item.current_version || "",
150
+ source: "formula",
151
+ })),
152
+ ...casks.map((item) => ({
153
+ manager: "brew",
154
+ name: item.name,
155
+ current: Array.isArray(item.installed_versions) ? item.installed_versions.join(", ") : "",
156
+ latest: item.current_version || "",
157
+ source: "cask",
158
+ })),
159
+ ].filter((item) => item.name);
160
+ } catch {
161
+ return [];
162
+ }
163
+ }
164
+
165
+ const MANAGER_PARSERS = {
166
+ apt: parseAptList,
167
+ dnf: parseDnfCheckUpdate,
168
+ pacman: parsePacmanQu,
169
+ zypper: parseZypperListUpdates,
170
+ snap: parseSnapRefreshList,
171
+ flatpak: parseFlatpakUpdates,
172
+ softwareupdate: parseSoftwareUpdate,
173
+ brew: parseBrewOutdated,
174
+ };
175
+