Skip to content

Commit bdd7c1c

Browse files
committed
✨ feat: add search command for package and workspace dependencies, refactor dependency architecture, and update documentation
1 parent 6ef0177 commit bdd7c1c

7 files changed

Lines changed: 232 additions & 91 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/), and this
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- Command: Search in Package and Workspace Dependencies... - Select a package and open VS Code search with the package and its workspace dependencies pre-filled
14+
15+
### Changed
16+
17+
- Refactored workspace dependencies architecture for better code reusability
18+
1119
## [0.2.1] - 2025-07-23
1220

1321
### Added

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ A comprehensive VS Code extension providing productivity tools for pnpm workspac
44

55
## 🚀 Current Features
66

7-
**Copy Workspace Dependencies** - Two powerful ways to extract workspace dependency information!
7+
**Workspace Productivity Tools** - Comprehensive utilities for pnpm workspace projects!
88

99
- **Copy Workspace Dependency Names**: Select any package in your pnpm workspace and copy its workspace dependency names to clipboard, with each dependency on a new line
1010
- **Copy Workspace Dependency Paths**: Select any package in your pnpm workspace and copy its workspace dependency paths to clipboard, with each dependency path on a new line
11+
- **Search in Package and Workspace Dependencies**: Select a package and open VS Code search with the package and all its workspace dependencies pre-filled in the search scope
1112
- **Re-scan Workspace**: Manually refresh the workspace package cache when needed
1213
- **Web Extension Support**: Works in VS Code for the Web and remote development environments
1314
- **Workspace Root Support**: Includes the workspace root package with special marking
@@ -28,6 +29,7 @@ This extension contributes the following commands:
2829

2930
- `pnpm Workspace: Copy Workspace Dependency Names Of...` - Opens a quick picker to select a package and copies its workspace dependency names
3031
- `pnpm Workspace: Copy Workspace Dependency Paths Of...` - Opens a quick picker to select a package and copies its workspace dependency paths
32+
- `pnpm Workspace: Search in Package and Workspace Dependencies...` - Opens a quick picker to select a package and opens search with the package and its dependencies
3133
- `pnpm Workspace: Re-scan Workspace Packages` - Clears the package cache and re-scans the workspace
3234

3335
## Usage
@@ -46,6 +48,14 @@ This extension contributes the following commands:
4648
3. Select the package you want to get dependency paths for
4749
4. The workspace dependency paths will be copied to your clipboard, separated by newlines
4850

51+
### Search in Package and Workspace Dependencies
52+
53+
1. Open a pnpm workspace (a project with `pnpm-workspace.yaml`)
54+
2. Run `pnpm Workspace: Search in Package and Workspace Dependencies...` from the Command Palette
55+
3. Select the package you want to search in along with its dependencies
56+
4. VS Code's search panel will open with the package and its workspace dependencies pre-filled in the "files to include" field
57+
5. Enter your search term and the search will be scoped to only those packages
58+
4959
The extension automatically scans your workspace on first use and caches the results. Use the re-scan command if you've added or removed packages.
5060

5161
## Configuration

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@
7070
"title": "pnpm Workspace: Copy Workspace Dependency Paths Of...",
7171
"shortTitle": "Copy Workspace Dependency Paths"
7272
},
73+
{
74+
"command": "pnpm-workspace.searchInPackageAndWorkspaceDependencies",
75+
"title": "pnpm Workspace: Search in Package and Workspace Dependencies...",
76+
"shortTitle": "Search in Package and Dependencies"
77+
},
7378
{
7479
"command": "pnpm-workspace.rescanWorkspacePackages",
7580
"title": "pnpm Workspace: Re-scan Workspace Packages",

src/commands.ts

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import * as vscode from 'vscode';
22
import { log, logError } from './logger.js';
33
import {
44
clearPackageCache,
5-
getWorkspaceDependencies,
5+
getPackageAndDependencyPaths,
66
getWorkspaceDependencyNames,
7+
getWorkspaceDependencyPaths,
78
getWorkspacePackages,
89
type WorkspacePackage,
910
} from './pnpm-workspace.js';
@@ -201,7 +202,7 @@ export function registerCommands(context: vscode.ExtensionContext) {
201202
return;
202203
}
203204

204-
const dependencyPaths = await getWorkspaceDependencies(selected.package.name);
205+
const dependencyPaths = await getWorkspaceDependencyPaths(selected.package.name);
205206
if (dependencyPaths.length === 0) {
206207
vscode.window.showInformationMessage(`Package "${selected.package.name}" has no workspace dependencies.`);
207208
return;
@@ -221,6 +222,119 @@ export function registerCommands(context: vscode.ExtensionContext) {
221222
}
222223
);
223224

225+
// Search in Package and Workspace Dependencies...
226+
const searchInPackageAndWorkspaceDependencies = vscode.commands.registerCommand(
227+
'pnpm-workspace.searchInPackageAndWorkspaceDependencies',
228+
async () => {
229+
log('=================================');
230+
log('Executing searchInPackageAndWorkspaceDependencies command');
231+
232+
try {
233+
// Create and show QuickPick immediately with loading state
234+
const quickPick = vscode.window.createQuickPick();
235+
quickPick.placeholder = 'Select a package to search in it and its workspace dependencies';
236+
quickPick.matchOnDescription = true;
237+
quickPick.busy = true;
238+
quickPick.items = [{ label: 'Loading packages...', description: 'Scanning workspace' }];
239+
quickPick.show();
240+
241+
let packages: WorkspacePackage[];
242+
try {
243+
// Load packages in background
244+
packages = await getWorkspacePackages();
245+
} catch (error) {
246+
quickPick.hide();
247+
logError('Failed to load workspace packages', error);
248+
vscode.window.showErrorMessage('Failed to load workspace packages');
249+
return;
250+
}
251+
252+
if (packages.length === 0) {
253+
quickPick.hide();
254+
vscode.window.showErrorMessage(
255+
'No pnpm workspace packages found. Make sure you have a pnpm-workspace.yaml file.'
256+
);
257+
return;
258+
}
259+
260+
// Update QuickPick with actual packages
261+
interface QuickPickItemWithPackage extends vscode.QuickPickItem {
262+
package: WorkspacePackage;
263+
}
264+
265+
const items: QuickPickItemWithPackage[] = packages
266+
.sort((a, b) => {
267+
// Workspace root always comes first
268+
if (a.isRoot && !b.isRoot) {
269+
return -1;
270+
}
271+
if (!a.isRoot && b.isRoot) {
272+
return 1;
273+
}
274+
// @ packages come after regular packages
275+
const aStartsWithAt = a.name.startsWith('@');
276+
const bStartsWithAt = b.name.startsWith('@');
277+
if (!aStartsWithAt && bStartsWithAt) {
278+
return -1;
279+
}
280+
if (aStartsWithAt && !bStartsWithAt) {
281+
return 1;
282+
}
283+
// Otherwise sort alphabetically by name
284+
return a.name.localeCompare(b.name);
285+
})
286+
.map((pkg: WorkspacePackage) => ({
287+
label: pkg.isRoot ? `${pkg.name} (Workspace Root)` : pkg.name,
288+
description: pkg.path,
289+
package: pkg,
290+
}));
291+
292+
quickPick.busy = false;
293+
quickPick.items = items;
294+
295+
// Wait for user selection
296+
const selected = await new Promise<QuickPickItemWithPackage | undefined>((resolve) => {
297+
quickPick.onDidAccept(() => {
298+
const selection = quickPick.selectedItems[0] as QuickPickItemWithPackage;
299+
quickPick.hide();
300+
resolve(selection);
301+
});
302+
quickPick.onDidHide(() => {
303+
resolve(undefined);
304+
});
305+
});
306+
307+
if (!selected) {
308+
return;
309+
}
310+
311+
// Get all paths (package + dependencies)
312+
const allPaths = await getPackageAndDependencyPaths(selected.package.name);
313+
if (allPaths.length === 0) {
314+
vscode.window.showInformationMessage(`No paths found for package "${selected.package.name}".`);
315+
return;
316+
}
317+
318+
// Format paths for search (comma-space separated)
319+
const searchPaths = allPaths.join(', ');
320+
log(`Opening search with paths: ${searchPaths}`);
321+
322+
// Open search view with paths pre-filled
323+
await vscode.commands.executeCommand('workbench.action.findInFiles', {
324+
filesToInclude: searchPaths,
325+
triggerSearch: false, // Don't auto-search, let user enter search term
326+
});
327+
328+
vscode.window.showInformationMessage(
329+
`Opened search in ${allPaths.length} locations: ${selected.package.name} and its ${allPaths.length - 1} dependencies.`
330+
);
331+
} catch (error) {
332+
logError('Failed to search in package and workspace dependencies', error);
333+
vscode.window.showErrorMessage('Failed to search in package and workspace dependencies');
334+
}
335+
}
336+
);
337+
224338
// Re-scan Workspace Packages
225339
const rescanWorkspacePackages = vscode.commands.registerCommand(
226340
'pnpm-workspace.rescanWorkspacePackages',
@@ -255,5 +369,10 @@ export function registerCommands(context: vscode.ExtensionContext) {
255369
}
256370
);
257371

258-
context.subscriptions.push(copyWorkspaceDependencyNames, copyWorkspaceDependencyPaths, rescanWorkspacePackages);
372+
context.subscriptions.push(
373+
copyWorkspaceDependencyNames,
374+
copyWorkspaceDependencyPaths,
375+
searchInPackageAndWorkspaceDependencies,
376+
rescanWorkspacePackages
377+
);
259378
}

src/pnpm-workspace.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { WorkspacePackage, discoverPackages, loadPackageInfo } from './package-s
55
import {
66
getWorkspaceDependencies as getWorkspaceDependenciesFromPackage,
77
getWorkspaceDependencyNames as getWorkspaceDependencyNamesFromPackage,
8+
getWorkspaceDependencyPaths as getWorkspaceDependencyPathsFromPackage,
89
} from './workspace-dependencies.js';
910
import { findPnpmWorkspaceFiles, loadWorkspaceConfig } from './workspace-discovery.js';
1011

@@ -137,9 +138,44 @@ export async function getWorkspaceDependencyNames(packageName: string): Promise<
137138
}
138139

139140
/**
140-
* Gets workspace dependency paths for a specific package
141+
* Gets workspace dependencies as objects with name and path
141142
*/
142-
export async function getWorkspaceDependencies(packageName: string): Promise<string[]> {
143+
export async function getWorkspaceDependencies(packageName: string): Promise<{ name: string; path: string }[]> {
143144
const packages = await getWorkspacePackages();
144145
return await getWorkspaceDependenciesFromPackage(packageName, packages);
145146
}
147+
148+
/**
149+
* Gets workspace dependency paths only
150+
*/
151+
export async function getWorkspaceDependencyPaths(packageName: string): Promise<string[]> {
152+
const packages = await getWorkspacePackages();
153+
return await getWorkspaceDependencyPathsFromPackage(packageName, packages);
154+
}
155+
156+
/**
157+
* Gets all paths for a package and its workspace dependencies (for searching)
158+
*/
159+
export async function getPackageAndDependencyPaths(packageName: string): Promise<string[]> {
160+
const packages = await getWorkspacePackages();
161+
162+
// Find the target package
163+
const targetPackage = packages.find((pkg) => pkg.name === packageName);
164+
if (!targetPackage) {
165+
log(`Target package ${packageName} not found in workspace`);
166+
return [];
167+
}
168+
169+
const allPaths: string[] = [];
170+
171+
// Add the target package path first
172+
allPaths.push(targetPackage.path);
173+
log(`Added target package path: ${targetPackage.path}`);
174+
175+
// Get workspace dependency paths
176+
const dependencyPaths = await getWorkspaceDependencyPathsFromPackage(packageName, packages);
177+
allPaths.push(...dependencyPaths);
178+
179+
log(`Final paths for search (${packageName} + dependencies): [${allPaths.join(', ')}]`);
180+
return allPaths;
181+
}

src/test/simple-integration.test.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import * as assert from 'assert';
2-
import { clearPackageCache, getWorkspaceDependencies, getWorkspaceDependencyNames } from '../pnpm-workspace.js';
2+
import {
3+
clearPackageCache,
4+
getPackageAndDependencyPaths,
5+
getWorkspaceDependencies,
6+
getWorkspaceDependencyNames,
7+
getWorkspaceDependencyPaths,
8+
} from '../pnpm-workspace.js';
39

410
suite('Simple Integration Tests', () => {
511
setup(() => {
@@ -17,28 +23,37 @@ suite('Simple Integration Tests', () => {
1723
const { WorkspaceConfigSchema } = await import('../schemas.js');
1824
const { findPnpmWorkspaceFiles } = await import('../workspace-discovery.js');
1925
const { loadPackageInfo } = await import('../package-scanner.js');
20-
const { getWorkspaceDependencies, getWorkspaceDependencyNames } = await import('../workspace-dependencies.js');
26+
const { getWorkspaceDependencies, getWorkspaceDependencyNames, getWorkspaceDependencyPaths } = await import(
27+
'../workspace-dependencies.js'
28+
);
2129

2230
assert.ok(WorkspaceConfigSchema, 'Schema module loaded');
2331
assert.ok(findPnpmWorkspaceFiles, 'Workspace discovery module loaded');
2432
assert.ok(loadPackageInfo, 'Package scanner module loaded');
2533
assert.ok(getWorkspaceDependencies, 'Workspace dependencies module loaded');
2634
assert.ok(getWorkspaceDependencyNames, 'Workspace dependency names module loaded');
35+
assert.ok(getWorkspaceDependencyPaths, 'Workspace dependency paths module loaded');
2736
});
2837

29-
test('should differentiate between dependency names and paths', async () => {
30-
// Test that both functions exist and can be called
38+
test('should differentiate between dependency names, paths, and search paths', async () => {
39+
// Test that all functions exist and can be called
3140
assert.ok(typeof getWorkspaceDependencyNames === 'function', 'getWorkspaceDependencyNames should be a function');
3241
assert.ok(typeof getWorkspaceDependencies === 'function', 'getWorkspaceDependencies should be a function');
42+
assert.ok(typeof getWorkspaceDependencyPaths === 'function', 'getWorkspaceDependencyPaths should be a function');
43+
assert.ok(typeof getPackageAndDependencyPaths === 'function', 'getPackageAndDependencyPaths should be a function');
3344

3445
// Note: These functions require a valid workspace to return meaningful results
3546
// In this test environment, they will return empty arrays, but should not throw errors
3647
try {
3748
const names = await getWorkspaceDependencyNames('test-package');
38-
const paths = await getWorkspaceDependencies('test-package');
49+
const dependencies = await getWorkspaceDependencies('test-package');
50+
const paths = await getWorkspaceDependencyPaths('test-package');
51+
const searchPaths = await getPackageAndDependencyPaths('test-package');
3952

4053
assert.ok(Array.isArray(names), 'getWorkspaceDependencyNames should return an array');
41-
assert.ok(Array.isArray(paths), 'getWorkspaceDependencies should return an array');
54+
assert.ok(Array.isArray(dependencies), 'getWorkspaceDependencies should return an array');
55+
assert.ok(Array.isArray(paths), 'getWorkspaceDependencyPaths should return an array');
56+
assert.ok(Array.isArray(searchPaths), 'getPackageAndDependencyPaths should return an array');
4257
} catch (error) {
4358
// Expected to fail in test environment without valid workspace
4459
assert.ok(error, 'Functions should handle missing workspace gracefully');

0 commit comments

Comments
 (0)