mirror of
https://github.com/harivansh-afk/deskctl.git
synced 2026-04-15 04:03:28 +00:00
nix
npm cargo
This commit is contained in:
parent
425a71095a
commit
511f21c7ba
16 changed files with 849 additions and 66 deletions
130
.github/workflows/ci.yml
vendored
130
.github/workflows/ci.yml
vendored
|
|
@ -13,7 +13,6 @@ on:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
packages: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
changes:
|
changes:
|
||||||
|
|
@ -37,7 +36,11 @@ jobs:
|
||||||
- 'tests/**'
|
- 'tests/**'
|
||||||
- 'Cargo.toml'
|
- 'Cargo.toml'
|
||||||
- 'Cargo.lock'
|
- 'Cargo.lock'
|
||||||
|
- 'npm/**'
|
||||||
|
- 'flake.nix'
|
||||||
|
- 'flake.lock'
|
||||||
- 'docker/**'
|
- 'docker/**'
|
||||||
|
- '.github/workflows/**'
|
||||||
- 'Makefile'
|
- 'Makefile'
|
||||||
|
|
||||||
- name: Set outputs
|
- name: Set outputs
|
||||||
|
|
@ -137,72 +140,36 @@ jobs:
|
||||||
- name: Xvfb integration tests
|
- name: Xvfb integration tests
|
||||||
run: make test-integration
|
run: make test-integration
|
||||||
|
|
||||||
build:
|
distribution:
|
||||||
name: Build (${{ matrix.target }})
|
name: Distribution Validate
|
||||||
needs: [changes, validate, integration]
|
needs: changes
|
||||||
if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true'
|
if: needs.changes.outputs.rust == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
target: [cargo, docker]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
# --- Cargo steps ---
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
if: matrix.target == 'cargo'
|
|
||||||
with:
|
|
||||||
components: clippy
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
if: matrix.target == 'cargo'
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
|
- uses: cachix/install-nix-action@v30
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
experimental-features = nix-command flakes
|
||||||
|
|
||||||
- name: Install system dependencies
|
- name: Install system dependencies
|
||||||
if: matrix.target == 'cargo'
|
|
||||||
run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev
|
run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev
|
||||||
|
|
||||||
- name: Clippy
|
- name: Distribution validation
|
||||||
if: matrix.target == 'cargo'
|
run: make dist-validate
|
||||||
run: cargo clippy -- -D warnings
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
if: matrix.target == 'cargo'
|
|
||||||
run: cargo build --release --locked
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
if: matrix.target == 'cargo'
|
|
||||||
with:
|
|
||||||
name: deskctl-linux-x86_64
|
|
||||||
path: target/release/deskctl
|
|
||||||
retention-days: 7
|
|
||||||
|
|
||||||
# --- Docker steps ---
|
|
||||||
- uses: docker/setup-buildx-action@v3
|
|
||||||
if: matrix.target == 'docker'
|
|
||||||
|
|
||||||
- uses: docker/login-action@v3
|
|
||||||
if: matrix.target == 'docker'
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- uses: docker/build-push-action@v6
|
|
||||||
if: matrix.target == 'docker'
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
ghcr.io/${{ github.repository }}:latest
|
|
||||||
ghcr.io/${{ github.repository }}:${{ needs.changes.outputs.tag }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
update-manifests:
|
update-manifests:
|
||||||
name: Update Manifests
|
name: Update Manifests
|
||||||
needs: [changes, build]
|
needs: [changes, validate, integration, distribution]
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -212,12 +179,17 @@ jobs:
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
- name: Update version in Cargo.toml
|
- name: Update version in Cargo.toml
|
||||||
run: |
|
run: |
|
||||||
CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
|
CURRENT=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
|
||||||
NEW="${{ needs.changes.outputs.version }}"
|
NEW="${{ needs.changes.outputs.version }}"
|
||||||
if [ "$CURRENT" != "$NEW" ]; then
|
if [ "$CURRENT" != "$NEW" ]; then
|
||||||
sed -i "0,/^version = \"${CURRENT}\"/s//version = \"${NEW}\"/" Cargo.toml
|
sed -i "0,/^version = \"${CURRENT}\"/s//version = \"${NEW}\"/" Cargo.toml
|
||||||
|
node -e 'const fs=require("node:fs"); const path="npm/deskctl-cli/package.json"; const pkg=JSON.parse(fs.readFileSync(path,"utf8")); pkg.version=process.argv[1]; fs.writeFileSync(path, JSON.stringify(pkg, null, 2)+"\n");' "$NEW"
|
||||||
cargo generate-lockfile
|
cargo generate-lockfile
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -227,7 +199,7 @@ jobs:
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
if ! git diff --quiet; then
|
if ! git diff --quiet; then
|
||||||
git add Cargo.toml Cargo.lock
|
git add Cargo.toml Cargo.lock npm/deskctl-cli/package.json
|
||||||
git commit -m "release: ${{ needs.changes.outputs.tag }} [skip ci]"
|
git commit -m "release: ${{ needs.changes.outputs.tag }} [skip ci]"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -236,6 +208,38 @@ jobs:
|
||||||
fi
|
fi
|
||||||
git push origin main --tags
|
git push origin main --tags
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build Release Asset
|
||||||
|
needs: [changes, update-manifests]
|
||||||
|
if: github.event_name != 'pull_request' && needs.changes.outputs.rust == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.changes.outputs.tag }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: clippy
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev
|
||||||
|
|
||||||
|
- name: Clippy
|
||||||
|
run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --release --locked
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: deskctl-linux-x86_64
|
||||||
|
path: target/release/deskctl
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
needs: [changes, build, update-manifests]
|
needs: [changes, build, update-manifests]
|
||||||
|
|
@ -256,9 +260,15 @@ jobs:
|
||||||
chmod +x artifacts/deskctl
|
chmod +x artifacts/deskctl
|
||||||
mv artifacts/deskctl artifacts/deskctl-linux-x86_64
|
mv artifacts/deskctl artifacts/deskctl-linux-x86_64
|
||||||
cd artifacts && sha256sum deskctl-linux-x86_64 > checksums.txt && cd ..
|
cd artifacts && sha256sum deskctl-linux-x86_64 > checksums.txt && cd ..
|
||||||
|
if gh release view "${{ needs.changes.outputs.tag }}" >/dev/null 2>&1; then
|
||||||
gh release create "${{ needs.changes.outputs.tag }}" \
|
gh release upload "${{ needs.changes.outputs.tag }}" \
|
||||||
--title "${{ needs.changes.outputs.tag }}" \
|
artifacts/deskctl-linux-x86_64 \
|
||||||
--generate-notes \
|
artifacts/checksums.txt \
|
||||||
artifacts/deskctl-linux-x86_64 \
|
--clobber
|
||||||
artifacts/checksums.txt
|
else
|
||||||
|
gh release create "${{ needs.changes.outputs.tag }}" \
|
||||||
|
--title "${{ needs.changes.outputs.tag }}" \
|
||||||
|
--generate-notes \
|
||||||
|
artifacts/deskctl-linux-x86_64 \
|
||||||
|
artifacts/checksums.txt
|
||||||
|
fi
|
||||||
|
|
|
||||||
102
.github/workflows/publish.yml
vendored
Normal file
102
.github/workflows/publish.yml
vendored
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
name: Publish Registries
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: Release tag to publish (for example v0.1.5)
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
publish_npm:
|
||||||
|
description: Publish deskctl-cli to npm
|
||||||
|
required: true
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
publish_crates:
|
||||||
|
description: Publish deskctl to crates.io
|
||||||
|
required: true
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.tag }}
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y libx11-dev libxtst-dev
|
||||||
|
|
||||||
|
- name: Verify release exists and contains canonical assets
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
gh release view "${{ inputs.tag }}" --json assets --jq '.assets[].name' > /tmp/release-assets.txt
|
||||||
|
grep -Fx "deskctl-linux-x86_64" /tmp/release-assets.txt >/dev/null
|
||||||
|
grep -Fx "checksums.txt" /tmp/release-assets.txt >/dev/null
|
||||||
|
|
||||||
|
- name: Verify versions align with tag
|
||||||
|
run: |
|
||||||
|
TAG="${{ inputs.tag }}"
|
||||||
|
VERSION="${TAG#v}"
|
||||||
|
CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
|
||||||
|
NPM_VERSION=$(node -p 'require("./npm/deskctl-cli/package.json").version')
|
||||||
|
|
||||||
|
test "$VERSION" = "$CARGO_VERSION"
|
||||||
|
test "$VERSION" = "$NPM_VERSION"
|
||||||
|
|
||||||
|
- name: Check current published state
|
||||||
|
id: published
|
||||||
|
run: |
|
||||||
|
VERSION="${{ inputs.tag }}"
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
|
|
||||||
|
if npm view "deskctl-cli@${VERSION}" version >/dev/null 2>&1; then
|
||||||
|
echo "npm=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "npm=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if curl -fsSL "https://crates.io/api/v1/crates/deskctl/${VERSION}" >/dev/null 2>&1; then
|
||||||
|
echo "crates=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "crates=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Validate npm package
|
||||||
|
run: |
|
||||||
|
mkdir -p ./tmp/npm-pack
|
||||||
|
node npm/deskctl-cli/scripts/validate-package.js
|
||||||
|
npm pack ./npm/deskctl-cli --pack-destination ./tmp/npm-pack >/dev/null
|
||||||
|
|
||||||
|
- name: Validate crate publish path
|
||||||
|
run: cargo publish --dry-run --locked
|
||||||
|
|
||||||
|
- name: Publish npm
|
||||||
|
if: inputs.publish_npm && steps.published.outputs.npm != 'true'
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
run: npm publish ./npm/deskctl-cli --access public
|
||||||
|
|
||||||
|
- name: Publish crates.io
|
||||||
|
if: inputs.publish_crates && steps.published.outputs.crates != 'true'
|
||||||
|
env:
|
||||||
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
run: cargo publish --locked
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: |
|
||||||
|
echo "tag=${{ inputs.tag }}"
|
||||||
|
echo "npm_already_published=${{ steps.published.outputs.npm }}"
|
||||||
|
echo "crates_already_published=${{ steps.published.outputs.crates }}"
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -5,3 +5,5 @@ secret/
|
||||||
.claude/
|
.claude/
|
||||||
.codex/
|
.codex/
|
||||||
openspec/
|
openspec/
|
||||||
|
npm/deskctl-cli/vendor/
|
||||||
|
npm/deskctl-cli/*.tgz
|
||||||
|
|
|
||||||
|
|
@ -35,10 +35,15 @@ make lint
|
||||||
make test-unit
|
make test-unit
|
||||||
make test-integration
|
make test-integration
|
||||||
make site-format-check
|
make site-format-check
|
||||||
|
make cargo-publish-dry-run
|
||||||
|
make npm-package-check
|
||||||
|
make nix-flake-check
|
||||||
|
make dist-validate
|
||||||
make validate
|
make validate
|
||||||
```
|
```
|
||||||
|
|
||||||
`make validate` runs the full Phase 2 validation stack. It requires Linux, `xvfb-run`, and site dependencies to be installed.
|
`make validate` runs the full Phase 2 validation stack. It requires Linux, `xvfb-run`, and site dependencies to be installed.
|
||||||
|
`make dist-validate` runs the distribution validation stack. It requires `npm`, `nix`, and Linux for the full npm runtime smoke path.
|
||||||
|
|
||||||
## Pre-commit Hooks
|
## Pre-commit Hooks
|
||||||
|
|
||||||
|
|
@ -60,6 +65,19 @@ The hook config intentionally stays small:
|
||||||
- Site files reuse the existing `site/` Prettier setup
|
- Site files reuse the existing `site/` Prettier setup
|
||||||
- Slower checks stay in CI or `make validate`
|
- Slower checks stay in CI or `make validate`
|
||||||
|
|
||||||
|
## Distribution Work
|
||||||
|
|
||||||
|
Distribution support currently ships through:
|
||||||
|
|
||||||
|
- crate: `deskctl`
|
||||||
|
- npm package: `deskctl-cli`
|
||||||
|
- repo flake: `flake.nix`
|
||||||
|
- command name on every channel: `deskctl`
|
||||||
|
|
||||||
|
For maintainer release and publish steps, see [docs/releasing.md](docs/releasing.md).
|
||||||
|
|
||||||
|
Source-build and packaging work should keep Docker as a local Linux build convenience, not as the canonical registry release path.
|
||||||
|
|
||||||
## Integration Tests
|
## Integration Tests
|
||||||
|
|
||||||
Integration coverage is Linux/X11-only in this phase. The supported local entrypoint is:
|
Integration coverage is Linux/X11-only in this phase. The supported local entrypoint is:
|
||||||
|
|
|
||||||
13
Cargo.toml
13
Cargo.toml
|
|
@ -5,6 +5,19 @@ edition = "2021"
|
||||||
description = "X11 desktop control CLI for agents"
|
description = "X11 desktop control CLI for agents"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/harivansh-afk/deskctl"
|
repository = "https://github.com/harivansh-afk/deskctl"
|
||||||
|
homepage = "https://github.com/harivansh-afk/deskctl"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["x11", "desktop", "automation", "cli", "agent"]
|
||||||
|
categories = ["command-line-utilities"]
|
||||||
|
rust-version = "1.75"
|
||||||
|
include = [
|
||||||
|
"/Cargo.toml",
|
||||||
|
"/Cargo.lock",
|
||||||
|
"/README.md",
|
||||||
|
"/LICENCE",
|
||||||
|
"/assets/**",
|
||||||
|
"/src/**",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4", features = ["derive", "env"] }
|
clap = { version = "4", features = ["derive", "env"] }
|
||||||
|
|
|
||||||
32
Makefile
32
Makefile
|
|
@ -1,4 +1,4 @@
|
||||||
.PHONY: fmt fmt-check lint test-unit test-integration site-format-check validate
|
.PHONY: fmt fmt-check lint test-unit test-integration site-format-check cargo-publish-dry-run npm-package-check nix-flake-check dist-validate validate
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
cargo fmt --all
|
cargo fmt --all
|
||||||
|
|
@ -30,4 +30,34 @@ site-format-check:
|
||||||
fi
|
fi
|
||||||
pnpm --dir site format:check
|
pnpm --dir site format:check
|
||||||
|
|
||||||
|
cargo-publish-dry-run:
|
||||||
|
cargo publish --dry-run --allow-dirty --locked
|
||||||
|
|
||||||
|
npm-package-check:
|
||||||
|
@if ! command -v npm >/dev/null 2>&1; then \
|
||||||
|
echo "npm is required for npm packaging validation."; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
node npm/deskctl-cli/scripts/validate-package.js
|
||||||
|
rm -rf tmp/npm-pack tmp/npm-install
|
||||||
|
mkdir -p tmp/npm-pack tmp/npm-install/bin
|
||||||
|
npm pack ./npm/deskctl-cli --pack-destination ./tmp/npm-pack >/dev/null
|
||||||
|
@if [ "$$(uname -s)" != "Linux" ]; then \
|
||||||
|
echo "Skipping npm package runtime smoke test on non-Linux host."; \
|
||||||
|
else \
|
||||||
|
cargo build && \
|
||||||
|
PACK_TGZ=$$(ls ./tmp/npm-pack/*.tgz | head -n 1) && \
|
||||||
|
DESKCTL_BINARY_PATH="$$(pwd)/target/debug/deskctl" npm install --prefix ./tmp/npm-install "$${PACK_TGZ}" && \
|
||||||
|
./tmp/npm-install/node_modules/.bin/deskctl --version; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
nix-flake-check:
|
||||||
|
@if ! command -v nix >/dev/null 2>&1; then \
|
||||||
|
echo "nix is required for flake validation."; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
nix flake check
|
||||||
|
|
||||||
|
dist-validate: test-unit cargo-publish-dry-run npm-package-check nix-flake-check
|
||||||
|
|
||||||
validate: fmt-check lint test-unit test-integration site-format-check
|
validate: fmt-check lint test-unit test-integration site-format-check
|
||||||
|
|
|
||||||
53
README.md
53
README.md
|
|
@ -4,11 +4,45 @@ Desktop control CLI for AI agents on Linux X11.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
|
### Cargo
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo install deskctl
|
cargo install deskctl
|
||||||
```
|
```
|
||||||
|
|
||||||
Build a Linux binary with Docker:
|
Source builds on Linux require:
|
||||||
|
|
||||||
|
- Rust 1.75+
|
||||||
|
- `pkg-config`
|
||||||
|
- X11 development libraries for input and windowing, typically `libx11-dev` and `libxtst-dev` on Debian/Ubuntu
|
||||||
|
|
||||||
|
### npm
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g deskctl-cli
|
||||||
|
deskctl --help
|
||||||
|
```
|
||||||
|
|
||||||
|
One-shot execution is also supported:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx deskctl-cli --help
|
||||||
|
```
|
||||||
|
|
||||||
|
`deskctl-cli` currently supports `linux-x64` and installs the `deskctl` command by downloading the matching GitHub Release asset.
|
||||||
|
|
||||||
|
### Nix
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix run github:harivansh-afk/deskctl -- --help
|
||||||
|
nix profile install github:harivansh-afk/deskctl
|
||||||
|
```
|
||||||
|
|
||||||
|
The repo flake is the supported Nix install surface in this phase.
|
||||||
|
|
||||||
|
### Docker Convenience
|
||||||
|
|
||||||
|
Build a Linux binary locally with Docker:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose -f docker/docker-compose.yml run --rm build
|
docker compose -f docker/docker-compose.yml run --rm build
|
||||||
|
|
@ -28,13 +62,12 @@ Run it on an X11 session:
|
||||||
DISPLAY=:1 XDG_SESSION_TYPE=x11 ~/deskctl --json snapshot --annotate
|
DISPLAY=:1 XDG_SESSION_TYPE=x11 ~/deskctl --json snapshot --annotate
|
||||||
```
|
```
|
||||||
|
|
||||||
Local source build requirements:
|
### Local Source Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo build
|
cargo build
|
||||||
```
|
```
|
||||||
|
|
||||||
At the moment there are no extra native build dependencies beyond a Rust toolchain.
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -78,7 +111,7 @@ Source layout:
|
||||||
## Runtime Requirements
|
## Runtime Requirements
|
||||||
|
|
||||||
- Linux with X11 session
|
- Linux with X11 session
|
||||||
- Rust 1.75+ (for build)
|
- Rust 1.75+ plus the source-build dependencies above when building from source
|
||||||
|
|
||||||
The binary itself only links the standard glibc runtime on Linux (`libc`, `libm`, `libgcc_s`).
|
The binary itself only links the standard glibc runtime on Linux (`libc`, `libm`, `libgcc_s`).
|
||||||
|
|
||||||
|
|
@ -158,6 +191,16 @@ Text mode is compact and follow-up-oriented, but JSON is the parsing contract.
|
||||||
|
|
||||||
See [docs/runtime-output.md](docs/runtime-output.md) for the exact stable-vs-best-effort breakdown.
|
See [docs/runtime-output.md](docs/runtime-output.md) for the exact stable-vs-best-effort breakdown.
|
||||||
|
|
||||||
|
## Distribution
|
||||||
|
|
||||||
|
- GitHub Releases are the canonical binary source
|
||||||
|
- crates.io package: `deskctl`
|
||||||
|
- npm package: `deskctl-cli`
|
||||||
|
- installed command on every channel: `deskctl`
|
||||||
|
- repo-owned Nix install path: `flake.nix`
|
||||||
|
|
||||||
|
For maintainer publishing and release steps, see [docs/releasing.md](docs/releasing.md).
|
||||||
|
|
||||||
## Selector Contract
|
## Selector Contract
|
||||||
|
|
||||||
Explicit selector modes:
|
Explicit selector modes:
|
||||||
|
|
|
||||||
110
docs/releasing.md
Normal file
110
docs/releasing.md
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
# Releasing deskctl
|
||||||
|
|
||||||
|
This document covers the operator flow for shipping `deskctl` across:
|
||||||
|
|
||||||
|
- GitHub Releases
|
||||||
|
- crates.io
|
||||||
|
- npm
|
||||||
|
- the repo flake
|
||||||
|
|
||||||
|
GitHub Releases are the canonical binary source. The npm package consumes those release assets instead of building a separate binary.
|
||||||
|
|
||||||
|
## Package Names
|
||||||
|
|
||||||
|
- crate: `deskctl`
|
||||||
|
- npm package: `deskctl-cli`
|
||||||
|
- installed command: `deskctl`
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before the first live publish on each registry:
|
||||||
|
|
||||||
|
- npm ownership for `deskctl-cli`
|
||||||
|
- crates.io ownership for `deskctl`
|
||||||
|
- repository secrets:
|
||||||
|
- `NPM_TOKEN`
|
||||||
|
- `CARGO_REGISTRY_TOKEN`
|
||||||
|
|
||||||
|
These are user-owned prerequisites. The repo can validate and automate the rest, but it cannot create registry ownership for you.
|
||||||
|
|
||||||
|
## Normal Release Flow
|
||||||
|
|
||||||
|
1. Merge release-ready changes to `main`.
|
||||||
|
2. Let CI run:
|
||||||
|
- validation
|
||||||
|
- integration
|
||||||
|
- distribution validation
|
||||||
|
- release asset build
|
||||||
|
3. Confirm the GitHub Release exists for the version tag and includes:
|
||||||
|
- `deskctl-linux-x86_64`
|
||||||
|
- `checksums.txt`
|
||||||
|
4. Trigger the `Publish Registries` workflow with:
|
||||||
|
- `tag`
|
||||||
|
- `publish_npm`
|
||||||
|
- `publish_crates`
|
||||||
|
5. Confirm the publish summary for each channel.
|
||||||
|
|
||||||
|
## What CI Validates
|
||||||
|
|
||||||
|
The repository validates:
|
||||||
|
|
||||||
|
- `cargo publish --dry-run --locked`
|
||||||
|
- npm package metadata and packability
|
||||||
|
- npm install smoke path on Linux using the packaged `deskctl` command
|
||||||
|
- repo flake evaluation/build
|
||||||
|
|
||||||
|
The repository release workflow:
|
||||||
|
|
||||||
|
- builds the Linux release binary
|
||||||
|
- publishes the canonical GitHub Release asset
|
||||||
|
- uploads `checksums.txt`
|
||||||
|
|
||||||
|
The registry publish workflow:
|
||||||
|
|
||||||
|
- targets an existing release tag
|
||||||
|
- checks that Cargo, npm, and the requested tag all agree on version
|
||||||
|
- checks whether that version is already published on npm and crates.io
|
||||||
|
- only publishes the channels explicitly requested
|
||||||
|
|
||||||
|
## Rerun Safety
|
||||||
|
|
||||||
|
Registry publishing is intentionally separate from release asset creation.
|
||||||
|
|
||||||
|
If a partial failure happens:
|
||||||
|
|
||||||
|
- GitHub Release assets remain the source of truth
|
||||||
|
- rerun the `Publish Registries` workflow for the same tag
|
||||||
|
- already-published channels are reported and skipped
|
||||||
|
- remaining channels can still be published
|
||||||
|
|
||||||
|
## Local Validation
|
||||||
|
|
||||||
|
Run the distribution checks locally with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make cargo-publish-dry-run
|
||||||
|
make npm-package-check
|
||||||
|
make nix-flake-check
|
||||||
|
make dist-validate
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `make npm-package-check` does a runtime smoke test only on Linux
|
||||||
|
- `make nix-flake-check` requires a local Nix installation
|
||||||
|
- Docker remains a local Linux build convenience, not the canonical release path
|
||||||
|
|
||||||
|
## Nix Boundary
|
||||||
|
|
||||||
|
The repo-owned `flake.nix` is the supported Nix surface in this phase.
|
||||||
|
|
||||||
|
In scope:
|
||||||
|
|
||||||
|
- `nix run github:harivansh-afk/deskctl`
|
||||||
|
- `nix profile install github:harivansh-afk/deskctl`
|
||||||
|
- CI validation for the repo flake
|
||||||
|
|
||||||
|
Out of scope for this phase:
|
||||||
|
|
||||||
|
- `nixpkgs` upstreaming
|
||||||
|
- extra distro packaging outside the repo
|
||||||
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1774386573,
|
||||||
|
"narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
77
flake.nix
Normal file
77
flake.nix
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
{
|
||||||
|
description = "deskctl - Desktop control CLI for AI agents on Linux X11";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{ self, nixpkgs, flake-utils }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
lib = pkgs.lib;
|
||||||
|
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||||
|
|
||||||
|
deskctl =
|
||||||
|
pkgs.rustPlatform.buildRustPackage {
|
||||||
|
pname = cargoToml.package.name;
|
||||||
|
version = cargoToml.package.version;
|
||||||
|
src = ./.;
|
||||||
|
cargoLock.lockFile = ./Cargo.lock;
|
||||||
|
nativeBuildInputs = [ pkgs.pkg-config ];
|
||||||
|
buildInputs = lib.optionals pkgs.stdenv.isLinux [
|
||||||
|
pkgs.libx11
|
||||||
|
pkgs.libxtst
|
||||||
|
];
|
||||||
|
doCheck = false;
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = cargoToml.package.description;
|
||||||
|
homepage = cargoToml.package.homepage;
|
||||||
|
license = licenses.mit;
|
||||||
|
mainProgram = "deskctl";
|
||||||
|
platforms = platforms.linux;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
formatter = pkgs.nixfmt;
|
||||||
|
|
||||||
|
packages = lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||||
|
inherit deskctl;
|
||||||
|
default = deskctl;
|
||||||
|
};
|
||||||
|
|
||||||
|
apps = lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||||
|
default = flake-utils.lib.mkApp { drv = deskctl; };
|
||||||
|
deskctl = flake-utils.lib.mkApp { drv = deskctl; };
|
||||||
|
};
|
||||||
|
|
||||||
|
checks = lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||||
|
build = deskctl;
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
packages =
|
||||||
|
[
|
||||||
|
pkgs.cargo
|
||||||
|
pkgs.clippy
|
||||||
|
pkgs.nodejs
|
||||||
|
pkgs.nixfmt
|
||||||
|
pkgs.pkg-config
|
||||||
|
pkgs.pnpm
|
||||||
|
pkgs.rustc
|
||||||
|
pkgs.rustfmt
|
||||||
|
]
|
||||||
|
++ lib.optionals pkgs.stdenv.isLinux [
|
||||||
|
pkgs.libx11
|
||||||
|
pkgs.libxtst
|
||||||
|
pkgs.xorg.xorgserver
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
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