Types/OpenPackage.Source/Directory.ps1
|
<# .SYNOPSIS Gets a Directory as a package .DESCRIPTION Gets a Directory as an Open Package #> param( # The list of directories [string[]] $Directory, # 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, # The current package [IO.Packaging.Package] $Package ) # Check for a directory if (-not $Directory) { # and error out if none is present. Write-Error "No -Directory" return } # Get all of the resolved items. $resolvedItems = Get-Item -Path $Directory # Adjust our base path as needed # (ensure it begins and ends with a slash) $BasePath = $BasePath -replace '^/?', '/' -replace '/?$', '/' # Go over each potential directory foreach ($resolvedItem in $resolvedItems) { # If it is not a directory, continue if ($resolvedItem -isnot [IO.DirectoryInfo]) { continue } Push-Location -LiteralPath $resolvedItem.FullName $gciSplat = [Ordered]@{LiteralPath=$resolvedItem.FullName;Recurse=$true;File=$true} if ($IncludeHidden) { $gciSplat.Force = $true } $filesToArchive = @(Get-ChildItem @gciSplat) # This make take a sec, so let's create a progress bar $Progress = [Ordered]@{ Status = " " Activity = "Creating Package $($resolvedItem.Name)" Id = Get-Random } $total = $filesToArchive.Length $counter = 0 # We will use file types to provide package metadata if (-not $package) { $memoryStream = [IO.MemoryStream]::new() $package = [IO.Packaging.Package]::Open($memoryStream, 'OpenOrCreate', 'ReadWrite') if ($resolvedItem.Parent.Name -and ( $resolvedItem.Name -as [version] -or $resolvedItem.Name -as [semver] ) ) { $package.PackageProperties.Identifier = $resolvedItem.Parent.Name $package.PackageProperties.Version = $resolvedItem.Name } else { $package.PackageProperties.Identifier = $resolvedItem.Name } Add-Member -InputObject $package NoteProperty MemoryStream $memoryStream -Force } # Try to get the git app $gitApp = $ExecutionContext.SessionState.InvokeCommand.GetCommand('git','application') # Then see if get can git it. $canGitIt = $gitApp -and # If git is loaded (Test-Path ( # and a .git directory exists Join-Path $resolvedItem.FullName '.git' )) # If we can git it. if ($canGitIt) { # get it's first remote $gitRemote = @(& $gitApp '-C' $resolvedItem.FullName remote)[0] | ForEach-Object { & $gitApp '-C' $resolvedItem.FullName remote get-url $_ } # and create a relationship to the repository $relation = $package.Relate($gitRemote,'git','repository') if ($VerbosePreference -notin 'silentlyContinue', 'ignore') { Write-Verbose "Related $( $relation.TargetUri ) as [$($relation.RelationshipType)]$($relation.id)" } } # So declare an oldest created file and newest write time. $oldestCreationTime = [DateTime]::Now $lastWriteTime = [DateTime]::MinValue #region Filter Filters $filteredFiles = @( # If any exclusions are present, # security dictates we process them first. # (deny before approve) :filterFiles foreach ($file in $filesToArchive) { $relativePath = $file.FullName.Substring($resolvedItem.FullName.Length) # If we have not excplitly included `.git`, if (-not $includeGit -and $relativePath -match '[\\/].git[\\/]') { continue # exclude `.git`. } # If we have not explicitly included `node_modules` if (-not $IncludeNodeModule -and $relativePath -match '[\\/]node_modules[\\/]') { continue # exclude `node_modules`. } # If we have not explicitly included `_site` if (-not $IncludeSite -and $relativePath -match '[\\/]_site[\\/]') { continue # exclude `_site`. } # If we have any exclusion wildcards if ($exclude) { foreach ($exclusion in $Exclude) { if ($file.FullName -like $exclusion) { continue filterFiles # exclude any files that match } } } # If we have any include wildcards if ($include) { $included = $false foreach ($inclusion in $include) { if ($file.FullName -like $inclusion) { $included = $true # only include things that match. break } } if (-not $included) { continue filterFiles } } $file } ) #region Filter Filters # Go over each file we want to archive :packingFiles foreach ($file in $filteredFiles) { # get each file as a relative uri, and then get it's bytes $relativeUri = ( $BasePath, ( $file.FullName.Substring($resolvedItem.FullName.Length) -replace '[\\/]', '/' ) -join '/' ) -replace '^/?', '/' -replace '//{2,}', '/' $fileBytes = Get-Content -AsByteStream -Raw -LiteralPath $file.FullName # If the file was blank if (-not $fileBytes) { # write a message to verbose indicating we are skipping the file. Write-Verbose "Skipping blank file $($file.FullName)" continue } # encode our URI, $encodedUri = [IO.Packaging.PackUriHelper]::CreatePartUri($relativeUri) # make it a root relative uri. $relativeUri = '/' + ($encodedUri -replace '^/') # Determine the right content type for the extension $fileContentType = $typeMap[$file.Extension] # and fall back to text/plain if (-not $fileContentType) { $fileContentType = 'text/plain'} # Then update our creation times / last write times as needed. if ($file.CreationTime -lt $oldestCreationTime) { $oldestCreationTime = $file.CreationTime } if ($file.LastWriteTime -gt $lastWriteTime) { $lastWriteTime = $file.LastWriteTime } # Write our progress message $progress.PercentComplete = (++$counter * 100 / $total) $Progress.Status = "$relativeUri" Write-Progress @Progress if (-not $fileBytes) { continue } # Try to create a new part try { $newPart = $package.CreatePart($relativeUri, $fileContentType, $CompressionOption) } catch { # If that didn't work, $ex = $_ # at least one exception has some well known answers if ($ex.Exception.HResult -eq 0x80131501) { # Open Packaging Conventions do not allow case-sensitive collisions # (most likely because they would not extract well on all operating systems) # If we find an exception indicating a conflict, and multiple copies $multipleCopies = @($filesToArchive | Where-Object Fullname -ieq $file.FullName | Select-Object -ExpandProperty Fullname) if ($multipleCopies.Count) { # warn the user Write-Warning "Skipping '$($File.Fullname)' - Case Sensitivity conflict between:$( @( [Environment]::NewLine # and provide a helpful pair of paths so they can resolve the conflict $multipleCopies ) -join [Environment]::NewLine ) $ex" } else { # If there were not multiple files, error (but do not return) Write-Error -ErrorRecord $ex } } else { # and if the error was unknown, error (but do not return) Write-Error -ErrorRecord $ex } } # If we could not create the part, continue if (-not $newPart) { continue } $newStream = $newPart.GetStream() $newStream.Write($fileBytes, 0, $fileBytes.Length) $newStream.Close() $null = $newStream.DisposeAsync() } Pop-Location $Progress.Remove('PercentComplete') $Progress.Completed = $true Write-Progress @Progress $package.PackageProperties.Created = $oldestCreationTime $package.PackageProperties.Modified = $lastWriteTime $package } |