functions/package.ps1

<#
.SYNOPSIS
    Returns a ChocolateyPackage object using the given configuration
.DESCRIPTION
    Validates the given package configuration data and then creates a new
    ChocolateyPackage object from the configuration data. The validation is
    strict, meaning all configuration properties must be present and no
    erroneous properties may be present.
.PARAMETER PackagePath
    The full file path to where the package files are located
.PARAMETER PackageConfig
    The package configuration data to use when creating the ChocolateyPackage
    object
.EXAMPLE
    $package = New-ChocolateyPackage `
        -PackagePath 'C:\my\package' `
        -PackageConfig $myConfig
.OUTPUTS
    A new instance of the ChocolateyPackage object
#>

Function New-ChocolateyPackage {
    param(
        [string] $PackagePath,
        [hashtable] $PackageConfig
    )

    # Clone config to prevent modifying passed in hash table
    $config = $PackageConfig.Clone()

    # Add package path and then validate configuration
    $config.Add('path', $PackagePath)
    Test-PackageConfiguration -Configuration $config

    # Fully qualify local file paths
    foreach ($localFile in $config.localFiles) {
        $localFile.localPath = Join-Path $PackagePath $localFile.localPath
    }
    $config.processScript = Join-Path $PackagePath $config.processScript

    # Build ChocolateyPackage object from configuration data
    $config.manifest.metadata.dependencies = $config.manifest.metadata.dependencies.ForEach( {
            New-Object PackageDependency -Property $_
        })
    $config.manifest = @{
        metadata = New-Object PackageMetadata -Property $config.manifest.metadata
        files    = $config.manifest.files.ForEach( {
                New-Object PackageFile -Property $_
            })
    }
    $config.localFiles = $config.localFiles.ForEach( {
            New-Object LocalFile -Property $_
        })
    $config.remoteFiles = $config.remoteFiles.ForEach( {
            New-Object RemoteFile -Property $_
        })

    New-Object ChocolateyPackage -Property $config
}

<#
.SYNOPSIS
    Creates an example package configuration structure at the given path
.DESCRIPTION
    Creates an example package configuration file along with an example process
    and Chocolatey install file at the given path. This is the easiest way to
    get started with making a new package.
.PARAMETER PackagePath
    The full file path to where the package files will be created
.EXAMPLE
    New-ChocolateyPackageConfig C:\my\package
.OUTPUTS
    None
#>

Function New-ChocolateyPackageConfig {
    param(
        [string] $PackagePath
    )

    if (!(Test-Path $PackagePath)) {
        Write-Verbose ('Creating {0}...' -f $PackagePath)
        New-Item -ItemType Directory $PackagePath | Out-Null
    }

    $packageFiles = Join-Path $PSScriptRoot '..\static'
    Write-Verbose ('Copying package files from {0} to {1}...' -f $packageFiles, $PackagePath)
    Copy-Item (Join-Path $packageFiles '*') $PackagePath -Recurse | Out-Null
}

Function Test-PackageConfiguration {
    param(
        [hashtable] $Configuration
    )

    Test-ConfigSection -Object ([ChocolateyPackage]::new()) -Properties $Configuration.Keys
    foreach ($property in $Configuration.GetEnumerator()) {
        switch ($property.Name) {
            Manifest {
                Test-ConfigSection -Object ([PackageManifest]::new()) -Properties $property.Value.Keys

                if (!($property.Value.metadata -is [hashtable])) {
                    throw 'Error validating package configuration: metadata property must be a hashtable'
                }
                Test-ConfigSection -Object ([PackageMetadata]::new()) -Properties $property.Value.metadata.Keys

                foreach ($dependency in $property.Value.metadata.dependencies) {
                    Test-ConfigSection -Object ([PackageDependency]::new()) -Properties $dependency.Keys
                }

                foreach ($file in $property.Value.files) {
                    Test-ConfigSection -Object ([PackageFile]::new()) -Properties $file.Keys
                }
            }
            LocalFiles {
                if (!($property.Value -is [array])) {
                    throw 'Error validating package configuration: localFiles property must be an array'
                }
                foreach ($file in $property.Value) {
                    Test-ConfigSection -Object ([LocalFile]::new()) -Properties $file.Keys
                }
            }
            RemoteFiles {
                if (!($property.Value -is [array])) {
                    throw 'Error validating package configuration: remoteFiles property must be an array'
                }
                foreach ($file in $property.Value) {
                    Test-ConfigSection -Object ([RemoteFile]::new()) -Properties $file.Keys
                }
            }
        }
    }
}

Function Test-ConfigSection {
    param(
        [object] $Object,
        [string[]] $Properties
    )

    $objectProperties = $Object | Get-Member | Where-Object MemberType -EQ 'Property' | Select-Object -ExpandProperty Name
    foreach ($property in $Properties) {
        if (!($property -in $objectProperties)) {
            throw 'Error validating package configuration: Object of type {0} does not contain a property with name {1}' -f $Object.GetType().Name, $property
        }
    }

    foreach ($property in $objectProperties) {
        if (!($property -in $Properties)) {
            throw 'Error validating package configuration: Configuration is missing property {0} for object {1}' -f $property, $Object.GetType().Name
        }
    }
}