@maravilla-labs/cli 0.1.5 → 0.1.7
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/bin/maravilla +0 -0
- package/bin/maravilla-update.js +4 -4
- package/package.json +3 -2
- package/scripts/postinstall.js +49 -20
- package/scripts/update.js +36 -7
package/bin/maravilla
CHANGED
|
Binary file
|
package/bin/maravilla-update.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Thin wrapper that runs the package updater
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import {
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
6
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = path.dirname(__filename);
|
|
9
|
-
const updater = path.join(__dirname, '..', 'scripts', 'update.js');
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// directly import the updater script
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
await import(path.join(__dirname, '..', 'scripts', 'update.js'));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maravilla-labs/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "NPM wrapper for the Maravilla CLI binary; downloads the right release for your platform.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Maravilla Labs",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"tar": "^6.2.1",
|
|
51
51
|
"unzipper": "^0.10.14",
|
|
52
52
|
"progress": "^2.0.3",
|
|
53
|
-
"chalk": "^5.3.0"
|
|
53
|
+
"chalk": "^5.3.0",
|
|
54
|
+
"https-proxy-agent": "^7.0.4"
|
|
54
55
|
}
|
|
55
56
|
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -11,6 +11,7 @@ import ProgressBar from 'progress';
|
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
import { resolveTarget, artifactName } from './platform.js';
|
|
13
13
|
import crypto from 'crypto';
|
|
14
|
+
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
14
15
|
|
|
15
16
|
// Allow CI to skip downloading the binary
|
|
16
17
|
if (process.env.MARAVILLA_CLI_SKIP_INSTALL === '1' || process.env.CI) {
|
|
@@ -28,6 +29,10 @@ const VERSION = process.env.MARAVILLA_VERSION || 'latest'; // cli-vX.Y.Z or 'lat
|
|
|
28
29
|
const BIN_DIR = path.join(__dirname, '..', 'bin');
|
|
29
30
|
const EXECUTABLE = process.platform === 'win32' ? `${BIN_NAME}.exe` : BIN_NAME;
|
|
30
31
|
const INSTALL_PATH = path.join(BIN_DIR, EXECUTABLE);
|
|
32
|
+
const GH_TOKEN = process.env.MARAVILLA_GH_TOKEN || process.env.GITHUB_TOKEN || '';
|
|
33
|
+
const PROXY = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || '';
|
|
34
|
+
const HTTP_TIMEOUT_MS = Number(process.env.MARAVILLA_HTTP_TIMEOUT_MS || 15000);
|
|
35
|
+
const HTTP_RETRIES = Math.max(1, Number(process.env.MARAVILLA_HTTP_RETRIES || 3));
|
|
31
36
|
|
|
32
37
|
function flower() {
|
|
33
38
|
return chalk.magenta('✿');
|
|
@@ -43,13 +48,13 @@ async function abortableFetch(url, opts = {}, timeoutMs = 30000) {
|
|
|
43
48
|
const controller = new AbortController();
|
|
44
49
|
const t = setTimeout(() => controller.abort(), timeoutMs);
|
|
45
50
|
try {
|
|
46
|
-
|
|
51
|
+
return await fetch(url, { ...opts, signal: controller.signal });
|
|
47
52
|
} finally {
|
|
48
53
|
clearTimeout(t);
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
async function withRetries(taskFn, tries =
|
|
57
|
+
async function withRetries(taskFn, tries = HTTP_RETRIES, baseDelay = 1000) {
|
|
53
58
|
let attempt = 0;
|
|
54
59
|
let lastErr;
|
|
55
60
|
while (attempt < tries) {
|
|
@@ -67,13 +72,37 @@ async function withRetries(taskFn, tries = 3, baseDelay = 1000) {
|
|
|
67
72
|
throw lastErr;
|
|
68
73
|
}
|
|
69
74
|
|
|
75
|
+
function makeFetchOpts() {
|
|
76
|
+
const headers = { 'User-Agent': 'maravilla-cli-installer' };
|
|
77
|
+
if (GH_TOKEN) headers['Authorization'] = `Bearer ${GH_TOKEN}`;
|
|
78
|
+
const opts = { headers };
|
|
79
|
+
if (PROXY) {
|
|
80
|
+
try { opts.agent = new HttpsProxyAgent(PROXY); } catch {}
|
|
81
|
+
}
|
|
82
|
+
return opts;
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
async function getReleaseTag() {
|
|
71
86
|
if (VERSION && VERSION !== 'latest') return VERSION;
|
|
72
87
|
const api = `https://api.github.com/repos/${REPO}/releases/latest`;
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
const opts = makeFetchOpts();
|
|
89
|
+
try {
|
|
90
|
+
console.log(chalk.gray(`${flower()} fetching latest release tag (${Math.round(HTTP_TIMEOUT_MS/100)/10}s timeout, ${HTTP_RETRIES} retries)`));
|
|
91
|
+
const res = await withRetries(() => abortableFetch(api, opts, HTTP_TIMEOUT_MS));
|
|
92
|
+
if (!res.ok) throw new Error(`Failed to fetch latest release: ${res.status} ${res.statusText}`);
|
|
93
|
+
const json = await res.json();
|
|
94
|
+
if (!json.tag_name) throw new Error('No tag_name in API response');
|
|
95
|
+
return json.tag_name;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.log(chalk.yellow(`${flower()} API lookup failed (${e.message}). Falling back to HTML redirect...`));
|
|
98
|
+
// Fallback: use HTML redirect to /releases/tag/<tag>
|
|
99
|
+
const html = `https://github.com/${REPO}/releases/latest`;
|
|
100
|
+
const res = await abortableFetch(html, { ...opts, redirect: 'manual' }, Math.min(HTTP_TIMEOUT_MS, 10000));
|
|
101
|
+
const loc = res.headers.get('location') || '';
|
|
102
|
+
const m = loc.match(/\/releases\/tag\/([^/]+)$/);
|
|
103
|
+
if (m) return m[1];
|
|
104
|
+
throw new Error('Could not resolve latest tag via fallback');
|
|
105
|
+
}
|
|
77
106
|
}
|
|
78
107
|
|
|
79
108
|
async function downloadWithProgress(url, dest) {
|
|
@@ -139,6 +168,19 @@ function cmpSemver(a, b) {
|
|
|
139
168
|
|
|
140
169
|
async function install() {
|
|
141
170
|
const { target, ext } = resolveTarget();
|
|
171
|
+
|
|
172
|
+
// Detect current installed version first to enable true offline fast-path
|
|
173
|
+
let cur = null;
|
|
174
|
+
try { cur = await currentVersion(); } catch {}
|
|
175
|
+
|
|
176
|
+
if (process.env.MARAVILLA_OFFLINE === '1') {
|
|
177
|
+
if (cur) {
|
|
178
|
+
log(chalk.yellow(`Offline mode: keeping existing v${cur}`));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
throw new Error('Offline mode: no existing binary; set MARAVILLA_VERSION to a specific tag or retry online');
|
|
182
|
+
}
|
|
183
|
+
|
|
142
184
|
log(`Resolving latest release tag from ${chalk.cyan(REPO)}`);
|
|
143
185
|
const tag = await getReleaseTag();
|
|
144
186
|
const art = artifactName(BIN_NAME, tag, target);
|
|
@@ -155,21 +197,8 @@ async function install() {
|
|
|
155
197
|
let requested = null;
|
|
156
198
|
const m = tag.match(/v(\d+\.\d+\.\d+)/);
|
|
157
199
|
if (m) requested = m[1];
|
|
158
|
-
let cur = null;
|
|
159
|
-
try { cur = await currentVersion(); } catch {}
|
|
160
200
|
|
|
161
201
|
if (!force) {
|
|
162
|
-
if (process.env.MARAVILLA_OFFLINE === '1') {
|
|
163
|
-
if (cur) {
|
|
164
|
-
if (requested && cmpSemver(cur, requested) < 0) {
|
|
165
|
-
log(chalk.yellow(`Offline mode: cannot update to v${requested}; keeping existing v${cur}`));
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
log(chalk.yellow(`Offline mode: keeping existing v${cur}`));
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
throw new Error('Offline mode: no existing binary to keep');
|
|
172
|
-
}
|
|
173
202
|
if (cur && requested && cmpSemver(cur, requested) >= 0) {
|
|
174
203
|
log(chalk.green(`Already up to date (v${cur})`));
|
|
175
204
|
return;
|
|
@@ -183,7 +212,7 @@ async function install() {
|
|
|
183
212
|
// Verify checksum if SHA256SUMS is available
|
|
184
213
|
try {
|
|
185
214
|
log('Verifying checksum');
|
|
186
|
-
|
|
215
|
+
const sumsRes = await abortableFetch(sumsUrl, makeFetchOpts(), 15000);
|
|
187
216
|
if (sumsRes.ok) {
|
|
188
217
|
const sumsText = await sumsRes.text();
|
|
189
218
|
const expected = sumsText
|
package/scripts/update.js
CHANGED
|
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'url';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { spawn } from 'child_process';
|
|
5
5
|
import fetch from 'node-fetch';
|
|
6
|
+
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
6
7
|
import chalk from 'chalk';
|
|
7
8
|
|
|
8
9
|
// Re-run postinstall only when an update is actually needed
|
|
@@ -15,6 +16,10 @@ const REPO = process.env.MARAVILLA_CLI_REPO || 'maravilla-labs/maravilla-cli';
|
|
|
15
16
|
const BIN_DIR = path.join(__dirname, '..', 'bin');
|
|
16
17
|
const EXECUTABLE = process.platform === 'win32' ? `${BIN_NAME}.exe` : BIN_NAME;
|
|
17
18
|
const INSTALL_PATH = path.join(BIN_DIR, EXECUTABLE);
|
|
19
|
+
const GH_TOKEN = process.env.MARAVILLA_GH_TOKEN || process.env.GITHUB_TOKEN || '';
|
|
20
|
+
const PROXY = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || '';
|
|
21
|
+
const HTTP_TIMEOUT_MS = Number(process.env.MARAVILLA_HTTP_TIMEOUT_MS || 15000);
|
|
22
|
+
const HTTP_RETRIES = Math.max(1, Number(process.env.MARAVILLA_HTTP_RETRIES || 3));
|
|
18
23
|
|
|
19
24
|
function flower() {
|
|
20
25
|
return chalk.magenta('✿');
|
|
@@ -43,15 +48,39 @@ async function currentVersion() {
|
|
|
43
48
|
});
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
function makeFetchOpts() {
|
|
52
|
+
const headers = { 'User-Agent': 'maravilla-cli-updater' };
|
|
53
|
+
if (GH_TOKEN) headers['Authorization'] = `Bearer ${GH_TOKEN}`;
|
|
54
|
+
const opts = { headers };
|
|
55
|
+
if (PROXY) {
|
|
56
|
+
try { opts.agent = new HttpsProxyAgent(PROXY); } catch {}
|
|
57
|
+
}
|
|
58
|
+
return opts;
|
|
59
|
+
}
|
|
60
|
+
|
|
46
61
|
async function latestVersion() {
|
|
47
62
|
const api = `https://api.github.com/repos/${REPO}/releases/latest`;
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
const opts = makeFetchOpts();
|
|
64
|
+
try {
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
const t = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS);
|
|
67
|
+
const res = await fetch(api, { ...opts, signal: controller.signal });
|
|
68
|
+
clearTimeout(t);
|
|
69
|
+
if (!res.ok) throw new Error(`Failed to fetch latest release: ${res.status} ${res.statusText}`);
|
|
70
|
+
const json = await res.json();
|
|
71
|
+
const tag = json.tag_name || '';
|
|
72
|
+
const m = tag.match(/v(\d+\.\d+\.\d+)/);
|
|
73
|
+
return { tag, version: m ? m[1] : null };
|
|
74
|
+
} catch (e) {
|
|
75
|
+
// Fallback to HTML redirect
|
|
76
|
+
const html = `https://github.com/${REPO}/releases/latest`;
|
|
77
|
+
const res = await fetch(html, { ...opts, redirect: 'manual' });
|
|
78
|
+
const loc = res.headers.get('location') || '';
|
|
79
|
+
const m = loc.match(/\/releases\/tag\/([^/]+)$/);
|
|
80
|
+
const tag = m ? m[1] : '';
|
|
81
|
+
const mv = tag.match(/v(\d+\.\d+\.\d+)/);
|
|
82
|
+
return { tag, version: mv ? mv[1] : null };
|
|
83
|
+
}
|
|
55
84
|
}
|
|
56
85
|
|
|
57
86
|
function cmpSemver(a, b) {
|