From 4d3c35286d6c824e49952b1229e6fdee8100a86f Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:56:10 -0400 Subject: [PATCH 1/4] fix for leaky global state in find-markdownimages.tests --- src/tests/Find-MarkdownImages.Tests.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tests/Find-MarkdownImages.Tests.ps1 b/src/tests/Find-MarkdownImages.Tests.ps1 index 254f0b6..cc67238 100644 --- a/src/tests/Find-MarkdownImages.Tests.ps1 +++ b/src/tests/Find-MarkdownImages.Tests.ps1 @@ -7,6 +7,11 @@ Describe "Find-MarkdownImages" { New-TestImage "TestDrive:\test-image1.png" New-TestImage "TestDrive:\subfolder\test-image2.jpg" New-TestImage "TestDrive:\absolute-image.gif" + + InModuleScope PSBlogger { + # reset blogger session to ensure that user preferences are not carried over into test + $BloggerSession.AttachmentsDirectory = $null + } } Context "Basic image detection" { From 1a5869788aab036c9243451dc31934d4836dcabc Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:44:28 -0400 Subject: [PATCH 2/4] always write output file if specified, regardless of changes --- src/public/Update-MarkdownImages.ps1 | 7 +++--- src/tests/Update-MarkdownImages.Tests.ps1 | 27 +++++++++++++++++------ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/public/Update-MarkdownImages.ps1 b/src/public/Update-MarkdownImages.ps1 index 88ff720..41a5ca5 100644 --- a/src/public/Update-MarkdownImages.ps1 +++ b/src/public/Update-MarkdownImages.ps1 @@ -36,6 +36,7 @@ function Update-MarkdownImages { [string]$File, [Parameter(Mandatory = $true)] + [AllowEmptyCollection()] [array]$ImageMappings, [Parameter()] @@ -71,11 +72,11 @@ function Update-MarkdownImages { $TargetFile = $OutFile } - # Only write the file if content has changed - if ($content -ne $originalContent) { + # Only write the file if content has changed or if OutFile is specified + if ($content -ne $originalContent -or $OutFile) { Set-Content -Path $TargetFile -Value $content -NoNewline Write-Verbose "Updated markdown file: $TargetFile" - return $true + return ($content -ne $originalContent) } else { Write-Verbose "No changes made to markdown file: $File" diff --git a/src/tests/Update-MarkdownImages.Tests.ps1 b/src/tests/Update-MarkdownImages.Tests.ps1 index 84c4f40..074849c 100644 --- a/src/tests/Update-MarkdownImages.Tests.ps1 +++ b/src/tests/Update-MarkdownImages.Tests.ps1 @@ -23,12 +23,11 @@ Describe "Update-MarkdownImages" { $expectedRegex = [regex]::Escape($expected) # create test file - @( - "# First Post" - "" - "Some content with an $original image" - ) -join [System.Environment]::NewLine | - Set-Content -Path "TestDrive:\test.md" + Set-MarkdownFile "TestDrive:\test.md" @( + "# First Post" + "" + "Some content with an $original image" + ) -join [System.Environment]::NewLine # act Update-MarkdownImages -File "TestDrive:\test.md" -ImageMappings $imagesMappings @@ -46,7 +45,7 @@ Describe "Update-MarkdownImages" { "" "Some content with an ![image](/path/image.png)" ) -join [System.Environment]::NewLine - $inputContent | Set-Content -Path $inputFile + Set-MarkdownFile $inputFile $inputContent $imagesMappings = @( New-MarkdownImage ` @@ -64,6 +63,20 @@ Describe "Update-MarkdownImages" { $outputContent | Should -Not -Be $inputContent } + It "Should write changes to the OutFile even if images weren't updated" { + # arrange + $inputFile = "TestDrive:\test.md" + $outputFile = "TestDrive:\output.md" + Set-MarkdownFile $inputFile "dummy content without images" + $imagesMappings = @() + + # act + Update-MarkdownImages -File $inputFile -ImageMappings $imagesMappings -OutFile $outputFile + + # assert + Test-Path $outputFile | Should -Be $true + } + } Context "Mixed format image replacement" { From d9103efed20f2e6a38f0f6c3a4ec629e4074fca8 Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Mon, 28 Jul 2025 12:02:50 -0400 Subject: [PATCH 3/4] Added -OutFile to Publish-MarkdownDriveImages --- src/public/Publish-MarkdownDriveImages.ps1 | 36 ++++++---- .../Publish-MarkdownDriveImages.Tests.ps1 | 70 +++++++++++++++++++ 2 files changed, 94 insertions(+), 12 deletions(-) diff --git a/src/public/Publish-MarkdownDriveImages.ps1 b/src/public/Publish-MarkdownDriveImages.ps1 index f42c20f..a48dad9 100644 --- a/src/public/Publish-MarkdownDriveImages.ps1 +++ b/src/public/Publish-MarkdownDriveImages.ps1 @@ -16,11 +16,17 @@ .PARAMETER Force If specified, will overwrite existing files in Google Drive with the same name. +.PARAMETER OutFile + If specified, writes the updated markdown content with Google Drive URLs to this file instead of modifying the original file. + .EXAMPLE Publish-MarkdownDriveImages -File "blog-post.md" .EXAMPLE Publish-MarkdownDriveImages -File "blog-post.md" -Force + +.EXAMPLE + Publish-MarkdownDriveImages -File "blog-post.md" -OutFile "blog-post-published.md" #> Function Publish-MarkdownDriveImages { @@ -34,7 +40,10 @@ Function Publish-MarkdownDriveImages [string]$AttachmentsDirectory, [Parameter(Mandatory=$false)] - [switch]$Force + [switch]$Force, + + [Parameter(Mandatory=$false)] + [string]$OutFile ) # Process images: detect, upload to Google Drive, and update markdown @@ -54,11 +63,7 @@ Function Publish-MarkdownDriveImages $uploadParams = @{ FilePath = $image.LocalPath FileName = $image.FileName - Force = $false - } - - if ($Force) { - $uploadParams.Force = $true + Force = $Force } $uploadResult = Add-GoogleDriveFile @uploadParams @@ -84,13 +89,20 @@ Function Publish-MarkdownDriveImages Write-Warning "Failed to upload image $($image.FileName): $($_.Exception.Message)$([Environment]::NewLine)$($_.ErrorDetails | ConvertTo-Json -Depth 10)" } } + } + + # Update the markdown file with new URLs, or ensure that OutFile is created if specified + if (($imageMappings -and $imageMappings.Count -gt 0) -or $OutFile) { + $updateParams = @{ + File = $File + ImageMappings = $imageMappings + OutFile = $OutFile + } - # Update the markdown file with new URLs - if ($imageMappings -and $imageMappings.Count -gt 0) { - $updated = Update-MarkdownImages -File $File -ImageMappings $imageMappings - if ($updated) { - Write-Verbose "Updated markdown file with $($imageMappings.Count) new image URLs" - } + $updated = Update-MarkdownImages @updateParams + if ($updated) { + $targetFile = if ($OutFile) { $OutFile } else { $File } + Write-Verbose "Updated markdown file $targetFile with $($imageMappings.Count) new image URLs" } } diff --git a/src/tests/Publish-MarkdownDriveImages.Tests.ps1 b/src/tests/Publish-MarkdownDriveImages.Tests.ps1 index f94b4f6..11da0f9 100644 --- a/src/tests/Publish-MarkdownDriveImages.Tests.ps1 +++ b/src/tests/Publish-MarkdownDriveImages.Tests.ps1 @@ -509,4 +509,74 @@ Describe "Publish-MarkdownDriveImages" { Should -InvokeVerifiable } } + + Context "OutFile Parameter" { + + BeforeEach { + InModuleScope PSBlogger { + Mock Add-GoogleDriveFile { + return @{ id = "123"; PublicUrl = "https://drive.google.com/uc?export=view&id=123" } + } + Mock Set-GoogleDriveFilePermission { } + Mock New-GoogleDriveFilePermission { return @{ role = "reader"; type = "anyone" } } + } + } + + It "Should write updated markdown to OutFile when specified" { + # arrange + $outFile = "TestDrive:\output-with-updated-images.md" + + # act + Publish-MarkdownDriveImages -File $fileWithSingleImage -OutFile $outFile + + # assert + Test-Path $outFile | Should -Be $true + $outFileContent = Get-Content $outFile -Raw + $outFileContent | Should -Match "https://drive.google.com/uc\?export=view&id=123" + } + + It "Should preserve original file content when OutFile is specified" { + # arrange + $outFile = "TestDrive:\modified-file.md" + + # act + Publish-MarkdownDriveImages -File $fileWithSingleImage -OutFile $outFile + + # assert + $originalFileContent = Get-Content $fileWithSingleImage -Raw + $originalFileContent.TrimEnd() | Should -Be $fileWithSingleImageMarkdown.TrimEnd() + $originalFileContent | Should -Not -Match "https://drive.google.com" + } + + It "Should always create OutFile parameter even when no images are found" { + # arrange + $markdownFile = "TestDrive:\no-images.md" + $outFile = "TestDrive:\no-images-output.md" + $markdownContent = "# Test Post`n`nThis post has no images." + Set-MarkdownFile $markdownFile $markdownContent + + # act + $result = Publish-MarkdownDriveImages -File $markdownFile -OutFile $outFile + + # assert + $result | Should -Be @() + Test-Path $outFile | Should -Be $true + } + + It "Should handle invalid OutFile path gracefully" { + # arrange + $invalidOutFile = "InvalidDrive:\nonexistent\path\output.md" + + InModuleScope PSBlogger { + Mock Add-GoogleDriveFile { + return @{ id = "123"; PublicUrl = "https://drive.google.com/uc?export=view&id=123" } + } + Mock Set-GoogleDriveFilePermission { } + Mock New-GoogleDriveFilePermission { return @{ role = "reader"; type = "anyone" } } + } + + # act & assert + { Publish-MarkdownDriveImages -File $fileWithSingleImage -OutFile $invalidOutFile } | Should -Throw + } + } } From 2d6bbf34f78921d0b1cf585c74f0dc16ca402d37 Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:06:06 -0400 Subject: [PATCH 4/4] Added -PreserveOriginal to Publish-MarkdownBloggerPost --- src/public/Publish-MarkdownBloggerPost.ps1 | 115 +++++++++------ .../Publish-MarkdownBloggerPost.Tests.ps1 | 132 ++++++++++++++++++ 2 files changed, 208 insertions(+), 39 deletions(-) diff --git a/src/public/Publish-MarkdownBloggerPost.ps1 b/src/public/Publish-MarkdownBloggerPost.ps1 index 9620798..c11cf7c 100644 --- a/src/public/Publish-MarkdownBloggerPost.ps1 +++ b/src/public/Publish-MarkdownBloggerPost.ps1 @@ -22,6 +22,10 @@ .PARAMETER Force If specified, will overwrite existing images in Google Drive with the same name. +.PARAMETER PreserveOriginal + If specified, preserves the original markdown file with local image references. The file with Google Drive URLs + is used only for HTML conversion and blog publishing. + .PARAMETER Open If specified, launches a browser to view the post after publishing. @@ -42,8 +46,12 @@ Publish-MarkdownBloggerPost -File "my-post.md" -Open .EXAMPLE - # publish or update a draft, launching a web browser with the page preview + # publish or update a post, launching a web browser with the page preview Publish-MarkdownBloggerPost -File "my-post.md" -Draft -Open + +.EXAMPLE + # publish or update a post while preserving local image references in the original file + Publish-MarkdownBloggerPost -File "my-post.md" -PreserveOriginal #> Function Publish-MarkdownBloggerPost { @@ -68,11 +76,15 @@ Function Publish-MarkdownBloggerPost [Parameter(Mandatory=$false)] [switch]$Force, + [Parameter(Mandatory=$false)] + [switch]$PreserveOriginal, + [Parameter(Mandatory=$false)] [switch]$Open ) + # Obtain -BlogId from User Preferences if available if (!$PSBoundParameters.ContainsKey("BlogId")) { $BlogId = $BloggerSession.BlogId @@ -81,6 +93,7 @@ Function Publish-MarkdownBloggerPost } } + # Obtain -ExcludeLabesl from User Preferences if availabe if (!$PSBoundParameters.ContainsKey("ExcludeLabels")) { $ExcludeLabels = $BloggerSession.ExcludeLabels } @@ -89,48 +102,72 @@ Function Publish-MarkdownBloggerPost $postInfo = Get-MarkdownFrontMatter -File $File # Process images: detect, upload to Google Drive, and update markdown - $imageMappings = Publish-MarkdownDriveImages -File $File -AttachmentsDirectory $AttachmentsDirectory -Force:$Force + $tempFile = $null + $fileForConversion = $File - # convert from markdown to html file - $content = ConvertTo-HtmlFromMarkdown -File $File - - # TODO: Extension point to apply corrections to HTML - # - eg: remove instances of
 from the content
-
-  # construct args
-  $postArgs = @{
-    BlogId = $BlogId
-    Title = $postInfo.title
-    Content = $content
-    Draft = $Draft
-    Open = $Open
-  }
+  try {
+    if ($PreserveOriginal) {
+      # Create temporary file for modified content
+      $tempFile = [System.IO.Path]::GetTempFileName()
+      $tempFile = [System.IO.Path]::ChangeExtension($tempFile, ".md")
+      Write-Verbose "Using temporary file for image processing: $tempFile"
+            
+      # publishes markdown drive images to google drive and writes the output to the specified OutFile
+      $imageMappings = Publish-MarkdownDriveImages -File $File -AttachmentsDirectory $AttachmentsDirectory -Force:$Force -OutFile $tempFile
+      $fileForConversion = $tempFile
+    }
+    else {
+      $imageMappings = Publish-MarkdownDriveImages -File $File -AttachmentsDirectory $AttachmentsDirectory -Force:$Force
+    }
+    
+    # convert from markdown to html file
+    $content = ConvertTo-HtmlFromMarkdown -File $fileForConversion
+
+    # TODO: Extension point to apply corrections to HTML
+    # - eg: remove instances of 
 from the content
+
+    # construct args
+    $postArgs = @{
+      BlogId = $BlogId
+      Title = $postInfo.title
+      Content = $content
+      Draft = $Draft
+      Open = $Open
+    }
 
-  if ($postInfo["postId"]) {
-    $postArgs.PostId = $postInfo.postid
-  }
+    if ($postInfo["postId"]) {
+      $postArgs.PostId = $postInfo.postid
+    }
 
-  if ($postInfo["tags"]) {
-    $postArgs.Labels = [array]$postInfo.tags | Where-Object { $_ -notin $ExcludeLabels }
-  }
-  
-  Write-Verbose "Publishing blogger post with args: $($postArgs | ConvertTo-Json -Depth 5)"
-  $post = Publish-BloggerPost @postArgs
-
-  # update post id
-  $postInfo["postId"] = $post.id
-  if ($Draft) {
-    Write-Verbose "Adding 'wip' to front matter"
-    $postInfo["wip"] = $true
-  } else {
-    if ($postInfo["wip"]) {
-      Write-Verbose "Removing 'wip' from front matter"
-      $postInfo.Remove("wip")
+    if ($postInfo["tags"]) {
+      $postArgs.Labels = [array]$postInfo.tags | Where-Object { $_ -notin $ExcludeLabels }
+    }
+    
+    Write-Verbose "Publishing blogger post with args: $($postArgs | ConvertTo-Json -Depth 5)"
+    $post = Publish-BloggerPost @postArgs
+
+    # update post id
+    $postInfo["postId"] = $post.id
+    if ($Draft) {
+      Write-Verbose "Adding 'wip' to front matter"
+      $postInfo["wip"] = $true
+    } else {
+      if ($postInfo["wip"]) {
+        Write-Verbose "Removing 'wip' from front matter"
+        $postInfo.Remove("wip")
+      }
     }
-  }
 
-  Write-Verbose "Updating front matter with post id: $($postInfo['postId'])"
-  Set-MarkdownFrontMatter -File $File -Replace $postInfo
+    Write-Verbose "Updating front matter with post id: $($postInfo['postId'])"
+    Set-MarkdownFrontMatter -File $File -Replace $postInfo
 
-  return $post
+    return $post
+  }
+  finally {
+    # Clean up temporary file if it was created
+    if ($tempFile -and (Test-Path $tempFile)) {
+      Write-Verbose "Cleaning up temporary file: $tempFile"
+      Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
+    }
+  }
 }
\ No newline at end of file
diff --git a/src/tests/Publish-MarkdownBloggerPost.Tests.ps1 b/src/tests/Publish-MarkdownBloggerPost.Tests.ps1
index 29670a8..5321bfb 100644
--- a/src/tests/Publish-MarkdownBloggerPost.Tests.ps1
+++ b/src/tests/Publish-MarkdownBloggerPost.Tests.ps1
@@ -327,4 +327,136 @@ postId: "123456"
     }
 
   }
+
+  Context "PreserveOriginal Parameter" {
+
+    BeforeEach {
+      InModuleScope PSBlogger {
+        $script:capturedTempFile = $null
+
+        # Simulate the behavior of Publish-MarkdownDriveImages with our temporary file
+        # and capture the tempoary file name
+        Mock Publish-MarkdownDriveImages -ParameterFilter {
+          $OutFile -ne $null -and $OutFile -ne "TestDrive:\validfile.md"
+        } { 
+          $script:capturedTempFile = $OutFile
+          # simulate creating the OutFile
+          New-Item -Path $OutFile -ItemType File -Force | Out-Null
+          return @()
+        } -Verifiable
+
+        # ensure temporary file is converted to html
+        Mock ConvertTo-HtmlFromMarkdown -ParameterFilter { $File -ne "TestDrive:\validfile.md" } {
+          return "dummy"
+        } -Verifiable
+      }
+    }
+
+    It "Should not modify original file when PreserveOriginal is specified" {
+      # arrange
+      InModuleScope PSBlogger {
+
+        # front matter should be modified on the original file
+        Mock Set-MarkdownFrontMatter -ParameterFilter { $File -like "*validfile.md"} -Verifiable
+      }
+
+      # act
+      Publish-MarkdownBloggerPost -File $validFile -BlogId 1234 -PreserveOriginal
+
+      # assert
+      Should -InvokeVerifiable
+    }
+
+    It "Should create and clean up temporary file when PreserveOriginal is used" {
+      # arrange
+
+      # act
+      Publish-MarkdownBloggerPost -File $validFile -BlogId 1234 -PreserveOriginal
+
+      # assert
+      InModuleScope PSBlogger {
+        $script:capturedTempFile | Should -Not -BeNullOrEmpty
+        $script:capturedTempFile | Should -Match "tmp.*\.md$"
+        Test-Path $script:capturedTempFile | Should -Be $false  # Should be cleaned up
+      }
+    }
+
+    It "Should clean up temporary file even when conversion fails" {
+      # arrange
+      InModuleScope PSBlogger {
+        # override existing mock to simulate conversion failure
+        Mock ConvertTo-HtmlFromMarkdown -ParameterFilter { $File -ne "TestDrive:\validfile.md" } {
+          throw "Conversion failed"
+        }
+      }
+
+      # act & assert
+      { 
+        Publish-MarkdownBloggerPost -File $validFile -BlogId 1234 -PreserveOriginal 
+      } | Should -Throw "Conversion failed"
+      
+      # assert cleanup occurred
+      InModuleScope PSBlogger {
+        if ($script:capturedTempFile) {
+          Test-Path $script:capturedTempFile | Should -Be $false
+        }
+      }
+    }
+
+    It "Should update front matter in original file even with PreserveOriginal" {
+      # arrange
+
+      InModuleScope PSBlogger {
+        Mock Set-MarkdownFrontMatter -ParameterFilter { $Replace.postId -eq "123" } { } -Verifiable
+      }
+
+      # act
+      Publish-MarkdownBloggerPost -File $validFile -BlogId 1234 -PreserveOriginal
+
+      # assert
+      Should -InvokeVerifiable
+    }
+  }
+
+  Context "Image Resolution when using PreserveOriginal" {
+
+    It "Should locate images relative to the original file" {
+      # arrange
+      # setup a markdown file with an image that refers to a file relative to the markdown file
+      $imageFile = New-TestImage "TestDrive:\note\image.png"
+      $markdownFile = "TestDrive:\note\post.md"
+      $markdownContent = @(
+        "# Test Post"
+        ""
+        "Image reference: ![Image Relative to note](image.png)"
+      ) -join "`n"
+      Set-MarkdownFile $markdownFile $markdownContent
+
+      InModuleScope PSBlogger {
+        # mock the image upload to google drive
+
+        Mock Add-GoogleDriveFile -Verifiable {
+          return @{
+            id = "12345"
+            PublicUrl = "https://drive.google.com/uc?export=view&id=12345"
+          }
+        }
+        Mock Set-GoogleDriveFilePermission {}
+
+        Mock Publish-BloggerPost {
+          # return the passed in content in the result
+          return @{ id = "123"; content = $Content}
+        }
+
+        # remove existing mock defined for ConvertTo-HtmlFromMarkdown
+        Remove-Alias ConvertTo-HtmlFromMarkdown
+      }
+
+      # act
+      $result = Publish-MarkdownBloggerPost -File $markdownFile -BlogId 1234 -PreserveOriginal
+
+      # assert
+      $result.content | Should -BeLike "*https://drive.google.com*"
+    }
+  }
 }