Compare commits

...

4 Commits

@ -1,70 +1,150 @@
name: "Build and Release"
# CI that:
#
# * checks for a Git Tag that looks like a release ("v1.2.0")
# * creates a Github Release™
# * builds binaries/packages with cargo-dist
# * uploads those packages to the Github Release™
#
# Note that the Github Release™ will be created before the packages,
# so there will be a few minutes where the release has no packages
# and then they will slowly trickle in, possibly failing. To make
# this more pleasant we mark the release as a "draft" until all
# artifacts have been successfully uploaded. This allows you to
# choose what to do with partial successes and avoids spamming
# anyone with notifications before the release is actually ready.
name: Release
permissions:
contents: write
# This task will run whenever you push a git tag that looks like
# a version number. We just look for `v` followed by at least one number
# and then whatever. so `v1`, `v1.0.0`, and `v1.0.0-prerelease` all work.
#
# If there's a prerelease-style suffix to the version then the Github Release™
# will be marked as a prerelease (handled by taiki-e/create-gh-release-action).
#
# Note that when generating links to uploaded artifacts, cargo-dist will currently
# assume that your git tag is always v{VERSION} where VERSION is the version in
# the published package's Cargo.toml (this is the default behaviour of cargo-release).
# In the future this may be made more robust/configurable.
on:
push:
tags:
- "v*"
workflow_dispatch:
- v[0-9]+.*
env:
ALL_CARGO_DIST_TARGET_ARGS: --target=x86_64-unknown-linux-gnu --target=x86_64-apple-darwin --target=x86_64-pc-windows-msvc
ALL_CARGO_DIST_INSTALLER_ARGS: --installer=github-shell --installer=github-powershell
jobs:
create-release-draft:
name: pre-release
# Create the Github Release™ so the packages have something to be uploaded to
create-release:
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
release_upload_url: ${{ steps.create_release.outputs.upload_url }}
tag: ${{ steps.create-gh-release.outputs.computed-prefix }}${{ steps.create-gh-release.outputs.version }}
steps:
- uses: actions/checkout@v2
if: ${{ !env.ACT }}
- id: create_release
uses: ncipollo/release-action@v1
if: ${{ !env.ACT }}
- uses: actions/checkout@v3
- id: create-gh-release
uses: taiki-e/create-gh-release-action@v1
with:
draft: true
artifacts: |
LICENSE
# (required) GitHub token for creating GitHub Releases.
token: ${{ secrets.GITHUB_TOKEN }}
build-release:
needs: create-release-draft
# Build and packages all the things
upload-artifacts:
needs: create-release
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
# For these target platforms
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
install-dist: curl --proto '=https' --tlsv1.2 -L -sSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.2/installer.sh | sh
- target: x86_64-apple-darwin
os: macos-11
install-dist: curl --proto '=https' --tlsv1.2 -L -sSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.2/installer.sh | sh
- target: x86_64-pc-windows-msvc
os: windows-2019
install-dist: irm 'https://github.com/axodotdev/cargo-dist/releases/download/v0.0.2/installer.ps1' | iex
runs-on: ${{ matrix.os }}
permissions:
contents: write
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v2
if: ${{ !env.ACT }}
- uses: actions/checkout@v3
- name: Install Rust
run: rustup update stable && rustup default stable
- name: Install cargo-dist
run: ${{ matrix.install-dist }}
- name: Run cargo-dist
# This logic is a bit janky because it's trying to be a polyglot between
# powershell and bash since this will run on windows, macos, and linux!
# The two platforms don't agree on how to talk about env vars but they
# do agree on 'cat' and '$()' so we use that to marshal values between commmands.
run: |
# Actually do builds and make zips and whatnot
cargo dist --target=${{ matrix.target }} --output-format=json > dist-manifest.json
echo "dist ran successfully"
cat dist-manifest.json
# Parse out what we just built and upload it to the Github Release™
cat dist-manifest.json | jq --raw-output ".releases[].artifacts[].path" > uploads.txt
echo "uploading..."
cat uploads.txt
gh release upload ${{ needs.create-release.outputs.tag }} $(cat uploads.txt)
echo "uploaded!"
- name: Cache build data
if: ${{ !env.ACT }}
uses: actions/cache@v2
with:
path: |
target
~/.cargo/
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Build
run: cargo build --release
# Compute and upload the manifest for everything
upload-manifest:
needs: create-release
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- name: Install Rust
run: rustup update stable && rustup default stable
- name: Install cargo-dist
run: curl --proto '=https' --tlsv1.2 -L -sSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.2/installer.sh | sh
- name: Run cargo-dist manifest
run: |
# Generate a manifest describing everything
cargo dist manifest --no-local-paths --output-format=json $ALL_CARGO_DIST_TARGET_ARGS $ALL_CARGO_DIST_INSTALLER_ARGS > dist-manifest.json
echo "dist manifest ran successfully"
cat dist-manifest.json
# Upload the manifest to the Github Release™
gh release upload ${{ needs.create-release.outputs.tag }} dist-manifest.json
echo "uploaded manifest!"
# Edit the Github Release™ title/body to match what cargo-dist thinks it should be
CHANGELOG_TITLE=$(cat dist-manifest.json | jq --raw-output ".releases[].changelog_title")
cat dist-manifest.json | jq --raw-output ".releases[].changelog_body" > new_dist_changelog.md
gh release edit ${{ needs.create-release.outputs.tag }} --title="$CHANGELOG_TITLE" --notes-file=new_dist_changelog.md
echo "updated release notes!"
- name: Run cargo-dist --installer=...
run: |
# Run cargo dist with --no-builds to get agnostic artifacts like installers
cargo dist --output-format=json --no-builds $ALL_CARGO_DIST_INSTALLER_ARGS > dist-manifest.json
echo "dist ran successfully"
cat dist-manifest.json
# Grab the installers that were generated and upload them.
# This filter is working around the fact that --no-builds is kinds hacky
# and still makes/reports malformed zips that we don't want to upload.
cat dist-manifest.json | jq --raw-output '.releases[].artifacts[] | select(.kind == "installer") | .path' > uploads.txt
echo "uploading..."
cat uploads.txt
gh release upload ${{ needs.create-release.outputs.tag }} $(cat uploads.txt)
echo "uploaded installers!"
- uses: vimtor/action-zip@v1
with:
files: |
target/release/nenv
target/release/nenv.exe
dest: nenv-${{ runner.os }}.zip
# Mark the Github Release™ as a non-draft now that everything has succeeded!
publish-release:
needs: [create-release, upload-artifacts, upload-manifest]
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- name: mark release as non-draft
run: |
gh release edit ${{ needs.create-release.outputs.tag }} --draft=false
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release-draft.outputs.release_upload_url }}
asset_path: ./nenv-${{ runner.os }}.zip
asset_name: nenv-${{ runner.os }}.zip
asset_content_type: application/zip

@ -1,6 +1,6 @@
[package]
name = "nenv"
version = "0.4.2"
version = "0.5.0"
authors = ["trivernis <trivernis at proton dot me>"]
edition = "2021"
license = "GPL-3.0"
@ -44,3 +44,10 @@ tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
xkcd_unreachable = "0.1.1"
zip = "0.6.3"
# generated by 'cargo dist init'
[profile.dist]
inherits = "release"
debug = true
split-debuginfo = "packed"

@ -2,6 +2,7 @@
A Node environment manager written in rust.
## Features
- Written in fast and safe rust
@ -9,6 +10,7 @@ A Node environment manager written in rust.
- Configuration for project specific versions
- Version matching with semver expressions
## Installation
You can either
@ -48,6 +50,20 @@ nenv default latest
nenv refresh
```
### Pinning binaries to specific node versions
```sh
# rome will always be executed with the lts version
nenv pin rome lts
# tsc will always be executed with the latest typescript version
nenv pin tsc latest
# undo
nenv unpin rome
nenv unpin tsc
```
### List nodejs versions
```sh

@ -58,6 +58,14 @@ pub enum Command {
/// Clears the download cache
#[command()]
ClearCache,
/// Pins binary to a specific node version
#[command()]
Pin(PinArgs),
/// Unpins a command
#[command()]
Unpin(UnpinArgs),
}
#[derive(Clone, Debug, Parser)]
@ -67,10 +75,24 @@ pub struct ExecArgs {
pub command: String,
/// The arguments for the command
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
#[arg(last = true, allow_hyphen_values = true)]
pub args: Vec<OsString>,
}
#[derive(Clone, Debug, Parser)]
pub struct PinArgs {
/// The command to pin
pub command: String,
/// The version to pin the command to
pub version: NodeVersion,
}
#[derive(Clone, Debug, Parser)]
pub struct UnpinArgs {
/// The command to unpin
pub command: String,
}
#[derive(Clone, Debug, Parser)]
pub struct InstallArgs {
/// the version to install

@ -17,6 +17,7 @@ use crate::{
use self::value::Config;
mod value;
pub use value::*;
#[derive(Clone)]
pub struct ConfigAccess {

@ -1,6 +1,6 @@
use std::{env, process};
use args::Args;
use args::{Args, PinArgs, UnpinArgs};
use clap::Parser;
use nenv::Nenv;
@ -51,6 +51,10 @@ async fn main() -> Result<()> {
args::Command::ListVersions => nenv.list_versions().await,
args::Command::Init => nenv.init_nenv().await,
args::Command::ClearCache => nenv.clear_cache().await,
args::Command::Pin(PinArgs { command, version }) => {
nenv.pin_command(command, version).await
}
args::Command::Unpin(UnpinArgs { command }) => nenv.unpin_command(command).await,
_ => xkcd_unreachable!(),
}?;

@ -42,7 +42,11 @@ impl NodeApp {
#[cfg(not(windows))]
async fn write_wrapper_script(&self, path: &Path) -> Result<(), io::Error> {
fs::write(path, format!("#!/bin/sh\nnenv exec {} \"$@\"", self.name)).await?;
fs::write(
path,
format!("#!/bin/sh\nnenv exec {} -- \"$@\"", self.name),
)
.await?;
let src_metadata = self.path.metadata()?;
fs::set_permissions(&path, src_metadata.permissions()).await?;
@ -53,7 +57,7 @@ impl NodeApp {
async fn write_wrapper_script(&self, path: &Path) -> Result<(), io::Error> {
fs::write(
path.with_extension("bat"),
format!("@echo off\nnenv exec {} %*", self.name),
format!("@echo off\nnenv exec {} -- %*", self.name),
)
.await?;
let src_metadata = self.path.metadata()?;

@ -1,7 +1,7 @@
use std::{ffi::OsString, str::FromStr};
use crate::{
config::ConfigAccess,
config::{ConfigAccess, ExecutableConfig},
consts::{BIN_DIR, CACHE_DIR, VERSION_FILE_PATH},
error::VersionError,
mapper::Mapper,
@ -224,6 +224,35 @@ impl Nenv {
Ok(())
}
/// Pins a given command
#[tracing::instrument(skip(self))]
pub async fn pin_command(&self, command: String, version: NodeVersion) -> Result<()> {
let mut config = self.config.get_mut().await;
config.bins.insert(
command.clone(),
ExecutableConfig {
node_version: version.clone(),
},
);
println!(
"Pinned {} to {}",
command.bold(),
version.to_string().yellow().bold()
);
Ok(())
}
/// Unpins a given command
#[tracing::instrument(skip(self))]
pub async fn unpin_command(&self, command: String) -> Result<()> {
let mut config = self.config.get_mut().await;
config.bins.remove(&command);
println!("Unpinned {}", command.bold());
Ok(())
}
/// Persits all changes made that aren't written to the disk yet
#[tracing::instrument(level = "debug", skip(self))]
pub async fn persist(&self) -> Result<()> {

Loading…
Cancel
Save