@zpl-toolchain/cli 0.1.12
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 +38 -0
- package/bin/zpl.mjs +183 -0
- package/lib/platform.mjs +22 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @zpl-toolchain/cli
|
|
2
|
+
|
|
3
|
+
`npx` wrapper for the `zpl` CLI binary.
|
|
4
|
+
|
|
5
|
+
This package downloads the matching pre-built binary from GitHub Releases on first run, caches it locally, and then executes it.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @zpl-toolchain/cli --help
|
|
11
|
+
npx @zpl-toolchain/cli doctor --output json
|
|
12
|
+
npx @zpl-toolchain/cli lint label.zpl
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## How it works
|
|
16
|
+
|
|
17
|
+
- Detects runtime platform/arch (`linux/x64`, `darwin/arm64`, `win32/x64`)
|
|
18
|
+
- Downloads the release binary archive for this package version
|
|
19
|
+
- Optionally verifies `sha256` when checksum files are available
|
|
20
|
+
- Extracts and caches the binary under:
|
|
21
|
+
- Linux/macOS: `$XDG_CACHE_HOME/zpl-toolchain/cli` or `~/.cache/zpl-toolchain/cli`
|
|
22
|
+
- Windows: `%LOCALAPPDATA%/zpl-toolchain/cli`
|
|
23
|
+
|
|
24
|
+
## Environment variables
|
|
25
|
+
|
|
26
|
+
- `ZPL_CLI_BASE_URL`: override release base URL (default GitHub Releases)
|
|
27
|
+
- Only set this when using a trusted internal mirror.
|
|
28
|
+
|
|
29
|
+
## Notes
|
|
30
|
+
|
|
31
|
+
- First run requires network access to GitHub Releases.
|
|
32
|
+
- Supported runtime targets: `linux/x64`, `darwin/arm64`, `win32/x64`.
|
|
33
|
+
- If your platform is unsupported (for example Intel Mac or Linux ARM64), build from source via:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cargo install zpl_toolchain_cli
|
|
37
|
+
```
|
|
38
|
+
|
package/bin/zpl.mjs
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { mkdir, readFile, stat, writeFile, chmod, rename, rm } from "node:fs/promises";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
|
|
10
|
+
import { resolveTarget } from "../lib/platform.mjs";
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
const packageJsonPath = path.join(__dirname, "..", "package.json");
|
|
15
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
16
|
+
const version = packageJson.version;
|
|
17
|
+
|
|
18
|
+
const target = resolveTarget(process.platform, process.arch);
|
|
19
|
+
if (!target) {
|
|
20
|
+
const runtime = `${process.platform}/${process.arch}`;
|
|
21
|
+
console.error(`Unsupported platform for @zpl-toolchain/cli: ${runtime}`);
|
|
22
|
+
console.error("Supported targets:");
|
|
23
|
+
console.error(" - linux/x64");
|
|
24
|
+
console.error(" - darwin/arm64");
|
|
25
|
+
console.error(" - win32/x64");
|
|
26
|
+
console.error(
|
|
27
|
+
"Use a pre-built release or cargo install zpl_toolchain_cli: https://github.com/trevordcampbell/zpl-toolchain/releases",
|
|
28
|
+
);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const baseUrl =
|
|
33
|
+
process.env.ZPL_CLI_BASE_URL ??
|
|
34
|
+
"https://github.com/trevordcampbell/zpl-toolchain/releases/download";
|
|
35
|
+
const tag = `v${version}`;
|
|
36
|
+
const archiveName = `zpl-${target.target}.${target.archiveExt}`;
|
|
37
|
+
const archiveUrl = `${baseUrl}/${tag}/${archiveName}`;
|
|
38
|
+
const checksumUrl = `${archiveUrl}.sha256`;
|
|
39
|
+
|
|
40
|
+
const cacheRoot = getCacheRoot();
|
|
41
|
+
const versionDir = path.join(cacheRoot, version, target.target);
|
|
42
|
+
const binaryPath = path.join(versionDir, target.binaryName);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await ensureBinary(binaryPath);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
+
console.error(`Failed to prepare zpl CLI binary: ${message}`);
|
|
49
|
+
if (message.includes("Download failed (404)")) {
|
|
50
|
+
console.error(
|
|
51
|
+
"Release assets may still be publishing for this version. Retry in a few minutes.",
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const result = spawnSync(binaryPath, process.argv.slice(2), {
|
|
58
|
+
stdio: "inherit",
|
|
59
|
+
windowsHide: true,
|
|
60
|
+
});
|
|
61
|
+
if (result.error) {
|
|
62
|
+
console.error(`Failed to execute zpl binary: ${result.error.message}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
process.exit(result.status ?? 1);
|
|
66
|
+
|
|
67
|
+
function getCacheRoot() {
|
|
68
|
+
if (process.platform === "win32") {
|
|
69
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
70
|
+
if (localAppData) {
|
|
71
|
+
return path.join(localAppData, "zpl-toolchain", "cli");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
75
|
+
if (xdgCache) {
|
|
76
|
+
return path.join(xdgCache, "zpl-toolchain", "cli");
|
|
77
|
+
}
|
|
78
|
+
return path.join(os.homedir(), ".cache", "zpl-toolchain", "cli");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function ensureBinary(expectedBinaryPath) {
|
|
82
|
+
const existing = await statSafe(expectedBinaryPath);
|
|
83
|
+
if (existing?.isFile()) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const tempDir = path.join(versionDir, ".tmp");
|
|
88
|
+
await mkdir(tempDir, { recursive: true });
|
|
89
|
+
await mkdir(versionDir, { recursive: true });
|
|
90
|
+
|
|
91
|
+
const archivePath = path.join(tempDir, archiveName);
|
|
92
|
+
const extractedDir = path.join(tempDir, "extract");
|
|
93
|
+
|
|
94
|
+
await downloadFile(archiveUrl, archivePath);
|
|
95
|
+
await verifyChecksumIfAvailable(archivePath);
|
|
96
|
+
await extractArchive(archivePath, extractedDir);
|
|
97
|
+
|
|
98
|
+
const extractedBinary = path.join(extractedDir, target.binaryName);
|
|
99
|
+
const extractedStat = await statSafe(extractedBinary);
|
|
100
|
+
if (!extractedStat?.isFile()) {
|
|
101
|
+
throw new Error(`Downloaded archive did not contain ${target.binaryName}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const finalTmpBinary = `${expectedBinaryPath}.tmp`;
|
|
105
|
+
await mkdir(path.dirname(expectedBinaryPath), { recursive: true });
|
|
106
|
+
await rm(finalTmpBinary, { force: true });
|
|
107
|
+
await rename(extractedBinary, finalTmpBinary);
|
|
108
|
+
if (process.platform !== "win32") {
|
|
109
|
+
await chmod(finalTmpBinary, 0o755);
|
|
110
|
+
}
|
|
111
|
+
await rm(expectedBinaryPath, { force: true });
|
|
112
|
+
await rename(finalTmpBinary, expectedBinaryPath);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function downloadFile(url, outPath) {
|
|
116
|
+
const response = await fetch(url);
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
throw new Error(`Download failed (${response.status}) for ${url}`);
|
|
119
|
+
}
|
|
120
|
+
const buf = Buffer.from(await response.arrayBuffer());
|
|
121
|
+
await writeFile(outPath, buf);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function verifyChecksumIfAvailable(archivePath) {
|
|
125
|
+
const checksumResp = await fetch(checksumUrl);
|
|
126
|
+
if (!checksumResp.ok) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const text = await checksumResp.text();
|
|
130
|
+
const expected = text.trim().split(/\s+/)[0]?.toLowerCase();
|
|
131
|
+
if (!expected || expected.length < 64) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const bytes = await readFile(archivePath);
|
|
135
|
+
const actual = createHash("sha256").update(bytes).digest("hex");
|
|
136
|
+
if (actual !== expected) {
|
|
137
|
+
throw new Error(`Checksum mismatch for ${archiveName}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function extractArchive(archivePath, outDir) {
|
|
142
|
+
await rm(outDir, { recursive: true, force: true });
|
|
143
|
+
await mkdir(outDir, { recursive: true });
|
|
144
|
+
|
|
145
|
+
if (target.archiveExt === "tar.gz") {
|
|
146
|
+
const result = spawnSync("tar", ["-xzf", archivePath, "-C", outDir], {
|
|
147
|
+
stdio: "pipe",
|
|
148
|
+
encoding: "utf8",
|
|
149
|
+
});
|
|
150
|
+
if (result.status !== 0) {
|
|
151
|
+
throw new Error(`Failed to extract tar.gz archive: ${result.stderr || result.stdout}`);
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (process.platform === "win32") {
|
|
157
|
+
const psCommand = `Expand-Archive -LiteralPath '${escapePsPath(archivePath)}' -DestinationPath '${escapePsPath(outDir)}' -Force`;
|
|
158
|
+
const result = spawnSync(
|
|
159
|
+
"powershell",
|
|
160
|
+
["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", psCommand],
|
|
161
|
+
{ stdio: "pipe", encoding: "utf8" },
|
|
162
|
+
);
|
|
163
|
+
if (result.status !== 0) {
|
|
164
|
+
throw new Error(`Failed to extract zip archive: ${result.stderr || result.stdout}`);
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
throw new Error(`Unsupported archive extraction for ${target.archiveExt} on ${process.platform}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function escapePsPath(value) {
|
|
173
|
+
return value.replaceAll("'", "''");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function statSafe(filePath) {
|
|
177
|
+
try {
|
|
178
|
+
return await stat(filePath);
|
|
179
|
+
} catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
package/lib/platform.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const SUPPORTED_TARGETS = {
|
|
2
|
+
"linux:x64": {
|
|
3
|
+
target: "x86_64-unknown-linux-gnu",
|
|
4
|
+
archiveExt: "tar.gz",
|
|
5
|
+
binaryName: "zpl",
|
|
6
|
+
},
|
|
7
|
+
"darwin:arm64": {
|
|
8
|
+
target: "aarch64-apple-darwin",
|
|
9
|
+
archiveExt: "tar.gz",
|
|
10
|
+
binaryName: "zpl",
|
|
11
|
+
},
|
|
12
|
+
"win32:x64": {
|
|
13
|
+
target: "x86_64-pc-windows-msvc",
|
|
14
|
+
archiveExt: "zip",
|
|
15
|
+
binaryName: "zpl.exe",
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function resolveTarget(platform, arch) {
|
|
20
|
+
return SUPPORTED_TARGETS[`${platform}:${arch}`] ?? null;
|
|
21
|
+
}
|
|
22
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zpl-toolchain/cli",
|
|
3
|
+
"version": "0.1.12",
|
|
4
|
+
"description": "npx wrapper for the zpl CLI binary",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"zpl": "./bin/zpl.mjs"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node --test ./test/*.test.mjs",
|
|
14
|
+
"prepublishOnly": "npm test"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"zpl",
|
|
18
|
+
"zebra",
|
|
19
|
+
"label",
|
|
20
|
+
"printer",
|
|
21
|
+
"cli"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT OR Apache-2.0",
|
|
24
|
+
"author": "zpl-toolchain contributors",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/trevordcampbell/zpl-toolchain.git",
|
|
28
|
+
"directory": "packages/ts/cli"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/trevordcampbell/zpl-toolchain",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/trevordcampbell/zpl-toolchain/issues"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"bin",
|
|
36
|
+
"lib",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|