@maravilla-labs/cli 0.1.6 → 0.1.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/README.md +5 -0
- package/bin/maravilla +0 -0
- package/bin/maravilla-update.js +4 -4
- package/package.json +3 -2
- package/scripts/postinstall.js +68 -24
- package/scripts/update.js +36 -7
package/README.md
CHANGED
|
@@ -16,6 +16,11 @@ pnpm add -g @maravilla-labs/cli
|
|
|
16
16
|
yarn global add @maravilla-labs/cli
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
**Note:** To see installation progress with npm 7+, use the `--foreground-scripts` flag:
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g @maravilla-labs/cli --foreground-scripts
|
|
22
|
+
```
|
|
23
|
+
|
|
19
24
|
Verify:
|
|
20
25
|
|
|
21
26
|
```bash
|
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.8",
|
|
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,14 +11,24 @@ 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) {
|
|
17
18
|
console.log('✿ Skipping Maravilla CLI binary install (CI or skip flag set)');
|
|
18
19
|
process.exit(0);
|
|
19
20
|
}
|
|
21
|
+
|
|
20
22
|
import { spawn } from 'child_process';
|
|
21
23
|
|
|
24
|
+
// Detect if output is being suppressed (npm 7+ behavior)
|
|
25
|
+
// Write to stderr as a fallback since it's more likely to be shown
|
|
26
|
+
const isOutputSuppressed = !process.stdout.isTTY && !process.env.npm_config_foreground_scripts;
|
|
27
|
+
if (isOutputSuppressed) {
|
|
28
|
+
console.error('✿ Installing Maravilla CLI binary...');
|
|
29
|
+
console.error('✿ To see detailed progress, run: npm install --foreground-scripts');
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
const __filename = fileURLToPath(import.meta.url);
|
|
23
33
|
const __dirname = path.dirname(__filename);
|
|
24
34
|
|
|
@@ -28,6 +38,10 @@ const VERSION = process.env.MARAVILLA_VERSION || 'latest'; // cli-vX.Y.Z or 'lat
|
|
|
28
38
|
const BIN_DIR = path.join(__dirname, '..', 'bin');
|
|
29
39
|
const EXECUTABLE = process.platform === 'win32' ? `${BIN_NAME}.exe` : BIN_NAME;
|
|
30
40
|
const INSTALL_PATH = path.join(BIN_DIR, EXECUTABLE);
|
|
41
|
+
const GH_TOKEN = process.env.MARAVILLA_GH_TOKEN || process.env.GITHUB_TOKEN || '';
|
|
42
|
+
const PROXY = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || '';
|
|
43
|
+
const HTTP_TIMEOUT_MS = Number(process.env.MARAVILLA_HTTP_TIMEOUT_MS || 15000);
|
|
44
|
+
const HTTP_RETRIES = Math.max(1, Number(process.env.MARAVILLA_HTTP_RETRIES || 3));
|
|
31
45
|
|
|
32
46
|
function flower() {
|
|
33
47
|
return chalk.magenta('✿');
|
|
@@ -43,13 +57,13 @@ async function abortableFetch(url, opts = {}, timeoutMs = 30000) {
|
|
|
43
57
|
const controller = new AbortController();
|
|
44
58
|
const t = setTimeout(() => controller.abort(), timeoutMs);
|
|
45
59
|
try {
|
|
46
|
-
|
|
60
|
+
return await fetch(url, { ...opts, signal: controller.signal });
|
|
47
61
|
} finally {
|
|
48
62
|
clearTimeout(t);
|
|
49
63
|
}
|
|
50
64
|
}
|
|
51
65
|
|
|
52
|
-
async function withRetries(taskFn, tries =
|
|
66
|
+
async function withRetries(taskFn, tries = HTTP_RETRIES, baseDelay = 1000) {
|
|
53
67
|
let attempt = 0;
|
|
54
68
|
let lastErr;
|
|
55
69
|
while (attempt < tries) {
|
|
@@ -67,13 +81,37 @@ async function withRetries(taskFn, tries = 3, baseDelay = 1000) {
|
|
|
67
81
|
throw lastErr;
|
|
68
82
|
}
|
|
69
83
|
|
|
84
|
+
function makeFetchOpts() {
|
|
85
|
+
const headers = { 'User-Agent': 'maravilla-cli-installer' };
|
|
86
|
+
if (GH_TOKEN) headers['Authorization'] = `Bearer ${GH_TOKEN}`;
|
|
87
|
+
const opts = { headers };
|
|
88
|
+
if (PROXY) {
|
|
89
|
+
try { opts.agent = new HttpsProxyAgent(PROXY); } catch {}
|
|
90
|
+
}
|
|
91
|
+
return opts;
|
|
92
|
+
}
|
|
93
|
+
|
|
70
94
|
async function getReleaseTag() {
|
|
71
95
|
if (VERSION && VERSION !== 'latest') return VERSION;
|
|
72
96
|
const api = `https://api.github.com/repos/${REPO}/releases/latest`;
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
97
|
+
const opts = makeFetchOpts();
|
|
98
|
+
try {
|
|
99
|
+
console.log(chalk.gray(`${flower()} fetching latest release tag (${Math.round(HTTP_TIMEOUT_MS/100)/10}s timeout, ${HTTP_RETRIES} retries)`));
|
|
100
|
+
const res = await withRetries(() => abortableFetch(api, opts, HTTP_TIMEOUT_MS));
|
|
101
|
+
if (!res.ok) throw new Error(`Failed to fetch latest release: ${res.status} ${res.statusText}`);
|
|
102
|
+
const json = await res.json();
|
|
103
|
+
if (!json.tag_name) throw new Error('No tag_name in API response');
|
|
104
|
+
return json.tag_name;
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.log(chalk.yellow(`${flower()} API lookup failed (${e.message}). Falling back to HTML redirect...`));
|
|
107
|
+
// Fallback: use HTML redirect to /releases/tag/<tag>
|
|
108
|
+
const html = `https://github.com/${REPO}/releases/latest`;
|
|
109
|
+
const res = await abortableFetch(html, { ...opts, redirect: 'manual' }, Math.min(HTTP_TIMEOUT_MS, 10000));
|
|
110
|
+
const loc = res.headers.get('location') || '';
|
|
111
|
+
const m = loc.match(/\/releases\/tag\/([^/]+)$/);
|
|
112
|
+
if (m) return m[1];
|
|
113
|
+
throw new Error('Could not resolve latest tag via fallback');
|
|
114
|
+
}
|
|
77
115
|
}
|
|
78
116
|
|
|
79
117
|
async function downloadWithProgress(url, dest) {
|
|
@@ -139,6 +177,19 @@ function cmpSemver(a, b) {
|
|
|
139
177
|
|
|
140
178
|
async function install() {
|
|
141
179
|
const { target, ext } = resolveTarget();
|
|
180
|
+
|
|
181
|
+
// Detect current installed version first to enable true offline fast-path
|
|
182
|
+
let cur = null;
|
|
183
|
+
try { cur = await currentVersion(); } catch {}
|
|
184
|
+
|
|
185
|
+
if (process.env.MARAVILLA_OFFLINE === '1') {
|
|
186
|
+
if (cur) {
|
|
187
|
+
log(chalk.yellow(`Offline mode: keeping existing v${cur}`));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
throw new Error('Offline mode: no existing binary; set MARAVILLA_VERSION to a specific tag or retry online');
|
|
191
|
+
}
|
|
192
|
+
|
|
142
193
|
log(`Resolving latest release tag from ${chalk.cyan(REPO)}`);
|
|
143
194
|
const tag = await getReleaseTag();
|
|
144
195
|
const art = artifactName(BIN_NAME, tag, target);
|
|
@@ -155,21 +206,8 @@ async function install() {
|
|
|
155
206
|
let requested = null;
|
|
156
207
|
const m = tag.match(/v(\d+\.\d+\.\d+)/);
|
|
157
208
|
if (m) requested = m[1];
|
|
158
|
-
let cur = null;
|
|
159
|
-
try { cur = await currentVersion(); } catch {}
|
|
160
209
|
|
|
161
210
|
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
211
|
if (cur && requested && cmpSemver(cur, requested) >= 0) {
|
|
174
212
|
log(chalk.green(`Already up to date (v${cur})`));
|
|
175
213
|
return;
|
|
@@ -183,7 +221,7 @@ async function install() {
|
|
|
183
221
|
// Verify checksum if SHA256SUMS is available
|
|
184
222
|
try {
|
|
185
223
|
log('Verifying checksum');
|
|
186
|
-
|
|
224
|
+
const sumsRes = await abortableFetch(sumsUrl, makeFetchOpts(), 15000);
|
|
187
225
|
if (sumsRes.ok) {
|
|
188
226
|
const sumsText = await sumsRes.text();
|
|
189
227
|
const expected = sumsText
|
|
@@ -244,7 +282,13 @@ async function sha256File(filePath) {
|
|
|
244
282
|
});
|
|
245
283
|
}
|
|
246
284
|
|
|
247
|
-
install()
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
285
|
+
install()
|
|
286
|
+
.then(() => {
|
|
287
|
+
if (isOutputSuppressed) {
|
|
288
|
+
console.error('✿ Maravilla CLI binary installed successfully!');
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
.catch(err => {
|
|
292
|
+
console.error(chalk.red(`${flower()} install failed: ${err.message}`));
|
|
293
|
+
process.exitCode = 1;
|
|
294
|
+
});
|
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) {
|