@maravilla-labs/cli 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.
- package/LICENSE +12 -0
- package/README.md +45 -0
- package/bin/maravilla +9 -0
- package/bin/maravilla.cmd +4 -0
- package/package.json +54 -0
- package/scripts/generate-readme-snippet.js +3 -0
- package/scripts/platform.js +15 -0
- package/scripts/postinstall.js +159 -0
- package/scripts/update.js +12 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Copyright (c) 2019 - 2025 SOLUTAS GmbH, Switzerland
|
|
2
|
+
|
|
3
|
+
All Rights Reserved.
|
|
4
|
+
-------------------------------------------------------
|
|
5
|
+
NOTICE:
|
|
6
|
+
All information contained herein is, and remains
|
|
7
|
+
the property of SOLUTAS GmbH. The intellectual and
|
|
8
|
+
technical concepts contained herein are confidential
|
|
9
|
+
and proprietary to SOLUTAS GmbH. Dissemination of this
|
|
10
|
+
information or reproduction of this material is strictly
|
|
11
|
+
forbidden without the express written permission of
|
|
12
|
+
SOLUTAS GmbH.
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Maravilla CLI
|
|
2
|
+
|
|
3
|
+
The official Maravilla command‑line tool.
|
|
4
|
+
|
|
5
|
+
> Preview (early access)
|
|
6
|
+
>
|
|
7
|
+
> This package is being published for testing and early feedback. It’s not yet generally available and may change without notice. Not for production use. We welcome feedback and bug reports.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @maravilla-labs/cli
|
|
13
|
+
# or
|
|
14
|
+
pnpm add -g @maravilla-labs/cli
|
|
15
|
+
# or
|
|
16
|
+
yarn global add @maravilla-labs/cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Verify:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
maravilla --version
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Update
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
maravilla update
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## What it does
|
|
32
|
+
|
|
33
|
+
- Develop: start a local dev server with hot reload.
|
|
34
|
+
- Build: produce optimized production artifacts for your app.
|
|
35
|
+
- Preview: run a local server to preview your production build.
|
|
36
|
+
- Platform tools: inspect status, browse KV and database collections.
|
|
37
|
+
- Self‑update: fetch and install the latest CLI release.
|
|
38
|
+
|
|
39
|
+
## Supported platforms
|
|
40
|
+
|
|
41
|
+
- Linux x64 (GNU)
|
|
42
|
+
- macOS x64 and arm64
|
|
43
|
+
- Windows x64
|
|
44
|
+
|
|
45
|
+
For other platforms and release notes, visit the Maravilla CLI releases page.
|
package/bin/maravilla
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Launcher that executes the downloaded Maravilla CLI binary in this package
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
BIN="$DIR/maravilla"
|
|
6
|
+
if [ "$(uname -s)" = "MINGW64_NT" ] || [ "$(uname -s)" = "MSYS_NT" ] || [ "$(uname -s)" = "CYGWIN_NT" ]; then
|
|
7
|
+
BIN="$DIR/maravilla.exe"
|
|
8
|
+
fi
|
|
9
|
+
exec "$BIN" "$@"
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@maravilla-labs/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "NPM wrapper for the Maravilla CLI binary; downloads the right release for your platform.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"author": "Maravilla Labs",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/solutas/maravilla-runtime.git",
|
|
10
|
+
"directory": "packages/cli"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/solutas/maravilla-runtime/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/solutas/maravilla-runtime#readme",
|
|
16
|
+
"bin": {
|
|
17
|
+
"maravilla": "bin/maravilla"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"postinstall": "node ./scripts/postinstall.js",
|
|
22
|
+
"download": "node ./scripts/postinstall.js",
|
|
23
|
+
"update": "node ./scripts/update.js",
|
|
24
|
+
"prepack": "node ./scripts/generate-readme-snippet.js || true"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"bin",
|
|
28
|
+
"scripts",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"os": [
|
|
33
|
+
"darwin",
|
|
34
|
+
"linux",
|
|
35
|
+
"win32"
|
|
36
|
+
],
|
|
37
|
+
"cpu": [
|
|
38
|
+
"x64",
|
|
39
|
+
"arm64"
|
|
40
|
+
],
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=16"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"node-fetch": "^3.3.2",
|
|
49
|
+
"tar": "^6.2.1",
|
|
50
|
+
"unzipper": "^0.10.14",
|
|
51
|
+
"progress": "^2.0.3",
|
|
52
|
+
"chalk": "^5.3.0"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Map Node's process.platform/process.arch to GitHub release target triple and file ext
|
|
2
|
+
export function resolveTarget() {
|
|
3
|
+
const p = process.platform;
|
|
4
|
+
const a = process.arch;
|
|
5
|
+
// Supported targets per release matrix
|
|
6
|
+
if (p === 'linux' && a === 'x64') return { target: 'x86_64-unknown-linux-gnu', ext: 'tar.gz' };
|
|
7
|
+
if (p === 'darwin' && a === 'x64') return { target: 'x86_64-apple-darwin', ext: 'tar.gz' };
|
|
8
|
+
if (p === 'darwin' && a === 'arm64') return { target: 'aarch64-apple-darwin', ext: 'tar.gz' };
|
|
9
|
+
if (p === 'win32' && a === 'x64') return { target: 'x86_64-pc-windows-msvc', ext: 'zip' };
|
|
10
|
+
throw new Error(`Unsupported platform/arch: ${p}/${a}. See https://github.com/maravilla-labs/maravilla-cli/releases for supported binaries.`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function artifactName(binName, tag, target) {
|
|
14
|
+
return `${binName}-${tag}-${target}`;
|
|
15
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { pipeline } from 'stream/promises';
|
|
7
|
+
import fetch from 'node-fetch';
|
|
8
|
+
import tar from 'tar';
|
|
9
|
+
import unzipper from 'unzipper';
|
|
10
|
+
import ProgressBar from 'progress';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import { resolveTarget, artifactName } from './platform.js';
|
|
13
|
+
import crypto from 'crypto';
|
|
14
|
+
|
|
15
|
+
// Allow CI to skip downloading the binary
|
|
16
|
+
if (process.env.MARAVILLA_CLI_SKIP_INSTALL === '1' || process.env.CI) {
|
|
17
|
+
console.log('✿ Skipping Maravilla CLI binary install (CI or skip flag set)');
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
import { spawn } from 'child_process';
|
|
21
|
+
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = path.dirname(__filename);
|
|
24
|
+
|
|
25
|
+
const BIN_NAME = 'maravilla';
|
|
26
|
+
const REPO = process.env.MARAVILLA_CLI_REPO || 'maravilla-labs/maravilla-cli';
|
|
27
|
+
const VERSION = process.env.MARAVILLA_VERSION || 'latest'; // cli-vX.Y.Z or 'latest'
|
|
28
|
+
const BIN_DIR = path.join(__dirname, '..', 'bin');
|
|
29
|
+
const EXECUTABLE = process.platform === 'win32' ? `${BIN_NAME}.exe` : BIN_NAME;
|
|
30
|
+
const INSTALL_PATH = path.join(BIN_DIR, EXECUTABLE);
|
|
31
|
+
|
|
32
|
+
function flower() {
|
|
33
|
+
return chalk.magenta('✿');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function log(msg) {
|
|
37
|
+
console.log(`${flower()} ${msg}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function getReleaseTag() {
|
|
41
|
+
if (VERSION && VERSION !== 'latest') return VERSION;
|
|
42
|
+
const api = `https://api.github.com/repos/${REPO}/releases/latest`;
|
|
43
|
+
const res = await fetch(api, { headers: { 'User-Agent': 'maravilla-cli-installer' } });
|
|
44
|
+
if (!res.ok) throw new Error(`Failed to fetch latest release: ${res.status} ${res.statusText}`);
|
|
45
|
+
const json = await res.json();
|
|
46
|
+
return json.tag_name;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function downloadWithProgress(url, dest) {
|
|
50
|
+
const res = await fetch(url);
|
|
51
|
+
if (!res.ok) throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`);
|
|
52
|
+
const total = Number(res.headers.get('content-length')) || 0;
|
|
53
|
+
const bar = total ? new ProgressBar(`${flower()} downloading [:bar] :percent :etas`, {
|
|
54
|
+
complete: '=', incomplete: ' ', width: 24, total
|
|
55
|
+
}) : null;
|
|
56
|
+
|
|
57
|
+
await pipeline(
|
|
58
|
+
res.body,
|
|
59
|
+
new (class extends fs.WriteStream {
|
|
60
|
+
constructor(dest) { super(dest); }
|
|
61
|
+
write(chunk, enc, cb) { if (bar) bar.tick(chunk.length); super.write(chunk, enc, cb); }
|
|
62
|
+
})(dest)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function extractArchive(archivePath, targetDir, ext) {
|
|
67
|
+
if (ext === 'tar.gz') {
|
|
68
|
+
await tar.x({ file: archivePath, cwd: targetDir });
|
|
69
|
+
} else if (ext === 'zip') {
|
|
70
|
+
await fs.createReadStream(archivePath).pipe(unzipper.Extract({ path: targetDir })).promise();
|
|
71
|
+
} else {
|
|
72
|
+
throw new Error(`Unknown archive type: ${ext}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function install() {
|
|
77
|
+
const { target, ext } = resolveTarget();
|
|
78
|
+
const tag = await getReleaseTag();
|
|
79
|
+
const art = artifactName(BIN_NAME, tag, target);
|
|
80
|
+
const assetFile = `${art}.${ext}`;
|
|
81
|
+
const url = `https://github.com/${REPO}/releases/download/${tag}/${assetFile}`;
|
|
82
|
+
const sumsUrl = `https://github.com/${REPO}/releases/download/${tag}/SHA256SUMS`;
|
|
83
|
+
|
|
84
|
+
await fs.promises.mkdir(BIN_DIR, { recursive: true });
|
|
85
|
+
const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'maravilla-cli-'));
|
|
86
|
+
const archive = path.join(tmpDir, assetFile);
|
|
87
|
+
|
|
88
|
+
log(chalk.bold(`Installing ${BIN_NAME} ${chalk.cyan(tag)} for ${chalk.cyan(target)}`));
|
|
89
|
+
log(`Downloading from ${url}`);
|
|
90
|
+
await downloadWithProgress(url, archive);
|
|
91
|
+
|
|
92
|
+
// Verify checksum if SHA256SUMS is available
|
|
93
|
+
try {
|
|
94
|
+
log('Verifying checksum');
|
|
95
|
+
const sumsRes = await fetch(sumsUrl, { headers: { 'User-Agent': 'maravilla-cli-installer' } });
|
|
96
|
+
if (sumsRes.ok) {
|
|
97
|
+
const sumsText = await sumsRes.text();
|
|
98
|
+
const expected = sumsText
|
|
99
|
+
.split(/\r?\n/)
|
|
100
|
+
.map((l) => l.trim())
|
|
101
|
+
.filter(Boolean)
|
|
102
|
+
.map((l) => l.split(/\s+/)) // [hash, filename]
|
|
103
|
+
.find(([, fname]) => fname === assetFile);
|
|
104
|
+
if (!expected) throw new Error('Checksum entry not found for asset');
|
|
105
|
+
const [expectedHash] = expected;
|
|
106
|
+
const hash = await sha256File(archive);
|
|
107
|
+
if (hash !== expectedHash) {
|
|
108
|
+
throw new Error(`Checksum mismatch for ${assetFile}: expected ${expectedHash}, got ${hash}`);
|
|
109
|
+
}
|
|
110
|
+
log(chalk.green('Checksum OK'));
|
|
111
|
+
} else {
|
|
112
|
+
log(chalk.yellow('SHA256SUMS not found; skipping verification'));
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.error(chalk.red(`${flower()} checksum verification failed: ${e.message}`));
|
|
116
|
+
throw e;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
log('Extracting archive');
|
|
120
|
+
await extractArchive(archive, tmpDir, ext);
|
|
121
|
+
|
|
122
|
+
const innerDir = path.join(tmpDir, art);
|
|
123
|
+
const srcPath = path.join(innerDir, EXECUTABLE);
|
|
124
|
+
await fs.promises.copyFile(srcPath, INSTALL_PATH);
|
|
125
|
+
await fs.promises.chmod(INSTALL_PATH, 0o755);
|
|
126
|
+
log(chalk.green(`Installed to ${INSTALL_PATH}`));
|
|
127
|
+
|
|
128
|
+
// Self-check: run --version
|
|
129
|
+
await new Promise((resolve, reject) => {
|
|
130
|
+
log('Verifying install by running --version');
|
|
131
|
+
const child = spawn(INSTALL_PATH, ['--version'], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
132
|
+
let out = '';
|
|
133
|
+
child.stdout.on('data', (d) => (out += d.toString()))
|
|
134
|
+
child.on('error', reject);
|
|
135
|
+
child.on('exit', (code) => {
|
|
136
|
+
if (code === 0) {
|
|
137
|
+
log(chalk.green(`Version: ${out.trim()}`));
|
|
138
|
+
resolve();
|
|
139
|
+
} else {
|
|
140
|
+
reject(new Error(`Version check failed with code ${code}`));
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function sha256File(filePath) {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
const hash = crypto.createHash('sha256');
|
|
149
|
+
const rs = fs.createReadStream(filePath);
|
|
150
|
+
rs.on('error', reject);
|
|
151
|
+
rs.on('data', (chunk) => hash.update(chunk));
|
|
152
|
+
rs.on('end', () => resolve(hash.digest('hex')));
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
install().catch(err => {
|
|
157
|
+
console.error(chalk.red(`${flower()} install failed: ${err.message}`));
|
|
158
|
+
process.exitCode = 1;
|
|
159
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
|
|
6
|
+
// Simply rerun the postinstall script to fetch latest/specified version
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const postinstall = path.join(__dirname, 'postinstall.js');
|
|
10
|
+
|
|
11
|
+
const child = spawn(process.execPath, [postinstall], { stdio: 'inherit' });
|
|
12
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|