@haystackeditor/cli 0.4.0 → 0.6.0
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 +2 -2
- package/dist/commands/config.d.ts +19 -0
- package/dist/commands/config.js +133 -0
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +34 -19
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +24 -4
- package/dist/types.d.ts +69 -2
- package/dist/types.js +1 -1
- package/dist/utils/config.d.ts +1 -1
- package/dist/utils/config.js +4 -8
- package/dist/utils/detect.d.ts +35 -2
- package/dist/utils/detect.js +139 -1
- package/dist/utils/secrets.d.ts +1 -1
- package/dist/utils/secrets.js +1 -1
- package/dist/utils/skill.d.ts +2 -2
- package/dist/utils/skill.js +655 -446
- package/package.json +1 -1
package/dist/utils/detect.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Project detection utilities
|
|
3
3
|
*
|
|
4
|
-
* Auto-detects framework, package manager, monorepo structure,
|
|
4
|
+
* Auto-detects framework, package manager, monorepo structure, services,
|
|
5
|
+
* and inter-service dependencies from proxy configurations.
|
|
5
6
|
*/
|
|
6
7
|
import * as fs from 'fs/promises';
|
|
7
8
|
import * as path from 'path';
|
|
@@ -19,6 +20,10 @@ export async function detectProject(rootDir = process.cwd()) {
|
|
|
19
20
|
result.isMonorepo = true;
|
|
20
21
|
result.monorepoTool = monorepo.tool;
|
|
21
22
|
result.services = await detectServices(rootDir, monorepo.workspaces);
|
|
23
|
+
// Detect proxy dependencies and add them to services
|
|
24
|
+
if (result.services && result.services.length > 0) {
|
|
25
|
+
result.services = await suggestServiceDependencies(rootDir, result.services);
|
|
26
|
+
}
|
|
22
27
|
}
|
|
23
28
|
// Detect framework
|
|
24
29
|
result.framework = await detectFramework(rootDir);
|
|
@@ -304,3 +309,136 @@ async function hasFile(dir, file) {
|
|
|
304
309
|
return false;
|
|
305
310
|
}
|
|
306
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Detect proxy dependencies from vite.config.ts/js
|
|
314
|
+
*
|
|
315
|
+
* Parses the vite config file to find proxy targets that point to localhost,
|
|
316
|
+
* which indicate dependencies on other local services.
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* // vite.config.ts with proxy
|
|
320
|
+
* server: {
|
|
321
|
+
* proxy: {
|
|
322
|
+
* '/api/review-chat': {
|
|
323
|
+
* target: 'http://localhost:8787',
|
|
324
|
+
* }
|
|
325
|
+
* }
|
|
326
|
+
* }
|
|
327
|
+
* // Returns: [{ path: '/api/review-chat', port: 8787, host: 'localhost' }]
|
|
328
|
+
*/
|
|
329
|
+
export async function detectProxyDependencies(rootDir) {
|
|
330
|
+
const dependencies = [];
|
|
331
|
+
// Try vite.config.ts, then vite.config.js
|
|
332
|
+
const configFiles = ['vite.config.ts', 'vite.config.js', 'vite.config.mjs'];
|
|
333
|
+
let configContent = null;
|
|
334
|
+
for (const file of configFiles) {
|
|
335
|
+
try {
|
|
336
|
+
configContent = await fs.readFile(path.join(rootDir, file), 'utf-8');
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// File not found, try next
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (!configContent) {
|
|
344
|
+
return dependencies;
|
|
345
|
+
}
|
|
346
|
+
// Parse proxy configurations using regex
|
|
347
|
+
// Matches patterns like:
|
|
348
|
+
// '/api/path': { target: 'http://localhost:8787' }
|
|
349
|
+
// '/api/path': 'http://localhost:8787'
|
|
350
|
+
// proxy: { '/api/path': { target: 'http://localhost:8787' } }
|
|
351
|
+
// Pattern for object-style proxy: '/path': { target: 'http://localhost:PORT' }
|
|
352
|
+
const objectProxyRegex = /['"]([^'"]+)['"]\s*:\s*\{[^}]*target\s*:\s*['"]https?:\/\/(localhost|127\.0\.0\.1):(\d+)['"]/g;
|
|
353
|
+
let match;
|
|
354
|
+
while ((match = objectProxyRegex.exec(configContent)) !== null) {
|
|
355
|
+
dependencies.push({
|
|
356
|
+
path: match[1],
|
|
357
|
+
host: match[2],
|
|
358
|
+
port: parseInt(match[3], 10),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
// Pattern for string-style proxy: '/path': 'http://localhost:PORT'
|
|
362
|
+
const stringProxyRegex = /['"]([^'"]+)['"]\s*:\s*['"]https?:\/\/(localhost|127\.0\.0\.1):(\d+)['"]/g;
|
|
363
|
+
let stringMatch;
|
|
364
|
+
while ((stringMatch = stringProxyRegex.exec(configContent)) !== null) {
|
|
365
|
+
// Avoid duplicates from object pattern
|
|
366
|
+
const exists = dependencies.some(d => d.path === stringMatch[1] && d.port === parseInt(stringMatch[3], 10));
|
|
367
|
+
if (!exists) {
|
|
368
|
+
dependencies.push({
|
|
369
|
+
path: stringMatch[1],
|
|
370
|
+
host: stringMatch[2],
|
|
371
|
+
port: parseInt(stringMatch[3], 10),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return dependencies;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Match proxy dependencies to detected services
|
|
379
|
+
*
|
|
380
|
+
* Given a list of proxy targets (ports) and detected services,
|
|
381
|
+
* determines which services are dependencies based on port matching.
|
|
382
|
+
*/
|
|
383
|
+
export function matchProxyToServices(proxyDeps, services) {
|
|
384
|
+
// Create a port → service name mapping
|
|
385
|
+
const portToService = new Map();
|
|
386
|
+
for (const service of services) {
|
|
387
|
+
if (service.suggestedPort) {
|
|
388
|
+
portToService.set(service.suggestedPort, service.name);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// For each service that has proxy deps pointing to another service's port,
|
|
392
|
+
// record the dependency
|
|
393
|
+
const serviceDeps = new Map();
|
|
394
|
+
for (const dep of proxyDeps) {
|
|
395
|
+
const matchedService = portToService.get(dep.port);
|
|
396
|
+
if (matchedService) {
|
|
397
|
+
dep.matchedService = matchedService;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return serviceDeps;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Suggest depends_on for services based on proxy configuration
|
|
404
|
+
*
|
|
405
|
+
* For the main service (usually frontend), detect if it proxies to other local services
|
|
406
|
+
* and suggest those as dependencies.
|
|
407
|
+
*/
|
|
408
|
+
export async function suggestServiceDependencies(rootDir, services) {
|
|
409
|
+
// Detect proxy dependencies from vite config
|
|
410
|
+
const proxyDeps = await detectProxyDependencies(rootDir);
|
|
411
|
+
if (proxyDeps.length === 0) {
|
|
412
|
+
return services;
|
|
413
|
+
}
|
|
414
|
+
// Create port → service mapping
|
|
415
|
+
const portToService = new Map();
|
|
416
|
+
for (const service of services) {
|
|
417
|
+
if (service.suggestedPort) {
|
|
418
|
+
portToService.set(service.suggestedPort, service.name);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// Match proxy ports to services
|
|
422
|
+
const dependsOn = [];
|
|
423
|
+
for (const dep of proxyDeps) {
|
|
424
|
+
const serviceName = portToService.get(dep.port);
|
|
425
|
+
if (serviceName) {
|
|
426
|
+
dep.matchedService = serviceName;
|
|
427
|
+
if (!dependsOn.includes(serviceName)) {
|
|
428
|
+
dependsOn.push(serviceName);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Update the main frontend service with depends_on
|
|
433
|
+
// The main service is typically the one at root with port 3000/5173
|
|
434
|
+
return services.map(service => {
|
|
435
|
+
// If this service is at root and has detected proxy dependencies
|
|
436
|
+
if ((service.root === '.' || service.root === './') && dependsOn.length > 0) {
|
|
437
|
+
return {
|
|
438
|
+
...service,
|
|
439
|
+
suggestedDependsOn: dependsOn,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
return service;
|
|
443
|
+
});
|
|
444
|
+
}
|
package/dist/utils/secrets.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export declare function scanForSecrets(content: string, filename: string): Secre
|
|
|
30
30
|
*/
|
|
31
31
|
export declare function scanFile(filePath: string): Promise<SecretFinding[]>;
|
|
32
32
|
/**
|
|
33
|
-
* Scan .haystack.
|
|
33
|
+
* Scan .haystack.json specifically for secrets
|
|
34
34
|
*/
|
|
35
35
|
export declare function scanHaystackConfig(configPath: string): Promise<SecretFinding[]>;
|
|
36
36
|
/**
|
package/dist/utils/secrets.js
CHANGED
|
@@ -202,7 +202,7 @@ export async function scanFile(filePath) {
|
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
/**
|
|
205
|
-
* Scan .haystack.
|
|
205
|
+
* Scan .haystack.json specifically for secrets
|
|
206
206
|
*/
|
|
207
207
|
export async function scanHaystackConfig(configPath) {
|
|
208
208
|
return scanFile(configPath);
|
package/dist/utils/skill.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare function createSkillFile(): Promise<string>;
|
|
6
6
|
/**
|
|
7
|
-
* Create the .claude/commands/
|
|
8
|
-
* Users can invoke with /haystack
|
|
7
|
+
* Create the .claude/commands/ files for Claude Code slash commands
|
|
8
|
+
* Users can invoke with /setup-haystack or /prepare-haystack
|
|
9
9
|
*/
|
|
10
10
|
export declare function createClaudeCommand(): Promise<string>;
|