@probelabs/probe 0.6.0-rc100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +583 -0
- package/bin/.gitkeep +0 -0
- package/bin/probe +158 -0
- package/bin/probe-binary +0 -0
- package/build/agent/ProbeAgent.d.ts +199 -0
- package/build/agent/ProbeAgent.js +1486 -0
- package/build/agent/acp/README.md +347 -0
- package/build/agent/acp/connection.js +237 -0
- package/build/agent/acp/connection.test.js +311 -0
- package/build/agent/acp/examples/simple-client.js +212 -0
- package/build/agent/acp/examples/tool-lifecycle.js +230 -0
- package/build/agent/acp/final-test.js +173 -0
- package/build/agent/acp/index.js +5 -0
- package/build/agent/acp/integration.test.js +385 -0
- package/build/agent/acp/manual-test.js +410 -0
- package/build/agent/acp/protocol-test.js +190 -0
- package/build/agent/acp/server.js +448 -0
- package/build/agent/acp/server.test.js +371 -0
- package/build/agent/acp/test-runner.js +216 -0
- package/build/agent/acp/test-utils/README.md +315 -0
- package/build/agent/acp/test-utils/acp-tester.js +484 -0
- package/build/agent/acp/test-utils/mock-acp-client.js +434 -0
- package/build/agent/acp/tools.js +368 -0
- package/build/agent/acp/tools.test.js +334 -0
- package/build/agent/acp/types.js +218 -0
- package/build/agent/acp/types.test.js +327 -0
- package/build/agent/appTracer.js +360 -0
- package/build/agent/fileSpanExporter.js +169 -0
- package/build/agent/index.js +7426 -0
- package/build/agent/mcp/client.js +338 -0
- package/build/agent/mcp/config.js +313 -0
- package/build/agent/mcp/index.js +64 -0
- package/build/agent/mcp/xmlBridge.js +371 -0
- package/build/agent/mockProvider.js +53 -0
- package/build/agent/probeTool.js +257 -0
- package/build/agent/schemaUtils.js +1726 -0
- package/build/agent/simpleTelemetry.js +267 -0
- package/build/agent/telemetry.js +225 -0
- package/build/agent/tokenCounter.js +395 -0
- package/build/agent/tools.js +163 -0
- package/build/cli.js +49 -0
- package/build/delegate.js +267 -0
- package/build/directory-resolver.js +237 -0
- package/build/downloader.js +750 -0
- package/build/extract.js +149 -0
- package/build/index.js +70 -0
- package/build/mcp/index.js +514 -0
- package/build/mcp/index.ts +608 -0
- package/build/query.js +116 -0
- package/build/search.js +247 -0
- package/build/tools/common.js +410 -0
- package/build/tools/index.js +40 -0
- package/build/tools/langchain.js +88 -0
- package/build/tools/system-message.js +121 -0
- package/build/tools/vercel.js +271 -0
- package/build/utils/file-lister.js +193 -0
- package/build/utils.js +128 -0
- package/cjs/agent/ProbeAgent.cjs +5829 -0
- package/cjs/index.cjs +6217 -0
- package/cjs/package.json +3 -0
- package/index.d.ts +401 -0
- package/package.json +114 -0
- package/scripts/postinstall.js +172 -0
- package/src/agent/ProbeAgent.d.ts +199 -0
- package/src/agent/ProbeAgent.js +1486 -0
- package/src/agent/acp/README.md +347 -0
- package/src/agent/acp/connection.js +237 -0
- package/src/agent/acp/connection.test.js +311 -0
- package/src/agent/acp/examples/simple-client.js +212 -0
- package/src/agent/acp/examples/tool-lifecycle.js +230 -0
- package/src/agent/acp/final-test.js +173 -0
- package/src/agent/acp/index.js +5 -0
- package/src/agent/acp/integration.test.js +385 -0
- package/src/agent/acp/manual-test.js +410 -0
- package/src/agent/acp/protocol-test.js +190 -0
- package/src/agent/acp/server.js +448 -0
- package/src/agent/acp/server.test.js +371 -0
- package/src/agent/acp/test-runner.js +216 -0
- package/src/agent/acp/test-utils/README.md +315 -0
- package/src/agent/acp/test-utils/acp-tester.js +484 -0
- package/src/agent/acp/test-utils/mock-acp-client.js +434 -0
- package/src/agent/acp/tools.js +368 -0
- package/src/agent/acp/tools.test.js +334 -0
- package/src/agent/acp/types.js +218 -0
- package/src/agent/acp/types.test.js +327 -0
- package/src/agent/appTracer.js +360 -0
- package/src/agent/fileSpanExporter.js +169 -0
- package/src/agent/index.js +813 -0
- package/src/agent/mcp/client.js +338 -0
- package/src/agent/mcp/config.js +313 -0
- package/src/agent/mcp/index.js +64 -0
- package/src/agent/mcp/xmlBridge.js +371 -0
- package/src/agent/mockProvider.js +53 -0
- package/src/agent/probeTool.js +257 -0
- package/src/agent/schemaUtils.js +1726 -0
- package/src/agent/simpleTelemetry.js +267 -0
- package/src/agent/telemetry.js +225 -0
- package/src/agent/tokenCounter.js +395 -0
- package/src/agent/tools.js +163 -0
- package/src/cli.js +49 -0
- package/src/delegate.js +267 -0
- package/src/directory-resolver.js +237 -0
- package/src/downloader.js +750 -0
- package/src/extract.js +149 -0
- package/src/index.js +70 -0
- package/src/mcp/index.ts +608 -0
- package/src/query.js +116 -0
- package/src/search.js +247 -0
- package/src/tools/common.js +410 -0
- package/src/tools/index.js +40 -0
- package/src/tools/langchain.js +88 -0
- package/src/tools/system-message.js +121 -0
- package/src/tools/vercel.js +271 -0
- package/src/utils/file-lister.js +193 -0
- package/src/utils.js +128 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools for Vercel AI SDK
|
|
3
|
+
* @module tools/vercel
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { tool } from 'ai';
|
|
7
|
+
import { search } from '../search.js';
|
|
8
|
+
import { query } from '../query.js';
|
|
9
|
+
import { extract } from '../extract.js';
|
|
10
|
+
import { delegate } from '../delegate.js';
|
|
11
|
+
import { searchSchema, querySchema, extractSchema, delegateSchema, searchDescription, queryDescription, extractDescription, delegateDescription } from './common.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Search tool generator
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} [options] - Configuration options
|
|
17
|
+
* @param {string} [options.sessionId] - Session ID for caching search results
|
|
18
|
+
* @param {number} [options.maxTokens=10000] - Default max tokens
|
|
19
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
20
|
+
* @returns {Object} Configured search tool
|
|
21
|
+
*/
|
|
22
|
+
export const searchTool = (options = {}) => {
|
|
23
|
+
const { sessionId, maxTokens = 10000, debug = false, outline = false } = options;
|
|
24
|
+
|
|
25
|
+
return tool({
|
|
26
|
+
name: 'search',
|
|
27
|
+
description: searchDescription,
|
|
28
|
+
inputSchema: searchSchema,
|
|
29
|
+
execute: async ({ query: searchQuery, path, allow_tests, exact, maxTokens: paramMaxTokens, language }) => {
|
|
30
|
+
try {
|
|
31
|
+
// Use parameter maxTokens if provided, otherwise use the default
|
|
32
|
+
const effectiveMaxTokens = paramMaxTokens || maxTokens;
|
|
33
|
+
|
|
34
|
+
// Use the path from parameters if provided, otherwise use defaultPath from config
|
|
35
|
+
let searchPath = path || options.defaultPath || '.';
|
|
36
|
+
|
|
37
|
+
// If path is "." or "./", use the defaultPath if available
|
|
38
|
+
if ((searchPath === "." || searchPath === "./") && options.defaultPath) {
|
|
39
|
+
if (debug) {
|
|
40
|
+
console.error(`Using default path "${options.defaultPath}" instead of "${searchPath}"`);
|
|
41
|
+
}
|
|
42
|
+
searchPath = options.defaultPath;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (debug) {
|
|
46
|
+
console.error(`Executing search with query: "${searchQuery}", path: "${searchPath}", exact: ${exact ? 'true' : 'false'}, language: ${language || 'all'}, session: ${sessionId || 'none'}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const searchOptions = {
|
|
50
|
+
query: searchQuery,
|
|
51
|
+
path: searchPath,
|
|
52
|
+
allowTests: allow_tests,
|
|
53
|
+
exact,
|
|
54
|
+
json: false,
|
|
55
|
+
maxTokens: effectiveMaxTokens,
|
|
56
|
+
session: sessionId, // Pass session ID if provided
|
|
57
|
+
language // Pass language parameter if provided
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Add outline format if enabled
|
|
61
|
+
if (outline) {
|
|
62
|
+
searchOptions.format = 'outline-xml';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const results = await search(searchOptions);
|
|
66
|
+
|
|
67
|
+
return results;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Error executing search command:', error);
|
|
70
|
+
return `Error executing search command: ${error.message}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Query tool generator
|
|
78
|
+
*
|
|
79
|
+
* @param {Object} [options] - Configuration options
|
|
80
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
81
|
+
* @returns {Object} Configured query tool
|
|
82
|
+
*/
|
|
83
|
+
export const queryTool = (options = {}) => {
|
|
84
|
+
const { debug = false } = options;
|
|
85
|
+
|
|
86
|
+
return tool({
|
|
87
|
+
name: 'query',
|
|
88
|
+
description: queryDescription,
|
|
89
|
+
inputSchema: querySchema,
|
|
90
|
+
execute: async ({ pattern, path, language, allow_tests }) => {
|
|
91
|
+
try {
|
|
92
|
+
// Use the path from parameters if provided, otherwise use defaultPath from config
|
|
93
|
+
let queryPath = path || options.defaultPath || '.';
|
|
94
|
+
|
|
95
|
+
// If path is "." or "./", use the defaultPath if available
|
|
96
|
+
if ((queryPath === "." || queryPath === "./") && options.defaultPath) {
|
|
97
|
+
if (debug) {
|
|
98
|
+
console.error(`Using default path "${options.defaultPath}" instead of "${queryPath}"`);
|
|
99
|
+
}
|
|
100
|
+
queryPath = options.defaultPath;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (debug) {
|
|
104
|
+
console.error(`Executing query with pattern: "${pattern}", path: "${queryPath}", language: ${language || 'auto'}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const results = await query({
|
|
108
|
+
pattern,
|
|
109
|
+
path: queryPath,
|
|
110
|
+
language,
|
|
111
|
+
allow_tests,
|
|
112
|
+
json: false
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return results;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('Error executing query command:', error);
|
|
118
|
+
return `Error executing query command: ${error.message}`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Extract tool generator
|
|
126
|
+
*
|
|
127
|
+
* @param {Object} [options] - Configuration options
|
|
128
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
129
|
+
* @returns {Object} Configured extract tool
|
|
130
|
+
*/
|
|
131
|
+
export const extractTool = (options = {}) => {
|
|
132
|
+
const { debug = false, outline = false } = options;
|
|
133
|
+
|
|
134
|
+
return tool({
|
|
135
|
+
name: 'extract',
|
|
136
|
+
description: extractDescription,
|
|
137
|
+
inputSchema: extractSchema,
|
|
138
|
+
execute: async ({ targets, input_content, line, end_line, allow_tests, context_lines, format }) => {
|
|
139
|
+
try {
|
|
140
|
+
// Use the defaultPath from config for context
|
|
141
|
+
let extractPath = options.defaultPath || '.';
|
|
142
|
+
|
|
143
|
+
// If path is "." or "./", use the defaultPath if available
|
|
144
|
+
if ((extractPath === "." || extractPath === "./") && options.defaultPath) {
|
|
145
|
+
if (debug) {
|
|
146
|
+
console.error(`Using default path "${options.defaultPath}" instead of "${extractPath}"`);
|
|
147
|
+
}
|
|
148
|
+
extractPath = options.defaultPath;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (debug) {
|
|
152
|
+
if (targets) {
|
|
153
|
+
console.error(`Executing extract with targets: "${targets}", path: "${extractPath}", context lines: ${context_lines || 10}`);
|
|
154
|
+
} else if (input_content) {
|
|
155
|
+
console.error(`Executing extract with input content, path: "${extractPath}", context lines: ${context_lines || 10}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Create a temporary file for input content if provided
|
|
160
|
+
let tempFilePath = null;
|
|
161
|
+
let extractOptions = { path: extractPath };
|
|
162
|
+
|
|
163
|
+
if (input_content) {
|
|
164
|
+
// Import required modules
|
|
165
|
+
const { writeFileSync, unlinkSync } = await import('fs');
|
|
166
|
+
const { join } = await import('path');
|
|
167
|
+
const { tmpdir } = await import('os');
|
|
168
|
+
const { randomUUID } = await import('crypto');
|
|
169
|
+
|
|
170
|
+
// Create a temporary file with the input content
|
|
171
|
+
tempFilePath = join(tmpdir(), `probe-extract-${randomUUID()}.txt`);
|
|
172
|
+
writeFileSync(tempFilePath, input_content);
|
|
173
|
+
|
|
174
|
+
if (debug) {
|
|
175
|
+
console.error(`Created temporary file for input content: ${tempFilePath}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Apply format mapping for outline-xml to xml
|
|
179
|
+
let effectiveFormat = format;
|
|
180
|
+
if (outline && format === 'outline-xml') {
|
|
181
|
+
effectiveFormat = 'xml';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Set up extract options with input file
|
|
185
|
+
extractOptions = {
|
|
186
|
+
inputFile: tempFilePath,
|
|
187
|
+
allowTests: allow_tests,
|
|
188
|
+
contextLines: context_lines,
|
|
189
|
+
format: effectiveFormat
|
|
190
|
+
};
|
|
191
|
+
} else if (targets) {
|
|
192
|
+
// Parse targets to handle line numbers and symbol names
|
|
193
|
+
const files = [targets];
|
|
194
|
+
|
|
195
|
+
// Apply format mapping for outline-xml to xml
|
|
196
|
+
let effectiveFormat = format;
|
|
197
|
+
if (outline && format === 'outline-xml') {
|
|
198
|
+
effectiveFormat = 'xml';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Set up extract options with files
|
|
202
|
+
extractOptions = {
|
|
203
|
+
files,
|
|
204
|
+
allowTests: allow_tests,
|
|
205
|
+
contextLines: context_lines,
|
|
206
|
+
format: effectiveFormat
|
|
207
|
+
};
|
|
208
|
+
} else {
|
|
209
|
+
throw new Error('Either targets or input_content must be provided');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Execute the extract command
|
|
213
|
+
const results = await extract(extractOptions);
|
|
214
|
+
|
|
215
|
+
// Clean up temporary file if created
|
|
216
|
+
if (tempFilePath) {
|
|
217
|
+
const { unlinkSync } = await import('fs');
|
|
218
|
+
try {
|
|
219
|
+
unlinkSync(tempFilePath);
|
|
220
|
+
if (debug) {
|
|
221
|
+
console.error(`Removed temporary file: ${tempFilePath}`);
|
|
222
|
+
}
|
|
223
|
+
} catch (cleanupError) {
|
|
224
|
+
console.error(`Warning: Failed to remove temporary file: ${cleanupError.message}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return results;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error('Error executing extract command:', error);
|
|
231
|
+
return `Error executing extract command: ${error.message}`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Delegate tool generator
|
|
239
|
+
*
|
|
240
|
+
* @param {Object} [options] - Configuration options
|
|
241
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
242
|
+
* @param {number} [options.timeout=300] - Default timeout in seconds
|
|
243
|
+
* @returns {Object} Configured delegate tool
|
|
244
|
+
*/
|
|
245
|
+
export const delegateTool = (options = {}) => {
|
|
246
|
+
const { debug = false, timeout = 300 } = options;
|
|
247
|
+
|
|
248
|
+
return tool({
|
|
249
|
+
name: 'delegate',
|
|
250
|
+
description: delegateDescription,
|
|
251
|
+
inputSchema: delegateSchema,
|
|
252
|
+
execute: async ({ task }) => {
|
|
253
|
+
try {
|
|
254
|
+
if (debug) {
|
|
255
|
+
console.error(`Executing delegate with task: "${task}"`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const result = await delegate({
|
|
259
|
+
task,
|
|
260
|
+
timeout,
|
|
261
|
+
debug
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
return result;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error('Error executing delegate command:', error);
|
|
267
|
+
return `Error executing delegate command: ${error.message}`;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File listing utility for the probe package
|
|
3
|
+
* @module utils/file-lister
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
import { exec } from 'child_process';
|
|
10
|
+
|
|
11
|
+
const execAsync = promisify(exec);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* List files in a directory by nesting level, respecting .gitignore
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} options - Options for listing files
|
|
17
|
+
* @param {string} options.directory - Directory to list files from
|
|
18
|
+
* @param {number} [options.maxFiles=100] - Maximum number of files to return
|
|
19
|
+
* @param {boolean} [options.respectGitignore=true] - Whether to respect .gitignore
|
|
20
|
+
* @returns {Promise<string[]>} - Array of file paths
|
|
21
|
+
*/
|
|
22
|
+
export async function listFilesByLevel(options) {
|
|
23
|
+
const {
|
|
24
|
+
directory,
|
|
25
|
+
maxFiles = 100,
|
|
26
|
+
respectGitignore = true
|
|
27
|
+
} = options;
|
|
28
|
+
|
|
29
|
+
// Check if directory exists
|
|
30
|
+
if (!fs.existsSync(directory)) {
|
|
31
|
+
throw new Error(`Directory does not exist: ${directory}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Use git ls-files if .git directory exists and respectGitignore is true
|
|
35
|
+
const gitDirExists = fs.existsSync(path.join(directory, '.git'));
|
|
36
|
+
if (gitDirExists && respectGitignore) {
|
|
37
|
+
try {
|
|
38
|
+
return await listFilesUsingGit(directory, maxFiles);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error(`Warning: Failed to use git ls-files: ${error.message}`);
|
|
41
|
+
console.error('Falling back to manual file listing');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Fall back to manual file listing
|
|
46
|
+
return await listFilesByLevelManually(directory, maxFiles, respectGitignore);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* List files using git ls-files (respects .gitignore by default)
|
|
51
|
+
*
|
|
52
|
+
* @param {string} directory - Directory to list files from
|
|
53
|
+
* @param {number} maxFiles - Maximum number of files to return
|
|
54
|
+
* @returns {Promise<string[]>} - Array of file paths
|
|
55
|
+
*/
|
|
56
|
+
async function listFilesUsingGit(directory, maxFiles) {
|
|
57
|
+
// Use git ls-files to get tracked files (respects .gitignore)
|
|
58
|
+
const { stdout } = await execAsync('git ls-files', { cwd: directory });
|
|
59
|
+
|
|
60
|
+
// Split output into lines and filter out empty lines
|
|
61
|
+
const files = stdout.split('\n').filter(Boolean);
|
|
62
|
+
|
|
63
|
+
// Sort files by directory depth (breadth-first)
|
|
64
|
+
const sortedFiles = files.sort((a, b) => {
|
|
65
|
+
const depthA = a.split(path.sep).length;
|
|
66
|
+
const depthB = b.split(path.sep).length;
|
|
67
|
+
return depthA - depthB;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Limit to maxFiles
|
|
71
|
+
return sortedFiles.slice(0, maxFiles);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* List files manually by nesting level
|
|
76
|
+
*
|
|
77
|
+
* @param {string} directory - Directory to list files from
|
|
78
|
+
* @param {number} maxFiles - Maximum number of files to return
|
|
79
|
+
* @param {boolean} respectGitignore - Whether to respect .gitignore
|
|
80
|
+
* @returns {Promise<string[]>} - Array of file paths
|
|
81
|
+
*/
|
|
82
|
+
async function listFilesByLevelManually(directory, maxFiles, respectGitignore) {
|
|
83
|
+
// Load .gitignore patterns if needed
|
|
84
|
+
let ignorePatterns = [];
|
|
85
|
+
if (respectGitignore) {
|
|
86
|
+
ignorePatterns = loadGitignorePatterns(directory);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Initialize result array
|
|
90
|
+
const result = [];
|
|
91
|
+
|
|
92
|
+
// Initialize queue with root directory
|
|
93
|
+
const queue = [{ dir: directory, level: 0 }];
|
|
94
|
+
|
|
95
|
+
// Process queue (breadth-first)
|
|
96
|
+
while (queue.length > 0 && result.length < maxFiles) {
|
|
97
|
+
const { dir, level } = queue.shift();
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
// Read directory contents
|
|
101
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
102
|
+
|
|
103
|
+
// Process files first (at current level)
|
|
104
|
+
const files = entries.filter(entry => entry.isFile());
|
|
105
|
+
for (const file of files) {
|
|
106
|
+
if (result.length >= maxFiles) break;
|
|
107
|
+
|
|
108
|
+
const filePath = path.join(dir, file.name);
|
|
109
|
+
const relativePath = path.relative(directory, filePath);
|
|
110
|
+
|
|
111
|
+
// Skip if file matches any ignore pattern
|
|
112
|
+
if (shouldIgnore(relativePath, ignorePatterns)) continue;
|
|
113
|
+
|
|
114
|
+
result.push(relativePath);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Then add directories to queue for next level
|
|
118
|
+
const dirs = entries.filter(entry => entry.isDirectory());
|
|
119
|
+
for (const subdir of dirs) {
|
|
120
|
+
const subdirPath = path.join(dir, subdir.name);
|
|
121
|
+
const relativeSubdirPath = path.relative(directory, subdirPath);
|
|
122
|
+
|
|
123
|
+
// Skip if directory matches any ignore pattern
|
|
124
|
+
if (shouldIgnore(relativeSubdirPath, ignorePatterns)) continue;
|
|
125
|
+
|
|
126
|
+
// Skip node_modules and .git directories
|
|
127
|
+
if (subdir.name === 'node_modules' || subdir.name === '.git') continue;
|
|
128
|
+
|
|
129
|
+
queue.push({ dir: subdirPath, level: level + 1 });
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(`Warning: Could not read directory ${dir}: ${error.message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Load .gitignore patterns from a directory
|
|
141
|
+
*
|
|
142
|
+
* @param {string} directory - Directory to load .gitignore from
|
|
143
|
+
* @returns {string[]} - Array of ignore patterns
|
|
144
|
+
*/
|
|
145
|
+
function loadGitignorePatterns(directory) {
|
|
146
|
+
const gitignorePath = path.join(directory, '.gitignore');
|
|
147
|
+
|
|
148
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
154
|
+
return content
|
|
155
|
+
.split('\n')
|
|
156
|
+
.map(line => line.trim())
|
|
157
|
+
.filter(line => line && !line.startsWith('#'));
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error(`Warning: Could not read .gitignore: ${error.message}`);
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Check if a file path should be ignored based on ignore patterns
|
|
166
|
+
*
|
|
167
|
+
* @param {string} filePath - File path to check
|
|
168
|
+
* @param {string[]} ignorePatterns - Array of ignore patterns
|
|
169
|
+
* @returns {boolean} - Whether the file should be ignored
|
|
170
|
+
*/
|
|
171
|
+
function shouldIgnore(filePath, ignorePatterns) {
|
|
172
|
+
if (!ignorePatterns.length) return false;
|
|
173
|
+
|
|
174
|
+
// Simple pattern matching (could be improved with minimatch or similar)
|
|
175
|
+
for (const pattern of ignorePatterns) {
|
|
176
|
+
// Exact match
|
|
177
|
+
if (pattern === filePath) return true;
|
|
178
|
+
|
|
179
|
+
// Directory match (pattern ends with /)
|
|
180
|
+
if (pattern.endsWith('/') && filePath.startsWith(pattern)) return true;
|
|
181
|
+
|
|
182
|
+
// File extension match (pattern starts with *.)
|
|
183
|
+
if (pattern.startsWith('*.') && filePath.endsWith(pattern.substring(1))) return true;
|
|
184
|
+
|
|
185
|
+
// Wildcard at start (pattern starts with *)
|
|
186
|
+
if (pattern.startsWith('*') && filePath.endsWith(pattern.substring(1))) return true;
|
|
187
|
+
|
|
188
|
+
// Wildcard at end (pattern ends with *)
|
|
189
|
+
if (pattern.endsWith('*') && filePath.startsWith(pattern.substring(0, pattern.length - 1))) return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return false;
|
|
193
|
+
}
|
package/build/utils.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for the probe package
|
|
3
|
+
* @module utils
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import fs from 'fs-extra';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { downloadProbeBinary } from './downloader.js';
|
|
10
|
+
import { getPackageBinDir } from './directory-resolver.js';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
|
|
15
|
+
// Note: binDir is now resolved dynamically using getPackageBinDir()
|
|
16
|
+
|
|
17
|
+
// Store the binary path
|
|
18
|
+
let probeBinaryPath = '';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the path to the probe binary, downloading it if necessary
|
|
22
|
+
* @param {Object} options - Options for getting the binary
|
|
23
|
+
* @param {boolean} [options.forceDownload=false] - Force download even if binary exists
|
|
24
|
+
* @param {string} [options.version] - Specific version to download
|
|
25
|
+
* @returns {Promise<string>} - Path to the binary
|
|
26
|
+
*/
|
|
27
|
+
export async function getBinaryPath(options = {}) {
|
|
28
|
+
const { forceDownload = false, version } = options;
|
|
29
|
+
|
|
30
|
+
// Return cached path if available and not forcing download
|
|
31
|
+
if (probeBinaryPath && !forceDownload && fs.existsSync(probeBinaryPath)) {
|
|
32
|
+
return probeBinaryPath;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check environment variable
|
|
36
|
+
if (process.env.PROBE_PATH && fs.existsSync(process.env.PROBE_PATH) && !forceDownload) {
|
|
37
|
+
probeBinaryPath = process.env.PROBE_PATH;
|
|
38
|
+
return probeBinaryPath;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Get dynamic bin directory (handles CI, npx, Docker scenarios)
|
|
42
|
+
const binDir = await getPackageBinDir();
|
|
43
|
+
|
|
44
|
+
// Check bin directory
|
|
45
|
+
const isWindows = process.platform === 'win32';
|
|
46
|
+
const binaryName = isWindows ? 'probe.exe' : 'probe';
|
|
47
|
+
const binaryPath = path.join(binDir, binaryName);
|
|
48
|
+
|
|
49
|
+
if (fs.existsSync(binaryPath) && !forceDownload) {
|
|
50
|
+
probeBinaryPath = binaryPath;
|
|
51
|
+
return probeBinaryPath;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Download if not found or force download
|
|
55
|
+
console.log(`${forceDownload ? 'Force downloading' : 'Binary not found. Downloading'} probe binary...`);
|
|
56
|
+
probeBinaryPath = await downloadProbeBinary(version);
|
|
57
|
+
return probeBinaryPath;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Manually set the path to the probe binary
|
|
62
|
+
* @param {string} binaryPath - Path to the probe binary
|
|
63
|
+
* @throws {Error} If the binary doesn't exist at the specified path
|
|
64
|
+
*/
|
|
65
|
+
export function setBinaryPath(binaryPath) {
|
|
66
|
+
if (!fs.existsSync(binaryPath)) {
|
|
67
|
+
throw new Error(`No binary found at path: ${binaryPath}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
probeBinaryPath = binaryPath;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Ensure the bin directory exists
|
|
75
|
+
* @returns {Promise<void>}
|
|
76
|
+
*/
|
|
77
|
+
export async function ensureBinDirectory() {
|
|
78
|
+
// This function is now handled by getPackageBinDir() which ensures directory exists
|
|
79
|
+
// Keeping for backward compatibility but it's no longer needed
|
|
80
|
+
const binDir = await getPackageBinDir();
|
|
81
|
+
await fs.ensureDir(binDir);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build command-line arguments from an options object
|
|
86
|
+
* @param {Object} options - Options object
|
|
87
|
+
* @param {Array<string>} flagMap - Map of option keys to command-line flags
|
|
88
|
+
* @returns {Array<string>} - Array of command-line arguments
|
|
89
|
+
*/
|
|
90
|
+
export function buildCliArgs(options, flagMap) {
|
|
91
|
+
const cliArgs = [];
|
|
92
|
+
|
|
93
|
+
for (const [key, flag] of Object.entries(flagMap)) {
|
|
94
|
+
if (key in options) {
|
|
95
|
+
const value = options[key];
|
|
96
|
+
|
|
97
|
+
if (typeof value === 'boolean') {
|
|
98
|
+
if (value) {
|
|
99
|
+
cliArgs.push(flag);
|
|
100
|
+
}
|
|
101
|
+
} else if (Array.isArray(value)) {
|
|
102
|
+
for (const item of value) {
|
|
103
|
+
cliArgs.push(flag, item);
|
|
104
|
+
}
|
|
105
|
+
} else if (value !== undefined && value !== null) {
|
|
106
|
+
cliArgs.push(flag, value.toString());
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return cliArgs;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Escape a string for use in a command line
|
|
116
|
+
* @param {string} str - String to escape
|
|
117
|
+
* @returns {string} - Escaped string
|
|
118
|
+
*/
|
|
119
|
+
export function escapeString(str) {
|
|
120
|
+
if (process.platform === 'win32') {
|
|
121
|
+
// For Windows PowerShell, escape double quotes and wrap with double quotes
|
|
122
|
+
return `"${str.replace(/"/g, '\\"')}"`;
|
|
123
|
+
} else {
|
|
124
|
+
// Use single quotes for POSIX shells
|
|
125
|
+
// Escape single quotes in the string by replacing ' with '\''
|
|
126
|
+
return `'${str.replace(/'/g, "'\\''")}'`;
|
|
127
|
+
}
|
|
128
|
+
}
|