@maravilla-labs/cli 0.1.0 → 0.1.1

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,12 @@
1
+ #!/usr/bin/env node
2
+ // Thin wrapper that runs the package updater
3
+ import { fileURLToPath } from 'url';
4
+ import path from 'path';
5
+ import { spawn } from 'child_process';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const updater = path.join(__dirname, '..', 'scripts', 'update.js');
10
+
11
+ const child = spawn(process.execPath, [updater], { stdio: 'inherit' });
12
+ child.on('exit', (code) => process.exit(code ?? 0));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maravilla-labs/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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",
@@ -14,7 +14,8 @@
14
14
  },
15
15
  "homepage": "https://github.com/solutas/maravilla-runtime#readme",
16
16
  "bin": {
17
- "maravilla": "bin/maravilla"
17
+ "maravilla": "bin/maravilla",
18
+ "maravilla-update": "bin/maravilla-update.js"
18
19
  },
19
20
  "type": "module",
20
21
  "scripts": {
@@ -73,6 +73,35 @@ async function extractArchive(archivePath, targetDir, ext) {
73
73
  }
74
74
  }
75
75
 
76
+ async function currentVersion() {
77
+ return new Promise((resolve) => {
78
+ const child = spawn(INSTALL_PATH, ['--version'], { stdio: ['ignore', 'pipe', 'ignore'] });
79
+ let out = '';
80
+ child.stdout.on('data', (d) => (out += d.toString()));
81
+ child.on('error', () => resolve(null));
82
+ child.on('exit', (code) => {
83
+ if (code === 0) {
84
+ const v = (out || '').trim();
85
+ const m = v.match(/(\d+\.\d+\.\d+)/);
86
+ resolve(m ? m[1] : null);
87
+ } else {
88
+ resolve(null);
89
+ }
90
+ });
91
+ });
92
+ }
93
+
94
+ function cmpSemver(a, b) {
95
+ if (!a || !b) return 1;
96
+ const pa = a.split('.').map(Number);
97
+ const pb = b.split('.').map(Number);
98
+ for (let i = 0; i < 3; i++) {
99
+ if (pa[i] > pb[i]) return 1;
100
+ if (pa[i] < pb[i]) return -1;
101
+ }
102
+ return 0;
103
+ }
104
+
76
105
  async function install() {
77
106
  const { target, ext } = resolveTarget();
78
107
  const tag = await getReleaseTag();
@@ -85,6 +114,19 @@ async function install() {
85
114
  const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'maravilla-cli-'));
86
115
  const archive = path.join(tmpDir, assetFile);
87
116
 
117
+ // Fast path: if installed and matches latest, skip
118
+ if (VERSION === 'latest') {
119
+ try {
120
+ const cur = await currentVersion();
121
+ const m = tag.match(/v(\d+\.\d+\.\d+)/);
122
+ const latest = m ? m[1] : null;
123
+ if (cur && latest && cmpSemver(cur, latest) >= 0) {
124
+ log(chalk.green(`Already up to date (v${cur})`));
125
+ return;
126
+ }
127
+ } catch {}
128
+ }
129
+
88
130
  log(chalk.bold(`Installing ${BIN_NAME} ${chalk.cyan(tag)} for ${chalk.cyan(target)}`));
89
131
  log(`Downloading from ${url}`);
90
132
  await downloadWithProgress(url, archive);
package/scripts/update.js CHANGED
@@ -2,11 +2,93 @@
2
2
  import { fileURLToPath } from 'url';
3
3
  import path from 'path';
4
4
  import { spawn } from 'child_process';
5
+ import fetch from 'node-fetch';
6
+ import chalk from 'chalk';
5
7
 
6
- // Simply rerun the postinstall script to fetch latest/specified version
8
+ // Re-run postinstall only when an update is actually needed
7
9
  const __filename = fileURLToPath(import.meta.url);
8
10
  const __dirname = path.dirname(__filename);
9
11
  const postinstall = path.join(__dirname, 'postinstall.js');
10
12
 
11
- const child = spawn(process.execPath, [postinstall], { stdio: 'inherit' });
12
- child.on('exit', (code) => process.exit(code ?? 0));
13
+ const BIN_NAME = 'maravilla';
14
+ const REPO = process.env.MARAVILLA_CLI_REPO || 'maravilla-labs/maravilla-cli';
15
+ const BIN_DIR = path.join(__dirname, '..', 'bin');
16
+ const EXECUTABLE = process.platform === 'win32' ? `${BIN_NAME}.exe` : BIN_NAME;
17
+ const INSTALL_PATH = path.join(BIN_DIR, EXECUTABLE);
18
+
19
+ function flower() {
20
+ return chalk.magenta('✿');
21
+ }
22
+
23
+ function log(msg) {
24
+ console.log(`${flower()} ${msg}`);
25
+ }
26
+
27
+ async function currentVersion() {
28
+ return new Promise((resolve) => {
29
+ const child = spawn(INSTALL_PATH, ['--version'], { stdio: ['ignore', 'pipe', 'ignore'] });
30
+ let out = '';
31
+ child.stdout.on('data', (d) => (out += d.toString()));
32
+ child.on('error', () => resolve(null));
33
+ child.on('exit', (code) => {
34
+ if (code === 0) {
35
+ const v = (out || '').trim();
36
+ // clap default prints: "maravilla X.Y.Z" — extract semver
37
+ const match = v.match(/(\d+\.\d+\.\d+)/);
38
+ resolve(match ? match[1] : null);
39
+ } else {
40
+ resolve(null);
41
+ }
42
+ });
43
+ });
44
+ }
45
+
46
+ async function latestVersion() {
47
+ const api = `https://api.github.com/repos/${REPO}/releases/latest`;
48
+ const res = await fetch(api, { headers: { 'User-Agent': 'maravilla-cli-updater' } });
49
+ if (!res.ok) throw new Error(`Failed to fetch latest release: ${res.status} ${res.statusText}`);
50
+ const json = await res.json();
51
+ const tag = json.tag_name || '';
52
+ // tags are like cli-vX.Y.Z
53
+ const m = tag.match(/v(\d+\.\d+\.\d+)/);
54
+ return { tag, version: m ? m[1] : null };
55
+ }
56
+
57
+ function cmpSemver(a, b) {
58
+ if (!a || !b) return 1; // treat unknown as needs update
59
+ const pa = a.split('.').map(Number);
60
+ const pb = b.split('.').map(Number);
61
+ for (let i = 0; i < 3; i++) {
62
+ if (pa[i] > pb[i]) return 1;
63
+ if (pa[i] < pb[i]) return -1;
64
+ }
65
+ return 0;
66
+ }
67
+
68
+ async function main() {
69
+ // Allow forcing a version via env (MARAVILLA_VERSION)
70
+ const forced = process.env.MARAVILLA_VERSION?.replace(/^v/, '') || null;
71
+ const cur = await currentVersion();
72
+ const { tag, version: latest } = await latestVersion();
73
+
74
+ if (forced) {
75
+ log(chalk.cyan(`Forcing update to v${forced}`));
76
+ } else if (cur && latest && cmpSemver(cur, latest) >= 0) {
77
+ log(chalk.green(`Already up to date (v${cur})`));
78
+ return 0;
79
+ } else if (cur && latest) {
80
+ log(`Updating from v${cur} to ${chalk.cyan(tag)}`);
81
+ } else {
82
+ log(`Updating to ${chalk.cyan(tag)}`);
83
+ }
84
+
85
+ return await new Promise((resolve) => {
86
+ const child = spawn(process.execPath, [postinstall], { stdio: 'inherit' });
87
+ child.on('exit', (code) => resolve(code ?? 0));
88
+ });
89
+ }
90
+
91
+ main().then((code) => process.exit(code)).catch((e) => {
92
+ console.error(chalk.red(`${flower()} update failed: ${e.message}`));
93
+ process.exit(1);
94
+ });