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: on:
push: push:
tags: tags:
- "v*" - v[0-9]+.*
workflow_dispatch:
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: jobs:
create-release-draft: # Create the Github Release™ so the packages have something to be uploaded to
name: pre-release create-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write
outputs: 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: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
if: ${{ !env.ACT }} - id: create-gh-release
uses: taiki-e/create-gh-release-action@v1
- id: create_release
uses: ncipollo/release-action@v1
if: ${{ !env.ACT }}
with: with:
draft: true draft: true
artifacts: | # (required) GitHub token for creating GitHub Releases.
LICENSE token: ${{ secrets.GITHUB_TOKEN }}
build-release: # Build and packages all the things
needs: create-release-draft upload-artifacts:
needs: create-release
strategy: strategy:
fail-fast: false
matrix: 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 }} runs-on: ${{ matrix.os }}
permissions: env:
contents: write GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
if: ${{ !env.ACT }} - 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 # Compute and upload the manifest for everything
if: ${{ !env.ACT }} upload-manifest:
uses: actions/cache@v2 needs: create-release
with: runs-on: ubuntu-latest
path: | env:
target GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
~/.cargo/ steps:
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} - uses: actions/checkout@v3
restore-keys: | - name: Install Rust
${{ runner.os }}-cargo- run: rustup update stable && rustup default stable
- name: Build - name: Install cargo-dist
run: cargo build --release 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 # Mark the Github Release™ as a non-draft now that everything has succeeded!
with: publish-release:
files: | needs: [create-release, upload-artifacts, upload-manifest]
target/release/nenv runs-on: ubuntu-latest
target/release/nenv.exe env:
dest: nenv-${{ runner.os }}.zip 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] [package]
name = "nenv" name = "nenv"
version = "0.4.2" version = "0.5.0"
authors = ["trivernis <trivernis at proton dot me>"] authors = ["trivernis <trivernis at proton dot me>"]
edition = "2021" edition = "2021"
license = "GPL-3.0" license = "GPL-3.0"
@ -44,3 +44,10 @@ tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
xkcd_unreachable = "0.1.1" xkcd_unreachable = "0.1.1"
zip = "0.6.3" 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. A Node environment manager written in rust.
## Features ## Features
- Written in fast and safe rust - Written in fast and safe rust
@ -9,6 +10,7 @@ A Node environment manager written in rust.
- Configuration for project specific versions - Configuration for project specific versions
- Version matching with semver expressions - Version matching with semver expressions
## Installation ## Installation
You can either You can either
@ -48,6 +50,20 @@ nenv default latest
nenv refresh 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 ### List nodejs versions
```sh ```sh

@ -58,6 +58,14 @@ pub enum Command {
/// Clears the download cache /// Clears the download cache
#[command()] #[command()]
ClearCache, ClearCache,
/// Pins binary to a specific node version
#[command()]
Pin(PinArgs),
/// Unpins a command
#[command()]
Unpin(UnpinArgs),
} }
#[derive(Clone, Debug, Parser)] #[derive(Clone, Debug, Parser)]
@ -67,10 +75,24 @@ pub struct ExecArgs {
pub command: String, pub command: String,
/// The arguments for the command /// 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>, 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)] #[derive(Clone, Debug, Parser)]
pub struct InstallArgs { pub struct InstallArgs {
/// the version to install /// the version to install

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

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

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

@ -1,7 +1,7 @@
use std::{ffi::OsString, str::FromStr}; use std::{ffi::OsString, str::FromStr};
use crate::{ use crate::{
config::ConfigAccess, config::{ConfigAccess, ExecutableConfig},
consts::{BIN_DIR, CACHE_DIR, VERSION_FILE_PATH}, consts::{BIN_DIR, CACHE_DIR, VERSION_FILE_PATH},
error::VersionError, error::VersionError,
mapper::Mapper, mapper::Mapper,
@ -224,6 +224,35 @@ impl Nenv {
Ok(()) 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 /// Persits all changes made that aren't written to the disk yet
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn persist(&self) -> Result<()> { pub async fn persist(&self) -> Result<()> {

Loading…
Cancel
Save