deskctl-cli 0.1.5
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 +36 -0
- package/bin/deskctl.js +36 -0
- package/package.json +36 -0
- package/scripts/postinstall.js +49 -0
- package/scripts/support.js +120 -0
- package/scripts/validate-package.js +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# deskctl-cli
|
|
2
|
+
|
|
3
|
+
`deskctl-cli` installs the `deskctl` command for Linux X11 systems.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g deskctl-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
After install, run:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
deskctl --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
One-shot usage is also supported:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx deskctl-cli --help
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Runtime Support
|
|
24
|
+
|
|
25
|
+
- Linux
|
|
26
|
+
- X11 session
|
|
27
|
+
- currently packaged release asset: `linux-x64`
|
|
28
|
+
|
|
29
|
+
`deskctl-cli` downloads the matching GitHub Release binary during install.
|
|
30
|
+
Unsupported targets fail during install with a clear runtime support error instead of installing a broken command.
|
|
31
|
+
|
|
32
|
+
If you want the Rust source-install path instead, use:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cargo install deskctl
|
|
36
|
+
```
|
package/bin/deskctl.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const { spawn } = require("node:child_process");
|
|
5
|
+
|
|
6
|
+
const { readPackageJson, releaseTag, supportedTarget, vendorBinaryPath } = require("../scripts/support");
|
|
7
|
+
|
|
8
|
+
function main() {
|
|
9
|
+
const pkg = readPackageJson();
|
|
10
|
+
const target = supportedTarget();
|
|
11
|
+
const binaryPath = vendorBinaryPath(target);
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(binaryPath)) {
|
|
14
|
+
console.error(
|
|
15
|
+
[
|
|
16
|
+
"deskctl binary is missing from the npm package install.",
|
|
17
|
+
`Expected: ${binaryPath}`,
|
|
18
|
+
`Package version: ${pkg.version}`,
|
|
19
|
+
`Release tag: ${releaseTag(pkg)}`,
|
|
20
|
+
"Try reinstalling deskctl-cli or check that your target is supported."
|
|
21
|
+
].join("\n")
|
|
22
|
+
);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit" });
|
|
27
|
+
child.on("exit", (code, signal) => {
|
|
28
|
+
if (signal) {
|
|
29
|
+
process.kill(process.pid, signal);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
process.exit(code ?? 1);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deskctl-cli",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Installable deskctl CLI package for Linux X11 agents",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/harivansh-afk/deskctl",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/harivansh-afk/deskctl.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/harivansh-afk/deskctl/issues"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"deskctl": "bin/deskctl.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"README.md",
|
|
22
|
+
"bin",
|
|
23
|
+
"scripts"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"postinstall": "node scripts/postinstall.js",
|
|
27
|
+
"validate": "node scripts/validate-package.js"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"deskctl",
|
|
31
|
+
"x11",
|
|
32
|
+
"desktop",
|
|
33
|
+
"automation",
|
|
34
|
+
"cli"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
checksumsUrl,
|
|
5
|
+
checksumForAsset,
|
|
6
|
+
download,
|
|
7
|
+
ensureVendorDir,
|
|
8
|
+
installLocalBinary,
|
|
9
|
+
readPackageJson,
|
|
10
|
+
releaseAssetUrl,
|
|
11
|
+
releaseTag,
|
|
12
|
+
sha256,
|
|
13
|
+
supportedTarget,
|
|
14
|
+
vendorBinaryPath
|
|
15
|
+
} = require("./support");
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
const pkg = readPackageJson();
|
|
19
|
+
const target = supportedTarget();
|
|
20
|
+
const targetPath = vendorBinaryPath(target);
|
|
21
|
+
|
|
22
|
+
ensureVendorDir();
|
|
23
|
+
|
|
24
|
+
if (process.env.DESKCTL_BINARY_PATH) {
|
|
25
|
+
installLocalBinary(process.env.DESKCTL_BINARY_PATH, targetPath);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const tag = releaseTag(pkg);
|
|
30
|
+
const assetUrl = releaseAssetUrl(tag, target.assetName);
|
|
31
|
+
const checksumText = (await download(checksumsUrl(tag))).toString("utf8");
|
|
32
|
+
const expectedSha = checksumForAsset(checksumText, target.assetName);
|
|
33
|
+
const asset = await download(assetUrl);
|
|
34
|
+
const actualSha = sha256(asset);
|
|
35
|
+
|
|
36
|
+
if (actualSha !== expectedSha) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Checksum mismatch for ${target.assetName}. Expected ${expectedSha}, got ${actualSha}.`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fs.writeFileSync(targetPath, asset);
|
|
43
|
+
fs.chmodSync(targetPath, 0o755);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
main().catch((error) => {
|
|
47
|
+
console.error(`deskctl-cli install failed: ${error.message}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const crypto = require("node:crypto");
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
const https = require("node:https");
|
|
5
|
+
|
|
6
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
7
|
+
const VENDOR_DIR = path.join(PACKAGE_ROOT, "vendor");
|
|
8
|
+
const PACKAGE_JSON = path.join(PACKAGE_ROOT, "package.json");
|
|
9
|
+
|
|
10
|
+
function readPackageJson() {
|
|
11
|
+
return JSON.parse(fs.readFileSync(PACKAGE_JSON, "utf8"));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function releaseTag(pkg) {
|
|
15
|
+
return process.env.DESKCTL_RELEASE_TAG || `v${pkg.version}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function supportedTarget(platform = process.platform, arch = process.arch) {
|
|
19
|
+
if (platform === "linux" && arch === "x64") {
|
|
20
|
+
return {
|
|
21
|
+
platform,
|
|
22
|
+
arch,
|
|
23
|
+
assetName: "deskctl-linux-x86_64",
|
|
24
|
+
binaryName: "deskctl-linux-x86_64"
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
throw new Error(
|
|
29
|
+
`deskctl-cli currently supports linux-x64 only. Received ${platform}-${arch}.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function vendorBinaryPath(target) {
|
|
34
|
+
return path.join(VENDOR_DIR, target.binaryName);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function releaseBaseUrl(tag) {
|
|
38
|
+
return (
|
|
39
|
+
process.env.DESKCTL_RELEASE_BASE_URL ||
|
|
40
|
+
`https://github.com/harivansh-afk/deskctl/releases/download/${tag}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function releaseAssetUrl(tag, assetName) {
|
|
45
|
+
return process.env.DESKCTL_DOWNLOAD_URL || `${releaseBaseUrl(tag)}/${assetName}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function checksumsUrl(tag) {
|
|
49
|
+
return `${releaseBaseUrl(tag)}/checksums.txt`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function ensureVendorDir() {
|
|
53
|
+
fs.mkdirSync(VENDOR_DIR, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function checksumForAsset(contents, assetName) {
|
|
57
|
+
const line = contents
|
|
58
|
+
.split("\n")
|
|
59
|
+
.map((value) => value.trim())
|
|
60
|
+
.find((value) => value.endsWith(` ${assetName}`) || value.endsWith(` *${assetName}`));
|
|
61
|
+
|
|
62
|
+
if (!line) {
|
|
63
|
+
throw new Error(`Could not find checksum entry for ${assetName}.`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return line.split(/\s+/)[0];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function sha256(buffer) {
|
|
70
|
+
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function download(url) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
https
|
|
76
|
+
.get(url, (response) => {
|
|
77
|
+
if (
|
|
78
|
+
response.statusCode &&
|
|
79
|
+
response.statusCode >= 300 &&
|
|
80
|
+
response.statusCode < 400 &&
|
|
81
|
+
response.headers.location
|
|
82
|
+
) {
|
|
83
|
+
response.resume();
|
|
84
|
+
resolve(download(response.headers.location));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (response.statusCode !== 200) {
|
|
89
|
+
reject(new Error(`Download failed for ${url}: HTTP ${response.statusCode}`));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const chunks = [];
|
|
94
|
+
response.on("data", (chunk) => chunks.push(chunk));
|
|
95
|
+
response.on("end", () => resolve(Buffer.concat(chunks)));
|
|
96
|
+
})
|
|
97
|
+
.on("error", reject);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function installLocalBinary(sourcePath, targetPath) {
|
|
102
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
103
|
+
fs.chmodSync(targetPath, 0o755);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
PACKAGE_ROOT,
|
|
108
|
+
VENDOR_DIR,
|
|
109
|
+
checksumsUrl,
|
|
110
|
+
checksumForAsset,
|
|
111
|
+
download,
|
|
112
|
+
ensureVendorDir,
|
|
113
|
+
installLocalBinary,
|
|
114
|
+
readPackageJson,
|
|
115
|
+
releaseAssetUrl,
|
|
116
|
+
releaseTag,
|
|
117
|
+
sha256,
|
|
118
|
+
supportedTarget,
|
|
119
|
+
vendorBinaryPath
|
|
120
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
|
|
4
|
+
const { readPackageJson, supportedTarget, vendorBinaryPath } = require("./support");
|
|
5
|
+
|
|
6
|
+
function readCargoVersion() {
|
|
7
|
+
const cargoToml = fs.readFileSync(
|
|
8
|
+
path.resolve(__dirname, "..", "..", "..", "Cargo.toml"),
|
|
9
|
+
"utf8"
|
|
10
|
+
);
|
|
11
|
+
const match = cargoToml.match(/^version = "([^"]+)"/m);
|
|
12
|
+
if (!match) {
|
|
13
|
+
throw new Error("Could not determine Cargo.toml version.");
|
|
14
|
+
}
|
|
15
|
+
return match[1];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function main() {
|
|
19
|
+
const pkg = readPackageJson();
|
|
20
|
+
const cargoVersion = readCargoVersion();
|
|
21
|
+
|
|
22
|
+
if (pkg.version !== cargoVersion) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Version mismatch: npm package is ${pkg.version}, Cargo.toml is ${cargoVersion}.`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (pkg.bin?.deskctl !== "bin/deskctl.js") {
|
|
29
|
+
throw new Error("deskctl-cli must expose the deskctl bin entrypoint.");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const target = supportedTarget("linux", "x64");
|
|
33
|
+
const targetPath = vendorBinaryPath(target);
|
|
34
|
+
const vendorDir = path.dirname(targetPath);
|
|
35
|
+
if (!vendorDir.endsWith(path.join("deskctl-cli", "vendor"))) {
|
|
36
|
+
throw new Error("Vendor binary directory resolved unexpectedly.");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
main();
|