Skip to content

Commit 5c58cd2

Browse files
committed
update
1 parent 85adf2f commit 5c58cd2

File tree

2 files changed

+93
-147
lines changed

2 files changed

+93
-147
lines changed

bin/handlers/pnpm-handler.ts

Lines changed: 77 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@ import {
1616
packageJsonScriptCompletion,
1717
packageJsonDependencyCompletion,
1818
} from '../completions/completion-producers.js';
19-
import {
20-
getDirectoriesInCwd,
21-
getCommonWorkspaceDirs,
22-
directoryExists,
23-
getDirectoriesMatching,
24-
} from '../utils/filesystem-utils.js';
19+
import { getWorkspacePatterns } from '../utils/filesystem-utils.js';
2520
import {
2621
stripAnsiEscapes,
2722
measureIndent,
@@ -35,124 +30,101 @@ import {
3530
// regex to detect options section in help text
3631
const OPTIONS_SECTION_RE = /^\s*Options:/i;
3732

38-
function getPnpmStorePaths(): string[] {
39-
const paths = ['~/.pnpm-store'];
33+
function extractValidValuesFromHelp(
34+
helpText: string,
35+
optionName: string
36+
): string[] {
37+
const lines = stripAnsiEscapes(helpText).split(/\r?\n/);
38+
39+
for (let i = 0; i < lines.length; i++) {
40+
const line = lines[i];
41+
if (line.includes(`--${optionName}`) || line.includes(`${optionName}:`)) {
42+
for (let j = i; j < Math.min(i + 3, lines.length); j++) {
43+
const searchLine = lines[j];
44+
45+
const levelMatch = searchLine.match(
46+
/(?:levels?|options?|values?)[^:]*:\s*([^.]+)/i
47+
);
48+
if (levelMatch) {
49+
return levelMatch[1]
50+
.split(/[,\s]+/)
51+
.map((v) => v.trim())
52+
.filter((v) => v && !v.includes('(') && !v.includes(')'));
53+
}
4054

41-
if (directoryExists('.pnpm-store')) {
42-
paths.push('./.pnpm-store');
55+
if (optionName === 'reporter') {
56+
const reporterMatch = searchLine.match(/--reporter\s+(\w+)/);
57+
if (reporterMatch) {
58+
const reporterValues = new Set<string>();
59+
for (const helpLine of lines) {
60+
const matches = helpLine.matchAll(/--reporter\s+(\w+)/g);
61+
for (const match of matches) {
62+
reporterValues.add(match[1]);
63+
}
64+
}
65+
return Array.from(reporterValues);
66+
}
67+
}
68+
}
69+
}
4370
}
4471

45-
return paths;
72+
return [];
4673
}
4774

4875
// completion handlers for pnpm options that take values
4976
const pnpmOptionHandlers = {
50-
dir: function (complete: (value: string, description: string) => void) {
51-
complete('./', 'Current directory');
52-
complete('../', 'Parent directory');
53-
54-
const dirs = getDirectoriesInCwd();
55-
for (const dir of dirs) {
56-
if (dir !== './') {
57-
complete(dir, `Directory: ${dir.slice(2)}`);
58-
}
59-
}
60-
},
77+
// Let shell handle directory completions - it's much better at it
78+
// dir, modules-dir, store-dir, lockfile-dir, virtual-store-dir removed
6179

6280
loglevel: function (complete: (value: string, description: string) => void) {
63-
complete('debug', 'Debug level');
64-
complete('info', 'Info level');
65-
complete('warn', 'Warning level');
66-
complete('error', 'Error level');
67-
complete('silent', 'Silent level');
68-
},
69-
70-
reporter: function (complete: (value: string, description: string) => void) {
71-
complete('default', 'Default reporter');
72-
complete('silent', 'Silent reporter');
73-
complete('append-only', 'Append-only reporter');
74-
complete('ndjson', 'NDJSON reporter');
75-
},
76-
77-
filter: function (complete: (value: string, description: string) => void) {
78-
complete('.', 'Current working directory');
79-
complete('...', 'Include dependents');
80-
81-
const workspaceDirs = getCommonWorkspaceDirs();
82-
for (const dir of workspaceDirs) {
83-
complete(`${dir}/*`, `All packages in ${dir.slice(2)}`);
84-
}
85-
86-
complete('@*/*', 'All scoped packages');
87-
complete('@types/*', 'All type packages');
88-
},
89-
90-
'modules-dir': function (
91-
complete: (value: string, description: string) => void
92-
) {
93-
complete('node_modules', 'Default modules directory');
94-
95-
const moduleRelatedDirs = getDirectoriesMatching('module').concat(
96-
getDirectoriesMatching('lib')
81+
// Try to get values from help, fall back to known values
82+
const helpValues = extractValidValuesFromHelp(
83+
execSync('pnpm install --help', { encoding: 'utf8', timeout: 500 }),
84+
'loglevel'
9785
);
98-
for (const dir of moduleRelatedDirs) {
99-
complete(dir.slice(2), `Existing directory: ${dir.slice(2)}`);
100-
}
101-
},
10286

103-
'store-dir': function (
104-
complete: (value: string, description: string) => void
105-
) {
106-
const storePaths = getPnpmStorePaths();
107-
for (const path of storePaths) {
108-
complete(
109-
path,
110-
path.startsWith('~') ? 'Default pnpm store' : 'Local store directory'
87+
if (helpValues.length > 0) {
88+
helpValues.forEach((value) => complete(value, `Log level: ${value}`));
89+
} else {
90+
// Fallback based on documented values
91+
['debug', 'info', 'warn', 'error', 'silent'].forEach((level) =>
92+
complete(level, `Log level: ${level}`)
11193
);
11294
}
11395
},
11496

115-
'lockfile-dir': function (
116-
complete: (value: string, description: string) => void
117-
) {
118-
complete('./', 'Current directory');
119-
complete('../', 'Parent directory');
120-
const dirs = getDirectoriesInCwd();
121-
for (const dir of dirs.slice(0, 5)) {
122-
if (dir !== './') {
123-
complete(dir, `Directory: ${dir.slice(2)}`);
124-
}
125-
}
97+
reporter: function (complete: (value: string, description: string) => void) {
98+
// valid values from pnpm help
99+
const reporters = [
100+
{ value: 'default', desc: 'Default reporter when stdout is TTY' },
101+
{
102+
value: 'append-only',
103+
desc: 'Output always appended, no cursor manipulation',
104+
},
105+
{ value: 'ndjson', desc: 'Most verbose reporter in NDJSON format' },
106+
{ value: 'silent', desc: 'No output logged to console' },
107+
];
108+
109+
reporters.forEach(({ value, desc }) => complete(value, desc));
126110
},
127111

128-
'virtual-store-dir': function (
129-
complete: (value: string, description: string) => void
130-
) {
131-
complete('node_modules/.pnpm', 'Default virtual store');
132-
complete('.pnpm', 'Custom virtual store');
133-
134-
if (directoryExists('.pnpm')) {
135-
complete('./.pnpm', 'Existing .pnpm directory');
136-
}
137-
},
112+
filter: function (complete: (value: string, description: string) => void) {
113+
// Based on pnpm documentation
114+
complete('.', 'Current working directory');
115+
complete('!<selector>', 'Exclude packages matching selector');
138116

139-
'hoist-pattern': function (
140-
complete: (value: string, description: string) => void
141-
) {
142-
complete('*', 'Hoist everything (default)');
143-
complete('@types/*', 'Hoist only type packages');
144-
complete('eslint*', 'Hoist ESLint packages');
145-
complete('*babel*', 'Hoist Babel packages');
146-
complete('*webpack*', 'Hoist Webpack packages');
147-
},
117+
// Get actual workspace patterns from pnpm-workspace.yaml
118+
const workspacePatterns = getWorkspacePatterns();
119+
workspacePatterns.forEach((pattern) => {
120+
complete(pattern, `Workspace pattern: ${pattern}`);
121+
complete(`${pattern}...`, `Include dependencies of ${pattern}`);
122+
});
148123

149-
'public-hoist-pattern': function (
150-
complete: (value: string, description: string) => void
151-
) {
152-
complete('*eslint*', 'Hoist ESLint packages to root');
153-
complete('*prettier*', 'Hoist Prettier packages to root');
154-
complete('@types/*', 'Hoist type packages to root');
155-
complete('*babel*', 'Hoist Babel packages to root');
124+
// Common scope patterns
125+
complete('@*/*', 'All scoped packages');
126+
complete('...<pattern>', 'Include dependencies of pattern');
127+
complete('<pattern>...', 'Include dependents of pattern');
156128
},
157129
};
158130

bin/utils/filesystem-utils.ts

Lines changed: 16 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,25 @@
1-
import { readdirSync, statSync } from 'node:fs';
1+
import { readFileSync } from 'node:fs';
22

3-
export function getDirectoriesInCwd(): string[] {
3+
export function getWorkspacePatterns(): string[] {
44
try {
5-
return readdirSync('.')
6-
.filter((item) => {
7-
try {
8-
return statSync(item).isDirectory();
9-
} catch {
10-
return false;
11-
}
12-
})
13-
.map((dir) => `./${dir}`)
14-
.slice(0, 10);
15-
} catch {
16-
return ['./'];
17-
}
18-
}
19-
20-
export function getCommonWorkspaceDirs(): string[] {
21-
const common = ['packages', 'apps', 'libs', 'modules', 'components'];
22-
const existing = [];
23-
24-
for (const dir of common) {
5+
let content: string;
256
try {
26-
if (statSync(dir).isDirectory()) {
27-
existing.push(`./${dir}`);
28-
}
29-
} catch {}
30-
}
7+
content = readFileSync('pnpm-workspace.yaml', 'utf8');
8+
} catch {
9+
content = readFileSync('pnpm-workspace.yml', 'utf8');
10+
}
3111

32-
return existing;
33-
}
12+
const packagesMatch = content.match(/packages:\s*\n((?:\s*-\s*.+\n?)*)/);
13+
if (!packagesMatch) return [];
3414

35-
export function directoryExists(path: string): boolean {
36-
try {
37-
return statSync(path).isDirectory();
38-
} catch {
39-
return false;
40-
}
41-
}
15+
const patterns = packagesMatch[1]
16+
.split('\n')
17+
.map((line) => line.trim())
18+
.filter((line) => line.startsWith('-'))
19+
.map((line) => line.substring(1).trim())
20+
.filter((pattern) => pattern && !pattern.startsWith('#'));
4221

43-
// Get directories that match a pattern (e.g., containing 'lib' or 'module')
44-
export function getDirectoriesMatching(pattern: string): string[] {
45-
try {
46-
return getDirectoriesInCwd().filter((dir) =>
47-
dir.toLowerCase().includes(pattern.toLowerCase())
48-
);
22+
return patterns;
4923
} catch {
5024
return [];
5125
}

0 commit comments

Comments
 (0)