Merge pull request 'feat-filter-ignore' (#1) from feat-filter-ignore into main

Reviewed-on: #1
This commit is contained in:
2025-03-17 07:23:37 +00:00
16 changed files with 494 additions and 467 deletions

View File

@@ -6,5 +6,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default { export default {
version: 'stable', version: 'stable',
extensionDevelopmentPath: __dirname, extensionDevelopmentPath: __dirname,
extensionTestsPath: path.join(__dirname, 'out', 'test') extensionTestsPath: path.join(__dirname, 'out', 'test'),
testFiles: ['**/**.test.js'],
workspaceFolder: __dirname
}; };

View File

@@ -8,6 +8,8 @@ src/**
**/.eslintrc.json **/.eslintrc.json
**/*.map **/*.map
**/*.ts **/*.ts
out/**
webpack.config.js
.replit .replit
.breakpoints .breakpoints
.local/** .local/**

View File

@@ -7,3 +7,9 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
## [Unreleased] ## [Unreleased]
- Initial release - Initial release
## [0.0.1] - 2025-03-13
### Added
- PromptGeneration extension with basic file selection and xml edits option.

View File

@@ -1,79 +1,48 @@
# Prompter # Prompter
Code smarter with AI—no more messy copy-pasting. Prompter structures your prompts and applies AI changes seamlessly, streamlining your coding workflow. Enhance your coding with AI—Prompter organizes your prompts and implements AI changes effortlessly, improving your development workflow.
## Why Prompter? ## Why Use Prompter?
- **Too much bloat in your repo?** Stop zipping everything—send only the files that matter. - **Reduce repository clutter** - Share only essential files instead of everything
- **LLM underperforming?** Cut the noise for sharper, more accurate responses. - **Improve AI responses** - Eliminate irrelevant context for better results
- **Better AI coding?** Select just the right context to optimize results. - **Optimize coding assistance** - Target exactly what you need for precise AI help
Prompter empowers you to work efficiently with AI, reducing token waste and improving clarity. Prompter helps you collaborate efficiently with AI, minimizing token usage and enhancing clarity.
## Features ## Key Features
- **Advanced File Selection & Token Estimation** - **Smart File Selection & Token Counting**
Precisely filter files and estimate token usage instantly for optimized, cost-effective prompts. Filter files and track token usage for efficient, economical prompts
- **Optimized XML Prompt** - **XML-Based Prompt Structure**
Structured file trees, CodeMaps, content, and instructions in XML for maximum LLM clarity. Organize file trees, CodeMaps, and instructions in XML for better AI comprehension
- **Structured XML Diffs** - **Clean XML Diff Application**
Converts LLM-generated XML edits into precise, reviewable diffs—works at any file size. Transform AI-generated XML changes into clear, reviewable diffs at any scale
- **Codemap Extraction** - **Intelligent Code Scanning**
Scans files locally to extract classes, functions, and references, minimizing tokens and hallucinations. Auto-detects referenced types. Extract code structure locally to reduce tokens and prevent hallucinations
- **Mac-Native Performance** - **Cross-Platform Support**
Built for macOS with native speed and responsiveness—because performance matters. Designed for any VSCode platform with native performance and responsiveness
- **Clipboard Integration** - **Direct Clipboard Support**
Copy structured prompts into any AI chat app—your data stays local, no external API needed. Copy structured prompts to any AI platform with your data remaining local
- **Works with Any Model** - **Privacy-Focused Design**
Compatible with OpenAI, Anthropic, DeepSeek, Gemini, Azure, OpenRouter, and local models—private and offline when you need it. Process everything locally without sending data to third parties
- **Privacy First** ## Quick Start
Local models, offline scanning, and direct clipboard use—no intermediaries required.
## Installation 1. Install the extension in VS Code
2. Select relevant files through the Prompter sidebar
*(Note: Installation steps are assumed based on the VS Code context from other files. Adjust as needed.)* 3. Generate and copy a structured XML prompt
1. Clone the repository: 4. Paste into your AI tool of choice
```bash
git clone <repository-url>
```
2. Open the project in VS Code.
3. Install dependencies:
```bash
npm install
```
4. Build the extension:
```bash
npm run compile
```
5. Press `F5` in VS Code to launch the extension in a development window.
## Usage
1. Open your project in VS Code.
2. Use the Prompter interface to select files and estimate tokens.
3. Generate a structured XML prompt via the clipboard.
4. Paste into your preferred AI model (e.g., ChatGPT, Claude, or a local LLM).
5. Apply the returned XML diffs directly through Prompter for seamless integration.
## Contributing ## Contributing
We welcome contributions! To get started: Contributions welcome!
1. Fork the repository.
2. Create a feature branch: `git checkout -b my-feature`.
3. Commit your changes: `git commit -m "Add my feature"`.
4. Push to your branch: `git push origin my-feature`.
5. Open a pull request.
See `vsc-extension-quickstart.md` for development setup and testing details.
--- ---
Built with ❤️ by the Prompter team. Built with ❤️ by the Prompter team.
Code smarter with AI—no more messy copy-pasting. Prompter structures your prompts and applies AI changes seamlessly, streamlining your coding workflow.

19
jest.config.js Normal file
View File

@@ -0,0 +1,19 @@
/** @type {import('ts-jest').JestConfigWithTsJest} **/
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\.tsx?$': ['ts-jest', {}],
},
testMatch: ['**/test/**/*.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
globals: {
'ts-jest': {
tsconfig: 'tsconfig.json',
},
},
// Mock the vscode module since it's not available during testing
moduleNameMapper: {
'^vscode$': '<rootDir>/src/test/vscode-mock.ts'
},
};

View File

@@ -12,7 +12,7 @@
"Other" "Other"
], ],
"activationEvents": [], "activationEvents": [],
"main": "./out/extension.js", "main": "./dist/extension.js",
"contributes": { "contributes": {
"commands": [ "commands": [
{ {
@@ -21,7 +21,13 @@
}, },
{ {
"command": "prompter.generatePrompt", "command": "prompter.generatePrompt",
"title": "Copy" "title": "Copy",
"icon": "$(clippy)"
},
{
"command": "prompter.openTreeView",
"title": "Show Tree View",
"icon": "$(list-tree)"
}, },
{ {
"command": "prompter.openSettings", "command": "prompter.openSettings",
@@ -42,7 +48,8 @@
"prompter-sidebar": [ "prompter-sidebar": [
{ {
"id": "prompterView", "id": "prompterView",
"name": "Prompter" "name": "Prompter",
"icon": "resources/icon.svg"
} }
] ]
}, },
@@ -54,9 +61,14 @@
"when": "view == prompterView" "when": "view == prompterView"
}, },
{ {
"command": "prompter.openSettings", "command": "prompter.openTreeView",
"group": "navigation@2", "group": "navigation@2",
"when": "view == prompterView" "when": "view == prompterView"
},
{
"command": "prompter.openSettings",
"group": "navigation@3",
"when": "view == prompterView"
} }
], ],
"view/item/context": [ "view/item/context": [
@@ -68,12 +80,14 @@
} }
}, },
"scripts": { "scripts": {
"vscode:prepublish": "npm run compile", "vscode:prepublish": "npm run webpack:prod",
"webpack:dev": "webpack --mode development",
"webpack:prod": "webpack --mode production",
"compile": "tsc -p ./", "compile": "tsc -p ./",
"watch": "tsc -watch -p ./", "watch": "webpack --watch --mode development",
"pretest": "npm run compile && npm run lint", "pretest": "npm run compile && npm run lint",
"lint": "eslint src", "lint": "eslint src",
"test": "vscode-test" "test": "node ./src/test/runTest.mjs"
}, },
"devDependencies": { "devDependencies": {
"@types/mocha": "^10.0.10", "@types/mocha": "^10.0.10",
@@ -84,7 +98,10 @@
"@vscode/test-cli": "^0.0.10", "@vscode/test-cli": "^0.0.10",
"@vscode/test-electron": "^2.4.1", "@vscode/test-electron": "^2.4.1",
"eslint": "^9.21.0", "eslint": "^9.21.0",
"typescript": "^5.7.3" "ts-loader": "^9.5.2",
"typescript": "^5.7.3",
"webpack": "^5.98.0",
"webpack-cli": "^6.0.1"
}, },
"dependencies": { "dependencies": {
"@vscode/vsce": "^3.2.2", "@vscode/vsce": "^3.2.2",

View File

@@ -16,7 +16,7 @@ Avoid placeholders like `...` or `// existing code here`. Provide complete lines
3. **modify** (search/replace) For partial edits with <search> + <content>. 3. **modify** (search/replace) For partial edits with <search> + <content>.
4. **delete** Remove a file entirely (empty <content>). 4. **delete** Remove a file entirely (empty <content>).
### **Format to Follow for Repo Prompt's Diff Protocol** ### **Format to Follow for Diff Protocol**
<Plan> <Plan>
Describe your approach or reasoning here. Describe your approach or reasoning here.
@@ -288,7 +288,7 @@ Remove an obsolete file.
5. You can always **create** new files and **delete** existing files. Provide full code for create, and empty content for delete. Avoid creating files you know exist already. 5. You can always **create** new files and **delete** existing files. Provide full code for create, and empty content for delete. Avoid creating files you know exist already.
6. If a file tree is provided, place your files logically within that structure. Respect the users relative or absolute paths. 6. If a file tree is provided, place your files logically within that structure. Respect the users relative or absolute paths.
7. Wrap your final output in ```XML ... ``` for clarity. 7. Wrap your final output in ```XML ... ``` for clarity.
8. **Important:** Do not wrap any XML output in CDATA tags (i.e. `<![CDATA[ ... ]]>`). Repo Prompt expects raw XML exactly as shown in the examples. 8. **Important:** Do not wrap any XML output in CDATA tags (i.e. `<![CDATA[ ... ]]>`). We xpect raw XML exactly as shown in the examples.
9. **IMPORTANT** IF MAKING FILE CHANGES, YOU MUST USE THE AVAILABLE XML FORMATTING CAPABILITIES PROVIDED ABOVE - IT IS THE ONLY WAY FOR YOUR CHANGES TO BE APPLIED. 9. **IMPORTANT** IF MAKING FILE CHANGES, YOU MUST USE THE AVAILABLE XML FORMATTING CAPABILITIES PROVIDED ABOVE - IT IS THE ONLY WAY FOR YOUR CHANGES TO BE APPLIED.
10. The final output must apply cleanly with no leftover syntax errors. 10. The final output must apply cleanly with no leftover syntax errors.
</xml_formatting_instructions> </xml_formatting_instructions>

View File

@@ -60,26 +60,33 @@ export function activate(context: vscode.ExtensionContext) {
// Update formatting instructions button text if that setting changed // Update formatting instructions button text if that setting changed
if (item.settingKey === 'includeFormattingInstructions') { if (item.settingKey === 'includeFormattingInstructions') {
xmlEditsButton.text = prompterTreeProvider.isXmlEditsEnabled() ? xmlEditsButton.text = prompterTreeProvider.isXmlEditsEnabled() ?
"$(check) Formatting Instructions" : "$(diff-added) Formatting Instructions"; "$(check) Formatting Instructions" : "$(diff-added) XML Edits";
} }
} }
} }
}); });
// Create formatting instructions toggle button // Create formatting instructions toggle button
const xmlEditsButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); const xmlEditsButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 1);
xmlEditsButton.text = "$(diff-added) Formatting Instructions"; xmlEditsButton.text = "$(diff-added) XML Edits";
xmlEditsButton.tooltip = "Toggle formatting instructions mode"; xmlEditsButton.tooltip = "Toggle formatting instructions mode";
xmlEditsButton.command = 'prompter.toggleXmlEdits'; xmlEditsButton.command = 'prompter.toggleXmlEdits';
xmlEditsButton.show(); xmlEditsButton.show();
// Create copy button // Create copy button
const copyButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right); const copyButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 3);
copyButton.text = "$(clippy) Copy"; copyButton.text = "$(clippy) Copy";
copyButton.tooltip = "Generate and copy prompt"; copyButton.tooltip = "Generate and copy prompt";
copyButton.command = 'prompter.generatePrompt'; copyButton.command = 'prompter.generatePrompt';
copyButton.show(); copyButton.show();
// Create settings button
const settingsButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 2);
settingsButton.text = "$(settings-gear)";
settingsButton.tooltip = "Prompter Settings";
settingsButton.command = 'prompter.openSettings';
settingsButton.show();
// Register command to toggle file selection // Register command to toggle file selection
let toggleSelectionCommand = vscode.commands.registerCommand('prompter.toggleSelection', (item: FileTreeItem) => { let toggleSelectionCommand = vscode.commands.registerCommand('prompter.toggleSelection', (item: FileTreeItem) => {
if (item.resourceUri) { if (item.resourceUri) {
@@ -95,13 +102,6 @@ export function activate(context: vscode.ExtensionContext) {
"$(check) XML Edits" : "$(diff-added) XML Edits"; "$(check) XML Edits" : "$(diff-added) XML Edits";
}); });
// Create settings button
const settingsButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
settingsButton.text = "$(settings-gear)";
settingsButton.tooltip = "Prompter Settings";
settingsButton.command = 'prompter.openSettings';
settingsButton.show();
// Register command to open settings // Register command to open settings
let openSettingsCommand = vscode.commands.registerCommand('prompter.openSettings', () => { let openSettingsCommand = vscode.commands.registerCommand('prompter.openSettings', () => {
prompterTreeProvider.toggleSettingsView(); prompterTreeProvider.toggleSettingsView();
@@ -113,6 +113,11 @@ export function activate(context: vscode.ExtensionContext) {
"Show Files" : "Prompter Settings"; "Show Files" : "Prompter Settings";
}); });
// Register command to show the tree view
let openTreeViewCommand = vscode.commands.registerCommand('prompter.openTreeView', () => {
prompterTreeProvider.showFilesView();
});
// Register command to generate prompt from selected files // Register command to generate prompt from selected files
let generatePromptCommand = vscode.commands.registerCommand('prompter.generatePrompt', async () => { let generatePromptCommand = vscode.commands.registerCommand('prompter.generatePrompt', async () => {
const selectedFiles = prompterTreeProvider.getSelectedFiles(); const selectedFiles = prompterTreeProvider.getSelectedFiles();
@@ -130,7 +135,14 @@ export function activate(context: vscode.ExtensionContext) {
prompterTreeProvider.getSettings() prompterTreeProvider.getSettings()
); );
// Copy to clipboard // Check if we got a valid prompt text
if (promptText === null) {
// Show warning if all files were filtered out
vscode.window.showWarningMessage('All selected files were filtered out by ignore patterns');
return;
}
// Copy to clipboard only if we have valid content
await vscode.env.clipboard.writeText(promptText); await vscode.env.clipboard.writeText(promptText);
vscode.window.showInformationMessage('Prompt copied to clipboard!'); vscode.window.showInformationMessage('Prompt copied to clipboard!');
} catch (error) { } catch (error) {
@@ -148,7 +160,8 @@ export function activate(context: vscode.ExtensionContext) {
toggleSelectionCommand, toggleSelectionCommand,
toggleXmlEditsCommand, toggleXmlEditsCommand,
generatePromptCommand, generatePromptCommand,
openSettingsCommand openSettingsCommand,
openTreeViewCommand
); );
} }

View File

@@ -1,6 +1,26 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs';
// Use require for ignore package with proper fallback
let ignoreFunc: () => any;
try {
const ignoreModule = require('ignore');
if (typeof ignoreModule === 'function') {
ignoreFunc = ignoreModule;
} else if (ignoreModule && typeof ignoreModule.default === 'function') {
ignoreFunc = ignoreModule.default;
} else {
throw new Error('Ignore module is neither a function nor has a default function');
}
console.log('Successfully loaded ignore function in FileSelectionManager');
} catch (error) {
console.error('Error loading ignore package in FileSelectionManager:', error);
ignoreFunc = () => ({
add: () => {},
ignores: () => false
});
}
/** /**
* Manages file selection state for the Prompter extension * Manages file selection state for the Prompter extension
@@ -11,40 +31,134 @@ export class FileSelectionManager {
constructor() {} constructor() {}
/** /**
* Add a file to the selection * Get the path relative to the workspace root
* @param filePath Absolute file path
* @param rootPath Workspace root path
* @returns Relative path
*/
private getRelativePath(filePath: string, rootPath: string): string {
if (filePath.startsWith(rootPath)) {
const relativePath = filePath.substring(rootPath.length);
return relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
}
return filePath;
}
/**
* Check if a path or any of its parent directories are ignored
* @param pathToCheck The path to check
* @param ig The ignore instance
* @param workspaceRoot The workspace root path
* @returns True if the path or any parent is ignored
*/
private isPathIgnored(pathToCheck: string, ig: any, workspaceRoot: string): boolean {
let currentPath = pathToCheck;
while (currentPath !== workspaceRoot) {
const relativePath = this.getRelativePath(currentPath, workspaceRoot);
const normalizedPath = relativePath.split(path.sep).join('/');
const isIgnored = ig.ignores(normalizedPath);
console.log(`Checking ${normalizedPath}: ignored = ${isIgnored}`);
if (isIgnored) {
console.log(`Path ${pathToCheck} ignored because parent ${normalizedPath} is ignored`);
return true;
}
currentPath = path.dirname(currentPath);
if (currentPath === workspaceRoot) {
break;
}
}
return false;
}
/**
* Load ignore patterns from .gitignore files in the given directory and its parent directories
* @param directoryPath The directory path to start searching from
* @param workspaceRoot The workspace root path
* @returns An ignore instance with loaded patterns
*/
private loadIgnorePatternsFromDirectory(directoryPath: string, workspaceRoot: string): any {
const ig = ignoreFunc();
let currentDir = directoryPath;
// Check for .gitignore in the current directory and all parent directories up to workspace root
while (currentDir.startsWith(workspaceRoot)) {
try {
const gitignorePath = path.join(currentDir, '.gitignore');
if (fs.existsSync(gitignorePath)) {
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
ig.add(gitignoreContent);
console.log(`Loaded .gitignore patterns from ${gitignorePath}`);
}
} catch (error) {
console.error(`Error loading .gitignore from ${currentDir}:`, error);
}
// Stop if we've reached the workspace root
if (currentDir === workspaceRoot) {
break;
}
// Move up to parent directory
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) { // Avoid infinite loop if we've reached the root
break;
}
currentDir = parentDir;
}
return ig;
}
/**
* Add a file to the selection if it's not ignored
*/ */
addFile(filePath: string): void { addFile(filePath: string): void {
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath || '';
const dirPath = path.dirname(filePath);
const ig = this.loadIgnorePatternsFromDirectory(dirPath, workspaceRoot);
if (this.isPathIgnored(filePath, ig, workspaceRoot)) {
console.log(`Ignoring file ${filePath} because it or a parent directory is ignored`);
return;
}
this.selectedFiles.add(filePath); this.selectedFiles.add(filePath);
console.log(`Added ${filePath} to selection`); console.log(`Added ${filePath} to selection`);
} }
/** /**
* Add a directory and all its contents to the selection * Add a directory and all its non-ignored contents to the selection
*/ */
async addDirectory(dirPath: string): Promise<void> { async addDirectory(dirPath: string): Promise<void> {
try { const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath || '';
// Add the directory itself const ig = this.loadIgnorePatternsFromDirectory(dirPath, workspaceRoot);
const relativeDirPath = this.getRelativePath(dirPath, workspaceRoot);
const normalizedDirPath = relativeDirPath.split(path.sep).join('/');
if (ig.ignores(normalizedDirPath)) {
console.log(`Directory ${dirPath} is ignored, skipping its contents`);
return;
}
this.selectedFiles.add(dirPath); this.selectedFiles.add(dirPath);
console.log(`Added directory ${dirPath} to selection`); console.log(`Added directory ${dirPath} to selection`);
// Read directory contents try {
const files = await vscode.workspace.fs.readDirectory(vscode.Uri.file(dirPath)); const files = await vscode.workspace.fs.readDirectory(vscode.Uri.file(dirPath));
// Process each item
for (const [name, type] of files) { for (const [name, type] of files) {
const filePath = path.join(dirPath, name); const filePath = path.join(dirPath, name);
const relativeFilePath = this.getRelativePath(filePath, workspaceRoot);
const normalizedFilePath = relativeFilePath.split(path.sep).join('/');
if (type === vscode.FileType.Directory) { if (type === vscode.FileType.Directory) {
// Recursively process subdirectories
await this.addDirectory(filePath); await this.addDirectory(filePath);
} else { } else if (!ig.ignores(normalizedFilePath)) {
// Add files
this.selectedFiles.add(filePath); this.selectedFiles.add(filePath);
console.log(`Added ${filePath} to selection (from directory)`); console.log(`Added ${filePath} to selection`);
} else {
console.log(`Ignoring ${filePath} due to ignore patterns`);
} }
} }
} catch (error) { } catch (error) {
console.error(`Error adding directory to selection: ${dirPath}`, error); console.error(`Error adding directory ${dirPath}:`, error);
} }
} }
@@ -61,22 +175,14 @@ export class FileSelectionManager {
*/ */
async removeDirectory(dirPath: string): Promise<void> { async removeDirectory(dirPath: string): Promise<void> {
try { try {
// Remove the directory itself
this.selectedFiles.delete(dirPath); this.selectedFiles.delete(dirPath);
console.log(`Removed directory ${dirPath} from selection`); console.log(`Removed directory ${dirPath} from selection`);
// Read directory contents
const files = await vscode.workspace.fs.readDirectory(vscode.Uri.file(dirPath)); const files = await vscode.workspace.fs.readDirectory(vscode.Uri.file(dirPath));
// Process each item
for (const [name, type] of files) { for (const [name, type] of files) {
const filePath = path.join(dirPath, name); const filePath = path.join(dirPath, name);
if (type === vscode.FileType.Directory) { if (type === vscode.FileType.Directory) {
// Recursively process subdirectories
await this.removeDirectory(filePath); await this.removeDirectory(filePath);
} else { } else {
// Remove files
this.selectedFiles.delete(filePath); this.selectedFiles.delete(filePath);
console.log(`Removed ${filePath} from selection (from directory)`); console.log(`Removed ${filePath} from selection (from directory)`);
} }

View File

@@ -32,6 +32,16 @@ export class PrompterTreeProvider implements vscode.TreeDataProvider<FileTreeIte
this.refresh(); this.refresh();
} }
showSettingsView(): void {
this.showingSettings = true;
this.refresh();
}
showFilesView(): void {
this.showingSettings = false;
this.refresh();
}
getTreeItem(element: FileTreeItem | SettingTreeItem): vscode.TreeItem { getTreeItem(element: FileTreeItem | SettingTreeItem): vscode.TreeItem {
// Return the element as is if it's a SettingTreeItem // Return the element as is if it's a SettingTreeItem
if (element instanceof SettingTreeItem) { if (element instanceof SettingTreeItem) {

View File

@@ -1,251 +0,0 @@
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
// Using require for the ignore package due to its module export style
const ignoreLib = require('ignore');
// A simplified version of our tree structure for testing
interface TreeNode {
name: string;
isDirectory: boolean;
children: Map<string, TreeNode>;
}
// Test implementation of the file tree generation logic
function generateFileTree(files: string[], rootPath: string): string {
// Create a tree representation
const treeLines: string[] = [];
// Create root node
const root: TreeNode = {
name: path.basename(rootPath),
isDirectory: true,
children: new Map<string, TreeNode>()
};
// Initialize the ignore instance
const ig = ignoreLib();
// Read .gitignore patterns if available
try {
const gitignorePath = path.join(rootPath, '.gitignore');
if (fs.existsSync(gitignorePath)) {
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
console.log('Using .gitignore content:');
console.log(gitignoreContent);
// Add patterns from .gitignore
ig.add(gitignoreContent);
// Always include .gitignore itself
ig.add('!.gitignore');
// Debug what's being ignored
console.log('\nIgnore patterns loaded. Testing patterns:');
['file1.txt', 'file2.log', 'node_modules/package.json', 'src/index.ts', 'temp/temp.txt'].forEach(testPath => {
console.log(`${testPath}: ${ig.ignores(testPath) ? 'IGNORED' : 'included'}`);
});
console.log();
}
} catch (error) {
console.error('Error reading .gitignore:', error);
}
// Build the tree structure
for (const filePath of files) {
const relativePath = getRelativePath(filePath, rootPath);
// Skip ignored files using the ignore package
// Use forward slashes for paths to ensure consistent matching
const normalizedPath = relativePath.split(path.sep).join('/');
if (ig.ignores(normalizedPath)) {
console.log(`Ignoring: ${normalizedPath}`);
continue;
}
// Split the path into parts
const parts = relativePath.split('/');
// Start from the root
let currentNode = root;
// Build the path in the tree
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (!part) { continue; } // Skip empty parts
const isDirectory = i < parts.length - 1;
if (!currentNode.children.has(part)) {
currentNode.children.set(part, {
name: part,
isDirectory,
children: new Map<string, TreeNode>()
});
} else if (isDirectory) {
// Ensure it's marked as a directory if we encounter it again
currentNode.children.get(part)!.isDirectory = true;
}
currentNode = currentNode.children.get(part)!;
}
}
// Function to recursively build the tree lines
const buildTreeLines = (node: TreeNode, prefix: string = '', isLast: boolean = true, parentPrefix: string = ''): void => {
// Skip the root node in the output
if (node !== root) {
const linePrefix = parentPrefix + (isLast ? '└── ' : '├── ');
treeLines.push(`${linePrefix}${node.name}${node.isDirectory ? '' : ''}`);
}
// Sort children: directories first, then files, both alphabetically
const sortedChildren = Array.from(node.children.values())
.sort((a, b) => {
if (a.isDirectory === b.isDirectory) {
return a.name.localeCompare(b.name);
}
return a.isDirectory ? -1 : 1;
});
// Process children
sortedChildren.forEach((child, index) => {
const isChildLast = index === sortedChildren.length - 1;
const childParentPrefix = node === root ? '' : parentPrefix + (isLast ? ' ' : '│ ');
buildTreeLines(child, prefix, isChildLast, childParentPrefix);
});
};
// Build the tree lines
buildTreeLines(root);
return treeLines.join('\n');
}
// Helper function to get relative path
function getRelativePath(filePath: string, rootPath: string): string {
if (filePath.startsWith(rootPath)) {
const relativePath = filePath.substring(rootPath.length);
return relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
}
return filePath;
}
// Run tests
function runTests() {
console.log('Running file tree generation tests...');
// Create a temporary directory for our tests
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'prompter-test-'));
console.log(`Created temp directory: ${tempDir}`);
try {
// Test 1: Basic file structure
console.log('\nTest 1: Basic file structure');
const files = [
path.join(tempDir, 'file1.txt'),
path.join(tempDir, 'folder1/file2.txt'),
path.join(tempDir, 'folder1/subfolder/file3.txt'),
path.join(tempDir, 'folder2/file4.txt')
];
// Create the directories and files
files.forEach(file => {
const dir = path.dirname(file);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(file, 'test content');
});
const result1 = generateFileTree(files, tempDir);
console.log(result1);
// Test 2: With .gitignore
console.log('\nTest 2: With .gitignore');
// Create a .gitignore file with more explicit patterns
const gitignorePath = path.join(tempDir, '.gitignore');
fs.writeFileSync(gitignorePath, '# Ignore log files\n*.log\n\n# Ignore node_modules directory\nnode_modules/\n\n# Ignore temp directory\ntemp/\n');
console.log('Created .gitignore with content:');
console.log(fs.readFileSync(gitignorePath, 'utf8'));
// Create test files for .gitignore testing
const files2 = [
path.join(tempDir, '.gitignore'),
path.join(tempDir, 'file1.txt'),
path.join(tempDir, 'file2.log'), // Should be ignored
path.join(tempDir, 'node_modules/package.json'), // Should be ignored
path.join(tempDir, 'src/index.ts'),
path.join(tempDir, 'temp/temp.txt') // Should be ignored
];
console.log('Test files created:');
files2.forEach(f => console.log(` - ${getRelativePath(f, tempDir)}`));
// Create the directories and files
files2.forEach(file => {
const dir = path.dirname(file);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Don't overwrite the .gitignore file
if (path.basename(file) !== '.gitignore') {
fs.writeFileSync(file, 'test content');
}
});
const result2 = generateFileTree(files2, tempDir);
console.log(result2);
// Test 3: Empty directories
console.log('\nTest 3: Empty directories');
const emptyDir = path.join(tempDir, 'emptyDir');
if (!fs.existsSync(emptyDir)) {
fs.mkdirSync(emptyDir, { recursive: true });
}
const files3 = [
path.join(tempDir, 'file1.txt'),
path.join(tempDir, 'emptyDir') // Empty directory
];
const result3 = generateFileTree(files3, tempDir);
console.log(result3);
// Test 4: Sorting
console.log('\nTest 4: Sorting (directories first, then files)');
const files4 = [
path.join(tempDir, 'z_file.txt'),
path.join(tempDir, 'a_file.txt'),
path.join(tempDir, 'z_folder/file.txt'),
path.join(tempDir, 'a_folder/file.txt')
];
// Create the directories and files
files4.forEach(file => {
const dir = path.dirname(file);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(file, 'test content');
});
const result4 = generateFileTree(files4, tempDir);
console.log(result4);
} catch (error) {
console.error('Test error:', error);
} finally {
// Clean up
console.log('\nCleaning up...');
try {
fs.rmSync(tempDir, { recursive: true, force: true });
console.log(`Removed temp directory: ${tempDir}`);
} catch (cleanupError) {
console.error('Error during cleanup:', cleanupError);
}
}
}
// Run the tests
runTests();

33
src/test/index.ts Normal file
View File

@@ -0,0 +1,33 @@
import * as path from 'path';
import * as fs from 'fs';
import Mocha from 'mocha';
export function run(): Promise<void> {
// Create the mocha test
const mocha = new Mocha({
ui: 'tdd',
color: true
});
const testsRoot = path.resolve(__dirname, '..');
return new Promise<void>((resolve, reject) => {
try {
// Simple approach - add extension.test.js directly
// This will find our basic test file
mocha.addFile(path.resolve(testsRoot, 'test/extension.test.js'));
// Run the mocha test
mocha.run((failures: number) => {
if (failures > 0) {
reject(new Error(`${failures} tests failed.`));
} else {
resolve();
}
});
} catch (err) {
console.error(err);
reject(err);
}
});
}

59
src/test/runTest.mjs Normal file
View File

@@ -0,0 +1,59 @@
import { runTests } from '@vscode/test-electron';
import * as path from 'path';
import { fileURLToPath } from 'url';
import * as os from 'os';
import * as fs from 'fs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
async function main() {
try {
// Create a temporary test workspace
// Using the system temp directory instead of external volume
const tmpDir = os.tmpdir();
const testWorkspaceDir = path.join(tmpDir, `vscode-test-workspace-${Math.random().toString(36).substring(2)}`);
const userDataDir = path.join(tmpDir, `vscode-test-user-data-${Math.random().toString(36).substring(2)}`);
// Ensure the test workspace directory exists
if (!fs.existsSync(testWorkspaceDir)) {
fs.mkdirSync(testWorkspaceDir, { recursive: true });
}
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
// The path to the extension test script
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, '../../out/test');
console.log('Running tests with the following configuration:');
console.log(`Extension Development Path: ${extensionDevelopmentPath}`);
console.log(`Extension Tests Path: ${extensionTestsPath}`);
console.log(`Workspace Dir: ${testWorkspaceDir}`);
console.log(`User Data Dir: ${userDataDir}`);
// Download VS Code, unzip it and run the integration test
await runTests({
version: '1.98.0', // Specify the exact version your extension is built for
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [
testWorkspaceDir,
'--disable-extensions',
`--user-data-dir=${userDataDir}`,
'--skip-getting-started',
'--skip-release-notes',
'--disable-telemetry',
'--disable-updates',
'--disable-crash-reporter',
'--disable-workspace-trust'
]
});
} catch (err) {
console.error('Failed to run tests', err);
process.exit(1);
}
}
main();

View File

@@ -3,26 +3,28 @@ import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { PrompterSettings } from '../models/settings'; import { PrompterSettings } from '../models/settings';
import { FileReader } from './fileReader'; import { FileReader } from './fileReader';
// Using require for the ignore package due to its module export style
let ignore: any;
try {
ignore = require('ignore');
console.log('Successfully loaded ignore package');
} catch (error) {
console.error('Error loading ignore package:', error);
// Fallback implementation if the package fails to load
ignore = {
default: () => ({
add: () => {},
ignores: () => false
})
};
}
import { TokenEstimator } from './tokenEstimator'; import { TokenEstimator } from './tokenEstimator';
/** // Use require for ignore package with proper fallback
* Utility class for generating prompts from selected files let ignoreFunc: () => any;
*/ try {
const ignoreModule = require('ignore');
if (typeof ignoreModule === 'function') {
ignoreFunc = ignoreModule;
} else if (ignoreModule && typeof ignoreModule.default === 'function') {
ignoreFunc = ignoreModule.default;
} else {
throw new Error('Ignore module is neither a function nor has a default function');
}
console.log('Successfully loaded ignore function');
} catch (error) {
console.error('Error loading ignore package:', error);
ignoreFunc = () => ({
add: () => {},
ignores: () => false
});
}
export class PromptGenerator { export class PromptGenerator {
/** /**
* Generate a prompt from the selected files * Generate a prompt from the selected files
@@ -30,54 +32,81 @@ export class PromptGenerator {
* @param settings Settings to apply when generating the prompt * @param settings Settings to apply when generating the prompt
* @returns The generated prompt text * @returns The generated prompt text
*/ */
static async generatePrompt(selectedFiles: Set<string>, settings: PrompterSettings): Promise<string> { static async generatePrompt(selectedFiles: Set<string>, settings: PrompterSettings): Promise<string | null> {
if (selectedFiles.size === 0) { if (selectedFiles.size === 0) {
throw new Error('No files selected'); throw new Error('No files selected');
} }
let totalTokens = 0; const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath || '';
const fileContents = new Map<string, { content: string; tokens: number }>();
// Process each selected file // Create ignore instance using the resolved function
const ig = ignoreFunc();
console.log('Created ignore instance');
// Load .gitignore patterns
try {
const gitignorePath = path.join(workspaceRoot, '.gitignore');
if (fs.existsSync(gitignorePath)) {
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
ig.add(gitignoreContent);
console.log('Successfully loaded .gitignore patterns:', gitignoreContent.split('\n').filter(Boolean));
} else {
console.log('No .gitignore file found at:', gitignorePath);
}
} catch (error) {
console.error('Error loading .gitignore:', error);
}
const fileContents = new Map<string, { content: string; tokens: number }>();
const filteredFiles = new Set<string>();
let totalTokens = 0;
// Process and filter files
for (const filePath of selectedFiles) { for (const filePath of selectedFiles) {
try {
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
console.log(`Skipping directory: ${filePath}`);
continue;
}
} catch (error) {
console.error(`Error checking file stats for ${filePath}:`, error);
continue;
}
const relativePath = this.getRelativePath(filePath, workspaceRoot);
const normalizedPath = relativePath.split(path.sep).join('/');
console.log(`Checking path: ${normalizedPath}`);
if (ig.ignores(normalizedPath)) {
console.log(`Ignoring file based on patterns: ${normalizedPath}`);
continue;
}
console.log(`Processing file: ${normalizedPath}`);
filteredFiles.add(filePath);
const content = await FileReader.readFileContent(filePath); const content = await FileReader.readFileContent(filePath);
const tokens = TokenEstimator.estimateTokens(content); const tokens = TokenEstimator.estimateTokens(content);
totalTokens += tokens; totalTokens += tokens;
fileContents.set(filePath, { content, tokens }); fileContents.set(filePath, { content, tokens });
} }
// Always generate XML prompt if (filteredFiles.size === 0) {
return this.generateXMLPrompt(fileContents, settings); // Return null to signal that no files were available after filtering
return null;
} }
/** // Create filtered contents map
* Generate a plain text prompt const filteredContents = new Map<string, { content: string; tokens: number }>();
* @param files Map of file paths to content and token counts for (const filePath of filteredFiles) {
* @param settings Settings to apply if (fileContents.has(filePath)) {
* @returns Plain text prompt filteredContents.set(filePath, fileContents.get(filePath)!);
*/ }
private static generatePlainPrompt(files: Map<string, { content: string; tokens: number }>, settings: PrompterSettings): string {
let promptText = '';
let totalTokenCount = 0;
for (const [filePath, { content, tokens }] of files) {
const fileName = path.basename(filePath);
totalTokenCount += tokens;
// Add the file to the prompt
promptText += `File: ${fileName}\n`;
promptText += `${content}\n`;
promptText += '\n';
} }
// Add token count if enabled return this.generateXMLPrompt(filteredContents, settings);
if (settings.tokenCalculationEnabled) {
promptText += `\nEstimated token count: ${totalTokenCount}`;
} }
return promptText;
}
/** /**
* Generate an XML formatted prompt following the new schema format * Generate an XML formatted prompt following the new schema format
@@ -88,11 +117,9 @@ export class PromptGenerator {
private static generateXMLPrompt(files: Map<string, { content: string; tokens: number }>, settings: PrompterSettings): string { private static generateXMLPrompt(files: Map<string, { content: string; tokens: number }>, settings: PrompterSettings): string {
const xmlParts: string[] = []; const xmlParts: string[] = [];
// Store formatting instructions to add at the end if enabled
let formattingInstructions = ''; let formattingInstructions = '';
if (settings.includeFormattingInstructions) { if (settings.includeFormattingInstructions) {
try { try {
// Get the extension path
const extensionPath = vscode.extensions.getExtension('prompter')?.extensionPath || const extensionPath = vscode.extensions.getExtension('prompter')?.extensionPath ||
path.join(__dirname, '..', '..'); path.join(__dirname, '..', '..');
const formattingInstructionsPath = path.join(extensionPath, 'resources', 'xml_formatting_instructions.xml'); const formattingInstructionsPath = path.join(extensionPath, 'resources', 'xml_formatting_instructions.xml');
@@ -107,29 +134,21 @@ export class PromptGenerator {
} }
} }
// Generate file map section if enabled in settings
if (settings.includeFileMap) { if (settings.includeFileMap) {
xmlParts.push('<file_map>'); xmlParts.push('<file_map>');
// Get the workspace root path
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath || ''; const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath || '';
xmlParts.push(workspaceRoot); xmlParts.push(workspaceRoot);
// Create a tree representation of the files
const fileTree = this.generateFileTree(files, workspaceRoot); const fileTree = this.generateFileTree(files, workspaceRoot);
xmlParts.push(fileTree); xmlParts.push(fileTree);
xmlParts.push('</file_map>'); xmlParts.push('</file_map>');
} }
// Generate file contents section
xmlParts.push('<file_contents>'); xmlParts.push('<file_contents>');
// Add each file with its content
for (const [filePath, { content, tokens }] of files) { for (const [filePath, { content, tokens }] of files) {
const extension = path.extname(filePath); const extension = path.extname(filePath);
let language = extension.substring(1); // Remove the dot let language = extension.substring(1);
// Handle special cases for language detection
if (extension === '.js' || extension === '.jsx') { if (extension === '.js' || extension === '.jsx') {
language = 'javascript'; language = 'javascript';
} else if (extension === '.ts' || extension === '.tsx') { } else if (extension === '.ts' || extension === '.tsx') {
@@ -146,13 +165,9 @@ export class PromptGenerator {
language = 'json'; language = 'json';
} }
// Use content as is
const formattedContent = content; const formattedContent = content;
// Get the workspace root path if not already defined
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath || ''; const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath || '';
// Add file entry
xmlParts.push(`File: ${this.getRelativePath(filePath, workspaceRoot)}`); xmlParts.push(`File: ${this.getRelativePath(filePath, workspaceRoot)}`);
xmlParts.push(`\`\`\`${language}`); xmlParts.push(`\`\`\`${language}`);
xmlParts.push(formattedContent); xmlParts.push(formattedContent);
@@ -161,14 +176,11 @@ export class PromptGenerator {
xmlParts.push('</file_contents>'); xmlParts.push('</file_contents>');
// Calculate tokens for toast notification but don't include in XML
if (settings.tokenCalculationEnabled) { if (settings.tokenCalculationEnabled) {
const totalTokens = Array.from(files.values()).reduce((sum, { tokens }) => sum + tokens, 0); const totalTokens = Array.from(files.values()).reduce((sum, { tokens }) => sum + tokens, 0);
// We'll show this in a toast notification but not include it in the XML
vscode.window.showInformationMessage(`Total tokens: ${totalTokens}`); vscode.window.showInformationMessage(`Total tokens: ${totalTokens}`);
} }
// Add formatting instructions at the end if enabled
if (settings.includeFormattingInstructions && formattingInstructions) { if (settings.includeFormattingInstructions && formattingInstructions) {
xmlParts.push(formattingInstructions); xmlParts.push(formattingInstructions);
} }
@@ -183,69 +195,43 @@ export class PromptGenerator {
* @returns String representation of the file tree * @returns String representation of the file tree
*/ */
private static generateFileTree(files: Map<string, any>, rootPath: string): string { private static generateFileTree(files: Map<string, any>, rootPath: string): string {
// Create a tree representation
const treeLines: string[] = []; const treeLines: string[] = [];
// Create a tree structure
interface TreeNode { interface TreeNode {
name: string; name: string;
isDirectory: boolean; isDirectory: boolean;
children: Map<string, TreeNode>; children: Map<string, TreeNode>;
} }
// Create root node
const root: TreeNode = { const root: TreeNode = {
name: path.basename(rootPath), name: path.basename(rootPath),
isDirectory: true, isDirectory: true,
children: new Map<string, TreeNode>() children: new Map<string, TreeNode>()
}; };
// Initialize the ignore instance const ig = ignoreFunc();
let ig;
try {
ig = typeof ignore === 'function' ? ignore() : ignore.default();
console.log('Successfully initialized ignore instance');
} catch (error) {
console.error('Error initializing ignore instance:', error);
// Fallback implementation
ig = {
add: () => {},
ignores: () => false
};
}
// Read .gitignore patterns if available
try { try {
const gitignorePath = path.join(rootPath, '.gitignore'); const gitignorePath = path.join(rootPath, '.gitignore');
if (fs.existsSync(gitignorePath)) { if (fs.existsSync(gitignorePath)) {
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8'); const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
// Add patterns from .gitignore
ig.add(gitignoreContent); ig.add(gitignoreContent);
// Always include .gitignore itself
ig.add('!.gitignore'); ig.add('!.gitignore');
} }
} catch (error) { } catch (error) {
console.error('Error reading .gitignore:', error); console.error('Error reading .gitignore:', error);
} }
// Build the tree structure
for (const filePath of files.keys()) { for (const filePath of files.keys()) {
const relativePath = this.getRelativePath(filePath, rootPath); const relativePath = this.getRelativePath(filePath, rootPath);
// Skip ignored files using the ignore package
// Use forward slashes for paths to ensure consistent matching across platforms
const normalizedPath = relativePath.split(path.sep).join('/'); const normalizedPath = relativePath.split(path.sep).join('/');
if (ig.ignores(normalizedPath)) { if (ig.ignores(normalizedPath)) {
continue; continue;
} }
// Split the path into parts
const parts = relativePath.split('/'); const parts = relativePath.split('/');
// Start from the root
let currentNode = root; let currentNode = root;
// Build the path in the tree
for (let i = 0; i < parts.length; i++) { for (let i = 0; i < parts.length; i++) {
const part = parts[i]; const part = parts[i];
const isDirectory = i < parts.length - 1; const isDirectory = i < parts.length - 1;
@@ -257,7 +243,6 @@ export class PromptGenerator {
children: new Map<string, TreeNode>() children: new Map<string, TreeNode>()
}); });
} else if (isDirectory) { } else if (isDirectory) {
// Ensure it's marked as a directory if we encounter it again
currentNode.children.get(part)!.isDirectory = true; currentNode.children.get(part)!.isDirectory = true;
} }
@@ -265,15 +250,12 @@ export class PromptGenerator {
} }
} }
// Function to recursively build the tree lines
const buildTreeLines = (node: TreeNode, prefix: string = '', isLast: boolean = true, parentPrefix: string = ''): void => { const buildTreeLines = (node: TreeNode, prefix: string = '', isLast: boolean = true, parentPrefix: string = ''): void => {
// Skip the root node in the output
if (node !== root) { if (node !== root) {
const linePrefix = parentPrefix + (isLast ? '└── ' : '├── '); const linePrefix = parentPrefix + (isLast ? '└── ' : '├── ');
treeLines.push(`${linePrefix}${node.name}${node.isDirectory ? '' : ''}`); treeLines.push(`${linePrefix}${node.name}${node.isDirectory ? '' : ''}`);
} }
// Sort children: directories first, then files, both alphabetically
const sortedChildren = Array.from(node.children.values()) const sortedChildren = Array.from(node.children.values())
.sort((a, b) => { .sort((a, b) => {
if (a.isDirectory === b.isDirectory) { if (a.isDirectory === b.isDirectory) {
@@ -282,18 +264,14 @@ export class PromptGenerator {
return a.isDirectory ? -1 : 1; return a.isDirectory ? -1 : 1;
}); });
// Process children
sortedChildren.forEach((child, index) => { sortedChildren.forEach((child, index) => {
const isChildLast = index === sortedChildren.length - 1; const isChildLast = index === sortedChildren.length - 1;
const childParentPrefix = node === root ? '' : parentPrefix + (isLast ? ' ' : '│ '); const childParentPrefix = node === root ? '' : parentPrefix + (isLast ? ' ' : '│ ');
buildTreeLines(child, prefix, isChildLast, childParentPrefix); buildTreeLines(child, prefix, isChildLast, childParentPrefix);
}); });
}; };
// Build the tree lines
buildTreeLines(root); buildTreeLines(root);
return treeLines.join('\n'); return treeLines.join('\n');
} }

18
tsconfig.webpack.json Normal file
View File

@@ -0,0 +1,18 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"outDir": "dist",
"sourceMap": true,
"esModuleInterop": true
},
"exclude": [
"node_modules",
"src/test/**",
"**/*.test.ts"
],
"include": [
"src/**/*.ts"
]
}

46
webpack.config.js Normal file
View File

@@ -0,0 +1,46 @@
const path = require('path');
const webpack = require('webpack');
/**
* @type {import('webpack').Configuration}
*/
const config = {
target: 'node',
mode: 'none', // Set to 'production' for minified output
entry: {
main: './src/extension.ts'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'extension.js',
libraryTarget: 'commonjs2',
devtoolModuleFilenameTemplate: '../[resource-path]'
},
devtool: 'source-map',
externals: {
vscode: 'commonjs vscode' // The vscode module is created on-the-fly and must be excluded
},
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules|src\/test/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
configFile: path.resolve(__dirname, './tsconfig.webpack.json')
}
}
]
}
]
}
};
module.exports = config;