Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow online installation of packages #56

Merged
merged 26 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d17c4a0
Start fetching package from registry
kubouch Dec 28, 2023
5a13551
Add comments
kubouch Dec 28, 2023
c482669
Add registries completions
kubouch Dec 28, 2023
7645765
Download online package and install it
kubouch Dec 28, 2023
2dd709b
Fix wrong local relativee path
kubouch Dec 28, 2023
06c37b7
Adjust comment
kubouch Dec 28, 2023
87198ea
Add --version to allow setting exact version
kubouch Dec 28, 2023
584e6f2
Adjust comment
kubouch Dec 28, 2023
2d33b3a
Fix error when missing --pkg-version
kubouch Dec 29, 2023
000b89e
Fix missing version sorting
kubouch Dec 29, 2023
00d8fe1
Do not require registry env with flag; Add tests
kubouch Jan 23, 2024
9b0ed0c
Fix whitespace
kubouch Jan 23, 2024
4bcc71f
Add nupm's own package registry
kubouch Jan 23, 2024
32b0ba0
Extend --registry to be more flexible
kubouch Jan 23, 2024
14fcf91
Make test env more robust
kubouch Jan 28, 2024
b546082
Fix word
kubouch Jan 28, 2024
5bcef1a
Add `nupm search` for searching packages
kubouch Jan 28, 2024
b4385de
Remove registry print
kubouch Jan 28, 2024
40a533d
Improve error message
kubouch Jan 28, 2024
0ab10c5
Test nupm search
kubouch Jan 28, 2024
7d28528
Add registry setting to test env
kubouch Jan 28, 2024
368294d
Add more installation tests; Better errors
kubouch Jan 28, 2024
3aa27a4
Improve error message
kubouch Jan 28, 2024
ef14a6a
Improve message
kubouch Jan 28, 2024
fadfac2
Use online registry by default
kubouch Jan 31, 2024
b410ae9
Remove registry file (comitted separately)
kubouch Jan 31, 2024
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
135 changes: 127 additions & 8 deletions nupm/install.nu
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
use std log

use utils/dirs.nu [ nupm-home-prompt script-dir module-dir tmp-dir ]
use utils/completions.nu complete-registries
use utils/dirs.nu [ nupm-home-prompt cache-dir module-dir script-dir tmp-dir ]
use utils/log.nu throw-error
use utils/misc.nu check-cols
use utils/registry.nu search-package
use utils/version.nu filter-by-version

def open-package-file [dir: path] {
if not ($dir | path exists) {
throw-error "package_dir_does_not_exist" (
$"Package directory ($dir) does not exist"
)
}

let package_file = $dir | path join "nupm.nuon"

if not ($package_file | path exists) {
Expand Down Expand Up @@ -94,8 +104,8 @@ def install-path [

if ($destination | path type) == dir {
throw-error "package_already_installed" (
$"Package ($package.name) is already installed."
+ "Use `--force` to override the package"
$"Package ($package.name) is already installed in"
+ $" ($destination). Use `--force` to override the package"
)
}

Expand Down Expand Up @@ -146,20 +156,129 @@ def install-path [
}
}


# Downloads a package and returns its downloaded path
def download-pkg [
pkg: record<
name: string,
version: string,
url: string,
revision: string,
path: string,
type: string,
>
]: nothing -> path {
# TODO: Add some kind of hashing to check that files really match

if ($pkg.type != 'git') {
throw-error 'Downloading non-git packages is not supported yet'
}

let cache_dir = cache-dir --ensure
cd $cache_dir

let git_dir = $cache_dir | path join git
mkdir $git_dir
cd $git_dir

let repo_name = $pkg.url | url parse | get path | path parse | get stem
let url_hash = $pkg.url | hash md5 # in case of git repo name collision
let clone_dir = $'($repo_name)-($url_hash)-($pkg.revision)'

let pkg_dir = $env.PWD | path join $clone_dir $pkg.path

if ($pkg_dir | path exists) {
print $'Package ($pkg.name) found in cache'
return $pkg_dir
}

try {
git clone $pkg.url $clone_dir
} catch {
throw-error $'Error cloning repository ($pkg.url)'
}

cd $clone_dir

try {
git checkout $pkg.revision
} catch {
throw-error $'Error checking out revision ($pkg.revision)'
}

if not ($pkg_dir | path exists) {
throw-error $'Path ($pkg.path) does not exist'
}

$pkg_dir
}

# Fetch a package from a registry
def fetch-package [
package: string # Name of the package
--registry: string # Which registry to use
--version: string # Package version to install (string or null)
]: nothing -> path {
let regs = (search-package $package
--registry $registry
--version $version
--exact-match)

if ($regs | is-empty) {
throw-error $'Package ($package) not found in any registry'
} else if ($regs | length) > 1 {
# TODO: Here could be interactive prompt
throw-error $'Multiple registries contain package ($package)'
}

# Now, only one registry contains the package
let reg = $regs | first
let pkgs = $reg.pkgs | filter-by-version $version

let pkg = try {
$pkgs | last
} catch {
throw-error $'No package matching version `($version)`'
}

print $pkg

if $pkg.type == 'git' {
download-pkg $pkg
} else {
# local package path is relative to the registry file (absolute paths
# are discouraged but work)
$reg.path | path dirname | path join $pkg.path
}
}

# Install a nupm package
#
# Installation consists of two parts:
# 1. Fetching the package (if the package is online)
# 2. Installing the package (build action, if any; copy files to install location)
export def main [
package # Name, path, or link to the package
package # Name, path, or link to the package
--registry: string@complete-registries # Which registry to use (either a name
# in $env.NUPM_REGISTRIES or a path)
--pkg-version(-v): string # Package version to install
--path # Install package from a directory with nupm.nuon given by 'name'
--force(-f) # Overwrite already installed package
--no-confirm # Allows to bypass the interactive confirmation, useful for scripting
--no-confirm # Allows to bypass the interactive confirmation, useful for scripting
]: nothing -> nothing {
if not (nupm-home-prompt --no-confirm=$no_confirm) {
return
}

if not $path {
throw-error "missing_required_option" "`nupm install` currently requires a `--path` flag"
let pkg: path = if not $path {
fetch-package $package --registry $registry --version $pkg_version
} else {
if $pkg_version != null {
throw-error "Use only --path or --pkg-version, not both"
}

$package
}

install-path $package --force=$force
install-path $pkg --force=$force
}
16 changes: 15 additions & 1 deletion nupm/mod.nu
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use utils/dirs.nu [ DEFAULT_NUPM_HOME DEFAULT_NUPM_TEMP nupm-home-prompt ]
use utils/dirs.nu [
DEFAULT_NUPM_HOME DEFAULT_NUPM_TEMP DEFAULT_NUPM_CACHE nupm-home-prompt
]

export module install.nu
export module test.nu
export module search.nu

export-env {
# Ensure that $env.NUPM_HOME is always set when running nupm. Any missing
Expand All @@ -10,6 +13,17 @@ export-env {

# Ensure temporary path is set.
$env.NUPM_TEMP = ($env.NUPM_TEMP? | default $DEFAULT_NUPM_TEMP)

# Ensure install cache is set
$env.NUPM_CACHE = ($env.NUPM_CACHE? | default $DEFAULT_NUPM_CACHE)

# TODO: Maybe this is not the best way to set registries, but should be
# good enough for now.
# TODO: Add `nupm registry` for showing info about registries
# TODO: Add `nupm registry add/remove` to add/remove registry from the env?
$env.NUPM_REGISTRIES = {
nupm_test: 'https://raw.githubusercontent.com/nushell/nupm/main/registry.nuon'
}
}

# Nushell Package Manager
Expand Down
12 changes: 12 additions & 0 deletions nupm/search.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use utils/completions.nu complete-registries
use utils/registry.nu search-package

# Search for a package
export def main [
package # Name, path, or link to the package
--registry: string@complete-registries # Which registry to use (either a name
# in $env.NUPM_REGISTRIES or a path)
--pkg-version(-v): string # Package version to install
]: nothing -> table {
search-package $package --registry $registry --version $pkg_version
}
3 changes: 3 additions & 0 deletions nupm/utils/completions.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export def complete-registries [] {
$env.NUPM_REGISTRIES? | default {} | columns
}
14 changes: 14 additions & 0 deletions nupm/utils/dirs.nu
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
# Default installation path for nupm packages
export const DEFAULT_NUPM_HOME = ($nu.default-config-dir | path join "nupm")

# Default path for installation cache
export const DEFAULT_NUPM_CACHE = ($nu.default-config-dir
| path join nupm cache)

# Default temporary path for various nupm purposes
export const DEFAULT_NUPM_TEMP = ($nu.temp-path | path join "nupm")

Expand Down Expand Up @@ -70,6 +74,16 @@ export def module-dir [--ensure]: nothing -> path {
$d
}

export def cache-dir [--ensure]: nothing -> path {
let d = $env.NUPM_CACHE

if $ensure {
mkdir $d
}

$d
}

export def tmp-dir [subdir: string, --ensure]: nothing -> path {
let d = $env.NUPM_TEMP
| path join $subdir
Expand Down
38 changes: 38 additions & 0 deletions nupm/utils/misc.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Misc unsorted helpers

# Make sure input has requested columns and no extra columns
export def check-cols [
what: string,
required_cols: list<string>
--extra-ok
--missing-ok
]: [ table -> table, record -> record ] {
let inp = $in

if ($inp | is-empty) {
return $inp
}

let cols = $inp | columns
if not $missing_ok {
let missing_cols = $required_cols | where {|req_col| $req_col not-in $cols }

if not ($missing_cols | is-empty) {
throw-error ($"Missing the following required columns in ($what):"
+ $" ($missing_cols | str join ', ')")
)
}
}

if not $extra_ok {
let extra_cols = $cols | where {|col| $col not-in $required_cols }

if not ($extra_cols | is-empty) {
throw-error ($"Got the following extra columns in ($what):"
+ $" ($extra_cols | str join ', ')")
)
}
}

$inp
}
77 changes: 77 additions & 0 deletions nupm/utils/registry.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Utilities related to nupm registries

# Search for a package in a registry
export def search-package [
package: string # Name of the package
--registry: string # Which registry to use
--version: any # Package version to install (string or null)
--exact-match # Searched package name must match exactly
] -> table {
let registries = if (not ($registry | is-empty)) and ($registry in $env.NUPM_REGISTRIES) {
# If $registry is a valid column in $env.NUPM_REGISTRIES, use that
{ $registry : ($env.NUPM_REGISTRIES | get $registry) }
} else if (not ($registry | is-empty)) and ($registry | path exists) {
# If $registry is a path, use that
let reg_name = $registry | path parse | get stem
{ $reg_name: $registry }
} else {
# Otherwise use $env.NUPM_REGISTRIES
$env.NUPM_REGISTRIES
}

let name_matcher: closure = if $exact_match {
{|row| $row.name == $package }
} else {
{|row| $package in $row.name }
}

# Collect all registries matching the package and all matching packages
let regs = $registries
| items {|name, path|
# Open registry (online or offline)
let registry = if ($path | path type) == file {
open $path
} else {
try {
let reg = http get $path

if local in $reg {
throw-error ("Can't have local packages in online registry"
+ $" '($path)'.")
}

$reg
} catch {
throw-error $"Cannot open '($path)' as a file or URL."
amtoine marked this conversation as resolved.
Show resolved Hide resolved
}
}

$registry | check-cols --missing-ok "registry" [ git local ] | ignore

# Find all packages matching $package in the registry
let pkgs_local = $registry.local?
| default []
| check-cols "local packages" [ name version path ]
| filter $name_matcher

let pkgs_git = $registry.git?
| default []
| check-cols "git packages" [ name version url revision path ]
| filter $name_matcher

let pkgs = $pkgs_local
| insert type local
| insert url null
| insert revision null
| append ($pkgs_git | insert type git)

{
name: $name
path: $path
pkgs: $pkgs
}
}
| compact

$regs | where not ($it.pkgs | is-empty)
}
24 changes: 24 additions & 0 deletions nupm/utils/version.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Commands related to handling versions
#
# We might move some of this to Nushell builtins

# Sort packages by version
def sort-by-version []: table<version: string> -> table<version: string> {
sort-by version
}

# Check if the target version is equal or higher than the target version
def matches-version [version: string]: string -> bool {
# TODO: Add proper version matching
$in == $version
}

# Filter packages by version and sort them by version
export def filter-by-version [version: any]: table<version: string> -> table<version: string> {
if $version == null {
$in
} else {
$in | filter {|row| $row.version | matches-version $version}
}
| sort-by-version
}
Loading
Loading