mirror of
https://github.com/harivansh-afk/deskctl.git
synced 2026-04-15 07:04:46 +00:00
parent
425a71095a
commit
714e34ba19
16 changed files with 849 additions and 66 deletions
36
npm/deskctl-cli/README.md
Normal file
36
npm/deskctl-cli/README.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# deskctl-cli
|
||||
|
||||
`deskctl-cli` installs the `deskctl` command for Linux X11 systems.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install -g deskctl-cli
|
||||
```
|
||||
|
||||
After install, run:
|
||||
|
||||
```bash
|
||||
deskctl --help
|
||||
```
|
||||
|
||||
One-shot usage is also supported:
|
||||
|
||||
```bash
|
||||
npx deskctl-cli --help
|
||||
```
|
||||
|
||||
## Runtime Support
|
||||
|
||||
- Linux
|
||||
- X11 session
|
||||
- currently packaged release asset: `linux-x64`
|
||||
|
||||
`deskctl-cli` downloads the matching GitHub Release binary during install.
|
||||
Unsupported targets fail during install with a clear runtime support error instead of installing a broken command.
|
||||
|
||||
If you want the Rust source-install path instead, use:
|
||||
|
||||
```bash
|
||||
cargo install deskctl
|
||||
```
|
||||
36
npm/deskctl-cli/bin/deskctl.js
Normal file
36
npm/deskctl-cli/bin/deskctl.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("node:fs");
|
||||
const { spawn } = require("node:child_process");
|
||||
|
||||
const { readPackageJson, releaseTag, supportedTarget, vendorBinaryPath } = require("../scripts/support");
|
||||
|
||||
function main() {
|
||||
const pkg = readPackageJson();
|
||||
const target = supportedTarget();
|
||||
const binaryPath = vendorBinaryPath(target);
|
||||
|
||||
if (!fs.existsSync(binaryPath)) {
|
||||
console.error(
|
||||
[
|
||||
"deskctl binary is missing from the npm package install.",
|
||||
`Expected: ${binaryPath}`,
|
||||
`Package version: ${pkg.version}`,
|
||||
`Release tag: ${releaseTag(pkg)}`,
|
||||
"Try reinstalling deskctl-cli or check that your target is supported."
|
||||
].join("\n")
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit" });
|
||||
child.on("exit", (code, signal) => {
|
||||
if (signal) {
|
||||
process.kill(process.pid, signal);
|
||||
return;
|
||||
}
|
||||
process.exit(code ?? 1);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
36
npm/deskctl-cli/package.json
Normal file
36
npm/deskctl-cli/package.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "deskctl-cli",
|
||||
"version": "0.1.5",
|
||||
"description": "Installable deskctl CLI package for Linux X11 agents",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/harivansh-afk/deskctl",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/harivansh-afk/deskctl.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/harivansh-afk/deskctl/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"bin": {
|
||||
"deskctl": "bin/deskctl.js"
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
"bin",
|
||||
"scripts"
|
||||
],
|
||||
"scripts": {
|
||||
"postinstall": "node scripts/postinstall.js",
|
||||
"validate": "node scripts/validate-package.js"
|
||||
},
|
||||
"keywords": [
|
||||
"deskctl",
|
||||
"x11",
|
||||
"desktop",
|
||||
"automation",
|
||||
"cli"
|
||||
]
|
||||
}
|
||||
49
npm/deskctl-cli/scripts/postinstall.js
Normal file
49
npm/deskctl-cli/scripts/postinstall.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
const fs = require("node:fs");
|
||||
|
||||
const {
|
||||
checksumsUrl,
|
||||
checksumForAsset,
|
||||
download,
|
||||
ensureVendorDir,
|
||||
installLocalBinary,
|
||||
readPackageJson,
|
||||
releaseAssetUrl,
|
||||
releaseTag,
|
||||
sha256,
|
||||
supportedTarget,
|
||||
vendorBinaryPath
|
||||
} = require("./support");
|
||||
|
||||
async function main() {
|
||||
const pkg = readPackageJson();
|
||||
const target = supportedTarget();
|
||||
const targetPath = vendorBinaryPath(target);
|
||||
|
||||
ensureVendorDir();
|
||||
|
||||
if (process.env.DESKCTL_BINARY_PATH) {
|
||||
installLocalBinary(process.env.DESKCTL_BINARY_PATH, targetPath);
|
||||
return;
|
||||
}
|
||||
|
||||
const tag = releaseTag(pkg);
|
||||
const assetUrl = releaseAssetUrl(tag, target.assetName);
|
||||
const checksumText = (await download(checksumsUrl(tag))).toString("utf8");
|
||||
const expectedSha = checksumForAsset(checksumText, target.assetName);
|
||||
const asset = await download(assetUrl);
|
||||
const actualSha = sha256(asset);
|
||||
|
||||
if (actualSha !== expectedSha) {
|
||||
throw new Error(
|
||||
`Checksum mismatch for ${target.assetName}. Expected ${expectedSha}, got ${actualSha}.`
|
||||
);
|
||||
}
|
||||
|
||||
fs.writeFileSync(targetPath, asset);
|
||||
fs.chmodSync(targetPath, 0o755);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(`deskctl-cli install failed: ${error.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
120
npm/deskctl-cli/scripts/support.js
Normal file
120
npm/deskctl-cli/scripts/support.js
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
const crypto = require("node:crypto");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const https = require("node:https");
|
||||
|
||||
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
||||
const VENDOR_DIR = path.join(PACKAGE_ROOT, "vendor");
|
||||
const PACKAGE_JSON = path.join(PACKAGE_ROOT, "package.json");
|
||||
|
||||
function readPackageJson() {
|
||||
return JSON.parse(fs.readFileSync(PACKAGE_JSON, "utf8"));
|
||||
}
|
||||
|
||||
function releaseTag(pkg) {
|
||||
return process.env.DESKCTL_RELEASE_TAG || `v${pkg.version}`;
|
||||
}
|
||||
|
||||
function supportedTarget(platform = process.platform, arch = process.arch) {
|
||||
if (platform === "linux" && arch === "x64") {
|
||||
return {
|
||||
platform,
|
||||
arch,
|
||||
assetName: "deskctl-linux-x86_64",
|
||||
binaryName: "deskctl-linux-x86_64"
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`deskctl-cli currently supports linux-x64 only. Received ${platform}-${arch}.`
|
||||
);
|
||||
}
|
||||
|
||||
function vendorBinaryPath(target) {
|
||||
return path.join(VENDOR_DIR, target.binaryName);
|
||||
}
|
||||
|
||||
function releaseBaseUrl(tag) {
|
||||
return (
|
||||
process.env.DESKCTL_RELEASE_BASE_URL ||
|
||||
`https://github.com/harivansh-afk/deskctl/releases/download/${tag}`
|
||||
);
|
||||
}
|
||||
|
||||
function releaseAssetUrl(tag, assetName) {
|
||||
return process.env.DESKCTL_DOWNLOAD_URL || `${releaseBaseUrl(tag)}/${assetName}`;
|
||||
}
|
||||
|
||||
function checksumsUrl(tag) {
|
||||
return `${releaseBaseUrl(tag)}/checksums.txt`;
|
||||
}
|
||||
|
||||
function ensureVendorDir() {
|
||||
fs.mkdirSync(VENDOR_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
function checksumForAsset(contents, assetName) {
|
||||
const line = contents
|
||||
.split("\n")
|
||||
.map((value) => value.trim())
|
||||
.find((value) => value.endsWith(` ${assetName}`) || value.endsWith(` *${assetName}`));
|
||||
|
||||
if (!line) {
|
||||
throw new Error(`Could not find checksum entry for ${assetName}.`);
|
||||
}
|
||||
|
||||
return line.split(/\s+/)[0];
|
||||
}
|
||||
|
||||
function sha256(buffer) {
|
||||
return crypto.createHash("sha256").update(buffer).digest("hex");
|
||||
}
|
||||
|
||||
function download(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
https
|
||||
.get(url, (response) => {
|
||||
if (
|
||||
response.statusCode &&
|
||||
response.statusCode >= 300 &&
|
||||
response.statusCode < 400 &&
|
||||
response.headers.location
|
||||
) {
|
||||
response.resume();
|
||||
resolve(download(response.headers.location));
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
reject(new Error(`Download failed for ${url}: HTTP ${response.statusCode}`));
|
||||
return;
|
||||
}
|
||||
|
||||
const chunks = [];
|
||||
response.on("data", (chunk) => chunks.push(chunk));
|
||||
response.on("end", () => resolve(Buffer.concat(chunks)));
|
||||
})
|
||||
.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
function installLocalBinary(sourcePath, targetPath) {
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
fs.chmodSync(targetPath, 0o755);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PACKAGE_ROOT,
|
||||
VENDOR_DIR,
|
||||
checksumsUrl,
|
||||
checksumForAsset,
|
||||
download,
|
||||
ensureVendorDir,
|
||||
installLocalBinary,
|
||||
readPackageJson,
|
||||
releaseAssetUrl,
|
||||
releaseTag,
|
||||
sha256,
|
||||
supportedTarget,
|
||||
vendorBinaryPath
|
||||
};
|
||||
40
npm/deskctl-cli/scripts/validate-package.js
Normal file
40
npm/deskctl-cli/scripts/validate-package.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const { readPackageJson, supportedTarget, vendorBinaryPath } = require("./support");
|
||||
|
||||
function readCargoVersion() {
|
||||
const cargoToml = fs.readFileSync(
|
||||
path.resolve(__dirname, "..", "..", "..", "Cargo.toml"),
|
||||
"utf8"
|
||||
);
|
||||
const match = cargoToml.match(/^version = "([^"]+)"/m);
|
||||
if (!match) {
|
||||
throw new Error("Could not determine Cargo.toml version.");
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
function main() {
|
||||
const pkg = readPackageJson();
|
||||
const cargoVersion = readCargoVersion();
|
||||
|
||||
if (pkg.version !== cargoVersion) {
|
||||
throw new Error(
|
||||
`Version mismatch: npm package is ${pkg.version}, Cargo.toml is ${cargoVersion}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (pkg.bin?.deskctl !== "bin/deskctl.js") {
|
||||
throw new Error("deskctl-cli must expose the deskctl bin entrypoint.");
|
||||
}
|
||||
|
||||
const target = supportedTarget("linux", "x64");
|
||||
const targetPath = vendorBinaryPath(target);
|
||||
const vendorDir = path.dirname(targetPath);
|
||||
if (!vendorDir.endsWith(path.join("deskctl-cli", "vendor"))) {
|
||||
throw new Error("Vendor binary directory resolved unexpectedly.");
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Loading…
Add table
Add a link
Reference in a new issue