@civicactions/cmsds-open-data-components 4.0.7 ā 4.0.8-alpha.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/package.json +5 -1
- package/scripts/README.md +119 -0
- package/scripts/generate-usage-report.cjs +445 -0
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@civicactions/cmsds-open-data-components",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.8-alpha.0",
|
|
4
4
|
"description": "Components for the open data catalog frontend using CMS Design System",
|
|
5
5
|
"main": "dist/main.js",
|
|
6
6
|
"source": "src/index.ts",
|
|
7
7
|
"types": "dist/types.d.ts",
|
|
8
8
|
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"generate-usage-report": "./scripts/generate-usage-report.cjs"
|
|
11
|
+
},
|
|
9
12
|
"scripts": {
|
|
10
13
|
"build": "parcel build",
|
|
11
14
|
"build:report": "parcel build --reporter @parcel/reporter-bundle-analyzer",
|
|
@@ -16,6 +19,7 @@
|
|
|
16
19
|
"storybook": "storybook dev -p 6006",
|
|
17
20
|
"build-storybook": "storybook build",
|
|
18
21
|
"generate:inventory": "node scripts/generate-inventory.cjs",
|
|
22
|
+
"generate:usage-report": "node scripts/generate-usage-report.cjs",
|
|
19
23
|
"prepare": "husky"
|
|
20
24
|
},
|
|
21
25
|
"author": "",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Component Inventory Generator
|
|
2
|
+
|
|
3
|
+
This script automatically generates a comprehensive markdown inventory report for the cmsds-open-data-components library.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Run the script using npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm run generate:inventory
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run it directly:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node scripts/generate-inventory.cjs
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Output
|
|
20
|
+
|
|
21
|
+
The script generates `COMPONENTS_INVENTORY.md` in the root directory containing:
|
|
22
|
+
|
|
23
|
+
- **Inventory Table**: Complete list of all components, services, templates, hooks, contexts, utilities, types, and assets
|
|
24
|
+
- **Public Export Status**: Indicates which items are publicly exported vs internal-only
|
|
25
|
+
- **Storybook Status**: Shows which items have Storybook stories for visual documentation
|
|
26
|
+
- **Unit Test Status**: Shows which items have unit test coverage
|
|
27
|
+
- **Quality Metrics**: Summary statistics for documentation and testing coverage
|
|
28
|
+
- **Export Summary**: Count of public vs internal items by category
|
|
29
|
+
|
|
30
|
+
## What It Scans
|
|
31
|
+
|
|
32
|
+
The script automatically scans the following directories:
|
|
33
|
+
|
|
34
|
+
- `src/components/` - React components
|
|
35
|
+
- `src/templates/` - Page-level templates
|
|
36
|
+
- `src/services/` - API service hooks
|
|
37
|
+
- `src/utilities/` - Utility functions
|
|
38
|
+
- `src/types/` - TypeScript type definitions
|
|
39
|
+
- `src/assets/` - Static assets and data files
|
|
40
|
+
|
|
41
|
+
It also automatically detects:
|
|
42
|
+
- **Hooks**: Directories or files starting with `use` (e.g., `useScrollToTop`)
|
|
43
|
+
- **Contexts**: Files ending with `Context` that contain `createContext()` calls
|
|
44
|
+
|
|
45
|
+
## How It Works
|
|
46
|
+
|
|
47
|
+
1. **Reads `src/index.ts`** to determine which items are publicly exported
|
|
48
|
+
2. **Scans each directory** for subdirectories and files
|
|
49
|
+
3. **Dynamically discovers hooks and contexts** by naming patterns and file content
|
|
50
|
+
4. **Checks for `.stories.*` files** to determine Storybook coverage
|
|
51
|
+
5. **Checks for `.test.*` and `.spec.*` files** to determine unit test coverage
|
|
52
|
+
6. **Calculates statistics** for overall project quality metrics
|
|
53
|
+
7. **Generates markdown** with formatted tables and summaries
|
|
54
|
+
|
|
55
|
+
## Maintenance
|
|
56
|
+
|
|
57
|
+
The script is designed to automatically adapt to changes in the codebase:
|
|
58
|
+
|
|
59
|
+
- New components/templates/services/hooks/contexts are automatically discovered
|
|
60
|
+
- Public export status updates when `src/index.ts` changes
|
|
61
|
+
- Story and test status updates as files are added/removed
|
|
62
|
+
- Statistics recalculate automatically
|
|
63
|
+
|
|
64
|
+
No manual updates needed when adding or removing hooks and contexts!
|
|
65
|
+
|
|
66
|
+
### Hook and Context Detection
|
|
67
|
+
|
|
68
|
+
**Hooks** are detected by:
|
|
69
|
+
- Directory names starting with `use` (e.g., `src/components/useScrollToTop/`)
|
|
70
|
+
- File names starting with `use` (e.g., `useAddLoginLink.ts`)
|
|
71
|
+
|
|
72
|
+
**Contexts** are detected by:
|
|
73
|
+
- File names ending with `Context` (e.g., `HeaderContext.tsx`)
|
|
74
|
+
- File must contain a `createContext()` call
|
|
75
|
+
- Export name is extracted from `export default` or `export const` statements
|
|
76
|
+
|
|
77
|
+
The scanner automatically excludes test and story files to prevent false positives.
|
|
78
|
+
|
|
79
|
+
### Known Special Cases
|
|
80
|
+
|
|
81
|
+
The script handles several special cases:
|
|
82
|
+
|
|
83
|
+
- **DatasetAdditionalInformation**: Exports `buildRows` function
|
|
84
|
+
- **DatasetTableTab**: Exported as `DatasetTable`
|
|
85
|
+
- **Datatable**: Exported as `DataTable` (case difference)
|
|
86
|
+
- **frequencyMap**: Commented out in exports
|
|
87
|
+
- **aca.ts**: Exports `acaToParams` function
|
|
88
|
+
|
|
89
|
+
To add new special cases, update the scanning functions in `generate-inventory.cjs`.
|
|
90
|
+
|
|
91
|
+
## File Structure
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
scripts/
|
|
95
|
+
generate-inventory.cjs # Main script file
|
|
96
|
+
README.md # This file
|
|
97
|
+
|
|
98
|
+
COMPONENTS_INVENTORY.md # Generated output file (root directory)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Requirements
|
|
102
|
+
|
|
103
|
+
- Node.js v14 or higher
|
|
104
|
+
- No additional dependencies required (uses Node.js built-in `fs` and `path` modules)
|
|
105
|
+
|
|
106
|
+
## Troubleshooting
|
|
107
|
+
|
|
108
|
+
**Error: "require is not defined"**
|
|
109
|
+
- The script uses `.cjs` extension to work with the ES module package type
|
|
110
|
+
- Make sure the file is named `generate-inventory.cjs` (not `.js`)
|
|
111
|
+
|
|
112
|
+
**Missing components in output**
|
|
113
|
+
- Ensure new components follow the standard directory structure
|
|
114
|
+
- Component directories should be in `src/components/`
|
|
115
|
+
- Each component should have an `index.tsx` or similar entry file
|
|
116
|
+
|
|
117
|
+
**Incorrect public export status**
|
|
118
|
+
- Check if the component is exported in `src/index.ts`
|
|
119
|
+
- For special export names, add them to the scanning logic in the script
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Component Usage Report Generator
|
|
5
|
+
*
|
|
6
|
+
* Scans a project that uses cmsds-open-data-components as a dependency and generates
|
|
7
|
+
* a comprehensive markdown usage report including:
|
|
8
|
+
* - Which components, templates, services, hooks, and utilities are being used
|
|
9
|
+
* - Where they are imported/used in the codebase
|
|
10
|
+
* - Usage frequency and patterns
|
|
11
|
+
* - Unused available components
|
|
12
|
+
*
|
|
13
|
+
* This script should be run from the root of a project that depends on
|
|
14
|
+
* @civicactions/cmsds-open-data-components
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
// Configuration
|
|
21
|
+
const DEFAULT_SCAN_DIRS = ['src', 'app', 'pages', 'components', 'templates'];
|
|
22
|
+
const FILE_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx'];
|
|
23
|
+
const PACKAGE_NAME = '@civicactions/cmsds-open-data-components';
|
|
24
|
+
|
|
25
|
+
// Command line arguments
|
|
26
|
+
const args = process.argv.slice(2);
|
|
27
|
+
const outputFile = args[0] || 'COMPONENT_USAGE_REPORT.md';
|
|
28
|
+
const projectRoot = process.cwd();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get the list of public exports from the installed package
|
|
32
|
+
*/
|
|
33
|
+
function getAvailableComponents() {
|
|
34
|
+
try {
|
|
35
|
+
const packagePath = path.join(projectRoot, 'node_modules', PACKAGE_NAME);
|
|
36
|
+
const indexPath = path.join(packagePath, 'dist', 'index.d.ts');
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(indexPath)) {
|
|
39
|
+
// Fallback to package.json exports
|
|
40
|
+
const pkgJsonPath = path.join(packagePath, 'package.json');
|
|
41
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
42
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
43
|
+
return {
|
|
44
|
+
version: pkgJson.version,
|
|
45
|
+
exports: new Set()
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
throw new Error('Could not find package index');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const content = fs.readFileSync(indexPath, 'utf8');
|
|
52
|
+
const exports = new Set();
|
|
53
|
+
|
|
54
|
+
// Match export declarations
|
|
55
|
+
const exportMatches = content.matchAll(/export\s+(?:declare\s+)?(?:const|function|class|interface|type)\s+(\w+)/g);
|
|
56
|
+
for (const match of exportMatches) {
|
|
57
|
+
exports.add(match[1]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Match default exports
|
|
61
|
+
const defaultMatches = content.matchAll(/export\s+\{\s*default\s+as\s+(\w+)\s*\}/g);
|
|
62
|
+
for (const match of defaultMatches) {
|
|
63
|
+
exports.add(match[1]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Get version
|
|
67
|
+
const pkgJsonPath = path.join(packagePath, 'package.json');
|
|
68
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
version: pkgJson.version,
|
|
72
|
+
exports
|
|
73
|
+
};
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.warn('ā ļø Could not read package exports, will detect from usage');
|
|
76
|
+
return {
|
|
77
|
+
version: 'unknown',
|
|
78
|
+
exports: new Set()
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Recursively find all files with specified extensions in a directory
|
|
85
|
+
*/
|
|
86
|
+
function findFiles(dir, extensions, results = []) {
|
|
87
|
+
if (!fs.existsSync(dir)) {
|
|
88
|
+
return results;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
93
|
+
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
const fullPath = path.join(dir, entry.name);
|
|
96
|
+
|
|
97
|
+
// Skip node_modules, .git, build directories
|
|
98
|
+
if (entry.name === 'node_modules' ||
|
|
99
|
+
entry.name === '.git' ||
|
|
100
|
+
entry.name === 'build' ||
|
|
101
|
+
entry.name === 'dist' ||
|
|
102
|
+
entry.name === '.next' ||
|
|
103
|
+
entry.name === 'coverage') {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (entry.isDirectory()) {
|
|
108
|
+
findFiles(fullPath, extensions, results);
|
|
109
|
+
} else if (extensions.some(ext => entry.name.endsWith(ext))) {
|
|
110
|
+
results.push(fullPath);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
// Skip directories we can't read
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return results;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Parse a file for imports from cmsds-open-data-components
|
|
122
|
+
*/
|
|
123
|
+
function parseFileForImports(filePath) {
|
|
124
|
+
try {
|
|
125
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
126
|
+
const imports = [];
|
|
127
|
+
|
|
128
|
+
// Match ES6 imports: import { Component1, Component2 } from '@civicactions/cmsds-open-data-components'
|
|
129
|
+
const namedImportRegex = new RegExp(
|
|
130
|
+
`import\\s+\\{([^}]+)\\}\\s+from\\s+['"]${PACKAGE_NAME}['"]`,
|
|
131
|
+
'g'
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
let match;
|
|
135
|
+
while ((match = namedImportRegex.exec(content)) !== null) {
|
|
136
|
+
const names = match[1]
|
|
137
|
+
.split(',')
|
|
138
|
+
.map(n => n.trim())
|
|
139
|
+
.filter(n => n.length > 0);
|
|
140
|
+
|
|
141
|
+
names.forEach(name => {
|
|
142
|
+
imports.push({
|
|
143
|
+
name,
|
|
144
|
+
type: 'named',
|
|
145
|
+
line: content.substring(0, match.index).split('\n').length
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Match default imports: import Component from '@civicactions/cmsds-open-data-components'
|
|
151
|
+
const defaultImportRegex = new RegExp(
|
|
152
|
+
`import\\s+(\\w+)\\s+from\\s+['"]${PACKAGE_NAME}['"]`,
|
|
153
|
+
'g'
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
while ((match = defaultImportRegex.exec(content)) !== null) {
|
|
157
|
+
imports.push({
|
|
158
|
+
name: match[1],
|
|
159
|
+
type: 'default',
|
|
160
|
+
line: content.substring(0, match.index).split('\n').length
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return imports;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Scan the project for component usage
|
|
172
|
+
*/
|
|
173
|
+
function scanProjectForUsage() {
|
|
174
|
+
console.log('š Scanning project for component usage...');
|
|
175
|
+
|
|
176
|
+
const usageMap = new Map();
|
|
177
|
+
const scanDirs = DEFAULT_SCAN_DIRS
|
|
178
|
+
.map(dir => path.join(projectRoot, dir))
|
|
179
|
+
.filter(dir => fs.existsSync(dir));
|
|
180
|
+
|
|
181
|
+
if (scanDirs.length === 0) {
|
|
182
|
+
console.warn('ā ļø No standard source directories found. Scanning entire project...');
|
|
183
|
+
scanDirs.push(projectRoot);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let filesScanned = 0;
|
|
187
|
+
|
|
188
|
+
for (const scanDir of scanDirs) {
|
|
189
|
+
const files = findFiles(scanDir, FILE_EXTENSIONS);
|
|
190
|
+
filesScanned += files.length;
|
|
191
|
+
|
|
192
|
+
for (const file of files) {
|
|
193
|
+
const imports = parseFileForImports(file);
|
|
194
|
+
|
|
195
|
+
if (imports.length > 0) {
|
|
196
|
+
const relativePath = path.relative(projectRoot, file);
|
|
197
|
+
|
|
198
|
+
for (const imp of imports) {
|
|
199
|
+
if (!usageMap.has(imp.name)) {
|
|
200
|
+
usageMap.set(imp.name, []);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
usageMap.get(imp.name).push({
|
|
204
|
+
file: relativePath,
|
|
205
|
+
line: imp.line,
|
|
206
|
+
type: imp.type
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log(` Scanned ${filesScanned} files`);
|
|
214
|
+
console.log(` Found ${usageMap.size} unique components in use`);
|
|
215
|
+
|
|
216
|
+
return usageMap;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Categorize components by type based on naming conventions
|
|
221
|
+
*/
|
|
222
|
+
function categorizeComponent(name) {
|
|
223
|
+
if (name.startsWith('use')) return 'Hook';
|
|
224
|
+
if (name.endsWith('Context') || name.endsWith('Provider')) return 'Context';
|
|
225
|
+
if (name.endsWith('Page') || name.endsWith('Template')) return 'Template';
|
|
226
|
+
if (name.includes('Service') || name.startsWith('fetch') || name.startsWith('get')) return 'Service';
|
|
227
|
+
if (name.endsWith('Util') || name.endsWith('Helper')) return 'Utility';
|
|
228
|
+
return 'Component';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Generate markdown table row
|
|
233
|
+
*/
|
|
234
|
+
function generateUsageRow(name, usages, category) {
|
|
235
|
+
const count = usages.length;
|
|
236
|
+
const files = [...new Set(usages.map(u => u.file))];
|
|
237
|
+
const fileList = files.length <= 3
|
|
238
|
+
? files.map(f => `\`${f}\``).join(', ')
|
|
239
|
+
: `${files.slice(0, 3).map(f => `\`${f}\``).join(', ')} and ${files.length - 3} more`;
|
|
240
|
+
|
|
241
|
+
return `| ${name} | ${category} | ${count} | ${fileList} |`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Generate the usage report
|
|
246
|
+
*/
|
|
247
|
+
function generateReport(usageMap, availableComponents) {
|
|
248
|
+
const lines = [];
|
|
249
|
+
|
|
250
|
+
// Get project info
|
|
251
|
+
const projectPkgPath = path.join(projectRoot, 'package.json');
|
|
252
|
+
let projectName = 'Unknown Project';
|
|
253
|
+
let projectVersion = '';
|
|
254
|
+
|
|
255
|
+
if (fs.existsSync(projectPkgPath)) {
|
|
256
|
+
const projectPkg = JSON.parse(fs.readFileSync(projectPkgPath, 'utf8'));
|
|
257
|
+
projectName = projectPkg.name || path.basename(projectRoot);
|
|
258
|
+
|
|
259
|
+
// Get the installed version
|
|
260
|
+
const deps = { ...projectPkg.dependencies, ...projectPkg.devDependencies };
|
|
261
|
+
projectVersion = deps[PACKAGE_NAME] || availableComponents.version;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Header
|
|
265
|
+
lines.push(`# ${projectName} Component Usage Report`);
|
|
266
|
+
lines.push('');
|
|
267
|
+
lines.push(`Analysis of \`${PACKAGE_NAME}\` usage in this project.`);
|
|
268
|
+
lines.push('');
|
|
269
|
+
lines.push(`**Library Version**: \`${PACKAGE_NAME}: ${projectVersion}\` `);
|
|
270
|
+
lines.push(`**Generated**: ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}`);
|
|
271
|
+
lines.push('');
|
|
272
|
+
lines.push('---');
|
|
273
|
+
lines.push('');
|
|
274
|
+
|
|
275
|
+
// Summary statistics
|
|
276
|
+
const totalUsages = Array.from(usageMap.values()).reduce((sum, usages) => sum + usages.length, 0);
|
|
277
|
+
const uniqueComponents = usageMap.size;
|
|
278
|
+
const filesUsingComponents = new Set();
|
|
279
|
+
|
|
280
|
+
usageMap.forEach(usages => {
|
|
281
|
+
usages.forEach(usage => filesUsingComponents.add(usage.file));
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
lines.push('## Summary Statistics');
|
|
285
|
+
lines.push('');
|
|
286
|
+
lines.push(`- **Unique Components Used**: ${uniqueComponents}`);
|
|
287
|
+
lines.push(`- **Total Import Statements**: ${totalUsages}`);
|
|
288
|
+
lines.push(`- **Files Using Components**: ${filesUsingComponents.size}`);
|
|
289
|
+
lines.push('');
|
|
290
|
+
|
|
291
|
+
// Categorize components
|
|
292
|
+
const categorized = new Map();
|
|
293
|
+
usageMap.forEach((usages, name) => {
|
|
294
|
+
const category = categorizeComponent(name);
|
|
295
|
+
if (!categorized.has(category)) {
|
|
296
|
+
categorized.set(category, []);
|
|
297
|
+
}
|
|
298
|
+
categorized.get(category).push({ name, usages });
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Category breakdown
|
|
302
|
+
lines.push('### By Category');
|
|
303
|
+
lines.push('');
|
|
304
|
+
lines.push('| Category | Count |');
|
|
305
|
+
lines.push('|----------|-------|');
|
|
306
|
+
|
|
307
|
+
const categories = ['Component', 'Template', 'Hook', 'Context', 'Service', 'Utility'];
|
|
308
|
+
categories.forEach(cat => {
|
|
309
|
+
const count = categorized.get(cat)?.length || 0;
|
|
310
|
+
if (count > 0) {
|
|
311
|
+
lines.push(`| ${cat}s | ${count} |`);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
lines.push('');
|
|
316
|
+
lines.push('---');
|
|
317
|
+
lines.push('');
|
|
318
|
+
|
|
319
|
+
// Detailed usage table
|
|
320
|
+
lines.push('## Component Usage Details');
|
|
321
|
+
lines.push('');
|
|
322
|
+
lines.push('| Component | Type | Usage Count | Used In |');
|
|
323
|
+
lines.push('|-----------|------|-------------|---------|');
|
|
324
|
+
|
|
325
|
+
// Sort by usage count (descending)
|
|
326
|
+
const sortedComponents = Array.from(usageMap.entries())
|
|
327
|
+
.sort((a, b) => b[1].length - a[1].length);
|
|
328
|
+
|
|
329
|
+
sortedComponents.forEach(([name, usages]) => {
|
|
330
|
+
const category = categorizeComponent(name);
|
|
331
|
+
lines.push(generateUsageRow(name, usages, category));
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
lines.push('');
|
|
335
|
+
lines.push('---');
|
|
336
|
+
lines.push('');
|
|
337
|
+
|
|
338
|
+
// Most used components
|
|
339
|
+
lines.push('## Most Used Components');
|
|
340
|
+
lines.push('');
|
|
341
|
+
const topComponents = sortedComponents.slice(0, 10);
|
|
342
|
+
|
|
343
|
+
topComponents.forEach(([name, usages], index) => {
|
|
344
|
+
lines.push(`${index + 1}. **${name}** - ${usages.length} usage${usages.length > 1 ? 's' : ''}`);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
lines.push('');
|
|
348
|
+
lines.push('---');
|
|
349
|
+
lines.push('');
|
|
350
|
+
|
|
351
|
+
// Detailed file listings
|
|
352
|
+
lines.push('## Detailed File Usage');
|
|
353
|
+
lines.push('');
|
|
354
|
+
|
|
355
|
+
categorized.forEach((items, category) => {
|
|
356
|
+
if (items.length > 0) {
|
|
357
|
+
lines.push(`### ${category}s`);
|
|
358
|
+
lines.push('');
|
|
359
|
+
|
|
360
|
+
items.sort((a, b) => a.name.localeCompare(b.name));
|
|
361
|
+
|
|
362
|
+
items.forEach(({ name, usages }) => {
|
|
363
|
+
lines.push(`#### ${name}`);
|
|
364
|
+
lines.push('');
|
|
365
|
+
|
|
366
|
+
// Group by file
|
|
367
|
+
const fileGroups = new Map();
|
|
368
|
+
usages.forEach(usage => {
|
|
369
|
+
if (!fileGroups.has(usage.file)) {
|
|
370
|
+
fileGroups.set(usage.file, []);
|
|
371
|
+
}
|
|
372
|
+
fileGroups.get(usage.file).push(usage);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
fileGroups.forEach((fileUsages, file) => {
|
|
376
|
+
const lineNumbers = fileUsages.map(u => u.line).join(', ');
|
|
377
|
+
lines.push(`- \`${file}\` (line${fileUsages.length > 1 ? 's' : ''} ${lineNumbers})`);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
lines.push('');
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
lines.push('---');
|
|
386
|
+
lines.push('');
|
|
387
|
+
lines.push(`*Generated by ${PACKAGE_NAME} usage report tool* `);
|
|
388
|
+
lines.push(`*Report generated: ${new Date().toISOString()}*`);
|
|
389
|
+
lines.push('');
|
|
390
|
+
|
|
391
|
+
return lines.join('\n');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Main execution
|
|
396
|
+
*/
|
|
397
|
+
function main() {
|
|
398
|
+
console.log('š Generating Component Usage Report...\n');
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
// Check if package is installed
|
|
402
|
+
const packagePath = path.join(projectRoot, 'node_modules', PACKAGE_NAME);
|
|
403
|
+
if (!fs.existsSync(packagePath)) {
|
|
404
|
+
console.error(`ā Error: ${PACKAGE_NAME} is not installed in this project`);
|
|
405
|
+
console.error(' Make sure to run this from a project that depends on cmsds-open-data-components');
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Get available components
|
|
410
|
+
const availableComponents = getAvailableComponents();
|
|
411
|
+
console.log(` Library version: ${availableComponents.version}`);
|
|
412
|
+
|
|
413
|
+
// Scan for usage
|
|
414
|
+
const usageMap = scanProjectForUsage();
|
|
415
|
+
|
|
416
|
+
if (usageMap.size === 0) {
|
|
417
|
+
console.warn('ā ļø No component usage found in this project');
|
|
418
|
+
console.warn(' Make sure you are running this from the correct directory');
|
|
419
|
+
process.exit(0);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Generate report
|
|
423
|
+
const report = generateReport(usageMap, availableComponents);
|
|
424
|
+
|
|
425
|
+
// Write to file
|
|
426
|
+
const outputPath = path.join(projectRoot, outputFile);
|
|
427
|
+
fs.writeFileSync(outputPath, report, 'utf8');
|
|
428
|
+
|
|
429
|
+
console.log(`\nā
Usage report generated successfully!`);
|
|
430
|
+
console.log(`š Output: ${outputPath}`);
|
|
431
|
+
console.log(`š File size: ${(Buffer.byteLength(report, 'utf8') / 1024).toFixed(2)} KB`);
|
|
432
|
+
console.log(`š¦ Components tracked: ${usageMap.size}`);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.error('ā Error generating report:', error.message);
|
|
435
|
+
console.error(error.stack);
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Run if called directly
|
|
441
|
+
if (require.main === module) {
|
|
442
|
+
main();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
module.exports = { main, scanProjectForUsage, generateReport };
|