<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged amazon-q</title>
<link>https://coderlegion.com/tag/amazon-q</link>
<description>Powered by Question2Answer</description>
<item>
<title>How I Built and Published a VS Code Extension to the Marketplace</title>
<link>https://coderlegion.com/16422/how-i-built-and-published-a-vs-code-extension-to-the-marketplace</link>
<description>&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;I recently built &lt;strong&gt;Q Log Session Viewer&lt;/strong&gt; — a VS Code extension that reads Amazon Q chat history and debug logs from your local machine and displays them in a browsable, filterable UI right inside VS Code. In this post I'll walk through every step: scaffolding the project, writing the extension code, packaging it, and publishing it to the VS Code Marketplace.&lt;/p&gt;
&lt;p&gt;By the end you'll have a clear mental model of how VS Code extensions work and a repeatable process for publishing your own.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-we-re-building&quot;&gt;What We're Building&lt;/h3&gt;
&lt;p&gt;The extension adds an &lt;strong&gt;Activity Bar icon&lt;/strong&gt; (sidebar panel) and a &lt;strong&gt;full editor panel&lt;/strong&gt; that reads:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;~/.aws/amazonq/history/chat-history-*.json&lt;/code&gt; — Amazon Q chat history&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;%APPDATA%\Code\logs\...\Amazon Q Logs.log&lt;/code&gt; — VS Code extension host logs&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It parses those files and renders sessions as cards, with drill-down into individual log entries.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/siddheshp/q-log-session-viewer-assets/main/screenshots/sessions-view.png&quot; alt=&quot;Sessions View&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sessions overview — chat history and log sessions shown as cards&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/siddheshp/q-log-session-viewer-assets/main/screenshots/entries-view.png&quot; alt=&quot;Entries View&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Entry detail view — filter by category, search, and inspect full JSON&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;Before starting, install:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; 18+&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://code.visualstudio.com/&quot;&gt;VS Code&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;Yeoman&lt;/strong&gt; scaffolder and VS Code extension generator (optional but helpful):&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;npm install -g yo generator-code
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-1-scaffold-the-project&quot;&gt;Step 1 — Scaffold the Project&lt;/h3&gt;
&lt;p&gt;Run the Yeoman generator and answer the prompts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yo code
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Choose:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;New Extension (TypeScript)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name: &lt;code&gt;q-log-session-viewer&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identifier: &lt;code&gt;q-log-session-viewer&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Description: &lt;em&gt;View and analyze local Q-related debug logs and chat history from VS Code&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Initialize git: Yes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bundle with webpack/esbuild: &lt;strong&gt;esbuild&lt;/strong&gt; (faster builds)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; If you prefer to skip Yeoman, just create the folder structure manually. The generator only saves a few minutes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The generated structure looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;q-log-session-viewer/
├── src/
│   └── extension.ts        ← entry point
├── resources/              ← icons, screenshots
├── .vscodeignore
├── esbuild.js
├── package.json
└── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-configure-package-json&quot;&gt;Step 2 — Configure &lt;code&gt;package.json&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; is the heart of a VS Code extension. It declares commands, views, menus, and metadata that VS Code reads at install time.&lt;/p&gt;
&lt;p&gt;Here is the full &lt;code&gt;package.json&lt;/code&gt; for this extension:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;name&quot;: &quot;q-log-session-viewer&quot;,
  &quot;displayName&quot;: &quot;Q Log Session Viewer (Unofficial)&quot;,
  &quot;description&quot;: &quot;View and analyze local Q-related debug logs and chat history from VS Code&quot;,
  &quot;version&quot;: &quot;0.1.1&quot;,
  &quot;publisher&quot;: &quot;SiddheshPrabhugaonkar&quot;,
  &quot;author&quot;: {
    &quot;name&quot;: &quot;Siddhesh Prabhugankar&quot;,
    &quot;url&quot;: &quot;https://github.com/siddheshp&quot;
  },
  &quot;license&quot;: &quot;MIT&quot;,
  &quot;icon&quot;: &quot;resources/icon.png&quot;,
  &quot;galleryBanner&quot;: { &quot;color&quot;: &quot;#232F3E&quot;, &quot;theme&quot;: &quot;dark&quot; },
  &quot;engines&quot;: { &quot;vscode&quot;: &quot;^1.85.0&quot; },
  &quot;categories&quot;: [&quot;Debuggers&quot;, &quot;Other&quot;],
  &quot;keywords&quot;: [&quot;logs&quot;, &quot;debug&quot;, &quot;chat&quot;, &quot;viewer&quot;, &quot;analysis&quot;],
  &quot;activationEvents&quot;: [],
  &quot;main&quot;: &quot;./out/extension.js&quot;,
  &quot;contributes&quot;: {
    &quot;commands&quot;: [
      {
        &quot;command&quot;: &quot;amazonq-logviewer.open&quot;,
        &quot;title&quot;: &quot;Q Log Session Viewer: Open&quot;,
        &quot;icon&quot;: {
          &quot;light&quot;: &quot;resources/icon-sidebar-light.svg&quot;,
          &quot;dark&quot;: &quot;resources/icon-sidebar-dark.svg&quot;
        }
      },
      {
        &quot;command&quot;: &quot;amazonq-logviewer.refresh&quot;,
        &quot;title&quot;: &quot;Q Log Session Viewer: Refresh&quot;,
        &quot;icon&quot;: &quot;$(refresh)&quot;
      }
    ],
    &quot;viewsContainers&quot;: {
      &quot;activitybar&quot;: [
        {
          &quot;id&quot;: &quot;amazonq-logviewer&quot;,
          &quot;title&quot;: &quot;Q Logs&quot;,
          &quot;icon&quot;: &quot;resources/icon-sidebar-dark.svg&quot;
        }
      ]
    },
    &quot;views&quot;: {
      &quot;amazonq-logviewer&quot;: [
        {
          &quot;type&quot;: &quot;webview&quot;,
          &quot;id&quot;: &quot;amazonq-logviewer.viewer&quot;,
          &quot;name&quot;: &quot;Log Viewer&quot;
        }
      ]
    },
    &quot;menus&quot;: {
      &quot;editor/title&quot;: [
        { &quot;command&quot;: &quot;amazonq-logviewer.open&quot;, &quot;group&quot;: &quot;navigation&quot; }
      ]
    }
  },
  &quot;scripts&quot;: {
    &quot;vscode:prepublish&quot;: &quot;npm run compile&quot;,
    &quot;compile&quot;: &quot;node esbuild.js&quot;,
    &quot;watch&quot;: &quot;node esbuild.js --watch&quot;,
    &quot;package&quot;: &quot;vsce package&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@types/node&quot;: &quot;^20.11.0&quot;,
    &quot;@types/vscode&quot;: &quot;^1.85.0&quot;,
    &quot;@vscode/vsce&quot;: &quot;^3.9.1&quot;,
    &quot;esbuild&quot;: &quot;^0.20.0&quot;,
    &quot;sharp&quot;: &quot;^0.34.5&quot;,
    &quot;typescript&quot;: &quot;^5.3.0&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key things to understand:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Field &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;publisher&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Must match your Marketplace publisher ID exactly&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;engines.vscode&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Minimum VS Code version required&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;activationEvents: []&lt;/code&gt; &lt;/td&gt;&lt;td&gt;With modern VS Code, contributed commands/views can activate the extension when used&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;contributes.viewsContainers&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Registers the Activity Bar icon&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;contributes.views&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Registers the webview panel inside the sidebar&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;vscode:prepublish&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Script that runs before &lt;code&gt;vsce package&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-set-up-esbuild&quot;&gt;Step 3 — Set Up esbuild&lt;/h3&gt;
&lt;p&gt;Instead of the default &lt;code&gt;tsc&lt;/code&gt; compiler, this extension uses &lt;strong&gt;esbuild&lt;/strong&gt; for fast bundling. Create &lt;code&gt;esbuild.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const esbuild = require('esbuild');

const watch = process.argv.includes('--watch');

const buildOptions = {
  entryPoints: ['src/extension.ts'],
  bundle: true,
  outfile: 'out/extension.js',
  external: ['vscode'],          // vscode is provided by the host, never bundle it
  format: 'cjs',
  platform: 'node',
  target: 'node18',
  sourcemap: true,
  minify: !watch,
};

if (watch) {
  esbuild.context(buildOptions).then(ctx =&amp;gt; {
    ctx.watch();
    console.log('Watching for changes...');
  });
} else {
  esbuild.build(buildOptions).then(() =&amp;gt; console.log('Build complete'));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Always add &lt;code&gt;vscode&lt;/code&gt; to &lt;code&gt;external&lt;/code&gt;. It is injected by VS Code at runtime and must never be bundled.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-4-write-the-extension-entry-point&quot;&gt;Step 4 — Write the Extension Entry Point&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/extension.ts&lt;/code&gt; is the file VS Code calls when the extension activates. It registers commands and the sidebar webview provider:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import * as vscode from 'vscode';
import { LogViewerPanel, LogViewerSidebarProvider } from './logViewerPanel';

export function activate(context: vscode.ExtensionContext) {
  // Register the sidebar webview (Activity Bar panel)
  const sidebarProvider = new LogViewerSidebarProvider(context.extensionUri);
  context.subscriptions.push(
    vscode.window.registerWebviewViewProvider('amazonq-logviewer.viewer', sidebarProvider)
  );

  // Command: open full editor panel
  context.subscriptions.push(
    vscode.commands.registerCommand('amazonq-logviewer.open', () =&amp;gt; {
      LogViewerPanel.createOrShow(context.extensionUri);
    })
  );

  // Command: refresh data
  context.subscriptions.push(
    vscode.commands.registerCommand('amazonq-logviewer.refresh', () =&amp;gt; {
      LogViewerPanel.currentPanel?.refresh();
      sidebarProvider.refresh();
    })
  );
}

export function deactivate() {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two patterns to note:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Push to&lt;/strong&gt; &lt;code&gt;context.subscriptions&lt;/code&gt; — VS Code automatically disposes these when the extension deactivates, preventing memory leaks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;deactivate()&lt;/code&gt; — called when VS Code shuts down or the extension is disabled. Leave it empty if you have nothing to clean up.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-5-read-local-log-files-logprovider-ts-&quot;&gt;Step 5 — Read Local Log Files (&lt;code&gt;logProvider.ts&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;This class handles all filesystem access. It resolves the correct log paths per OS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';

export class LogProvider {
  private logBase: string;
  private historyDir: string;

  constructor() {
    const home = os.homedir();
    const platform = os.platform();

    if (platform === 'win32') {
      const appdata = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
      this.logBase = path.join(appdata, 'Code', 'logs');
    } else if (platform === 'darwin') {
      this.logBase = path.join(home, 'Library', 'Application Support', 'Code', 'logs');
    } else {
      this.logBase = path.join(home, '.config', 'Code', 'logs');
    }

    this.historyDir = path.join(home, '.aws', 'amazonq', 'history');
  }

  // ... getSessionLogs() and getChatHistoryFiles() methods
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Log paths by OS:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;OS &lt;/th&gt;&lt;th&gt;Extension Logs &lt;/th&gt;&lt;th&gt;Chat History&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Windows &lt;/td&gt;&lt;td&gt;&lt;code&gt;%APPDATA%\Code\logs\...\Amazon Q Logs.log&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;~\.aws\amazonq\history\&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;macOS &lt;/td&gt;&lt;td&gt;&lt;code&gt;~/Library/Application Support/Code/logs/...&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;~/.aws/amazonq/history/&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Linux &lt;/td&gt;&lt;td&gt;&lt;code&gt;~/.config/Code/logs/...&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;~/.aws/amazonq/history/&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-6-build-the-webview-panel-logviewerpanel-ts-&quot;&gt;Step 6 — Build the Webview Panel (&lt;code&gt;logViewerPanel.ts&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;VS Code extensions can render arbitrary HTML inside &lt;strong&gt;WebviewPanel&lt;/strong&gt; (full editor tab) or &lt;strong&gt;WebviewView&lt;/strong&gt; (sidebar). Both are used here.&lt;/p&gt;
&lt;h4 id=&quot;security-content-security-policy-nonce&quot;&gt;Security: Content Security Policy + Nonce&lt;/h4&gt;
&lt;p&gt;Every webview must set a strict CSP. A &lt;strong&gt;nonce&lt;/strong&gt; (random string per render) is used to allow only your inline scripts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function getNonce(): string {
  let text = '';
  const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  for (let i = 0; i &amp;lt; 32; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The CSP meta tag in the HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;meta http-equiv=&quot;Content-Security-Policy&quot;
  content=&quot;default-src 'none';
           style-src 'nonce-${nonce}';
           script-src 'nonce-${nonce}';&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;two-way-messaging&quot;&gt;Two-Way Messaging&lt;/h4&gt;
&lt;p&gt;The webview and extension communicate via &lt;code&gt;postMessage&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Extension → Webview: send data
panel.webview.postMessage({ command: 'dataLoaded', historyFiles, logSessions });

// Webview → Extension: request data
panel.webview.onDidReceiveMessage(message =&amp;gt; {
  if (message.command === 'loadData') {
    const data = logProvider.loadAllData();
    panel.webview.postMessage({ command: 'dataLoaded', ...data });
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the webview HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const vscode = acquireVsCodeApi();

// Send message to extension
vscode.postMessage({ command: 'loadData' });

// Receive message from extension
window.addEventListener('message', event =&amp;gt; {
  if (event.data.command === 'dataLoaded') {
    renderSessions(event.data.historyFiles, event.data.logSessions);
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;sidebar-provider&quot;&gt;Sidebar Provider&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export class LogViewerSidebarProvider implements vscode.WebviewViewProvider {
  resolveWebviewView(webviewView: vscode.WebviewView, ...) {
    webviewView.webview.options = {
      enableScripts: true,
      localResourceRoots: [vscode.Uri.joinPath(this._extensionUri, 'resources')]
    };
    webviewView.webview.html = getViewerHtml(getNonce());
    // ... message handler
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-7-add-icons&quot;&gt;Step 7 — Add Icons&lt;/h3&gt;
&lt;p&gt;VS Code requires icons in specific formats:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Marketplace icon&lt;/strong&gt;: &lt;code&gt;resources/icon.png&lt;/code&gt; — 128×128 PNG, referenced in &lt;code&gt;package.json&lt;/code&gt; as &lt;code&gt;&quot;icon&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Activity Bar icon&lt;/strong&gt;: SVG file — VS Code tints it automatically to match the theme; keep it a simple monochrome shape&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;viewsContainers&quot;: {
  &quot;activitybar&quot;: [
    {
      &quot;id&quot;: &quot;amazonq-logviewer&quot;,
      &quot;title&quot;: &quot;Q Logs&quot;,
      &quot;icon&quot;: &quot;resources/icon-sidebar-dark.svg&quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; Activity Bar icons are always rendered as monochrome by VS Code regardless of the SVG colors. Design them as single-color silhouettes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-8-configure-vscodeignore&quot;&gt;Step 8 — Configure &lt;code&gt;.vscodeignore&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;.vscodeignore&lt;/code&gt; works like &lt;code&gt;.gitignore&lt;/code&gt; but for the packaged &lt;code&gt;.vsix&lt;/code&gt; file. Exclude everything that isn't needed at runtime:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;.vscode/**
node_modules/**
src/**
esbuild.js
tsconfig.json
**/*.map
**/*-b64.txt
resources/screenshots/*.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keep in the package:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;out/extension.js&lt;/code&gt; (compiled bundle)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;resources/&lt;/code&gt; (icons used by the extension)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;LICENSE&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-9-test-locally&quot;&gt;Step 9 — Test Locally&lt;/h3&gt;
&lt;p&gt;Press &lt;strong&gt;F5&lt;/strong&gt; in VS Code to launch the &lt;strong&gt;Extension Development Host&lt;/strong&gt; — a second VS Code window with your extension loaded.&lt;/p&gt;
&lt;p&gt;You'll see the Q Logs icon appear in the Activity Bar:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/siddheshp/q-log-session-viewer-assets/main/screenshots/sessions-view.png&quot; alt=&quot;Activity Bar Icon&quot;&gt;&lt;/p&gt;
&lt;p&gt;Iterate quickly with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run watch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;esbuild rebuilds in milliseconds on every save. Reload the Extension Development Host with &lt;strong&gt;Ctrl+R&lt;/strong&gt; (or &lt;strong&gt;Cmd+R&lt;/strong&gt; on Mac) to pick up changes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-10-package-the-extension&quot;&gt;Step 10 — Package the Extension&lt;/h3&gt;
&lt;p&gt;Install &lt;code&gt;vsce&lt;/code&gt; (the VS Code Extension CLI) if you haven't already:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -g @vscode/vsce
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;vsce package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This produces a &lt;code&gt;.vsix&lt;/code&gt; file (e.g. &lt;code&gt;q-log-session-viewer-0.1.1.vsix&lt;/code&gt;). You can install it locally to test the final artifact:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;code --install-extension q-log-session-viewer-0.1.1.vsix
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-11-create-a-publisher-account&quot;&gt;Step 11 — Create a Publisher Account&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/manage&quot;&gt;https://marketplace.visualstudio.com/manage&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sign in with a Microsoft account&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Create publisher&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose a publisher ID (e.g. &lt;code&gt;SiddheshPrabhugaonkar&lt;/code&gt;) — this must match the &lt;code&gt;&quot;publisher&quot;&lt;/code&gt; field in &lt;code&gt;package.json&lt;/code&gt; exactly&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You also need a &lt;strong&gt;Personal Access Token (PAT)&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.azure.com&quot;&gt;https://dev.azure.com&lt;/a&gt; → your organization → &lt;strong&gt;User Settings&lt;/strong&gt; → &lt;strong&gt;Personal Access Tokens&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;New Token&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set scope to &lt;strong&gt;Marketplace → Manage&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the token — you won't see it again&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Authenticate &lt;code&gt;vsce&lt;/code&gt; with your token:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;vsce login SiddheshPrabhugaonkar
# Paste your PAT when prompted
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-12-write-a-good-readme&quot;&gt;Step 12 — Write a Good README&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;README.md&lt;/code&gt; in your extension folder becomes the &lt;strong&gt;Marketplace listing page&lt;/strong&gt;. Make it count:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Lead with what the extension does and who it's for&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Include screenshots (host them on GitHub or a CDN — relative paths don't work on the Marketplace)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;List features, commands, and requirements&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a disclaimer if your extension reads data from another product&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Screenshot URLs must be absolute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;![Sessions View](https://raw.githubusercontent.com/youruser/your-assets-repo/main/screenshots/sessions-view.png)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Create a separate public GitHub repo just for assets (screenshots, GIFs). This keeps your extension repo clean and the URLs stable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-13-publish-to-the-marketplace&quot;&gt;Step 13 — Publish to the Marketplace&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;vsce publish
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. &lt;code&gt;vsce&lt;/code&gt; will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;npm run vscode:prepublish&lt;/code&gt; (which runs &lt;code&gt;npm run compile&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Package the &lt;code&gt;.vsix&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upload it to the Marketplace&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To publish a specific version bump:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;vsce publish patch   # 0.1.0 → 0.1.1
vsce publish minor   # 0.1.0 → 0.2.0
vsce publish major   # 0.1.0 → 1.0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After a few minutes your extension appears at: &lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=SiddheshPrabhugaonkar.q-log-session-viewer&amp;amp;ssr=false#review-details&quot;&gt;https://marketplace.visualstudio.com/items?itemName=SiddheshPrabhugaonkar.q-log-session-viewer&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-14-update-the-extension&quot;&gt;Step 14 — Update the Extension&lt;/h3&gt;
&lt;p&gt;For subsequent releases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Make your code changes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update &lt;code&gt;CHANGELOG&lt;/code&gt; / release notes in &lt;code&gt;README.md&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;vsce publish patch&lt;/code&gt; (or &lt;code&gt;minor&lt;/code&gt;/&lt;code&gt;major&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Marketplace auto-notifies users who have the extension installed.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;project-file-structure-final-&quot;&gt;Project File Structure (Final)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;VSCodeExtention/
├── resources/
│   ├── icon.png                  ← Marketplace icon (128×128 PNG)
│   ├── icon-sidebar-dark.svg     ← Activity Bar icon
│   └── icon-sidebar-light.svg
├── src/
│   ├── extension.ts              ← activate() / deactivate()
│   ├── logProvider.ts            ← filesystem reads
│   └── logViewerPanel.ts         ← WebviewPanel + WebviewView + HTML
├── .vscodeignore
├── esbuild.js
├── package.json
├── tsconfig.json
└── README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;key-concepts-recap&quot;&gt;Key Concepts Recap&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Concept &lt;/th&gt;&lt;th&gt;What it does&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;contributes.viewsContainers&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Adds an icon to the Activity Bar&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;contributes.views&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Registers a panel inside that container&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;WebviewPanel&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Full editor tab with custom HTML&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;WebviewViewProvider&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Sidebar panel with custom HTML&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;postMessage&lt;/code&gt; / &lt;code&gt;onDidReceiveMessage&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Two-way communication between extension and webview&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Nonce + CSP &lt;/td&gt;&lt;td&gt;Security: prevents XSS in webviews&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;context.subscriptions&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Automatic cleanup on deactivation&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;vsce package&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Creates the installable &lt;code&gt;.vsix&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;vsce publish&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Uploads to the VS Code Marketplace&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;common-gotchas&quot;&gt;Common Gotchas&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;vscode&lt;/code&gt; &lt;strong&gt;must be in&lt;/strong&gt; &lt;code&gt;external&lt;/code&gt; in your bundler config — never bundle it&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Marketplace icon must be PNG&lt;/strong&gt;, not SVG&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Screenshot URLs in README must be absolute&lt;/strong&gt; — relative paths break on the Marketplace page&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Publisher ID in&lt;/strong&gt; &lt;code&gt;package.json&lt;/code&gt; &lt;strong&gt;must exactly match&lt;/strong&gt; your Marketplace publisher account&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Activity Bar SVG icons are always monochrome&lt;/strong&gt; — VS Code tints them; don't rely on color&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CSP&lt;/strong&gt; &lt;code&gt;default-src 'none'&lt;/code&gt; — be explicit about what your webview is allowed to load; no external CDNs unless you add them to the CSP&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;resources&quot;&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://code.visualstudio.com/api&quot;&gt;VS Code Extension API&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://code.visualstudio.com/api/extension-guides/webview&quot;&gt;Webview API Guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://code.visualstudio.com/api/working-with-extensions/publishing-extension&quot;&gt;Publishing Extensions&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/microsoft/vscode-vsce&quot;&gt;vsce CLI Reference&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=SiddheshPrabhugaonkar.q-log-session-viewer&amp;amp;ssr=false#review-details&quot;&gt;Q Log Session Viewer on Marketplace&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Built by Siddhesh Prabhugankar — Microsoft Certified Trainer &amp;amp; AI Consultant&lt;/em&gt;&lt;br&gt;
&lt;em&gt;GitHub:&lt;/em&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/siddheshp&quot;&gt;&lt;em&gt;github.com/siddheshp&lt;/em&gt;&lt;/a&gt; &lt;em&gt;· LinkedIn:&lt;/em&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/siddheshprabhugaonkar&quot;&gt;&lt;em&gt;linkedin.com/in/siddheshprabhugaonkar&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/16422/how-i-built-and-published-a-vs-code-extension-to-the-marketplace</guid>
<pubDate>Sat, 02 May 2026 21:36:11 +0000</pubDate>
</item>
</channel>
</rss>