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/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/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/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" {
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*"
+    }
+  }
 }
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
+    }
+  }
 }
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" {