Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.16.0] - 2026-10-30

### Added

- `npm` plugin: installs the latest npm from the npm registry into the
DevKit folder with `npm` / `npx` wrappers. Needs Node.js (installs the
`node` plugin first when `node` is missing).
- `yarn` plugin: installs the latest Yarn classic from the npm registry
with `yarn` / `yarnpkg` wrappers. Needs Node.js.
- Shared `node_pm` helper module for package managers distributed as npm
registry tarballs.

### Changed

- All Markdown filenames are now uppercase (e.g. `docs/PLUGINS.md`,
`docs/GETTING-STARTED.md`, `wiki/HOME.md`, issue and discussion
templates), and every link points to the new names.

### Fixed

- Installing pnpm no longer fails with "Failed to query GitHub release
pnpm/pnpm" when the GitHub API rate limit (60 requests/hour
unauthenticated) is used up. pnpm now falls back to the github.com
`releases/latest` redirect, which is not rate-limited, via the new shared
`github_latest_tag_redirect` helper.
- Deno, Bun, and uv get the same fallback. All four plugins now resolve
their download through the shared `github_latest_asset` helper (API
first, redirect fallback second).

## [0.15.4] - 2026-10-07

### Fixed
Expand Down Expand Up @@ -444,6 +473,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Initial project scaffold from auto-gen-py-project

[0.16.0]: https://github.com/axcel-blade/DevKit/releases/tag/v0.16.0
[0.15.4]: https://github.com/axcel-blade/DevKit/releases/tag/v0.15.4
[0.15.3]: https://github.com/axcel-blade/DevKit/releases/tag/v0.15.3
[0.15.2]: https://github.com/axcel-blade/DevKit/releases/tag/v0.15.2
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ cargo run --release -- doctor
2. Implement `status`, `install`, `uninstall`, and `env_spec`. Override `supported_os` when the plugin does not run on every desktop OS.
3. Register the plugin in `crate::plugins::all()` (`src/plugins/mod.rs`).
4. Add tests in a `#[cfg(test)] mod tests` block at the bottom of the file.
5. Document the plugin in `README.md` and `docs/plugins.md`.
5. Document the plugin in `README.md` and `docs/PLUGINS.md`.

See [docs/plugins.md](docs/plugins.md).
See [docs/PLUGINS.md](docs/PLUGINS.md).

## Commit messages

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "devkit"
version = "0.15.4"
version = "0.16.0"
edition = "2021"
description = "DevKit application — install developer SDKs and configure environment variables"
license = "MIT"
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# DevKit

[![Version](https://img.shields.io/badge/version-0.15.3-blue.svg)](CHANGELOG.md)
[![Version](https://img.shields.io/badge/version-0.16.0-blue.svg)](CHANGELOG.md)
[![CI](https://github.com/axcel-blade/DevKit/actions/workflows/ci.yml/badge.svg)](https://github.com/axcel-blade/DevKit/actions/workflows/ci.yml)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE.md)
[![Rust](https://img.shields.io/badge/rust-2021_edition-orange.svg?logo=rust)](https://www.rust-lang.org/)
[![Platforms](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](docs/getting-started.md)
[![Plugins](https://img.shields.io/badge/plugins-35-blueviolet.svg)](docs/plugins.md)
[![Platforms](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](docs/GETTING-STARTED.md)
[![Plugins](https://img.shields.io/badge/plugins-37-blueviolet.svg)](docs/PLUGINS.md)

DevKit is a **CLI application** that sets up developer environments on Windows, macOS, and Linux: download an SDK, install it under a machine `dev` folder, and configure PATH / environment variables through plugins.

Expand All @@ -31,7 +31,7 @@ Checking for available versions ...

DevKit
OS: Windows
Version: 0.15.4
Version: 0.16.0
# ID NAME STATUS INSTALLED AVAILABLE
--------------------------------------------------------------------------------------------------------
1 anaconda Anaconda not_installed - 2026.07-1
Expand Down Expand Up @@ -131,6 +131,7 @@ is where the menu lists that plugin and where `devkit install` accepts it.
| `mysql` | Windows, macOS, Linux | MySQL 8.4 LTS portable |
| `ninja` | Windows, macOS, Linux | Latest Ninja binary |
| `node` | Windows, macOS, Linux | Latest Node.js LTS (npm / npx) |
| `npm` | Windows, macOS, Linux | Latest standalone npm / npx (needs Node.js) |
| `php` | Windows, macOS, Linux | PHP NTS (Windows); system wrappers (Unix) |
| `platform-tools` | Windows, macOS, Linux | Android platform-tools (`adb`) |
| `pmd` | Windows, macOS, Linux | PMD Source Code Analyzer binary (`--version`) |
Expand All @@ -142,6 +143,7 @@ is where the menu lists that plugin and where `devkit install` accepts it.
| `sqlite` | Windows, macOS, Linux | Official sqlite-tools CLI |
| `terraform` | Windows, macOS, Linux | Latest Terraform |
| `uv` | Windows, macOS, Linux | Latest uv and uvx |
| `yarn` | Windows, macOS, Linux | Latest Yarn classic (needs Node.js) |

Default install root:

Expand All @@ -154,9 +156,9 @@ Override with `DEVKIT_HOME`. Open a **new terminal** after install so PATH updat

| Doc | Purpose |
|-----|---------|
| [docs/getting-started.md](docs/getting-started.md) | Run the app and the Makefile targets |
| [docs/plugins.md](docs/plugins.md) | Write a plugin |
| [docs/architecture.md](docs/architecture.md) | How DevKit works |
| [docs/GETTING-STARTED.md](docs/GETTING-STARTED.md) | Run the app and the Makefile targets |
| [docs/PLUGINS.md](docs/PLUGINS.md) | Write a plugin |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | How DevKit works |
| [CHANGELOG.md](CHANGELOG.md) | Version history |
| [ROADMAP.md](ROADMAP.md) | Future plans |
| [CONTRIBUTING.md](CONTRIBUTING.md) | How to contribute |
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ time for investigation before any public disclosure.

## Security notes for users

- DevKit downloads third-party SDK archives (Flutter, Temurin, Mono, Composer, Git for Windows / MinGit, GNU Make, Chocolatey CLI, Gradle, JUnit, PMD, PHP, MySQL, Node.js, Android cmdline-tools / platform-tools / NDK, Python, Go, Rust, .NET, CMake, Ninja, Maven, PostgreSQL, SQLite, kubectl, Terraform, pnpm, Deno, Bun, uv, Anaconda, Docker CLI).
- DevKit downloads third-party SDK archives (Flutter, Temurin, Mono, Composer, Git for Windows / MinGit, GNU Make, Chocolatey CLI, Gradle, JUnit, PMD, PHP, MySQL, Node.js, Android cmdline-tools / platform-tools / NDK, Python, Go, Rust, .NET, CMake, Ninja, Maven, PostgreSQL, SQLite, kubectl, Terraform, npm, Yarn, pnpm, Deno, Bun, uv, Anaconda, Docker CLI).
Only use trusted networks when installing.
- On Windows, GNU Make is the Chocolatey `make` 4.4.1 package, unpacked into the DevKit install folder. DevKit does not run an elevated `choco install`.
- Plugins may modify **user** environment variables (Windows registry or shell profiles).
Expand Down
2 changes: 1 addition & 1 deletion SUPPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## How to get help

1. Read [README.md](README.md) and [docs/getting-started.md](docs/getting-started.md).
1. Read [README.md](README.md) and [docs/GETTING-STARTED.md](docs/GETTING-STARTED.md).
2. Run diagnostics:

```bash
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Architecture

DevKit **0.15.4** is a CLI application with a plugin pipeline.
DevKit **0.16.0** is a CLI application with a plugin pipeline.

```text
target/release/devkit (binary) / devkit.bat / devkit.sh
Expand Down Expand Up @@ -66,7 +66,7 @@ Checking for available versions ...

DevKit
OS: Windows
Version: 0.15.4
Version: 0.16.0
# ID NAME STATUS INSTALLED AVAILABLE
--------------------------------------------------------------------------------------------------------
1 anaconda Anaconda not_installed - 2026.07-1
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Checking for available versions ...

DevKit
OS: Windows
Version: 0.15.4
Version: 0.16.0
# ID NAME STATUS INSTALLED AVAILABLE
--------------------------------------------------------------------------------------------------------
1 anaconda Anaconda not_installed - 2026.07-1
Expand Down
8 changes: 4 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Documentation

DevKit **0.15.4** documentation index.
DevKit **0.16.0** documentation index.

| Page | Description |
|------|-------------|
| [getting-started.md](getting-started.md) | Install requirements, Makefile targets, and how to run the app |
| [plugins.md](plugins.md) | Built-in plugins and how to write your own |
| [architecture.md](architecture.md) | Internals: registry, env, downloads |
| [GETTING-STARTED.md](GETTING-STARTED.md) | Install requirements, Makefile targets, and how to run the app |
| [PLUGINS.md](PLUGINS.md) | Built-in plugins and how to write your own |
| [ARCHITECTURE.md](ARCHITECTURE.md) | Internals: registry, env, downloads |

Also see the repository root: README (Makefile targets and `make bat` / `make sh`), CHANGELOG, CONTRIBUTING, SECURITY, SUPPORT, ROADMAP, TODO.
7 changes: 5 additions & 2 deletions docs/plugins.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Plugins

DevKit **0.15.4** ships these built-in plugins. The OS column is where the menu lists the plugin and where `devkit install` accepts it.
DevKit **0.16.0** ships these built-in plugins. The OS column is where the menu lists the plugin and where `devkit install` accepts it.

| ID | OS | Notes |
|----|----|-------|
Expand All @@ -9,7 +9,9 @@ DevKit **0.15.4** ships these built-in plugins. The OS column is where the menu
| `rust` | Windows, macOS, Linux | rustup stable into `CARGO_HOME` / `RUSTUP_HOME` |
| `dotnet` | Windows, macOS, Linux | .NET SDK 10.0 LTS ZIP from Microsoft release metadata |
| `node` | Windows, macOS, Linux | Latest Node.js LTS (npm / npx) |
| `npm` | Windows, macOS, Linux | Latest npm tarball from the npm registry + `npm` / `npx` wrappers; needs Node.js |
| `pnpm` | Windows, macOS, Linux | Latest pnpm standalone ZIP/tar from GitHub |
| `yarn` | Windows, macOS, Linux | Latest Yarn classic tarball from the npm registry + `yarn` / `yarnpkg` wrappers; needs Node.js |
| `deno` | Windows, macOS, Linux | Latest Deno ZIP from GitHub |
| `bun` | Windows, macOS, Linux | Latest Bun ZIP from GitHub |
| `uv` | Windows, macOS, Linux | Latest uv and uvx archive from GitHub |
Expand Down Expand Up @@ -107,7 +109,7 @@ Checking for available versions ...

DevKit
OS: Windows
Version: 0.15.4
Version: 0.16.0
# ID NAME STATUS INSTALLED AVAILABLE
--------------------------------------------------------------------------------------------------------
1 anaconda Anaconda not_installed - 2026.07-1
Expand Down Expand Up @@ -147,6 +149,7 @@ it installs that plugin first, prerequisites of prerequisites included.
| `android`, `gradle`, `junit`, `maven`, `pmd` | `jdk` (`java`) |
| `composer` | `php` (`php`) |
| `flutter` | `git` (`git`) |
| `npm`, `yarn` | `node` (`node`) |

Updates (`u` / `a`) do not install prerequisites again.

Expand Down
57 changes: 57 additions & 0 deletions src/plugin_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,63 @@ pub fn github_release_by_tag(owner: &str, repo: &str, tag: &str) -> Result<Value
Ok(value)
}

/// Latest release tag for `owner/repo`, read from github.com's
/// `releases/latest` redirect. Unlike the REST API this is not subject to
/// the 60 requests/hour unauthenticated limit, so plugins use it as a
/// fallback when [`github_latest_release`] fails.
pub fn github_latest_tag_redirect(owner: &str, repo: &str) -> Result<String> {
crate::download::ensure_internet_access()?;
let url = format!("https://github.com/{owner}/{repo}/releases/latest");
let resp = ureq::get(&url)
.set("User-Agent", "DevKit")
.call()
.with_context(|| format!("Failed to open {url}"))?;
// Final URL looks like .../releases/tag/v10.0.0
match resp.get_url().rsplit_once("/tag/") {
Some((_, tag)) if !tag.is_empty() => Ok(tag.to_string()),
_ => bail!("Could not read latest release tag for {owner}/{repo}"),
}
}

/// Return `(download_url, tag)` for the asset named exactly `asset` in the
/// latest `owner/repo` release.
///
/// Tries the GitHub API first; if that fails (e.g. the unauthenticated rate
/// limit is exhausted), reads the tag from [`github_latest_tag_redirect`] and
/// builds the standard `releases/download/<tag>/<asset>` URL instead.
pub fn github_latest_asset(owner: &str, repo: &str, asset: &str) -> Result<(String, String)> {
let api = github_latest_release(owner, repo).and_then(|release| {
let tag = release
.get("tag_name")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
release
.get("assets")
.and_then(|v| v.as_array())
.into_iter()
.flatten()
.filter(|a| a.get("name").and_then(|v| v.as_str()) == Some(asset))
.find_map(|a| a.get("browser_download_url").and_then(|v| v.as_str()))
.filter(|url| !url.is_empty())
.map(|url| (url.to_string(), tag.clone()))
.with_context(|| format!("No GitHub asset named {asset} in {owner}/{repo} {tag}"))
});
match api {
Ok(found) => Ok(found),
Err(api_err) => {
let tag = github_latest_tag_redirect(owner, repo).map_err(|fallback_err| {
anyhow::anyhow!(
"{api_err:#}
Fallback lookup also failed: {fallback_err:#}"
)
})?;
let url = format!("https://github.com/{owner}/{repo}/releases/download/{tag}/{asset}");
Ok((url, tag))
}
}
}

/// Return `(browser_download_url, tag_name)` for the first matching asset name.
pub fn pick_release_asset(release: &Value, name_substrings: &[&str]) -> Result<(String, String)> {
let tag = release
Expand Down
29 changes: 3 additions & 26 deletions src/plugins/bun.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@
use crate::download::install_archive_from_url;
use crate::platform::{cpu_arch, current_os, is_windows, HostOS};
use crate::plugin::{EnvSpec, InstallContext, InstallResult, Plugin, PluginStatus};
use crate::plugin_utils::{
binary_status, find_files_named, github_latest_release, pick_release_asset,
};
use crate::plugin_utils::{binary_status, find_files_named, github_latest_asset};
use crate::progress::download_progress;
use anyhow::{bail, Result};
use std::path::PathBuf;

const MARKER: &str = ".devkit-bun";

fn resolve_bun_download() -> Result<(String, String)> {
let release = github_latest_release("oven-sh", "bun")?;
let host = current_os();
let arch = cpu_arch();
let needle = match host {
Expand Down Expand Up @@ -41,28 +38,8 @@ fn resolve_bun_download() -> Result<(String, String)> {
HostOS::Other => bail!("Bun is not supported on this OS: other"),
};

// Prefer exact asset name (skip -profile / -baseline variants).
if let Some(assets) = release.get("assets").and_then(|v| v.as_array()) {
for asset in assets {
if asset.get("name").and_then(|v| v.as_str()) == Some(needle) {
let url = asset
.get("browser_download_url")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let tag = release
.get("tag_name")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
if !url.is_empty() {
return Ok((url, tag));
}
}
}
}
let stem = needle.trim_end_matches(".zip");
pick_release_asset(&release, &[stem, ".zip"])
// Exact name skips the -profile / -baseline variants.
github_latest_asset("oven-sh", "bun", needle)
}

/// ZIP often contains bun-<plat>/bun(.exe); after strip or nested search.
Expand Down
20 changes: 10 additions & 10 deletions src/plugins/deno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,41 @@
use crate::download::install_archive_from_url;
use crate::platform::{cpu_arch, current_os, is_windows, HostOS};
use crate::plugin::{EnvSpec, InstallContext, InstallResult, Plugin, PluginStatus};
use crate::plugin_utils::{binary_status, github_latest_release, pick_release_asset};
use crate::plugin_utils::{binary_status, github_latest_asset};
use crate::progress::download_progress;
use anyhow::{bail, Result};
use std::path::PathBuf;

const MARKER: &str = ".devkit-deno";

fn resolve_deno_download() -> Result<(String, String)> {
let release = github_latest_release("denoland", "deno")?;
let host = current_os();
let arch = cpu_arch();
match host {
let asset = match host {
HostOS::Windows => {
if arch == "aarch64" {
pick_release_asset(&release, &["deno-aarch64-pc-windows-msvc.zip"])
"deno-aarch64-pc-windows-msvc.zip"
} else {
pick_release_asset(&release, &["deno-x86_64-pc-windows-msvc.zip"])
"deno-x86_64-pc-windows-msvc.zip"
}
}
HostOS::Linux => {
if arch == "aarch64" {
pick_release_asset(&release, &["deno-aarch64-unknown-linux-gnu.zip"])
"deno-aarch64-unknown-linux-gnu.zip"
} else {
pick_release_asset(&release, &["deno-x86_64-unknown-linux-gnu.zip"])
"deno-x86_64-unknown-linux-gnu.zip"
}
}
HostOS::MacOS => {
if arch == "aarch64" {
pick_release_asset(&release, &["deno-aarch64-apple-darwin.zip"])
"deno-aarch64-apple-darwin.zip"
} else {
pick_release_asset(&release, &["deno-x86_64-apple-darwin.zip"])
"deno-x86_64-apple-darwin.zip"
}
}
HostOS::Other => bail!("Deno is not supported on this OS: other"),
}
};
github_latest_asset("denoland", "deno", asset)
}

fn deno_bin(ctx: &InstallContext) -> PathBuf {
Expand Down
Loading
Loading