refactor:
This commit is contained in:
parent
0d4751ff68
commit
88aae9e3a6
5 changed files with 578 additions and 328 deletions
34
README.md
34
README.md
|
|
@ -92,6 +92,22 @@ to exist.
|
||||||
Publish-Page
|
Publish-Page
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
This program is compatible with the following Microsoft PowerShell runtimes:
|
||||||
|
|
||||||
|
- Microsoft PowerShell Desktop >=5
|
||||||
|
- Microsoft PowerShell Core >=7
|
||||||
|
|
||||||
|
## Runtime Dependencies
|
||||||
|
|
||||||
|
This program has no runtime dependencies.
|
||||||
|
|
||||||
|
On PowerShell Desktop, however, it is necessary to obtain the
|
||||||
|
`Microsoft.PowerShell.Utility` module for JSON schema verification of the
|
||||||
|
manifest. Whether that's possible for PowerShell Desktop; We do not know.
|
||||||
|
Should the aforementioned module not be present, JSON validation is skipped.
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
|
|
||||||
To display debug messages, set
|
To display debug messages, set
|
||||||
|
|
@ -108,7 +124,13 @@ Execute `pwsh scripts/analyze.ps1` to do a static code analysis.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
This program requires [Pester v5](https://pester.dev/) to execute it's test suite.
|
This program requires [Pester](https://pester.dev/) to execute it's test suite.
|
||||||
|
|
||||||
|
The test suite aims to be executable under most circumstances. We've been
|
||||||
|
dropping usage of Pester v5 functionalities so that it works with Pester down
|
||||||
|
to version 3, since Pester v3 is available in PowerShell (5) Desktop by default.
|
||||||
|
Due to the security mechanisms implemented in PowerShell Desktop, installing the
|
||||||
|
Pester v5 module may not be feasible for some.
|
||||||
|
|
||||||
Execute `pwsh scripts/test.ps1` to run the entire test suite.
|
Execute `pwsh scripts/test.ps1` to run the entire test suite.
|
||||||
|
|
||||||
|
|
@ -116,9 +138,13 @@ Execute `pwsh scripts/test.ps1` to run the entire test suite.
|
||||||
|
|
||||||
This program does not adhere to Microsoft's Best-Practices of publishing
|
This program does not adhere to Microsoft's Best-Practices of publishing
|
||||||
PowerShell modules, in the sense of that it does not use the *PowerShellGet*
|
PowerShell modules, in the sense of that it does not use the *PowerShellGet*
|
||||||
module to do so and uses plain `nuget` CLI instead.
|
module to do so and uses the plain `nuget` CLI instead.
|
||||||
|
|
||||||
Execute `pwsh scripts/pack.ps1` to create a nuget package.
|
This program requires [nuget
|
||||||
|
CLI](https://learn.microsoft.com/en-us/nuget/install-nuget-client-tools). Be
|
||||||
|
aware that the `dotnet nuget` CLI may not be sufficient on some platforms.
|
||||||
|
|
||||||
Execute `pwsh scripts/publish` to publish the nuget package to
|
Execute `pwsh scripts/pack.ps1` to create the nuget package.
|
||||||
|
|
||||||
|
Execute `pwsh scripts/publish.ps1` to publish the nuget package to
|
||||||
[PowerShellGallery](https://www.powershellgallery.com).
|
[PowerShellGallery](https://www.powershellgallery.com).
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
|
||||||
$script:schema = Get-Content (
|
$script:schema = Get-Content `
|
||||||
Join-Path -Path $PSScriptRoot 'schemas' 'manifest.schema.json'
|
"$PSScriptRoot/schemas/manifest.schema.json" | Out-String
|
||||||
) | Out-String
|
|
||||||
|
|
||||||
|
|
||||||
function Get-Manifest
|
function Get-Manifest
|
||||||
|
|
@ -21,6 +20,11 @@ function Get-Manifest
|
||||||
[Parameter(Mandatory)] [string] $File
|
[Parameter(Mandatory)] [string] $File
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Begin
|
||||||
|
{
|
||||||
|
$basePath = Split-Path $File
|
||||||
|
}
|
||||||
|
|
||||||
Process
|
Process
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -35,9 +39,59 @@ function Get-Manifest
|
||||||
$raw = '{"Pages":[], "attachments": []}'
|
$raw = '{"Pages":[], "attachments": []}'
|
||||||
}
|
}
|
||||||
|
|
||||||
$raw | Test-JSON -Schema $script:schema | Out-Null
|
If ($PSVersionTable.PSEdition -eq 'Core')
|
||||||
|
{
|
||||||
|
$raw | Test-JSON -Schema $script:schema | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
$raw | ConvertFrom-JSON
|
$manifest = $raw | ConvertFrom-JSON
|
||||||
|
|
||||||
|
ForEach($pageMeta in $manifest.Pages)
|
||||||
|
{
|
||||||
|
# patching to be an absolute path, the inverse function
|
||||||
|
# (Set-Manifest) must check, whether _Ref is set and substitute for
|
||||||
|
# Ref before writing to filesystem
|
||||||
|
If ($pageMeta.Ref)
|
||||||
|
{
|
||||||
|
$pageMeta | Add-Member `
|
||||||
|
-NotePropertyName '_Ref' `
|
||||||
|
-NotePropertyValue $pageMeta.Ref `
|
||||||
|
-Force
|
||||||
|
|
||||||
|
$pageMeta | Add-Member `
|
||||||
|
-NotePropertyName 'Ref' `
|
||||||
|
-NotePropertyValue (
|
||||||
|
Join-Path $basePath $pageMeta.Ref | Resolve-Path
|
||||||
|
) `
|
||||||
|
-Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ForEach($attachmentMeta in $manifest.Attachments)
|
||||||
|
{
|
||||||
|
# patching to be an absolute path, the inverse function
|
||||||
|
# (Set-Manifest) must check, whether _Ref is set and substitute for
|
||||||
|
# Ref before writing to filesystem
|
||||||
|
If ($attachmentMeta.Ref)
|
||||||
|
{
|
||||||
|
$attachmentMeta | Add-Member `
|
||||||
|
-NotePropertyName '_Ref' `
|
||||||
|
-NotePropertyValue $attachmentMeta.Ref `
|
||||||
|
-Force
|
||||||
|
|
||||||
|
$attachmentMeta | Add-Member `
|
||||||
|
-NotePropertyName 'Ref' `
|
||||||
|
-NotePropertyValue (
|
||||||
|
Join-Path $basePath $attachmentMeta.Ref | Resolve-Path
|
||||||
|
) `
|
||||||
|
-Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
End
|
||||||
|
{
|
||||||
|
$manifest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,9 +116,38 @@ function Set-Manifest
|
||||||
|
|
||||||
Process
|
Process
|
||||||
{
|
{
|
||||||
|
ForEach($pageMeta in $Manifest.Pages)
|
||||||
|
{
|
||||||
|
# patching to be an absolute path, the inverse function
|
||||||
|
# (Set-Manifest) must check, whether _Ref is set and substitute for
|
||||||
|
# Ref before writing to filesystem
|
||||||
|
If ($pageMeta._Ref)
|
||||||
|
{
|
||||||
|
$pageMeta.Ref = $pageMeta._Ref
|
||||||
|
|
||||||
|
$Manifest.Pages.PSObject.Properties.Remove('_Ref')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ForEach($attachmentMeta in $Manifest.Attachments)
|
||||||
|
{
|
||||||
|
# patching to be an absolute path, the inverse function
|
||||||
|
# (Set-Manifest) must check, whether _Ref is set and substitute for
|
||||||
|
# Ref before writing to filesystem
|
||||||
|
If ($attachmentMeta._Ref)
|
||||||
|
{
|
||||||
|
$attachmentMeta.Ref = $attachmentMeta._Ref
|
||||||
|
|
||||||
|
$Manifest.Attachments.PSObject.Properties.Remove('_Ref')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$raw = $Manifest | ConvertTo-JSON
|
$raw = $Manifest | ConvertTo-JSON
|
||||||
|
|
||||||
$raw | Test-JSON -Schema $script:schema
|
If ($PSVersionTable.PSEdition -eq 'Core')
|
||||||
|
{
|
||||||
|
$raw | Test-JSON -Schema $script:schema
|
||||||
|
}
|
||||||
|
|
||||||
if ($Backup)
|
if ($Backup)
|
||||||
{
|
{
|
||||||
|
|
@ -136,7 +219,7 @@ function New-AncestralPageGenerationCache {
|
||||||
{
|
{
|
||||||
$generation = 0
|
$generation = 0
|
||||||
|
|
||||||
$pageMeta = $Title ? $Manifest[$Index.$Title] : $pageMeta
|
$pageMeta = If ($Title) {$Manifest[$Index.$Title]} Else {$pageMeta}
|
||||||
|
|
||||||
$ancestor = $pageMeta.AncestorTitle
|
$ancestor = $pageMeta.AncestorTitle
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,3 +33,112 @@
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
|
||||||
|
function Initialize-Manifest
|
||||||
|
{
|
||||||
|
<#
|
||||||
|
|
||||||
|
#>
|
||||||
|
Param(
|
||||||
|
# path of manifest to load
|
||||||
|
[Parameter(Mandatory)] [String] $Path
|
||||||
|
)
|
||||||
|
|
||||||
|
Begin
|
||||||
|
{
|
||||||
|
$literalPath = Resolve-Path -Path $Path
|
||||||
|
}
|
||||||
|
|
||||||
|
Process
|
||||||
|
{
|
||||||
|
Write-Debug 'loading manifest...'
|
||||||
|
|
||||||
|
$manifest = Get-Manifest $literalPath
|
||||||
|
|
||||||
|
Write-Debug 'creating pages manifest index...'
|
||||||
|
|
||||||
|
$pagesManifestIndex = New-PagesManifestIndex -Manifest $manifest.Pages
|
||||||
|
|
||||||
|
Write-Debug 'creating ancestral page generation cache...'
|
||||||
|
|
||||||
|
$ancestralGenerationCache = New-AncestralPageGenerationCache `
|
||||||
|
-Manifest $manifest.Pages `
|
||||||
|
-Index $pagesManifestIndex
|
||||||
|
|
||||||
|
Write-Debug 'sorting pages manifest...'
|
||||||
|
|
||||||
|
Optimize-PagesManifest `
|
||||||
|
-Manifest $manifest.Pages `
|
||||||
|
-Lo 0 `
|
||||||
|
-Hi ($manifest.Pages.Count - 1) `
|
||||||
|
-GenerationCache $ancestralGenerationCache | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
End
|
||||||
|
{
|
||||||
|
@{
|
||||||
|
'Path' = $literalPath
|
||||||
|
'Manifest' = $manifest
|
||||||
|
'Index' = @{
|
||||||
|
'Pages' = New-PagesManifestIndex `
|
||||||
|
-Manifest $manifest.Pages
|
||||||
|
'Attachments' = New-AttachmentsManifestIndex `
|
||||||
|
-Manifest $manifest.Attachments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Initialize-Connection
|
||||||
|
{
|
||||||
|
Param(
|
||||||
|
[Parameter(Mandatory)] [String]$Host,
|
||||||
|
[Parameter(Mandatory)] [String]$Space,
|
||||||
|
[Parameter(Mandatory)] [String]$PersonalAccessToken
|
||||||
|
)
|
||||||
|
|
||||||
|
Process
|
||||||
|
{
|
||||||
|
Register-PersonalAccessToken `
|
||||||
|
-Host $Host `
|
||||||
|
-Token $PersonalAccessToken | Out-Null
|
||||||
|
|
||||||
|
Test-Connection -Host $Host | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
End
|
||||||
|
{
|
||||||
|
@{
|
||||||
|
'Host' = $Host
|
||||||
|
'Space' = $Space
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Publish-Pages
|
||||||
|
{
|
||||||
|
Param(
|
||||||
|
# connection object created through Initialize-Connection
|
||||||
|
[Parameter(Mandatory)] [Collections.Hashtable]$Connection,
|
||||||
|
# manifest object created through Initialize-Manifest
|
||||||
|
[Parameter(Mandatory)] [PSCustomObject]$Manifest,
|
||||||
|
#
|
||||||
|
[Parameter()] [Switch]$Strict,
|
||||||
|
#
|
||||||
|
[Parameter()] [Switch]$Force,
|
||||||
|
# title of page to be published
|
||||||
|
[Parameter()] [String]$Title
|
||||||
|
)
|
||||||
|
|
||||||
|
Process
|
||||||
|
{
|
||||||
|
$Manifest.Manifest.Pages | Publish-Page `
|
||||||
|
-Host $Connection.Host `
|
||||||
|
-Space $Connection.Space `
|
||||||
|
-Title $Title `
|
||||||
|
-Index $Manifest.Index.Pages `
|
||||||
|
-Strict:$Strict `
|
||||||
|
-Force:$Force | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
410
src/Page.psm1
410
src/Page.psm1
|
|
@ -38,8 +38,8 @@ function New-Page
|
||||||
# title of page to be published
|
# title of page to be published
|
||||||
[Parameter()] [string]$Title,
|
[Parameter()] [string]$Title,
|
||||||
# pages manifest
|
# pages manifest
|
||||||
[Parameter(Mandatory, ValueFromPipeline)]
|
[Parameter(Mandatory)]
|
||||||
[Collections.Hashtable[]]$Manifest,
|
[PSCustomObject[]]$Manifest,
|
||||||
# pages manifest index, mandatory for ancestor lookup
|
# pages manifest index, mandatory for ancestor lookup
|
||||||
[Parameter(Mandatory)] [Collections.Hashtable]$Index,
|
[Parameter(Mandatory)] [Collections.Hashtable]$Index,
|
||||||
# flag on whether to fail hard, or just continue
|
# flag on whether to fail hard, or just continue
|
||||||
|
|
@ -66,7 +66,8 @@ function New-Page
|
||||||
|
|
||||||
ElseIf (-Not $pageMeta.Ref)
|
ElseIf (-Not $pageMeta.Ref)
|
||||||
{
|
{
|
||||||
$errMsg = "no reference to local content for page ``$Title``."
|
$errMsg = ("``$($pageMeta.Title)``: no reference to local " +
|
||||||
|
'content for page .')
|
||||||
|
|
||||||
If ($Strict) {throw $errMsg}
|
If ($Strict) {throw $errMsg}
|
||||||
|
|
||||||
|
|
@ -78,7 +79,10 @@ function New-Page
|
||||||
ElseIf ($pageMeta.Id)
|
ElseIf ($pageMeta.Id)
|
||||||
{
|
{
|
||||||
|
|
||||||
Write-Debug "skipping, page already published: $($pageMeta.Id)"
|
Write-Debug (
|
||||||
|
"New-Page: ``$($pageMeta.Title)``: skipping, already " +
|
||||||
|
"published ($($pageMeta.Id))"
|
||||||
|
)
|
||||||
|
|
||||||
$pageMeta
|
$pageMeta
|
||||||
|
|
||||||
|
|
@ -87,13 +91,29 @@ function New-Page
|
||||||
|
|
||||||
Else
|
Else
|
||||||
{
|
{
|
||||||
$content = Get-Content -Path $pageMeta.Ref
|
Write-Host "New-Page: ``$($pageMeta.Title)``: creating"
|
||||||
|
|
||||||
|
Try
|
||||||
|
{
|
||||||
|
$content = Get-Content -Path $pageMeta.Ref | Out-String
|
||||||
|
}
|
||||||
|
|
||||||
|
Catch
|
||||||
|
{
|
||||||
|
$errMsg = "``New-Page: $($PageMeta.Title)``: $_"
|
||||||
|
|
||||||
|
If ($Strict) {throw $errMsg}
|
||||||
|
|
||||||
|
Write-Host $errMsg
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
$contentHash = (Get-StringHash $content).Hash
|
$contentHash = (Get-StringHash $content).Hash
|
||||||
|
|
||||||
$transportBody = @{
|
$transportBody = @{
|
||||||
'type' = 'page'
|
'type' = 'page'
|
||||||
'title' = $Title
|
'title' = $pageMeta.Title
|
||||||
'space' = @{
|
'space' = @{
|
||||||
'key' = $Space
|
'key' = $Space
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +123,7 @@ function New-Page
|
||||||
'representation' = 'storage'
|
'representation' = 'storage'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} | ConvertTo-JSON
|
} | ConvertTo-JSON -Depth 5
|
||||||
|
|
||||||
Try
|
Try
|
||||||
{
|
{
|
||||||
|
|
@ -120,10 +140,12 @@ function New-Page
|
||||||
|
|
||||||
Catch
|
Catch
|
||||||
{
|
{
|
||||||
$errMsg = "skipping ``$pageMeta.Title``: $_"
|
$errMsg = "skipping ``$($pageMeta.Title)``: $($_)"
|
||||||
|
|
||||||
If ($Strict)
|
If ($Strict)
|
||||||
{
|
{
|
||||||
|
$_
|
||||||
|
|
||||||
throw $errMsg
|
throw $errMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,7 +163,7 @@ function New-Page
|
||||||
|
|
||||||
$pageMeta | Add-Member `
|
$pageMeta | Add-Member `
|
||||||
-NotePropertyName 'Version' `
|
-NotePropertyName 'Version' `
|
||||||
-NotePropertyValue $response.version.number `
|
-NotePropertyValue "1" `
|
||||||
-Force
|
-Force
|
||||||
|
|
||||||
$pageMeta | Add-Member `
|
$pageMeta | Add-Member `
|
||||||
|
|
@ -176,107 +198,224 @@ function Update-Page
|
||||||
{
|
{
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Add a confluence page
|
Update an existing Confluence page
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
|
|
||||||
|
This function is unaware of the publishing status of ancestors and
|
||||||
|
assumes that ancestral hierarchy is maintained through the
|
||||||
|
manifest's item order, therefore an index must be supplied.
|
||||||
|
|
||||||
|
If a page's metadata does not include a reference, it will be
|
||||||
|
treated as a publishing failure and therefore not output the
|
||||||
|
original metadata.
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
|
||||||
|
When no $Title is provided and the $Manifest array only contains 1
|
||||||
|
page metadata, the ``Count`` attribute is faulty. Why? Don't know.
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
Update-ConfluencePage
|
Add-ConfluencePage `
|
||||||
-Host 'confluence.contoso.com' `
|
-Host 'confluence.contoso.com' `
|
||||||
-Space 'TIARA' `
|
-Space 'TIARA' `
|
||||||
-Title 'Testitest' `
|
-Title 'Testitest' `
|
||||||
-Manifest @{}
|
-Content @{}
|
||||||
#>
|
#>
|
||||||
Param(
|
Param(
|
||||||
[Parameter(Mandatory)] [string] $Host,
|
# confluence instance hostname
|
||||||
# The name of the Confluence space to publish to
|
[Parameter(Mandatory)] [string]$Host,
|
||||||
[Parameter(Mandatory)] [string] $Space,
|
# name of the Confluence space to publish to
|
||||||
|
[Parameter(Mandatory)] [string]$Space,
|
||||||
# title of page to be published
|
# title of page to be published
|
||||||
[Parameter(Mandatory)] [string] $Title,
|
[Parameter()] [string]$Title,
|
||||||
# pages manifest
|
# pages manifest
|
||||||
[Parameter(Mandatory)] [Array] $Manifest,
|
[Parameter(Mandatory, ValueFromPipeline)]
|
||||||
# pages manifest index
|
[PSCustomObject[]]$Manifest,
|
||||||
[Parameter()] [Collections.Hashtable] $Index
|
# pages manifest index, mandatory for ancestor lookup
|
||||||
|
[Parameter(Mandatory)] [Collections.Hashtable]$Index,
|
||||||
|
# flag on whether to fail hard, or just continue
|
||||||
|
[Parameter()] [Switch]$Strict,
|
||||||
|
# flag on whether to force update of page, regardless of content
|
||||||
|
[Parameter()] [Switch]$Force
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Begin
|
||||||
|
{
|
||||||
|
$pat = Get-PersonalAccessToken $Host
|
||||||
|
}
|
||||||
|
|
||||||
Process
|
Process
|
||||||
{
|
{
|
||||||
$pageMeta = Get-PageMeta `
|
If ($Title -And $Manifest[$Index.$Title])
|
||||||
-Host $Host `
|
|
||||||
-Space $Space `
|
|
||||||
-Title $Title `
|
|
||||||
-Manifest $Manifest `
|
|
||||||
-Index $Index
|
|
||||||
|
|
||||||
if (-Not $pageMeta.Ref)
|
|
||||||
{
|
{
|
||||||
throw "no reference to local content for page '$Title'."
|
$Manifest = @(
|
||||||
|
$Manifest[$Index.$Title]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-Not $pageMeta.Id)
|
ForEach($pageMeta in $Manifest)
|
||||||
{
|
{
|
||||||
throw "no id for page '$Title'."
|
If ($Title -And $pageMeta.Title -ne $Title) {continue}
|
||||||
}
|
|
||||||
|
|
||||||
$content = Get-Content -Path $pageMeta.Ref
|
ElseIf (-Not $pageMeta.Ref)
|
||||||
|
{
|
||||||
|
$errMsg = "no reference to local content for page ``$Title``."
|
||||||
|
|
||||||
$hash = (Get-StringHash $content).Hash
|
If ($Strict) {throw $errMsg}
|
||||||
|
|
||||||
if ($hash -eq $pageMeta.Hash)
|
Write-Host $errMsg
|
||||||
{
|
|
||||||
Write-Host "content unchanged, skipping: '$Title'"
|
|
||||||
|
|
||||||
# yep, this is funny... This behaves like a return statement, because
|
continue
|
||||||
# a cmdlet, treats the input as an array of inputs. We keep it that
|
|
||||||
# way so that all functions can properly act upon pipes. See
|
|
||||||
# additional information on 'Process' blocks.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
# we're not updating this in place, so that we don't have to reset the
|
|
||||||
# value opon failure
|
|
||||||
$version = $pageMeta.Version + 1
|
|
||||||
|
|
||||||
$transportBody = @{
|
|
||||||
'id' = $PageMeta.Id
|
|
||||||
'type' = 'page'
|
|
||||||
'title' = $Title
|
|
||||||
'space' = @{
|
|
||||||
'key' = $Space
|
|
||||||
}
|
}
|
||||||
'body' = @{
|
|
||||||
'storage' = @{
|
ElseIf (-Not $pageMeta.Id)
|
||||||
'value' = $content
|
{
|
||||||
'representation' = 'storage'
|
$errMsg = "``$($pageMeta.Title)``: unknown page id"
|
||||||
|
|
||||||
|
If ($Strict) {throw $errMsg}
|
||||||
|
|
||||||
|
Write-Host $errMsg
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ElseIf (-Not $pageMeta.Version)
|
||||||
|
{
|
||||||
|
Write-Host "``$($pageMeta.Title)``: unknown (current) version"
|
||||||
|
|
||||||
|
$pageMeta
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
Else
|
||||||
|
{
|
||||||
|
Try
|
||||||
|
{
|
||||||
|
$content = Get-Content -Path $pageMeta.Ref | Out-String
|
||||||
|
}
|
||||||
|
|
||||||
|
Catch
|
||||||
|
{
|
||||||
|
$errMsg = "``$Title``: $_"
|
||||||
|
|
||||||
|
If ($Strict) {throw $errMsg}
|
||||||
|
|
||||||
|
Write-Host $errMsg
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = [Int]($pageMeta.Version) + 1
|
||||||
|
|
||||||
|
$contentHash = (Get-StringHash $content).Hash
|
||||||
|
|
||||||
|
If (
|
||||||
|
$pageMeta.Hash -And
|
||||||
|
$pageMeta.Hash -eq $contentHash -And
|
||||||
|
-Not $Force
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Write-Debug (
|
||||||
|
"Update-Page: ``$($pageMeta.Title)``: skipping, no " +
|
||||||
|
"content changes"
|
||||||
|
)
|
||||||
|
|
||||||
|
$pageMeta
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
Else
|
||||||
|
{
|
||||||
|
Write-Host "``$($pageMeta.Title)``: updating"
|
||||||
|
}
|
||||||
|
|
||||||
|
# status needs to be set as to restore the page, if it is
|
||||||
|
# trashed
|
||||||
|
$transportBody = @{
|
||||||
|
'id' = $PageMeta.Id
|
||||||
|
'type' = 'page'
|
||||||
|
'title' = $pageMeta.Title
|
||||||
|
'space' = @{
|
||||||
|
'key' = $Space
|
||||||
|
}
|
||||||
|
'body' = @{
|
||||||
|
'storage' = @{
|
||||||
|
'value' = $content
|
||||||
|
'representation' = 'storage'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'status' = 'current'
|
||||||
|
'version' = @{
|
||||||
|
'number' = $version
|
||||||
|
}
|
||||||
|
} | ConvertTo-JSON -WarningAction 'SilentlyContinue'
|
||||||
|
|
||||||
|
Try
|
||||||
|
{
|
||||||
|
Invoke-WebRequest `
|
||||||
|
-Uri ("https://${Host}/rest/api/content/" +
|
||||||
|
$PageMeta.Id) `
|
||||||
|
-Method 'Put' `
|
||||||
|
-Headers @{
|
||||||
|
'Authorization' = "Bearer $pat"
|
||||||
|
} `
|
||||||
|
-ContentType "application/json" `
|
||||||
|
-Body $transportBody `
|
||||||
|
-OutVariable rawResponse | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
Catch
|
||||||
|
{
|
||||||
|
$errMsg = "skipping ``$($pageMeta.Title)``: $_"
|
||||||
|
|
||||||
|
If ($Strict)
|
||||||
|
{
|
||||||
|
$_
|
||||||
|
|
||||||
|
throw $errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host $errMsg
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# response isn't needed since no field will be updated by the
|
||||||
|
# Confluence instance itself
|
||||||
|
#$response = ($rawResponse.Content | ConvertFrom-JSON)
|
||||||
|
|
||||||
|
$pageMeta | Add-Member `
|
||||||
|
-NotePropertyName 'Version' `
|
||||||
|
-NotePropertyValue $version `
|
||||||
|
-Force
|
||||||
|
|
||||||
|
$pageMeta | Add-Member `
|
||||||
|
-NotePropertyName 'Hash' `
|
||||||
|
-NotePropertyValue $contentHash `
|
||||||
|
-Force
|
||||||
|
|
||||||
|
If (
|
||||||
|
($Title -And $pageMeta.Title -eq $Title) -Or
|
||||||
|
$Manifest.Count -eq 1
|
||||||
|
)
|
||||||
|
{
|
||||||
|
# TODO: further research mechanism of expanding single item
|
||||||
|
# array pipelines. For now we have to apply the unary
|
||||||
|
# operator, otherwise we get a wrong count on the output
|
||||||
|
,@($pageMeta)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
Else
|
||||||
|
{
|
||||||
|
$pageMeta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'version' = @{
|
}
|
||||||
'number' = $version
|
|
||||||
}
|
|
||||||
} | ConvertTo-JSON
|
|
||||||
|
|
||||||
Invoke-WebRequest `
|
|
||||||
-Uri "https://${Host}/rest/api/content/$($PageMeta.Id)" `
|
|
||||||
-Method 'Put' `
|
|
||||||
-Headers @{
|
|
||||||
'Authorization' = "Bearer $(Get-PersonalAccessToken $Host)"
|
|
||||||
} `
|
|
||||||
-ContentType "application/json" `
|
|
||||||
-Body $transportBody `
|
|
||||||
-OutVariable rawResponse | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
End
|
|
||||||
{
|
|
||||||
$response = ($rawResponse.Content | ConvertFrom-JSON)
|
|
||||||
|
|
||||||
Update-PageMeta `
|
|
||||||
-Title $Title `
|
|
||||||
-Id $pageMeta.Id `
|
|
||||||
-Version $response.version.number `
|
|
||||||
-Hash $hash `
|
|
||||||
-Manifest $Manifest `
|
|
||||||
-Index $Index
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,82 +423,43 @@ function Update-Page
|
||||||
function Publish-Page
|
function Publish-Page
|
||||||
{
|
{
|
||||||
Param(
|
Param(
|
||||||
# title of the page (used for manifest lookup)
|
# confluence instance hostname
|
||||||
[Parameter(Mandatory)] [string] $Title,
|
[Parameter(Mandatory)] [string]$Host,
|
||||||
# hostname of Confluence instance
|
# name of the Confluence space to publish to
|
||||||
[Parameter(Mandatory)] [string] $Host,
|
[Parameter(Mandatory)] [string]$Space,
|
||||||
# name of Confluence space
|
# title of page to be published
|
||||||
[Parameter(Mandatory)] [string] $Space,
|
[Parameter()] [string]$Title,
|
||||||
# manifest object
|
# pages manifest
|
||||||
[Parameter(Mandatory, ValueFromPipeline)] [PSObject] $Meta
|
[Parameter(Mandatory, ValueFromPipeline)]
|
||||||
|
[PSCustomObject[]]$Manifest,
|
||||||
|
# pages manifest index, mandatory for ancestor lookup
|
||||||
|
[Parameter(Mandatory)] [Collections.Hashtable]$Index,
|
||||||
|
# flag on whether to fail hard, or just continue
|
||||||
|
[Parameter()] [Switch]$Strict,
|
||||||
|
# flag on whether to force update of page, regardless of content
|
||||||
|
[Parameter()] [Switch]$Force
|
||||||
)
|
)
|
||||||
|
|
||||||
Process
|
Process
|
||||||
{
|
{
|
||||||
ForEach($meta in $Meta)
|
$result = Update-Page `
|
||||||
{
|
-Host $Host `
|
||||||
$meta = Get-PageMeta `
|
-Space $Space `
|
||||||
-Host $hostname `
|
-Manifest $Manifest `
|
||||||
-Space $spaceName `
|
-Index $Index `
|
||||||
-Title $Title `
|
-Strict:$Strict `
|
||||||
-Manifest $Manifest
|
-Force:$Force
|
||||||
|
|
||||||
if ($meta.AncestorTitle)
|
$result = New-Page `
|
||||||
{
|
-Host $Host `
|
||||||
$ancestorPageMeta = Get-PageMeta `
|
-Space $Space `
|
||||||
-Host $hostname `
|
-Manifest $result `
|
||||||
-Space $spaceName `
|
-Index $Index `
|
||||||
-Title $pageMeta.AncestorTitle `
|
-Strict:$Strict
|
||||||
-Manifest $Manifest
|
}
|
||||||
|
|
||||||
if (-Not ($ancestorPageMeta -Or $ancestorPageMeta.PageId))
|
End
|
||||||
{
|
{
|
||||||
Write-Host "ancestor, not published, skipping: $Title"
|
$result
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-Not $pageId)
|
|
||||||
{
|
|
||||||
Write-Host ("create ${_}: $prettyName")
|
|
||||||
|
|
||||||
try {
|
|
||||||
New-Page `
|
|
||||||
-Host $hostname `
|
|
||||||
-Space $spaceName `
|
|
||||||
-Title $Title `
|
|
||||||
-Manifest $Manifest
|
|
||||||
}
|
|
||||||
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Write-Host "error for '$Title', skipping: $_"
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Write-Host ("update ${_} (${pageId}): $prettyName")
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Update-Page `
|
|
||||||
-Host $hostname `
|
|
||||||
-Space $Space `
|
|
||||||
-Title $Title `
|
|
||||||
-Manifest $Manifest
|
|
||||||
}
|
|
||||||
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Write-Host "error for '$Title', skipping: $_"
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,7 @@
|
||||||
#!/usr/bin/env pwsh
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
|
||||||
function Get-PageMetaCache
|
|
||||||
{
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Get a locally indexed/cached Confluence page id
|
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
Get-PageMetaCache `
|
|
||||||
-Title 'Page Title' `
|
|
||||||
-Manifest @() `
|
|
||||||
-Index @{}
|
|
||||||
|
|
||||||
.NOTES
|
|
||||||
To test or not to test, that is the question... Since the
|
|
||||||
`Test-JSON` cmdlet requires serialized JSON, but we are working with
|
|
||||||
the deserialized Hashtable, it's too computationally intense to
|
|
||||||
always test the input upon every call. We therefore only make sure,
|
|
||||||
that correct data is written to the filesystem. For the rest, each
|
|
||||||
function is responsible for themself (learned that that's a valid
|
|
||||||
reflexive pronoun today 🤓).
|
|
||||||
|
|
||||||
This function is lucky to get this note, because it's at the top 💯.
|
|
||||||
Of course this applies to every function.
|
|
||||||
#>
|
|
||||||
Param(
|
|
||||||
[Parameter(Mandatory)] [string] $Title,
|
|
||||||
[Parameter(Mandatory)] [Array] $Manifest,
|
|
||||||
[Parameter()] [Collections.Hashtable] $Index
|
|
||||||
)
|
|
||||||
|
|
||||||
Process
|
|
||||||
{
|
|
||||||
If ($Index -And $Manifest.Count -gt 0 -And $Manifest[$Index.$Title])
|
|
||||||
{
|
|
||||||
$Manifest[$Index.$Title]
|
|
||||||
}
|
|
||||||
|
|
||||||
Else
|
|
||||||
{
|
|
||||||
For ($i = 0; $i -lt $Manifest.Count; $i += 1)
|
|
||||||
{
|
|
||||||
If ($Manifest[$i].Title -eq $Title)
|
|
||||||
{
|
|
||||||
$Manifest[$i]
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function Get-PageMeta
|
function Get-PageMeta
|
||||||
{
|
{
|
||||||
<#
|
<#
|
||||||
|
|
@ -74,127 +21,112 @@ function Get-PageMeta
|
||||||
-CacheIndexFile 'confluence-page-cache.json'
|
-CacheIndexFile 'confluence-page-cache.json'
|
||||||
#>
|
#>
|
||||||
Param(
|
Param(
|
||||||
|
# Confluence instance hostname
|
||||||
[Parameter(Mandatory)] [string] $Host,
|
[Parameter(Mandatory)] [string] $Host,
|
||||||
[Parameter(Mandatory)] [string] $Title,
|
# Page title
|
||||||
|
[Parameter()] [string] $Title,
|
||||||
|
# Confluence space id
|
||||||
[Parameter(Mandatory)] [string] $Space,
|
[Parameter(Mandatory)] [string] $Space,
|
||||||
[Parameter(Mandatory)] [Array] $Manifest,
|
# pages manifest
|
||||||
[Parameter()] [Collections.Hashtable] $Index
|
[Parameter(Mandatory, ValueFromPipeline)] [Array] $Manifest,
|
||||||
|
# page metadata index for faster lookup of single page
|
||||||
|
[Parameter()] [Collections.Hashtable] $Index,
|
||||||
|
# force to get metadata from remote
|
||||||
|
[Parameter()] [Switch] $Force = $false,
|
||||||
|
# throw an exception on error
|
||||||
|
[Parameter()] [Switch] $Strict = $true
|
||||||
)
|
)
|
||||||
|
|
||||||
Begin
|
|
||||||
{
|
|
||||||
$pageMeta = Get-PageMetaCache `
|
|
||||||
-Title $Title `
|
|
||||||
-Manifest $Manifest `
|
|
||||||
-Index $Index
|
|
||||||
}
|
|
||||||
|
|
||||||
Process
|
Process
|
||||||
{
|
{
|
||||||
If ($pageMeta -And $pageMeta.Id)
|
If ($Title -And $Index -And $Manifest[$Index.$Title].Id)
|
||||||
{
|
{
|
||||||
$pageMeta
|
$Manifest[$Index.$Title]
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$escapedTitle = [Uri]::EscapeDataString($Title)
|
ForEach ($pageMeta in $Manifest)
|
||||||
|
|
||||||
#TODO: move this to a separate function
|
|
||||||
$query = "title=${escapedTitle}&spaceKey=${Space}&expand=history"
|
|
||||||
|
|
||||||
Invoke-WebRequest `
|
|
||||||
-Uri "https://${Host}/rest/api/content?$query" `
|
|
||||||
-Method 'Get' `
|
|
||||||
-Headers @{
|
|
||||||
'Authorization' = "Bearer $(Get-PersonalAccessToken $Host)"
|
|
||||||
} `
|
|
||||||
-OutVariable response | Out-Null
|
|
||||||
|
|
||||||
$results = ($response.Content | ConvertFrom-JSON).results
|
|
||||||
|
|
||||||
if ($results.Count -gt 1)
|
|
||||||
{
|
{
|
||||||
throw "more than one result for query: $query"
|
If ($Title -And $pageMeta.Title -ne $Title) {continue}
|
||||||
}
|
|
||||||
|
|
||||||
elseif ($results.Count -eq 1)
|
If ($pageMeta.Id -And -Not $Force)
|
||||||
{
|
{
|
||||||
Update-PageMeta `
|
Write-Debug "local (cache): $($pageMeta.Title) ($($pageMeta.Id))"
|
||||||
-Id $results[0].id `
|
|
||||||
-Version $results[0]._expandable.version `
|
$pageMeta
|
||||||
-Title $Title `
|
}
|
||||||
-Manifest $Manifest
|
|
||||||
|
Else
|
||||||
|
{
|
||||||
|
$escapedTitle = [Uri]::EscapeDataString($pageMeta.Title)
|
||||||
|
|
||||||
|
$query = (
|
||||||
|
"title=${escapedTitle}&spaceKey=${Space}&expand=version"
|
||||||
|
)
|
||||||
|
|
||||||
|
Invoke-WebRequest `
|
||||||
|
-Uri "https://${Host}/rest/api/content?$query" `
|
||||||
|
-Method 'Get' `
|
||||||
|
-Headers @{
|
||||||
|
'Authorization' = 'Bearer ' +
|
||||||
|
$(Get-PersonalAccessToken $Host)
|
||||||
|
} `
|
||||||
|
-OutVariable response | Out-Null
|
||||||
|
|
||||||
|
$results = ($response.Content | ConvertFrom-JSON).results
|
||||||
|
|
||||||
|
If ($results.Count -gt 1)
|
||||||
|
{
|
||||||
|
$errMsg = "error: more than one result for query: $query"
|
||||||
|
|
||||||
|
If ($Strict) {throw $errMsg}
|
||||||
|
|
||||||
|
Write-Host $errMsg
|
||||||
|
|
||||||
|
$pageMeta
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ElseIf ($results.Count -eq 1)
|
||||||
|
{
|
||||||
|
Write-Debug (
|
||||||
|
"Get-PageMetadata: ``$($pageMeta.Title)``: " +
|
||||||
|
"updating metadata through remote ($($results[0].id))"
|
||||||
|
)
|
||||||
|
|
||||||
|
$pageMeta | Add-Member `
|
||||||
|
-NotePropertyName Id `
|
||||||
|
-NotePropertyValue $results[0].id `
|
||||||
|
-Force
|
||||||
|
|
||||||
|
$pageMeta | Add-Member `
|
||||||
|
-NotePropertyName 'Version' `
|
||||||
|
-NotePropertyValue `
|
||||||
|
$results[0].version.number `
|
||||||
|
-Force
|
||||||
|
}
|
||||||
|
|
||||||
|
Else
|
||||||
|
{
|
||||||
|
Write-Debug "local: $($pageMeta.Title) (no remote)"
|
||||||
|
}
|
||||||
|
|
||||||
|
If (-Not $pageMeta.Hash)
|
||||||
|
{
|
||||||
|
$content = Get-Content $pageMeta.Ref | Out-String
|
||||||
|
|
||||||
|
$hash = (Get-StringHash $content).Hash
|
||||||
|
|
||||||
|
$pageMeta | Add-Member `
|
||||||
|
-NotePropertyName 'Hash' `
|
||||||
|
-NotePropertyValue $hash `
|
||||||
|
-Force
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageMeta
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function Update-PageMeta
|
|
||||||
{
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Register a Confluence page's metadata in the local cache
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Synchronizes the locally cached page metadata (in manifest) with the
|
|
||||||
data stored by the Confluence instance. Therefore it is required to
|
|
||||||
supply a page id, since this is the reference linking the locally
|
|
||||||
cached page to a published instance of a page.
|
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
Update-PageMeta `
|
|
||||||
-Title 'foobar' `
|
|
||||||
-PageId 'pageId' `
|
|
||||||
-Version 9001 `
|
|
||||||
-AncestorTitle 'ancestorTitle' `
|
|
||||||
-Hash 'hash' `
|
|
||||||
-Manifest $mockManifest
|
|
||||||
#>
|
|
||||||
Param(
|
|
||||||
[Parameter(Mandatory)] [String] $Title,
|
|
||||||
# remote Confluence page instance id
|
|
||||||
[Parameter(Mandatory)] [String] $Id,
|
|
||||||
[Parameter()] [Int] $Version,
|
|
||||||
[Parameter()] [String] $AncestorTitle,
|
|
||||||
[Parameter()] [String] $Hash,
|
|
||||||
[Parameter(Mandatory)] [Array] $Manifest,
|
|
||||||
[Parameter()] [Collections.Hashtable] $Index
|
|
||||||
)
|
|
||||||
|
|
||||||
Process
|
|
||||||
{
|
|
||||||
$pageMeta = Get-PageMetaCache `
|
|
||||||
-Title $Title `
|
|
||||||
-Manifest $Manifest `
|
|
||||||
-Index $Index
|
|
||||||
|
|
||||||
If (-Not $pageMeta)
|
|
||||||
{
|
|
||||||
throw "page titled `$Title` not indexed in Manifest."
|
|
||||||
}
|
|
||||||
|
|
||||||
$pageMeta.Id = $Id
|
|
||||||
|
|
||||||
If ($Version)
|
|
||||||
{
|
|
||||||
$pageMeta.Version = $Version
|
|
||||||
}
|
|
||||||
|
|
||||||
If ($AncestorTitle)
|
|
||||||
{
|
|
||||||
$pageMeta.AncestorTitle = $AncestorTitle
|
|
||||||
}
|
|
||||||
|
|
||||||
# if content didn't update, hash stays the same
|
|
||||||
If ($Hash)
|
|
||||||
{
|
|
||||||
$pageMeta.Hash = $Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Debug "register: $Title -> $PageId"
|
|
||||||
|
|
||||||
$pageMeta
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue