Types/OpenPackage.Source/Node.ps1

<#
.SYNOPSIS
    Gets a node package
.DESCRIPTION
    Gets a node package as an open package
#>

param(
# A node package.
# This can be the name of the package in npm or a package link from `npmjs.com` or `npmx.dev`
[Parameter(ValueFromPipelineByPropertyName)]
[Alias('nodepackages', 'nodepack')]
[string[]]
$NodePackage,

# A list of file wildcards to include.
[Parameter(ValueFromPipelineByPropertyName)]
[SupportsWildcards()]
[string[]]
$Include,

# A list of file wildcards to exclude.
[Parameter(ValueFromPipelineByPropertyName)]
[SupportsWildcards()]
[string[]]
$Exclude,

# The base path within the package.
# Content should be added beneath this base path.
[string]
$BasePath = '/',

# A content type map.
# This maps extensions and URIs to a content type.
[Collections.IDictionary]
$TypeMap = $(
    ([PSCustomObject]@{PSTypeName='OpenPackage.ContentTypeMap'}).TypeMap
),

# The compression option.
[IO.Packaging.CompressionOption]
[Alias('CompressionLevel')]
$CompressionOption = 'Superfast',
        
# If set, will force the redownload of various resources and remove existing files or directories
[switch]
$Force,

# If set, will include hidden files and folders, except for files beneath `.git`
[Alias('IncludeDotFiles')]
[switch]
$IncludeHidden,

# If set, will include the `.git` directory contents if found.
# By default, this content will be excluded.
[Alias('IncludeGitFile','IncludeGitFiles','IncludeGitDirectory')]
[switch]
$IncludeGit,

# If set, will include any content found in `/node_modules`.
# By default, this content will be excluded.
[Alias('IncludeNodeModules')]
[switch]
$IncludeNodeModule,

# If set, will include any content found in `/_site`.
# By default, this content will be excluded.
[Alias('IncludeWebsite')]
[switch]
$IncludeSite,

[string[]]
$NodeRepositoryDomain = @(
    'npmjs.com'
    'npmx.dev'
    'www.npmjs.com'
    'www.npmx.dev'
)
)

$npmApp = $ExecutionContext.SessionState.InvokeCommand.GetCommand('npm', 'Application')
if (-not $npmApp) { throw "npm not installed or in path"}

# Collect all named parameters to this,
# so we can pass most of them to the next step.
$namedParameters = [Ordered]@{}

foreach ($key in $MyInvocation.MyCommand.Parameters.Keys) {
    $var = $ExecutionContext.SessionState.PSVariable.Get($key)
    if (-not [string]::IsNullOrEmpty($var.value)) {
        $namedParameters[$key] = $var.value
    }
}

# Remove any node package specific parameters.
$namedParameters.Remove('NodePackage')
$namedParameters.Remove('NodeRepositoryDomain')

# We want to
if (-not $env:OpenPackagePath) {throw "No Open Package Path"}

# Get our first OpenPackage package path
$myAppData = @($env:OpenPackagePath -split $(
    if ($IsLinux -or $IsMacOS) {
        ':'
    } else {
        ';'
    }
))[0]

# and put all npm packages beneathh this location.
$nodePackRoot = Join-Path $myAppData "node_packages"

# If we do not have this directory already, create it
if (-not (Test-Path $nodePackRoot)) {
    $null = New-Item -ItemType Directory -Path $nodePackRoot -Force 
}

# Push into this location
Push-Location $nodePackRoot

# Go over each package we want to get.
foreach ($nodePack in $nodePackage) {
    # Packages can be in direct named form, or in a url
    $nodePackUri = $nodePack -as [uri]
    # If the url was absolute
    if ($nodePackUri.IsAbsoluteUri) {
        # check it against possible domain names.
        if ($nodePackUri.DnsSafeHost -notin $NodeRepositoryDomain) {            
            Write-Error "Can only use an absolute url from $NodeRepositoryDomain"
            continue        
        }
        # If the url was versioned
        if ($nodePackUri.Segments -match 'v/') {
            # split it into two parts
            $beforeV, $afterV = $nodePackUri.Segments -join '' -split 'v/'
            # and put things in the format npm wants.
            $nodePack = "$($beforeV -replace '^/' -replace '^/package' -replace '/$')@$afterV"
        } else {
            # Otherwise, split off the package segment of the url
            $nodePack = 
                $nodePackUri.Segments[0..3] -ne '/' -join 
                    '' -replace '^package/' -replace '/$'
        }
    }
    # `npm pack` will download a package as .tar.gz
    # (with a number of messages on standard error)
    $npmOutput = @(& $npmApp pack $nodePack *>&1) 
    # some of these may actually be errors, so create a collection.
    $npmErrors = @()
    # and make sure we track if the package has been downloaded
    # (for it will actually provide the name twice)
    $packageDownloaded = $false
    # Go over each output.
    :gotPackage foreach ($npmOut in $npmOutput) {
        # If any of the output is an error record
        if ($npmOut -is [Management.Automation.ErrorRecord]) {
            # add it to our list
            $npmErrors += $npmOut
        }
        # If the output is not .tgz
        if ($npmOut -notmatch '\S+\.tgz$') {
            continue # continue
        }
        # Get the file path from our match
        $npmTarFile = $matches.0
        # If the file does not exist
        if (-not (Test-Path $npmTarFile)) {
            # output our errors
            $npmErrors
        } else {
            # Otherwise, get the tar file as an open package.
            $nodePackage = Get-OpenPackage -FilePath "./$npmTarFile" @namedParameters
            # If the output was a package
            if ($nodePackage -is [IO.Packaging.Package]) {
                # mark that we've downloaded it,
                $packageDownloaded = $true
                $nodePackage # emit the package,
                break gotPackage # and take a break.
            }            
        }        
    }
    # If the packge was not downloaded, output errors.
    if (-not $packageDownloaded) {
        $npmErrors
    }
}
Pop-Location