chainlesschain 0.37.7 → 0.37.8
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/package.json +1 -1
- package/src/constants.js +1 -8
- package/src/lib/downloader.js +91 -35
- package/src/lib/platform.js +0 -29
- package/src/lib/process-manager.js +33 -12
package/package.json
CHANGED
package/src/constants.js
CHANGED
|
@@ -69,14 +69,7 @@ export const EDITIONS = {
|
|
|
69
69
|
},
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
win32: { x64: "chainlesschain-desktop-vue-{version}-win-x64.exe" },
|
|
74
|
-
darwin: {
|
|
75
|
-
x64: "chainlesschain-desktop-vue-{version}-mac-x64.dmg",
|
|
76
|
-
arm64: "chainlesschain-desktop-vue-{version}-mac-arm64.dmg",
|
|
77
|
-
},
|
|
78
|
-
linux: { x64: "chainlesschain-desktop-vue-{version}-linux-x64.deb" },
|
|
79
|
-
};
|
|
72
|
+
// Asset matching is now done by platform patterns in downloader.js
|
|
80
73
|
|
|
81
74
|
export const CONFIG_DIR_NAME = ".chainlesschain";
|
|
82
75
|
|
package/src/lib/downloader.js
CHANGED
|
@@ -1,30 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createWriteStream,
|
|
3
|
+
existsSync,
|
|
4
|
+
unlinkSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
chmodSync,
|
|
7
|
+
} from "node:fs";
|
|
2
8
|
import { join } from "node:path";
|
|
3
9
|
import { pipeline } from "node:stream/promises";
|
|
4
10
|
import { Readable } from "node:stream";
|
|
11
|
+
import { execSync } from "node:child_process";
|
|
5
12
|
import ora from "ora";
|
|
6
13
|
import { GITHUB_RELEASES_URL } from "../constants.js";
|
|
7
14
|
import { getBinDir, ensureDir } from "./paths.js";
|
|
8
|
-
import {
|
|
9
|
-
import { verifySha256 } from "./checksum.js";
|
|
15
|
+
import { getPlatform, getArch } from "./platform.js";
|
|
10
16
|
import logger from "./logger.js";
|
|
11
17
|
|
|
18
|
+
// Platform-specific asset matching patterns
|
|
19
|
+
const ASSET_PATTERNS = {
|
|
20
|
+
win32: { x64: /chainlesschain.*win32.*x64.*\.zip$/i },
|
|
21
|
+
darwin: {
|
|
22
|
+
x64: /chainlesschain.*darwin.*x64.*\.zip$/i,
|
|
23
|
+
arm64: /chainlesschain.*darwin.*arm64.*\.zip$/i,
|
|
24
|
+
},
|
|
25
|
+
linux: { x64: /chainlesschain.*linux.*x64.*\.zip$/i },
|
|
26
|
+
};
|
|
27
|
+
|
|
12
28
|
export async function downloadRelease(version, options = {}) {
|
|
13
|
-
const binaryName = getBinaryName(version);
|
|
14
29
|
const binDir = ensureDir(getBinDir());
|
|
15
|
-
const destPath = join(binDir, binaryName);
|
|
16
30
|
|
|
17
|
-
if
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
// Check if we already have an extracted app
|
|
32
|
+
if (!options.force && hasExtractedApp(binDir)) {
|
|
33
|
+
logger.info("Application already installed");
|
|
34
|
+
return binDir;
|
|
20
35
|
}
|
|
21
36
|
|
|
22
|
-
const
|
|
23
|
-
options.url
|
|
24
|
-
|
|
37
|
+
const { url: assetUrl, name: assetName } = options.url
|
|
38
|
+
? { url: options.url, name: "download.zip" }
|
|
39
|
+
: await resolveAssetUrl(version);
|
|
40
|
+
|
|
41
|
+
const destPath = join(binDir, assetName);
|
|
42
|
+
const spinner = ora(`Downloading ${assetName}...`).start();
|
|
25
43
|
|
|
26
44
|
try {
|
|
27
|
-
const response = await fetch(
|
|
45
|
+
const response = await fetch(assetUrl, {
|
|
28
46
|
headers: { Accept: "application/octet-stream" },
|
|
29
47
|
redirect: "follow",
|
|
30
48
|
});
|
|
@@ -52,7 +70,7 @@ export async function downloadRelease(version, options = {}) {
|
|
|
52
70
|
downloadedBytes += value.byteLength;
|
|
53
71
|
if (totalBytes > 0) {
|
|
54
72
|
const pct = ((downloadedBytes / totalBytes) * 100).toFixed(1);
|
|
55
|
-
spinner.text = `Downloading ${
|
|
73
|
+
spinner.text = `Downloading ${assetName}... ${pct}% (${formatBytes(downloadedBytes)}/${formatBytes(totalBytes)})`;
|
|
56
74
|
}
|
|
57
75
|
controller.enqueue(value);
|
|
58
76
|
},
|
|
@@ -62,23 +80,23 @@ export async function downloadRelease(version, options = {}) {
|
|
|
62
80
|
await pipeline(nodeStream, createWriteStream(destPath));
|
|
63
81
|
|
|
64
82
|
spinner.succeed(
|
|
65
|
-
`Downloaded ${
|
|
83
|
+
`Downloaded ${assetName} (${formatBytes(downloadedBytes)})`,
|
|
66
84
|
);
|
|
67
85
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
86
|
+
// Extract zip
|
|
87
|
+
if (destPath.endsWith(".zip")) {
|
|
88
|
+
const extractSpinner = ora("Extracting...").start();
|
|
89
|
+
try {
|
|
90
|
+
extractZip(destPath, binDir);
|
|
72
91
|
unlinkSync(destPath);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
extractSpinner.succeed("Extracted and ready");
|
|
93
|
+
} catch (err) {
|
|
94
|
+
extractSpinner.fail(`Extraction failed: ${err.message}`);
|
|
95
|
+
throw err;
|
|
77
96
|
}
|
|
78
|
-
verifySpinner.succeed("Checksum verified");
|
|
79
97
|
}
|
|
80
98
|
|
|
81
|
-
return
|
|
99
|
+
return binDir;
|
|
82
100
|
} catch (err) {
|
|
83
101
|
spinner.fail(`Download failed: ${err.message}`);
|
|
84
102
|
if (existsSync(destPath)) {
|
|
@@ -88,7 +106,7 @@ export async function downloadRelease(version, options = {}) {
|
|
|
88
106
|
}
|
|
89
107
|
}
|
|
90
108
|
|
|
91
|
-
async function resolveAssetUrl(version
|
|
109
|
+
async function resolveAssetUrl(version) {
|
|
92
110
|
// Try exact version first, then fall back to latest release
|
|
93
111
|
const tagName = `v${version}`;
|
|
94
112
|
let release = await fetchRelease(`${GITHUB_RELEASES_URL}/tags/${tagName}`);
|
|
@@ -99,24 +117,32 @@ async function resolveAssetUrl(version, binaryName) {
|
|
|
99
117
|
}
|
|
100
118
|
|
|
101
119
|
if (!release) {
|
|
102
|
-
throw new Error(
|
|
120
|
+
throw new Error("No releases found on GitHub");
|
|
103
121
|
}
|
|
104
122
|
|
|
105
|
-
|
|
106
|
-
const actualVersion = release.tag_name.replace(/^v/, "");
|
|
107
|
-
const actualBinaryName = binaryName.replace(version, actualVersion);
|
|
123
|
+
logger.info(`Using release ${release.tag_name}`);
|
|
108
124
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
125
|
+
// Match asset by platform/arch pattern
|
|
126
|
+
const p = getPlatform();
|
|
127
|
+
const a = getArch();
|
|
128
|
+
const patterns = ASSET_PATTERNS[p];
|
|
129
|
+
if (!patterns) {
|
|
130
|
+
throw new Error(`Unsupported platform: ${p}`);
|
|
131
|
+
}
|
|
132
|
+
const pattern = patterns[a];
|
|
133
|
+
if (!pattern) {
|
|
134
|
+
throw new Error(`Unsupported architecture: ${a} on ${p}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const asset = release.assets.find((ast) => pattern.test(ast.name));
|
|
112
138
|
if (!asset) {
|
|
113
|
-
const available = release.assets.map((
|
|
139
|
+
const available = release.assets.map((ast) => ast.name).join(", ");
|
|
114
140
|
throw new Error(
|
|
115
|
-
`No matching asset in release ${release.tag_name}. Available: ${available}`,
|
|
141
|
+
`No matching asset for ${p}/${a} in release ${release.tag_name}. Available: ${available}`,
|
|
116
142
|
);
|
|
117
143
|
}
|
|
118
144
|
|
|
119
|
-
return asset.browser_download_url;
|
|
145
|
+
return { url: asset.browser_download_url, name: asset.name };
|
|
120
146
|
}
|
|
121
147
|
|
|
122
148
|
async function fetchRelease(url) {
|
|
@@ -127,6 +153,36 @@ async function fetchRelease(url) {
|
|
|
127
153
|
return response.json();
|
|
128
154
|
}
|
|
129
155
|
|
|
156
|
+
function extractZip(zipPath, destDir) {
|
|
157
|
+
const p = getPlatform();
|
|
158
|
+
if (p === "win32") {
|
|
159
|
+
// Use PowerShell on Windows
|
|
160
|
+
execSync(
|
|
161
|
+
`powershell -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`,
|
|
162
|
+
{ stdio: "ignore" },
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
execSync(`unzip -o "${zipPath}" -d "${destDir}"`, { stdio: "ignore" });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function hasExtractedApp(binDir) {
|
|
170
|
+
if (!existsSync(binDir)) return false;
|
|
171
|
+
try {
|
|
172
|
+
const files = readdirSync(binDir, { recursive: true });
|
|
173
|
+
return files.some(
|
|
174
|
+
(f) =>
|
|
175
|
+
(typeof f === "string" ? f : f.toString())
|
|
176
|
+
.toLowerCase()
|
|
177
|
+
.includes("chainlesschain") &&
|
|
178
|
+
((typeof f === "string" ? f : f.toString()).endsWith(".exe") ||
|
|
179
|
+
!(typeof f === "string" ? f : f.toString()).includes(".")),
|
|
180
|
+
);
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
130
186
|
function formatBytes(bytes) {
|
|
131
187
|
if (bytes < 1024) return `${bytes}B`;
|
|
132
188
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
package/src/lib/platform.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { platform, arch } from "node:os";
|
|
2
|
-
import { BINARY_NAMES } from "../constants.js";
|
|
3
2
|
|
|
4
3
|
export function getPlatform() {
|
|
5
4
|
return platform();
|
|
@@ -12,34 +11,6 @@ export function getArch() {
|
|
|
12
11
|
return a;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
export function getBinaryName(version) {
|
|
16
|
-
const p = getPlatform();
|
|
17
|
-
const a = getArch();
|
|
18
|
-
const platformBinaries = BINARY_NAMES[p];
|
|
19
|
-
if (!platformBinaries) {
|
|
20
|
-
throw new Error(`Unsupported platform: ${p}`);
|
|
21
|
-
}
|
|
22
|
-
const template = platformBinaries[a];
|
|
23
|
-
if (!template) {
|
|
24
|
-
throw new Error(`Unsupported architecture: ${a} on ${p}`);
|
|
25
|
-
}
|
|
26
|
-
return template.replace("{version}", version);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function getBinaryExtension() {
|
|
30
|
-
const p = getPlatform();
|
|
31
|
-
switch (p) {
|
|
32
|
-
case "win32":
|
|
33
|
-
return ".exe";
|
|
34
|
-
case "darwin":
|
|
35
|
-
return ".dmg";
|
|
36
|
-
case "linux":
|
|
37
|
-
return ".deb";
|
|
38
|
-
default:
|
|
39
|
-
return "";
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
14
|
export function isWindows() {
|
|
44
15
|
return getPlatform() === "win32";
|
|
45
16
|
}
|
|
@@ -110,19 +110,40 @@ export function getAppPid() {
|
|
|
110
110
|
function findExecutable(binDir, extension) {
|
|
111
111
|
if (!existsSync(binDir)) return null;
|
|
112
112
|
try {
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
isChainless &&
|
|
119
|
-
!f.endsWith(".dmg") &&
|
|
120
|
-
!f.endsWith(".deb") &&
|
|
121
|
-
!f.endsWith(".sha256")
|
|
122
|
-
);
|
|
123
|
-
});
|
|
124
|
-
return match ? join(binDir, match) : null;
|
|
113
|
+
// Search recursively for the executable
|
|
114
|
+
const results = [];
|
|
115
|
+
searchDir(binDir, extension, results);
|
|
116
|
+
return results.length > 0 ? results[0] : null;
|
|
125
117
|
} catch {
|
|
126
118
|
return null;
|
|
127
119
|
}
|
|
128
120
|
}
|
|
121
|
+
|
|
122
|
+
function searchDir(dir, extension, results) {
|
|
123
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
const fullPath = join(dir, entry.name);
|
|
126
|
+
if (entry.isDirectory()) {
|
|
127
|
+
searchDir(fullPath, extension, results);
|
|
128
|
+
} else {
|
|
129
|
+
const name = entry.name.toLowerCase();
|
|
130
|
+
const isChainless = name.includes("chainlesschain");
|
|
131
|
+
if (!isChainless) continue;
|
|
132
|
+
if (extension && name.endsWith(extension)) {
|
|
133
|
+
results.push(fullPath);
|
|
134
|
+
} else if (
|
|
135
|
+
!extension &&
|
|
136
|
+
!name.endsWith(".dmg") &&
|
|
137
|
+
!name.endsWith(".deb") &&
|
|
138
|
+
!name.endsWith(".zip") &&
|
|
139
|
+
!name.endsWith(".sha256") &&
|
|
140
|
+
!name.endsWith(".dll") &&
|
|
141
|
+
!name.endsWith(".dat") &&
|
|
142
|
+
!name.endsWith(".pak") &&
|
|
143
|
+
!name.endsWith(".json")
|
|
144
|
+
) {
|
|
145
|
+
results.push(fullPath);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|