@previewio/cli 0.2.0-007a84d
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/preview +3 -0
- package/install.mjs +128 -0
- package/package.json +27 -0
package/bin/preview
ADDED
package/install.mjs
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const PUBLIC_DOWNLOAD_ORIGIN = 'https://download.preview.io';
|
|
6
|
+
const VERSION_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
7
|
+
const RESERVED_VERSIONS = new Set(['latest', 'install', '.', '..']);
|
|
8
|
+
const BINARY_FETCH_TIMEOUT_MS = 120_000;
|
|
9
|
+
|
|
10
|
+
export function binaryObjectName(platform, arch) {
|
|
11
|
+
if (platform === 'darwin' && arch === 'arm64') return 'preview-darwin-arm64';
|
|
12
|
+
if (platform === 'darwin' && (arch === 'x64' || arch === 'x86_64')) return 'preview-darwin-x64';
|
|
13
|
+
if (platform === 'linux' && (arch === 'x64' || arch === 'x86_64')) return 'preview-linux-x64';
|
|
14
|
+
if (platform === 'win32' && (arch === 'x64' || arch === 'x86_64')) return 'preview-windows-x64.exe';
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function assertReleaseVersion(version) {
|
|
19
|
+
if (!VERSION_PATTERN.test(version) || RESERVED_VERSIONS.has(version) || version === '0.0.0-dev') {
|
|
20
|
+
throw new Error(`Invalid CLI release version ${version}. Publish stamps the package version to the CDN stamp.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function downloadUrl(origin, version, object) {
|
|
25
|
+
assertReleaseVersion(version);
|
|
26
|
+
return `${origin.replace(/\/+$/, '')}/cli/${version}/${object}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function installedBinaryName(platform) {
|
|
30
|
+
return platform === 'win32' ? 'preview.exe' : 'preview';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function npmGlobalInstall(env) {
|
|
34
|
+
const value = env.npm_config_global?.trim().toLowerCase();
|
|
35
|
+
return value === '1' || value === 'true' || value === 'yes';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function windowsShimDirs(packageRoot, env) {
|
|
39
|
+
const dirs = [join(packageRoot, '..', '..', '.bin')];
|
|
40
|
+
const prefix = env.npm_config_prefix?.trim();
|
|
41
|
+
if (prefix && npmGlobalInstall(env)) dirs.push(prefix);
|
|
42
|
+
return dirs;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function writeWindowsLaunchers(exePath, packageRoot, env) {
|
|
46
|
+
const cmd = `@ECHO OFF\r\n"${exePath}" %*\r\n`;
|
|
47
|
+
const ps1 = `& '${exePath.replaceAll("'", "''")}' @args\r\n`;
|
|
48
|
+
const sh = `#!/bin/sh\nexec "${exePath}" "$@"\n`;
|
|
49
|
+
for (const dir of windowsShimDirs(packageRoot, env)) {
|
|
50
|
+
mkdirSync(dir, { recursive: true });
|
|
51
|
+
writeFileSync(join(dir, 'preview.cmd'), cmd);
|
|
52
|
+
writeFileSync(join(dir, 'preview.ps1'), ps1);
|
|
53
|
+
writeFileSync(join(dir, 'preview'), sh);
|
|
54
|
+
chmodSync(join(dir, 'preview'), 0o755);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function looksLikePublishedBinary(bytes, platform) {
|
|
59
|
+
if (platform === 'linux') {
|
|
60
|
+
return bytes.byteLength >= 4 && bytes[0] === 0x7f && bytes[1] === 0x45 && bytes[2] === 0x4c && bytes[3] === 0x46;
|
|
61
|
+
}
|
|
62
|
+
if (platform === 'darwin') {
|
|
63
|
+
if (bytes.byteLength < 4) return false;
|
|
64
|
+
const magic = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0;
|
|
65
|
+
return (
|
|
66
|
+
magic === 0xfeedface ||
|
|
67
|
+
magic === 0xfeedfacf ||
|
|
68
|
+
magic === 0xcefaedfe ||
|
|
69
|
+
magic === 0xcffaedfe ||
|
|
70
|
+
magic === 0xcafebabe ||
|
|
71
|
+
magic === 0xbebafeca
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (platform === 'win32') {
|
|
75
|
+
return bytes.byteLength >= 2 && bytes[0] === 0x4d && bytes[1] === 0x5a;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function packageVersion(packageRoot) {
|
|
81
|
+
return JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')).version;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function fetchBinary(url, fetchImpl) {
|
|
85
|
+
const response = await fetchImpl(url, { signal: AbortSignal.timeout(BINARY_FETCH_TIMEOUT_MS) });
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(`Failed to download Preview CLI from ${url} (${response.status})`);
|
|
88
|
+
}
|
|
89
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
90
|
+
if (contentType.includes('text/html')) {
|
|
91
|
+
throw new Error(`Refused HTML body from ${url}`);
|
|
92
|
+
}
|
|
93
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function install(opts = {}) {
|
|
97
|
+
const packageRoot = opts.packageRoot ?? dirname(fileURLToPath(import.meta.url));
|
|
98
|
+
const version = opts.version ?? packageVersion(packageRoot);
|
|
99
|
+
const platform = opts.platform ?? process.platform;
|
|
100
|
+
const arch = opts.arch ?? process.arch;
|
|
101
|
+
const object = binaryObjectName(platform, arch);
|
|
102
|
+
if (!object) {
|
|
103
|
+
throw new Error(`Unsupported platform: ${platform} ${arch}`);
|
|
104
|
+
}
|
|
105
|
+
const url = downloadUrl(opts.origin ?? PUBLIC_DOWNLOAD_ORIGIN, version, object);
|
|
106
|
+
const dest = join(opts.binDir ?? join(packageRoot, 'bin'), installedBinaryName(platform));
|
|
107
|
+
const bytes = await fetchBinary(url, opts.fetch ?? globalThis.fetch);
|
|
108
|
+
if (bytes.byteLength === 0 || !looksLikePublishedBinary(bytes, platform)) {
|
|
109
|
+
throw new Error(`Refused non-binary body from ${url}`);
|
|
110
|
+
}
|
|
111
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
112
|
+
const tmp = `${dest}.download`;
|
|
113
|
+
writeFileSync(tmp, bytes);
|
|
114
|
+
chmodSync(tmp, 0o755);
|
|
115
|
+
renameSync(tmp, dest);
|
|
116
|
+
if (platform === 'win32') {
|
|
117
|
+
writeWindowsLaunchers(dest, packageRoot, opts.env ?? process.env);
|
|
118
|
+
}
|
|
119
|
+
return dest;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const invoked = process.argv[1] ? resolve(process.argv[1]) : '';
|
|
123
|
+
if (invoked && invoked === fileURLToPath(import.meta.url)) {
|
|
124
|
+
install({}).catch((err) => {
|
|
125
|
+
console.error(err instanceof Error ? err.message : err);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
});
|
|
128
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@previewio/cli",
|
|
3
|
+
"version": "0.2.0-007a84d",
|
|
4
|
+
"description": "Preview CLI — JSON-first client for agentic tooling",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/TheRenderDepartment/preview.git",
|
|
9
|
+
"directory": "tools/preview-cli/npm"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"preview": "bin/preview"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin",
|
|
16
|
+
"install.mjs"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"postinstall": "node install.mjs"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
27
|
+
}
|