@boxes-dev/cli 1.0.573
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/dvb +9 -0
- package/bin/dvbd +9 -0
- package/package.json +33 -0
- package/scripts/postinstall.cjs +19 -0
- package/scripts/postinstall.mjs +221 -0
package/bin/dvb
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
DEVBOX_DIR="${DEVBOX_HOME:-$HOME/.devbox}"
|
|
3
|
+
STANDALONE_DIR="$DEVBOX_DIR/standalone/current"
|
|
4
|
+
if [ ! -x "$STANDALONE_DIR/dvb" ]; then
|
|
5
|
+
echo "dvb: standalone runtime not installed." >&2
|
|
6
|
+
echo "Run: npm install -g @boxes-dev/cli" >&2
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
exec "$STANDALONE_DIR/dvb" "$@"
|
package/bin/dvbd
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
DEVBOX_DIR="${DEVBOX_HOME:-$HOME/.devbox}"
|
|
3
|
+
STANDALONE_DIR="$DEVBOX_DIR/standalone/current"
|
|
4
|
+
if [ ! -x "$STANDALONE_DIR/dvbd" ]; then
|
|
5
|
+
echo "dvbd: standalone runtime not installed." >&2
|
|
6
|
+
echo "Run: npm install -g @boxes-dev/cli" >&2
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
exec "$STANDALONE_DIR/dvbd" "$@"
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@boxes-dev/cli",
|
|
3
|
+
"version": "1.0.573",
|
|
4
|
+
"description": "Boxes.dev CLI (standalone, includes bundled Node runtime)",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"dvb": "./bin/dvb",
|
|
11
|
+
"dvbd": "./bin/dvbd"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin/dvb",
|
|
15
|
+
"bin/dvbd",
|
|
16
|
+
"scripts/postinstall.cjs",
|
|
17
|
+
"scripts/postinstall.mjs"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"postinstall": "node scripts/postinstall.cjs"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"os": [
|
|
26
|
+
"darwin",
|
|
27
|
+
"linux"
|
|
28
|
+
],
|
|
29
|
+
"cpu": [
|
|
30
|
+
"arm64",
|
|
31
|
+
"x64"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// CJS wrapper for postinstall — spawns the ESM postinstall script.
|
|
2
|
+
// npm postinstall runs with the package's Node, which may be 18+ without
|
|
3
|
+
// native ESM support in all contexts. This wrapper ensures compatibility.
|
|
4
|
+
const { execFileSync } = require("node:child_process");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
execFileSync(
|
|
9
|
+
process.execPath,
|
|
10
|
+
[path.join(__dirname, "postinstall.mjs")],
|
|
11
|
+
{ stdio: "inherit", env: { ...process.env } },
|
|
12
|
+
);
|
|
13
|
+
} catch (err) {
|
|
14
|
+
// Non-zero exit from the ESM script.
|
|
15
|
+
if (err.status != null) {
|
|
16
|
+
process.exit(err.status);
|
|
17
|
+
}
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @boxes-dev/cli postinstall script.
|
|
3
|
+
*
|
|
4
|
+
* Downloads the platform-specific standalone archive from deploy.boxes.dev,
|
|
5
|
+
* verifies its SHA256 checksum, extracts it, validates it, and promotes it
|
|
6
|
+
* to ~/.devbox/standalone/current/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { pipeline } from "node:stream/promises";
|
|
14
|
+
import { execFileSync } from "node:child_process";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Configuration
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
// Build-time profile: if set to "dev" at build time, use the dev deploy host.
|
|
21
|
+
// For the published npm package this is baked in; for local dev testing,
|
|
22
|
+
// override via DEVBOX_STANDALONE_MANIFEST_URL env var.
|
|
23
|
+
const DEFAULT_MANIFEST_URL = "https://deploy.boxes.dev/cli/latest.json";
|
|
24
|
+
const DEV_MANIFEST_URL = "https://deploy-dev.boxes.dev/cli/latest.json";
|
|
25
|
+
|
|
26
|
+
function getManifestUrl() {
|
|
27
|
+
// Explicit override for testing.
|
|
28
|
+
if (process.env.DEVBOX_STANDALONE_MANIFEST_URL) {
|
|
29
|
+
return process.env.DEVBOX_STANDALONE_MANIFEST_URL;
|
|
30
|
+
}
|
|
31
|
+
// Build-time profile detection: the published package always uses prod.
|
|
32
|
+
// Local dev builds set DEVBOX_BUILD_PROFILE=dev.
|
|
33
|
+
if (process.env.DEVBOX_BUILD_PROFILE === "dev") {
|
|
34
|
+
return DEV_MANIFEST_URL;
|
|
35
|
+
}
|
|
36
|
+
return DEFAULT_MANIFEST_URL;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Helpers
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
function getDevboxDir() {
|
|
44
|
+
return process.env.DEVBOX_HOME || path.join(os.homedir(), ".devbox");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getPlatformKey() {
|
|
48
|
+
const platform = process.platform; // "darwin" or "linux"
|
|
49
|
+
const arch = process.arch; // "arm64" or "x64"
|
|
50
|
+
return `${platform}-${arch}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** SHA-256 hex digest of a file. */
|
|
54
|
+
function sha256File(filePath) {
|
|
55
|
+
const hash = createHash("sha256");
|
|
56
|
+
hash.update(fs.readFileSync(filePath));
|
|
57
|
+
return hash.digest("hex");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Fetch JSON from a URL. */
|
|
61
|
+
async function fetchJson(url) {
|
|
62
|
+
const res = await fetch(url);
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
65
|
+
}
|
|
66
|
+
return res.json();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Download a URL to a file. */
|
|
70
|
+
async function downloadFile(url, dest) {
|
|
71
|
+
const res = await fetch(url);
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`);
|
|
74
|
+
}
|
|
75
|
+
const fileStream = fs.createWriteStream(dest);
|
|
76
|
+
await pipeline(res.body, fileStream);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Main
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
async function main() {
|
|
84
|
+
const platformKey = getPlatformKey();
|
|
85
|
+
const devboxDir = getDevboxDir();
|
|
86
|
+
const standaloneDir = path.join(devboxDir, "standalone");
|
|
87
|
+
|
|
88
|
+
console.log(`@boxes-dev/cli: installing standalone dvb for ${platformKey}...`);
|
|
89
|
+
|
|
90
|
+
// 1. Fetch manifest
|
|
91
|
+
const manifestUrl = getManifestUrl();
|
|
92
|
+
let manifest;
|
|
93
|
+
try {
|
|
94
|
+
manifest = await fetchJson(manifestUrl);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error(
|
|
97
|
+
`@boxes-dev/cli: failed to fetch manifest from ${manifestUrl}`,
|
|
98
|
+
);
|
|
99
|
+
console.error(` ${err.message}`);
|
|
100
|
+
console.error(
|
|
101
|
+
`\nIf you are offline, you can retry later with: npm install -g @boxes-dev/cli`,
|
|
102
|
+
);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const archiveInfo = manifest.archives?.[platformKey];
|
|
107
|
+
if (!archiveInfo) {
|
|
108
|
+
console.error(
|
|
109
|
+
`@boxes-dev/cli: no archive available for platform ${platformKey}`,
|
|
110
|
+
);
|
|
111
|
+
console.error(`Available platforms: ${Object.keys(manifest.archives || {}).join(", ")}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const version = manifest.version;
|
|
116
|
+
const versionDir = path.join(
|
|
117
|
+
standaloneDir,
|
|
118
|
+
`${version}-${platformKey}`,
|
|
119
|
+
);
|
|
120
|
+
const currentLink = path.join(standaloneDir, "current");
|
|
121
|
+
|
|
122
|
+
// 2. Check if already installed at this version
|
|
123
|
+
if (fs.existsSync(versionDir) && fs.existsSync(path.join(versionDir, "dvb"))) {
|
|
124
|
+
// Check if current symlink already points here.
|
|
125
|
+
try {
|
|
126
|
+
const currentTarget = fs.readlinkSync(currentLink);
|
|
127
|
+
if (path.resolve(standaloneDir, currentTarget) === versionDir ||
|
|
128
|
+
currentTarget === versionDir) {
|
|
129
|
+
console.log(
|
|
130
|
+
`@boxes-dev/cli: standalone dvb v${version} already installed, skipping.`,
|
|
131
|
+
);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// Symlink doesn't exist or can't be read — continue to update it.
|
|
136
|
+
}
|
|
137
|
+
// Version dir exists but symlink is stale/missing — just update symlink.
|
|
138
|
+
updateCurrentSymlink(standaloneDir, currentLink, versionDir, version);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 3. Download archive
|
|
143
|
+
fs.mkdirSync(standaloneDir, { recursive: true });
|
|
144
|
+
const tmpSuffix = `${version}-${platformKey}-${Date.now()}`;
|
|
145
|
+
const tmpDir = path.join(standaloneDir, `.tmp-${tmpSuffix}`);
|
|
146
|
+
const tarballPath = path.join(standaloneDir, `.download-${tmpSuffix}.tar.gz`);
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
console.log(`@boxes-dev/cli: downloading dvb v${version}...`);
|
|
150
|
+
await downloadFile(archiveInfo.url, tarballPath);
|
|
151
|
+
|
|
152
|
+
// 4. Verify SHA256
|
|
153
|
+
const actualHash = sha256File(tarballPath);
|
|
154
|
+
if (actualHash !== archiveInfo.sha256) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`SHA256 mismatch:\n expected: ${archiveInfo.sha256}\n actual: ${actualHash}`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
console.log(`@boxes-dev/cli: SHA256 verified.`);
|
|
160
|
+
|
|
161
|
+
// 5. Extract to tmp directory
|
|
162
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
163
|
+
execFileSync("tar", [
|
|
164
|
+
"xzf",
|
|
165
|
+
tarballPath,
|
|
166
|
+
"-C",
|
|
167
|
+
tmpDir,
|
|
168
|
+
"--strip-components=1",
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
// 6. Validate: run dvb --help
|
|
172
|
+
try {
|
|
173
|
+
execFileSync(path.join(tmpDir, "dvb"), ["--help"], {
|
|
174
|
+
stdio: "pipe",
|
|
175
|
+
timeout: 15_000,
|
|
176
|
+
});
|
|
177
|
+
} catch (err) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Validation failed: ./dvb --help exited with code ${err.status}`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 7. Promote: rename tmp to version dir
|
|
184
|
+
if (fs.existsSync(versionDir)) {
|
|
185
|
+
fs.rmSync(versionDir, { recursive: true });
|
|
186
|
+
}
|
|
187
|
+
fs.renameSync(tmpDir, versionDir);
|
|
188
|
+
|
|
189
|
+
// 8. Update current symlink
|
|
190
|
+
updateCurrentSymlink(standaloneDir, currentLink, versionDir, version);
|
|
191
|
+
|
|
192
|
+
console.log(`@boxes-dev/cli: successfully installed dvb v${version}`);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
console.error(`@boxes-dev/cli: installation failed: ${err.message}`);
|
|
195
|
+
// Clean up
|
|
196
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
197
|
+
process.exit(1);
|
|
198
|
+
} finally {
|
|
199
|
+
// Always clean up the tarball
|
|
200
|
+
try { fs.unlinkSync(tarballPath); } catch {}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function updateCurrentSymlink(standaloneDir, currentLink, versionDir, version) {
|
|
205
|
+
// Atomic symlink update: create a temp symlink and rename it over the old one.
|
|
206
|
+
const tmpLink = path.join(standaloneDir, `.current-tmp-${Date.now()}`);
|
|
207
|
+
try {
|
|
208
|
+
// Use relative path for the symlink so it works if devbox dir is moved.
|
|
209
|
+
const relTarget = path.relative(standaloneDir, versionDir);
|
|
210
|
+
fs.symlinkSync(relTarget, tmpLink);
|
|
211
|
+
fs.renameSync(tmpLink, currentLink);
|
|
212
|
+
} catch {
|
|
213
|
+
// Fallback: remove and recreate.
|
|
214
|
+
try { fs.unlinkSync(tmpLink); } catch {}
|
|
215
|
+
try { fs.unlinkSync(currentLink); } catch {}
|
|
216
|
+
const relTarget = path.relative(standaloneDir, versionDir);
|
|
217
|
+
fs.symlinkSync(relTarget, currentLink);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
main();
|