claude-code-templates 1.22.0 ā 1.22.1
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/bin/create-claude-config.js +1 -0
- package/package.json +7 -2
- package/src/analytics-web/chats_mobile.html +17 -16
- package/src/console-bridge.js +3 -3
- package/src/index.js +121 -9
- package/src/security-audit.js +164 -0
- package/src/validation/ARCHITECTURE.md +309 -0
- package/src/validation/BaseValidator.js +152 -0
- package/src/validation/README.md +543 -0
- package/src/validation/ValidationOrchestrator.js +305 -0
- package/src/validation/validators/IntegrityValidator.js +338 -0
- package/src/validation/validators/ProvenanceValidator.js +399 -0
- package/src/validation/validators/ReferenceValidator.js +373 -0
- package/src/validation/validators/SemanticValidator.js +449 -0
- package/src/validation/validators/StructuralValidator.js +376 -0
|
@@ -67,6 +67,7 @@ program
|
|
|
67
67
|
.option('--mcp <mcp>', 'install specific MCP component (supports comma-separated values)')
|
|
68
68
|
.option('--setting <setting>', 'install specific setting component (supports comma-separated values)')
|
|
69
69
|
.option('--hook <hook>', 'install specific hook component (supports comma-separated values)')
|
|
70
|
+
.option('--skill <skill>', 'install specific skill component (supports comma-separated values)')
|
|
70
71
|
.option('--workflow <workflow>', 'install workflow from hash (#hash) OR workflow YAML (base64 encoded) when used with --agent/--command/--mcp')
|
|
71
72
|
.option('--prompt <prompt>', 'execute the provided prompt in Claude Code after installation or in sandbox')
|
|
72
73
|
.option('--create-agent <agent>', 'create a global agent accessible from anywhere (e.g., customer-support)')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.1",
|
|
4
4
|
"description": "CLI tool to setup Claude Code configurations with framework-specific commands, automation hooks and MCP Servers for your projects",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -32,7 +32,11 @@
|
|
|
32
32
|
"dev:unlink": "npm unlink -g claude-code-templates",
|
|
33
33
|
"pretest:commands": "npm run dev:link",
|
|
34
34
|
"analytics:start": "node src/analytics.js",
|
|
35
|
-
"analytics:test": "npm run test:analytics"
|
|
35
|
+
"analytics:test": "npm run test:analytics",
|
|
36
|
+
"security-audit": "node src/security-audit.js",
|
|
37
|
+
"security-audit:ci": "node src/security-audit.js --ci",
|
|
38
|
+
"security-audit:verbose": "node src/security-audit.js --verbose",
|
|
39
|
+
"security-audit:json": "node src/security-audit.js --json --output=security-report.json"
|
|
36
40
|
},
|
|
37
41
|
"keywords": [
|
|
38
42
|
"claude",
|
|
@@ -67,6 +71,7 @@
|
|
|
67
71
|
"express": "^4.18.2",
|
|
68
72
|
"fs-extra": "^11.1.1",
|
|
69
73
|
"inquirer": "^8.2.6",
|
|
74
|
+
"js-yaml": "^4.1.0",
|
|
70
75
|
"open": "^8.4.2",
|
|
71
76
|
"ora": "^5.4.1",
|
|
72
77
|
"uuid": "^11.1.0",
|
|
@@ -1576,20 +1576,20 @@
|
|
|
1576
1576
|
async loadMoreMessages(conversationId, isInitialLoad = false) {
|
|
1577
1577
|
const chatMessages = document.getElementById('chatMessages');
|
|
1578
1578
|
if (!chatMessages) return;
|
|
1579
|
-
|
|
1579
|
+
|
|
1580
1580
|
// Prevent concurrent loading
|
|
1581
1581
|
if (this.messagesPagination.isLoading || !this.messagesPagination.hasMore) {
|
|
1582
1582
|
return;
|
|
1583
1583
|
}
|
|
1584
|
-
|
|
1584
|
+
|
|
1585
1585
|
// Ensure we're loading for the correct conversation
|
|
1586
1586
|
if (this.messagesPagination.conversationId !== conversationId) {
|
|
1587
1587
|
return;
|
|
1588
1588
|
}
|
|
1589
|
-
|
|
1589
|
+
|
|
1590
1590
|
try {
|
|
1591
1591
|
this.messagesPagination.isLoading = true;
|
|
1592
|
-
|
|
1592
|
+
|
|
1593
1593
|
if (isInitialLoad) {
|
|
1594
1594
|
// Show loading state for initial load
|
|
1595
1595
|
chatMessages.innerHTML = `
|
|
@@ -1604,16 +1604,16 @@
|
|
|
1604
1604
|
// Show loading indicator at top for infinite scroll
|
|
1605
1605
|
this.showMessagesLoadingIndicator(true);
|
|
1606
1606
|
}
|
|
1607
|
-
|
|
1607
|
+
|
|
1608
1608
|
// Fetch paginated messages from the server
|
|
1609
1609
|
const response = await fetch(`/api/conversations/${conversationId}/messages?page=${this.messagesPagination.currentPage}&limit=${this.messagesPagination.limit}`);
|
|
1610
|
-
|
|
1610
|
+
|
|
1611
1611
|
if (!response.ok) {
|
|
1612
1612
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
1613
1613
|
}
|
|
1614
|
-
|
|
1614
|
+
|
|
1615
1615
|
const messagesData = await response.json();
|
|
1616
|
-
|
|
1616
|
+
|
|
1617
1617
|
if (messagesData && messagesData.messages) {
|
|
1618
1618
|
// Update pagination state - handle both paginated and non-paginated responses
|
|
1619
1619
|
if (messagesData.pagination) {
|
|
@@ -1625,24 +1625,25 @@
|
|
|
1625
1625
|
this.messagesPagination.hasMore = false;
|
|
1626
1626
|
this.messagesPagination.currentPage = 1;
|
|
1627
1627
|
}
|
|
1628
|
-
|
|
1628
|
+
|
|
1629
1629
|
// Get existing messages or initialize
|
|
1630
1630
|
let existingMessages = this.loadedMessages.get(conversationId) || [];
|
|
1631
|
-
|
|
1631
|
+
|
|
1632
1632
|
if (isInitialLoad) {
|
|
1633
1633
|
// For initial load, replace all messages (newest messages first)
|
|
1634
1634
|
existingMessages = messagesData.messages;
|
|
1635
|
+
// Render all messages for initial load
|
|
1636
|
+
this.renderCachedMessages(existingMessages, false);
|
|
1635
1637
|
} else {
|
|
1636
1638
|
// For infinite scroll, prepend older messages to the beginning
|
|
1637
1639
|
existingMessages = [...messagesData.messages, ...existingMessages];
|
|
1640
|
+
// Render ONLY the new messages (not all accumulated messages)
|
|
1641
|
+
this.renderCachedMessages(messagesData.messages, true);
|
|
1638
1642
|
}
|
|
1639
|
-
|
|
1643
|
+
|
|
1640
1644
|
// Cache the combined messages
|
|
1641
1645
|
this.loadedMessages.set(conversationId, existingMessages);
|
|
1642
|
-
|
|
1643
|
-
// Render messages
|
|
1644
|
-
this.renderCachedMessages(existingMessages, !isInitialLoad);
|
|
1645
|
-
|
|
1646
|
+
|
|
1646
1647
|
// Setup scroll listener for infinite scroll (only on initial load)
|
|
1647
1648
|
if (isInitialLoad) {
|
|
1648
1649
|
this.setupMessagesScrollListener(conversationId);
|
|
@@ -1655,7 +1656,7 @@
|
|
|
1655
1656
|
// Update status based on conversation state
|
|
1656
1657
|
this.analyzeConversationStatus(existingMessages);
|
|
1657
1658
|
}
|
|
1658
|
-
|
|
1659
|
+
|
|
1659
1660
|
} else if (isInitialLoad) {
|
|
1660
1661
|
chatMessages.innerHTML = `
|
|
1661
1662
|
<div class="no-messages-found">
|
package/src/console-bridge.js
CHANGED
|
@@ -338,12 +338,12 @@ class ConsoleBridge extends EventEmitter {
|
|
|
338
338
|
if (Math.random() > 0.7) { // Simulate occasional prompts
|
|
339
339
|
this.handleDetectedPrompt(`Read file
|
|
340
340
|
|
|
341
|
-
Search(pattern: "(?:Yes|No|yes|no)(?:,\\s*and\\s*don't\\s*ask\\s*again)?", path:
|
|
342
|
-
"../../../../../../../.claude/projects/-Users-
|
|
341
|
+
Search(pattern: "(?:Yes|No|yes|no)(?:,\\s*and\\s*don't\\s*ask\\s*again)?", path:
|
|
342
|
+
"../../../../../../../.claude/projects/-Users-username-Projects-project-name", include: "*.jsonl")
|
|
343
343
|
|
|
344
344
|
Do you want to proceed?
|
|
345
345
|
⯠1. Yes
|
|
346
|
-
2. Yes, and add
|
|
346
|
+
2. Yes, and add ~/.claude/projects/-Users-username-Projects-project-name as a working directory for this session
|
|
347
347
|
3. No, and tell Claude what to do differently (esc)`);
|
|
348
348
|
}
|
|
349
349
|
|
package/src/index.js
CHANGED
|
@@ -129,7 +129,7 @@ async function createClaudeConfig(options = {}) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// Handle multiple components installation (new approach)
|
|
132
|
-
if (options.agent || options.command || options.mcp || options.setting || options.hook) {
|
|
132
|
+
if (options.agent || options.command || options.mcp || options.setting || options.hook || options.skill) {
|
|
133
133
|
// If --workflow is used with components, treat it as YAML
|
|
134
134
|
if (options.workflow) {
|
|
135
135
|
options.yaml = options.workflow;
|
|
@@ -1391,6 +1391,104 @@ async function getAvailableAgentsFromGitHub() {
|
|
|
1391
1391
|
}
|
|
1392
1392
|
}
|
|
1393
1393
|
|
|
1394
|
+
async function installIndividualSkill(skillName, targetDir, options) {
|
|
1395
|
+
console.log(chalk.blue(`š” Installing skill: ${skillName}`));
|
|
1396
|
+
|
|
1397
|
+
try {
|
|
1398
|
+
// Skills always use SKILL.md as the main file (Anthropic standard)
|
|
1399
|
+
// Format: skills/skill-name/SKILL.md
|
|
1400
|
+
const githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/skills/${skillName}/SKILL.md`;
|
|
1401
|
+
|
|
1402
|
+
console.log(chalk.gray(`š„ Downloading from GitHub (main branch)...`));
|
|
1403
|
+
|
|
1404
|
+
const response = await fetch(githubUrl);
|
|
1405
|
+
if (!response.ok) {
|
|
1406
|
+
if (response.status === 404) {
|
|
1407
|
+
console.log(chalk.red(`ā Skill "${skillName}" not found`));
|
|
1408
|
+
console.log(chalk.yellow('Available skills: pdf-processing, excel-analysis, git-commit-helper, email-composer'));
|
|
1409
|
+
return false;
|
|
1410
|
+
}
|
|
1411
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
const skillContent = await response.text();
|
|
1415
|
+
|
|
1416
|
+
// Check if there are additional files to download (e.g., referenced .md files)
|
|
1417
|
+
const additionalFiles = {};
|
|
1418
|
+
|
|
1419
|
+
// Look for references to additional files like [FORMS.md](FORMS.md)
|
|
1420
|
+
const referencePattern = /\[([A-Z_]+\.md)\]\(\1\)/g;
|
|
1421
|
+
let match;
|
|
1422
|
+
const skillBaseName = skillName;
|
|
1423
|
+
|
|
1424
|
+
while ((match = referencePattern.exec(skillContent)) !== null) {
|
|
1425
|
+
const referencedFile = match[1];
|
|
1426
|
+
const referencedUrl = githubUrl.replace('SKILL.md', referencedFile);
|
|
1427
|
+
|
|
1428
|
+
try {
|
|
1429
|
+
console.log(chalk.gray(`š„ Downloading referenced file: ${referencedFile}...`));
|
|
1430
|
+
const refResponse = await fetch(referencedUrl);
|
|
1431
|
+
if (refResponse.ok) {
|
|
1432
|
+
const refContent = await refResponse.text();
|
|
1433
|
+
additionalFiles[`.claude/skills/${skillBaseName}/${referencedFile}`] = {
|
|
1434
|
+
content: refContent,
|
|
1435
|
+
executable: false
|
|
1436
|
+
};
|
|
1437
|
+
console.log(chalk.green(`ā Found referenced file: ${referencedFile}`));
|
|
1438
|
+
}
|
|
1439
|
+
} catch (error) {
|
|
1440
|
+
// Referenced file is optional, continue if not found
|
|
1441
|
+
console.log(chalk.gray(` (Referenced file ${referencedFile} not found, continuing...)`));
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// Create .claude/skills/skill-name directory (Anthropic standard structure)
|
|
1446
|
+
const skillsDir = path.join(targetDir, '.claude', 'skills');
|
|
1447
|
+
const skillSubDir = path.join(skillsDir, skillBaseName);
|
|
1448
|
+
await fs.ensureDir(skillSubDir);
|
|
1449
|
+
|
|
1450
|
+
// Always use SKILL.md as filename (Anthropic standard)
|
|
1451
|
+
const targetFile = path.join(skillSubDir, 'SKILL.md');
|
|
1452
|
+
|
|
1453
|
+
// Write the main skill file
|
|
1454
|
+
await fs.writeFile(targetFile, skillContent, 'utf8');
|
|
1455
|
+
|
|
1456
|
+
// Write any additional files
|
|
1457
|
+
for (const [filePath, fileData] of Object.entries(additionalFiles)) {
|
|
1458
|
+
const fullPath = path.join(targetDir, filePath);
|
|
1459
|
+
await fs.ensureDir(path.dirname(fullPath));
|
|
1460
|
+
await fs.writeFile(fullPath, fileData.content, 'utf8');
|
|
1461
|
+
|
|
1462
|
+
if (fileData.executable) {
|
|
1463
|
+
await fs.chmod(fullPath, '755');
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
if (!options.silent) {
|
|
1468
|
+
console.log(chalk.green(`ā
Skill "${skillName}" installed successfully!`));
|
|
1469
|
+
console.log(chalk.cyan(`š Installed to: ${path.relative(targetDir, targetFile)}`));
|
|
1470
|
+
if (Object.keys(additionalFiles).length > 0) {
|
|
1471
|
+
console.log(chalk.cyan(`š Additional files: ${Object.keys(additionalFiles).length}`));
|
|
1472
|
+
}
|
|
1473
|
+
console.log(chalk.cyan(`š¦ Downloaded from: ${githubUrl}`));
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// Track successful skill installation
|
|
1477
|
+
trackingService.trackDownload('skill', skillName, {
|
|
1478
|
+
installation_type: 'individual_skill',
|
|
1479
|
+
target_directory: path.relative(process.cwd(), targetDir),
|
|
1480
|
+
source: 'github_main',
|
|
1481
|
+
has_additional_files: Object.keys(additionalFiles).length > 0
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1484
|
+
return true;
|
|
1485
|
+
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
console.log(chalk.red(`ā Error installing skill: ${error.message}`));
|
|
1488
|
+
return false;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1394
1492
|
/**
|
|
1395
1493
|
* Install multiple components with optional YAML workflow
|
|
1396
1494
|
*/
|
|
@@ -1403,7 +1501,8 @@ async function installMultipleComponents(options, targetDir) {
|
|
|
1403
1501
|
commands: [],
|
|
1404
1502
|
mcps: [],
|
|
1405
1503
|
settings: [],
|
|
1406
|
-
hooks: []
|
|
1504
|
+
hooks: [],
|
|
1505
|
+
skills: []
|
|
1407
1506
|
};
|
|
1408
1507
|
|
|
1409
1508
|
// Parse comma-separated values for each component type
|
|
@@ -1431,8 +1530,13 @@ async function installMultipleComponents(options, targetDir) {
|
|
|
1431
1530
|
const hooksInput = Array.isArray(options.hook) ? options.hook.join(',') : options.hook;
|
|
1432
1531
|
components.hooks = hooksInput.split(',').map(h => h.trim()).filter(h => h);
|
|
1433
1532
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1533
|
+
|
|
1534
|
+
if (options.skill) {
|
|
1535
|
+
const skillsInput = Array.isArray(options.skill) ? options.skill.join(',') : options.skill;
|
|
1536
|
+
components.skills = skillsInput.split(',').map(s => s.trim()).filter(s => s);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
const totalComponents = components.agents.length + components.commands.length + components.mcps.length + components.settings.length + components.hooks.length + components.skills.length;
|
|
1436
1540
|
|
|
1437
1541
|
if (totalComponents === 0) {
|
|
1438
1542
|
console.log(chalk.yellow('ā ļø No components specified to install.'));
|
|
@@ -1445,6 +1549,7 @@ async function installMultipleComponents(options, targetDir) {
|
|
|
1445
1549
|
console.log(chalk.gray(` MCPs: ${components.mcps.length}`));
|
|
1446
1550
|
console.log(chalk.gray(` Settings: ${components.settings.length}`));
|
|
1447
1551
|
console.log(chalk.gray(` Hooks: ${components.hooks.length}`));
|
|
1552
|
+
console.log(chalk.gray(` Skills: ${components.skills.length}`));
|
|
1448
1553
|
|
|
1449
1554
|
// Counter for successfully installed components
|
|
1450
1555
|
let successfullyInstalled = 0;
|
|
@@ -1526,14 +1631,21 @@ async function installMultipleComponents(options, targetDir) {
|
|
|
1526
1631
|
// Install hooks (using shared installation locations)
|
|
1527
1632
|
for (const hook of components.hooks) {
|
|
1528
1633
|
console.log(chalk.gray(` Installing hook: ${hook}`));
|
|
1529
|
-
const hookSuccess = await installIndividualHook(hook, targetDir, {
|
|
1530
|
-
...options,
|
|
1531
|
-
silent: true,
|
|
1532
|
-
sharedInstallLocations: sharedInstallLocations
|
|
1634
|
+
const hookSuccess = await installIndividualHook(hook, targetDir, {
|
|
1635
|
+
...options,
|
|
1636
|
+
silent: true,
|
|
1637
|
+
sharedInstallLocations: sharedInstallLocations
|
|
1533
1638
|
});
|
|
1534
1639
|
if (hookSuccess > 0) successfullyInstalled++;
|
|
1535
1640
|
}
|
|
1536
|
-
|
|
1641
|
+
|
|
1642
|
+
// Install skills
|
|
1643
|
+
for (const skill of components.skills) {
|
|
1644
|
+
console.log(chalk.gray(` Installing skill: ${skill}`));
|
|
1645
|
+
const skillSuccess = await installIndividualSkill(skill, targetDir, { ...options, silent: true });
|
|
1646
|
+
if (skillSuccess) successfullyInstalled++;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1537
1649
|
// Handle YAML workflow if provided
|
|
1538
1650
|
if (options.yaml) {
|
|
1539
1651
|
console.log(chalk.blue('\nš Processing workflow YAML...'));
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Security Audit CLI - Validates component security
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node src/security-audit.js [options]
|
|
8
|
+
* npm run security-audit
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const ValidationOrchestrator = require('./validation/ValidationOrchestrator');
|
|
12
|
+
const fs = require('fs-extra');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const chalk = require('chalk');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Scan directory for component files
|
|
18
|
+
*/
|
|
19
|
+
async function scanComponents(directory) {
|
|
20
|
+
const components = [];
|
|
21
|
+
const componentTypes = ['agents', 'commands', 'mcps', 'settings', 'hooks'];
|
|
22
|
+
|
|
23
|
+
for (const type of componentTypes) {
|
|
24
|
+
const typeDir = path.join(directory, type);
|
|
25
|
+
|
|
26
|
+
if (!await fs.pathExists(typeDir)) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Recursively find all .md files
|
|
31
|
+
const files = await findMarkdownFiles(typeDir);
|
|
32
|
+
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
const content = await fs.readFile(file, 'utf8');
|
|
35
|
+
const relativePath = path.relative(process.cwd(), file);
|
|
36
|
+
|
|
37
|
+
components.push({
|
|
38
|
+
content,
|
|
39
|
+
path: relativePath,
|
|
40
|
+
type: type.slice(0, -1) // Remove 's' (agents -> agent)
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return components;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Recursively find all markdown files
|
|
50
|
+
*/
|
|
51
|
+
async function findMarkdownFiles(dir) {
|
|
52
|
+
const files = [];
|
|
53
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
54
|
+
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const fullPath = path.join(dir, entry.name);
|
|
57
|
+
|
|
58
|
+
if (entry.isDirectory()) {
|
|
59
|
+
const subFiles = await findMarkdownFiles(fullPath);
|
|
60
|
+
files.push(...subFiles);
|
|
61
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
62
|
+
files.push(fullPath);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return files;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Main execution
|
|
71
|
+
*/
|
|
72
|
+
async function main() {
|
|
73
|
+
const args = process.argv.slice(2);
|
|
74
|
+
const ciMode = args.includes('--ci');
|
|
75
|
+
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
76
|
+
const jsonOutput = args.includes('--json');
|
|
77
|
+
const outputFile = args.find(arg => arg.startsWith('--output='))?.split('=')[1];
|
|
78
|
+
|
|
79
|
+
console.log(chalk.blue('\nš Claude Code Templates - Security Audit\n'));
|
|
80
|
+
console.log(chalk.gray('ā'.repeat(60)));
|
|
81
|
+
|
|
82
|
+
// Determine components directory
|
|
83
|
+
// Check if we're running from the cli-tool directory or the root
|
|
84
|
+
let componentsDir = path.join(process.cwd(), 'components');
|
|
85
|
+
if (!await fs.pathExists(componentsDir)) {
|
|
86
|
+
componentsDir = path.join(process.cwd(), 'cli-tool', 'components');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!await fs.pathExists(componentsDir)) {
|
|
90
|
+
console.error(chalk.red('ā Components directory not found:', componentsDir));
|
|
91
|
+
console.error(chalk.gray(' Tried:', path.join(process.cwd(), 'components')));
|
|
92
|
+
console.error(chalk.gray(' Tried:', path.join(process.cwd(), 'cli-tool', 'components')));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(chalk.blue('š Scanning components directory...'));
|
|
97
|
+
const components = await scanComponents(componentsDir);
|
|
98
|
+
console.log(chalk.gray(` Found ${components.length} components\n`));
|
|
99
|
+
|
|
100
|
+
// Validate all components
|
|
101
|
+
const orchestrator = new ValidationOrchestrator();
|
|
102
|
+
|
|
103
|
+
console.log(chalk.blue('š Running security validation...\n'));
|
|
104
|
+
const results = await orchestrator.validateComponents(components, {
|
|
105
|
+
strict: ciMode,
|
|
106
|
+
updateRegistry: false
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Generate report
|
|
110
|
+
if (jsonOutput) {
|
|
111
|
+
const report = orchestrator.generateJsonReport(results);
|
|
112
|
+
|
|
113
|
+
if (outputFile) {
|
|
114
|
+
await fs.writeFile(outputFile, report);
|
|
115
|
+
console.log(chalk.green(`ā
JSON report saved to: ${outputFile}`));
|
|
116
|
+
} else {
|
|
117
|
+
console.log(report);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
const report = orchestrator.generateReport(results, {
|
|
121
|
+
verbose,
|
|
122
|
+
colors: !ciMode
|
|
123
|
+
});
|
|
124
|
+
console.log(report);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Summary
|
|
128
|
+
console.log(chalk.bold('\nš Validation Summary:'));
|
|
129
|
+
console.log(chalk.gray('ā'.repeat(60)));
|
|
130
|
+
console.log(` Total components: ${results.summary.total}`);
|
|
131
|
+
console.log(` ${chalk.green('ā
Passed')}: ${results.summary.passed}`);
|
|
132
|
+
console.log(` ${chalk.red('ā Failed')}: ${results.summary.failed}`);
|
|
133
|
+
console.log(` ${chalk.yellow('ā ļø Warnings')}: ${results.summary.warnings}`);
|
|
134
|
+
console.log(chalk.gray('ā'.repeat(60)));
|
|
135
|
+
|
|
136
|
+
// Exit with appropriate code
|
|
137
|
+
if (ciMode && results.summary.failed > 0) {
|
|
138
|
+
console.error(chalk.red('\nā Security audit failed in CI mode\n'));
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (results.summary.failed > 0) {
|
|
143
|
+
console.log(chalk.yellow('\nā ļø Some components failed validation\n'));
|
|
144
|
+
process.exit(0); // Don't fail in non-CI mode
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(chalk.green('\nā
All components passed security validation\n'));
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Error handling
|
|
152
|
+
process.on('unhandledRejection', (error) => {
|
|
153
|
+
console.error(chalk.red('\nā Unhandled error:'), error);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Run
|
|
158
|
+
main().catch((error) => {
|
|
159
|
+
console.error(chalk.red('\nā Security audit failed:'), error.message);
|
|
160
|
+
if (process.argv.includes('--verbose')) {
|
|
161
|
+
console.error(error);
|
|
162
|
+
}
|
|
163
|
+
process.exit(1);
|
|
164
|
+
});
|