Public/Install-PCReleaseWorkflow.ps1

<#
.SYNOPSIS
    Installs CI and Release workflow templates into a target repository.
.DESCRIPTION
    Detects project technology (PowerShell, .NET, Node, Python), selects the
    appropriate workflow templates, and installs them with all tokens resolved
    from the project manifest.

    Token resolution:
      {{MODULE_NAME}} — Module/project name from manifest
      {{RUNNER}} — CI runner OS (default: ubuntu-latest)
      {{DEFAULT_BRANCH}} — Default branch name (default: main)
      {{SECRETS_BOOTSTRAP}} — Secrets step (auto-generated when PowerCraft.Secrets detected)

    Override defaults with an optional .powercraft/release.json config file.
    That file is never overwritten by this command.
.PARAMETER Path
    Target repository root directory. Defaults to current directory.
.PARAMETER Force
    Overwrite existing workflow files without prompting.
.EXAMPLE
    Install-PCReleaseWorkflow
    # Installs workflows into current directory's .github/workflows/
.EXAMPLE
    Install-PCReleaseWorkflow -Path C:\repos\MyModule -Force
    # Overwrites any existing workflows
.OUTPUTS
    [PSCustomObject[]] List of files created/updated.
#>

function Install-PCReleaseWorkflow {
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([PSCustomObject[]])]
    param(
        [Parameter()]
        [string]$Path = (Get-Location).Path,

        [switch]$Force
    )

    $templateRoot = Join-Path $PSScriptRoot '..' 'Templates'
    if (-not (Test-Path $templateRoot)) {
        throw "Templates directory not found at '$templateRoot'. Module may be incorrectly installed."
    }

    # --- Technology detection ---
    $projectType = Get-ProjectType -Path $Path
    $techName = $projectType.Type.ToLower()
    Write-Verbose "Detected project type: $techName"

    $techDir = Join-Path $templateRoot $techName
    if (-not (Test-Path $techDir)) {
        throw "No templates found for project type '$techName' at '$techDir'."
    }

    # --- Detect module/project name ---
    switch ($techName) {
        'powershell' {
            $psd1 = Get-ChildItem -Path $Path -Filter '*.psd1' -File | Select-Object -First 1
            if (-not $psd1) { throw "No .psd1 file found in '$Path'." }
            $projectName = $psd1.BaseName
            $manifestData = Import-PowerShellDataFile $psd1.FullName
        }
        default {
            throw "Technology '$techName' template support is not yet implemented."
        }
    }
    Write-Verbose "Project name: $projectName"

    # --- Load config (optional .powercraft/release.json) ---
    $config = Read-PCReleaseConfig -Path $Path

    # --- Build secrets bootstrap block ---
    $secretsBlock = ''
    if ($techName -eq 'powershell' -and $psd1) {
        $secretsBlock = Get-SecretBootstrapBlock -ManifestPath $psd1.FullName
    }

    # --- Build token table ---
    $defaultBranch = if ($config.ci.branches) { $config.ci.branches[0] } else { 'main' }
    $tokens = @{
        MODULE_NAME       = $projectName
        RUNNER            = $config.ci.runner
        DEFAULT_BRANCH    = $defaultBranch
        SECRETS_BOOTSTRAP = $secretsBlock
    }

    # --- Ensure .github/workflows exists ---
    $workflowDir = Join-Path $Path '.github' 'workflows'
    if (-not (Test-Path $workflowDir)) {
        New-Item -ItemType Directory -Path $workflowDir -Force | Out-Null
        Write-Verbose "Created: $workflowDir"
    }

    $results = @()

    # --- Version stamp ---
    $pcReleasePsd1 = Join-Path $PSScriptRoot '..' 'PowerCraft.Release.psd1'
    $pcReleaseVersion = (Import-PowerShellDataFile $pcReleasePsd1).ModuleVersion
    $versionStamp = "# Generated by PowerCraft.Release v$pcReleaseVersion — do not edit manually"

    # --- Install technology-specific templates ---
    $techTemplates = Get-ChildItem -Path $techDir -Filter '*.yml' -File
    foreach ($template in $techTemplates) {
        if ($config.disabled -contains $template.BaseName) {
            Write-Verbose "Skipping disabled template: $($template.Name)"
            $results += [PSCustomObject]@{ File = $template.Name; Status = 'Skipped'; Reason = 'Disabled in config' }
            continue
        }

        $targetFile = Join-Path $workflowDir $template.Name
        if ((Test-Path $targetFile) -and -not $Force) {
            Write-Warning "$($template.Name) already exists. Use -Force to overwrite."
            $results += [PSCustomObject]@{ File = $template.Name; Status = 'Skipped'; Reason = 'Already exists' }
            continue
        }

        if ($PSCmdlet.ShouldProcess($targetFile, "Install $($template.Name) workflow")) {
            $content = Get-Content -Path $template.FullName -Raw
            $content = Resolve-TemplateTokens -Content $content -Tokens $tokens
            Set-Content -Path $targetFile -Value "$versionStamp`n$content" -NoNewline
            $results += [PSCustomObject]@{ File = $template.Name; Status = 'Installed'; Reason = '' }
        }
    }

    # --- Install shared templates ---
    $sharedDir = Join-Path $templateRoot 'shared'
    if (Test-Path $sharedDir) {
        $sharedTemplates = Get-ChildItem -Path $sharedDir -Filter '*.yml' -File
        foreach ($template in $sharedTemplates) {
            # Skip dependency-check if no dependencies
            if ($template.BaseName -eq 'dependency-check' -and -not $manifestData.RequiredModules) {
                Write-Verbose "Skipping dependency-check.yml — no RequiredModules"
                continue
            }

            if ($config.disabled -contains $template.BaseName) {
                Write-Verbose "Skipping disabled template: $($template.Name)"
                $results += [PSCustomObject]@{ File = $template.Name; Status = 'Skipped'; Reason = 'Disabled in config' }
                continue
            }

            $targetFile = Join-Path $workflowDir $template.Name
            if ((Test-Path $targetFile) -and -not $Force) {
                Write-Warning "$($template.Name) already exists. Use -Force to overwrite."
                $results += [PSCustomObject]@{ File = $template.Name; Status = 'Skipped'; Reason = 'Already exists' }
                continue
            }

            if ($PSCmdlet.ShouldProcess($targetFile, "Install $($template.Name) workflow")) {
                $content = Get-Content -Path $template.FullName -Raw
                $content = Resolve-TemplateTokens -Content $content -Tokens $tokens
                Set-Content -Path $targetFile -Value "$versionStamp`n$content" -NoNewline
                $results += [PSCustomObject]@{ File = $template.Name; Status = 'Installed'; Reason = '' }
            }
        }
    }

    # --- Clean up legacy files ---
    $publishYml = Join-Path $workflowDir 'publish.yml'
    if (Test-Path $publishYml) {
        Write-Host " Found legacy publish.yml — consider removing it (superseded by release.yml)" -ForegroundColor Yellow
    }

    # --- Summary ---
    Write-Host "`nWorkflow installation for '$projectName' ($techName):" -ForegroundColor Cyan
    foreach ($r in $results) {
        $color = if ($r.Status -eq 'Installed') { 'Green' } else { 'Yellow' }
        $detail = if ($r.Reason) { " ($($r.Reason))" } else { '' }
        Write-Host " $($r.File): $($r.Status)$detail" -ForegroundColor $color
    }

    Write-Host "`nReminder:" -ForegroundColor Cyan
    Write-Host " Ensure PSGALLERY_API_KEY secret is set:" -ForegroundColor White
    Write-Host " gh secret set PSGALLERY_API_KEY --repo <owner>/<repo>" -ForegroundColor Gray

    return $results
}