diff --git a/src/Install/GuidelineWriter.php b/src/Install/GuidelineWriter.php
index 2851e52..a9115ea 100644
--- a/src/Install/GuidelineWriter.php
+++ b/src/Install/GuidelineWriter.php
@@ -68,6 +68,11 @@ public function write(string $guidelines): int
$newContent = $frontMatter.$existingContent.$separatingNewlines.$replacement;
}
+ // Ensure file content ends with a newline
+ if (! str_ends_with($newContent, "\n")) {
+ $newContent .= "\n";
+ }
+
if (ftruncate($handle, 0) === false || fseek($handle, 0) === -1) {
throw new RuntimeException("Failed to reset file pointer: {$filePath}");
}
diff --git a/tests/Unit/Install/GuidelineWriterTest.php b/tests/Unit/Install/GuidelineWriterTest.php
index 6cab099..d2bb040 100644
--- a/tests/Unit/Install/GuidelineWriterTest.php
+++ b/tests/Unit/Install/GuidelineWriterTest.php
@@ -60,7 +60,7 @@
$writer->write('test guidelines content');
$content = file_get_contents($tempFile);
- expect($content)->toBe("\ntest guidelines content\n");
+ expect($content)->toBe("\ntest guidelines content\n\n");
unlink($tempFile);
});
@@ -77,7 +77,7 @@
$writer->write('new guidelines');
$content = file_get_contents($tempFile);
- expect($content)->toBe("# Existing content\n\nSome text here.\n\n===\n\n\nnew guidelines\n");
+ expect($content)->toBe("# Existing content\n\nSome text here.\n\n===\n\n\nnew guidelines\n\n");
unlink($tempFile);
});
@@ -95,7 +95,30 @@
$writer->write('updated guidelines');
$content = file_get_contents($tempFile);
- expect($content)->toBe("# Header\n\n\nupdated guidelines\n\n\n# Footer");
+ expect($content)->toBe("# Header\n\n\nupdated guidelines\n\n\n# Footer\n");
+
+ unlink($tempFile);
+});
+
+test('it avoids adding extra newline if one already exists', function () {
+ $tempFile = tempnam(sys_get_temp_dir(), 'boost_test_');
+ $initialContent = "# Header\n\n\nold guidelines\n\n\n# Footer\n";
+ file_put_contents($tempFile, $initialContent);
+
+ $agent = Mockery::mock(Agent::class);
+ $agent->shouldReceive('guidelinesPath')->andReturn($tempFile);
+ $agent->shouldReceive('frontmatter')->andReturn(false);
+
+ $writer = new GuidelineWriter($agent);
+ $writer->write('updated guidelines');
+
+ $content = file_get_contents($tempFile);
+ expect($content)->toBe("# Header\n\n\nupdated guidelines\n\n\n# Footer\n");
+
+ // Assert no double newline at the end
+ expect(substr($content, -2))->not->toBe("\n\n");
+ // Assert still ends with exactly one newline
+ expect(substr($content, -1))->toBe("\n");
unlink($tempFile);
});
@@ -114,7 +137,7 @@
$content = file_get_contents($tempFile);
// Should replace in-place, preserving structure
- expect($content)->toBe("Start\n\nsingle line\n\nEnd");
+ expect($content)->toBe("Start\n\nsingle line\n\nEnd\n");
unlink($tempFile);
});
@@ -133,7 +156,7 @@
$content = file_get_contents($tempFile);
// Should replace first occurrence, second block remains untouched due to non-greedy matching
- expect($content)->toBe("Start\n\nreplacement\n\nMiddle\n\nsecond\n\nEnd");
+ expect($content)->toBe("Start\n\nreplacement\n\nMiddle\n\nsecond\n\nEnd\n");
unlink($tempFile);
});
@@ -165,7 +188,7 @@
$writer->write('my guidelines');
$content = file_get_contents($tempFile);
- expect($content)->toBe("# Title\n\nParagraph 1\n\nParagraph 2\n\n===\n\n\nmy guidelines\n");
+ expect($content)->toBe("# Title\n\nParagraph 1\n\nParagraph 2\n\n===\n\n\nmy guidelines\n\n");
unlink($tempFile);
});
@@ -182,7 +205,7 @@
$writer->write('first guidelines');
$content = file_get_contents($tempFile);
- expect($content)->toBe("\nfirst guidelines\n");
+ expect($content)->toBe("\nfirst guidelines\n\n");
unlink($tempFile);
});
@@ -199,7 +222,7 @@
$writer->write('clean guidelines');
$content = file_get_contents($tempFile);
- expect($content)->toBe("\nclean guidelines\n");
+ expect($content)->toBe("\nclean guidelines\n\n");
unlink($tempFile);
});
@@ -218,7 +241,7 @@
expect($result)->toBe(GuidelineWriter::REPLACED);
$content = file_get_contents($tempFile);
- expect($content)->toBe("# Title\n\n\nShould not be touched\n\n\n\nnew guidelines\n\n\n\nAlso untouched\n");
+ expect($content)->toBe("# Title\n\n\nShould not be touched\n\n\n\nnew guidelines\n\n\n\nAlso untouched\n\n");
unlink($tempFile);
});
@@ -248,7 +271,7 @@
->and($content)->toContain('More content here.');
// Verify exact structure
- expect($content)->toBe("# My Project\n\n\nupdated guidelines from boost\n\n\n# User Added Section\nThis content was added by the user after the guidelines.\n\n## Another user section\nMore content here.");
+ expect($content)->toBe("# My Project\n\n\nupdated guidelines from boost\n\n\n# User Added Section\nThis content was added by the user after the guidelines.\n\n## Another user section\nMore content here.\n");
unlink($tempFile);
});
@@ -269,7 +292,7 @@
$writer->write('new guidelines');
$content = file_get_contents($tempFile);
- expect($content)->toBe("---\nalwaysApply: true\n---\n# Existing content\n\nSome text here.\n\n===\n\n\nnew guidelines\n");
+ expect($content)->toBe("---\nalwaysApply: true\n---\n# Existing content\n\nSome text here.\n\n===\n\n\nnew guidelines\n\n");
unlink($tempFile);
});
@@ -286,7 +309,7 @@
$writer->write('new guidelines');
$content = file_get_contents($tempFile);
- expect($content)->toBe("---\ncustomOption: true\n---\n# Existing content\n\nSome text here.\n\n===\n\n\nnew guidelines\n");
+ expect($content)->toBe("---\ncustomOption: true\n---\n# Existing content\n\nSome text here.\n\n===\n\n\nnew guidelines\n\n");
unlink($tempFile);
});
@@ -304,7 +327,7 @@
expect($result)->toBe(GuidelineWriter::NEW);
$content = file_get_contents($tempFile);
- expect($content)->toBe("# Existing content\n\nSome text here.\n\n===\n\n\nnew guidelines\n");
+ expect($content)->toBe("# Existing content\n\nSome text here.\n\n===\n\n\nnew guidelines\n\n");
unlink($tempFile);
});