diff --git a/package.json b/package.json index cf29a18..76b7e5e 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,7 @@ "astro": "astro", "new": "node scripts/new-post.js", "cover": "node scripts/generate-cover.js", - "copy-originals": "node scripts/copy-originals.js", - "compress": "node scripts/compress-images.js" + "copy-originals": "node scripts/copy-originals.js" }, "dependencies": { "@astrojs/rss": "^4.0.14", diff --git a/scripts/compress-images.js b/scripts/compress-images.js deleted file mode 100644 index 6f1b49c..0000000 --- a/scripts/compress-images.js +++ /dev/null @@ -1,155 +0,0 @@ -import sharp from "sharp"; -import { readdir, stat } from "fs/promises"; -import { join, extname } from "path"; -import { fileURLToPath } from "url"; -import { dirname } from "path"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const blogDir = join(__dirname, "..", "src", "content", "blog"); - -// Compression settings -const PNG_QUALITY = 80; // 1-100, lower = smaller file -const PNG_COMPRESSION = 9; // 0-9, higher = more compression (slower) -const MIN_SAVINGS_PERCENT = 5; // Only replace if we save at least this much - -async function findImages(dir) { - const images = []; - - async function scan(currentDir) { - const entries = await readdir(currentDir, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = join(currentDir, entry.name); - - if (entry.isDirectory()) { - await scan(fullPath); - } else if (entry.isFile()) { - const ext = extname(entry.name).toLowerCase(); - if ([".png", ".jpg", ".jpeg"].includes(ext)) { - images.push(fullPath); - } - } - } - } - - await scan(dir); - return images; -} - -async function compressImage(imagePath) { - const ext = extname(imagePath).toLowerCase(); - const originalStats = await stat(imagePath); - const originalSize = originalStats.size; - - let pipeline = sharp(imagePath); - const metadata = await pipeline.metadata(); - - // Skip if already very small - if (originalSize < 10000) { - return { skipped: true, reason: "already small" }; - } - - let outputBuffer; - - if (ext === ".png") { - // For PNGs: use palette-based compression when possible - outputBuffer = await sharp(imagePath) - .png({ - compressionLevel: PNG_COMPRESSION, - palette: true, - quality: PNG_QUALITY, - effort: 10, // max effort for smallest size - }) - .toBuffer(); - } else if (ext === ".jpg" || ext === ".jpeg") { - outputBuffer = await sharp(imagePath) - .jpeg({ - quality: 85, - mozjpeg: true, - }) - .toBuffer(); - } - - const newSize = outputBuffer.length; - const savingsPercent = ((originalSize - newSize) / originalSize) * 100; - - if (savingsPercent >= MIN_SAVINGS_PERCENT) { - await sharp(outputBuffer).toFile(imagePath); - return { - compressed: true, - originalSize, - newSize, - savingsPercent: savingsPercent.toFixed(1), - }; - } - - return { - skipped: true, - reason: `savings too small (${savingsPercent.toFixed(1)}%)`, - }; -} - -function formatBytes(bytes) { - if (bytes < 1024) return bytes + " B"; - if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"; - return (bytes / (1024 * 1024)).toFixed(1) + " MB"; -} - -async function main() { - const args = process.argv.slice(2); - let targetDir = blogDir; - - // Allow passing a specific directory - if (args[0]) { - targetDir = args[0]; - } - - console.log(`\nScanning for images in: ${targetDir}\n`); - - const images = await findImages(targetDir); - console.log(`Found ${images.length} images\n`); - - let totalOriginal = 0; - let totalNew = 0; - let compressedCount = 0; - let skippedCount = 0; - - for (const imagePath of images) { - const relativePath = imagePath.replace(blogDir, "").replace(/^[/\\]/, ""); - process.stdout.write(`Processing: ${relativePath}... `); - - try { - const result = await compressImage(imagePath); - - if (result.compressed) { - console.log( - `✓ ${formatBytes(result.originalSize)} → ${formatBytes(result.newSize)} (-${result.savingsPercent}%)` - ); - totalOriginal += result.originalSize; - totalNew += result.newSize; - compressedCount++; - } else { - console.log(`⊘ skipped (${result.reason})`); - skippedCount++; - } - } catch (err) { - console.log(`✗ error: ${err.message}`); - } - } - - console.log(`\n${"─".repeat(60)}`); - console.log(`Compressed: ${compressedCount} images`); - console.log(`Skipped: ${skippedCount} images`); - - if (compressedCount > 0) { - const totalSavings = totalOriginal - totalNew; - const totalPercent = ((totalSavings / totalOriginal) * 100).toFixed(1); - console.log( - `Total savings: ${formatBytes(totalSavings)} (${totalPercent}%)` - ); - } - - console.log(); -} - -main().catch(console.error); diff --git a/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/assets.png b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/assets.png new file mode 100644 index 0000000..75b0d4a Binary files /dev/null and b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/assets.png differ diff --git a/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/content.png b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/content.png new file mode 100644 index 0000000..cde02c0 Binary files /dev/null and b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/content.png differ diff --git a/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/cover.png b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/cover.png new file mode 100644 index 0000000..c499e59 Binary files /dev/null and b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/cover.png differ diff --git a/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/dependencies.png b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/dependencies.png new file mode 100644 index 0000000..188b29e Binary files /dev/null and b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/dependencies.png differ diff --git a/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/index.md b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/index.md new file mode 100644 index 0000000..808c127 --- /dev/null +++ b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/index.md @@ -0,0 +1,75 @@ +--- +title: "Introducing the 'VSIX Manifest Designer' Visual Studio Extension" +date: "2026-01-29T12:00:00-05:00" +categories: [dotnet, csharp, extensibility, visualstudio] +description: "Tired of editing raw XML in your VSIX manifest files? Me too. So I built a modern, visual designer that integrates seamlessly with Visual Studio 2022 and 2026." +subtitle: "A modern designer for extension developers!" +--- + +I'll be honest - I've been building Visual Studio extensions for years now, and one thing that has always bugged me is editing the `source.extension.vsixmanifest` file. Sure, Visual Studio has a built-in designer, but let's just say it hasn't aged gracefully. It feels clunky, looks outdated, and doesn't play nice with modern SDK-style VSIX projects (like the ones you can create with [VsixSdk](https://www.codingwithcalvin.net/sdk-style-projects-for-your-visual-studio-extensions/)). + +So, I did what any reasonable developer would do - I built my own. + +Introducing "[VSIX Manifest Designer](https://marketplace.visualstudio.com/items?itemName=CodingWithCalvin.VS-VsixManifestDesigner)", an extension for Visual Studio 2022 and 2026 that provides a clean, modern, WPF-based designer for your VSIX manifest files. It seamlessly integrates with Visual Studio's Light, Dark, and Blue themes, and has first-class support for SDK-style VSIX projects. + +## What Does It Do? + +When you open a `source.extension.vsixmanifest` file, instead of getting the ancient built-in designer (or worse, raw XML), you get a clean sidebar-based interface with six sections to manage every aspect of your extension's manifest. + +### Metadata + +This is where you configure the core identity of your extension - things like the ID, version, publisher, display name, and description. You can also set up your icon, preview image, tags, license, getting started guide, release notes, and more. + +![Metadata View](./metadata.png) + +### Installation Targets + +Here you define which versions of Visual Studio your extension supports. You can specify target IDs (like `Microsoft.VisualStudio.Community`), version ranges, and even target specific architectures (AMD64, ARM64). + +![Installation Targets View](./install-targets.png) + +### Dependencies + +If your extension depends on other extensions, this is where you configure those relationships. You can add dependencies manually, reference installed extensions, or even point to project references for SDK-style projects. + +![Dependencies View](./dependencies.png) + +### Prerequisites + +Prerequisites define the Visual Studio components that must be installed for your extension to work. Think of things like the .NET desktop development workload or specific SDK components. + +![Prerequisites View](./prerequisites.png) + +### Assets + +This is probably the section you'll use the most. Assets are the actual "stuff" your extension includes - things like your VsPackage, MEF components, analyzers, CodeLens providers, project templates, item templates, and more. The designer provides smart configuration based on the asset type you select, and includes a project picker for SDK-style projects. + +![Assets View](./assets.png) + +### Content + +If you're building project or item templates, this section lets you configure the template declarations that tie your template assets together. + +![Content View](./content.png) + +## VsixSdk Integration + +One thing I'm particularly proud of is the deep integration with [VsixSdk](https://www.nuget.org/packages/CodingWithCalvin.VsixSdk). If you're using SDK-style projects for your extension development (and you should be!), the designer automatically detects this and provides smart project enumeration, automatic project reference handling for assets and dependencies, and proper template asset path validation. + +## Theme Support + +I put a lot of effort into making sure this designer looks and feels like a native part of Visual Studio. All the UI controls use dynamic theme brushes via `VsBrushes` and `VsColors`, so whether you're a Light mode person, a Dark mode person, or one of those Blue theme people (no judgment), it'll look right at home. + +## A Few Caveats + +This is the first release, and while I've been using it myself for a while now, I'm sure there are edge cases I haven't hit yet. If you run into any issues or have suggestions for improvements, please let me know! + +## Wrapping Up + +If you're building Visual Studio extensions and you're tired of wrestling with the built-in manifest designer (or raw XML), give this a shot. It's designed to make your life easier, and it's built by someone who actually uses it every day. + +Feel free to check it out, and let me know if you have any suggestions for it - I realize it could seem like its "done", but you never know what ideas folks might have! + +And, of course, [it's open source](https://github.com/CodingWithCalvin/VS-VsixManifestDesigner), so feel free to peruse the source code, create issues, and have discussions on ways we can make this tool even better. PRs accepted, too, if you're into that sort of thing 😉. + +Thanks for reading, friends! diff --git a/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/install-targets.png b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/install-targets.png new file mode 100644 index 0000000..0708082 Binary files /dev/null and b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/install-targets.png differ diff --git a/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/metadata.png b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/metadata.png new file mode 100644 index 0000000..67fb5a5 Binary files /dev/null and b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/metadata.png differ diff --git a/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/prerequisites.png b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/prerequisites.png new file mode 100644 index 0000000..999a4a6 Binary files /dev/null and b/src/content/blog/2026/introducing-the-vsix-manifest-designer-visual-studio-extension/prerequisites.png differ