Refactor Usage Statistics Tracker to support multiple package types, removing the config input in favor of specific inputs for npm, GitHub, PyPI, Homebrew, PowerShell, Postman, and Go. Update action.yml, README, and examples to reflect new input structure and enhance documentation.

This commit is contained in:
Luke Hagar
2025-07-29 14:50:30 -05:00
parent ce4a2e80ee
commit cb1866631c
11 changed files with 303 additions and 307 deletions

View File

@@ -10,7 +10,13 @@ Add this to your GitHub Actions workflow:
- name: Usage Statistics Tracker - name: Usage Statistics Tracker
uses: LukeHagar/usage-statistics@v1 uses: LukeHagar/usage-statistics@v1
with: with:
config: 'production' npm-packages: 'lodash,axios'
github-repositories: 'microsoft/vscode,facebook/react'
pypi-packages: 'requests,numpy'
homebrew-formulas: 'git,node'
powershell-modules: 'PowerShellGet,PSReadLine'
postman-collections: '12345,67890'
go-modules: 'github.com/gin-gonic/gin,github.com/go-chi/chi'
json-output-path: 'stats.json' json-output-path: 'stats.json'
update-readme: 'true' update-readme: 'true'
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -37,7 +43,13 @@ jobs:
- name: Usage Statistics Tracker - name: Usage Statistics Tracker
uses: LukeHagar/usage-statistics@v1 uses: LukeHagar/usage-statistics@v1
with: with:
config: 'production' npm-packages: 'lodash,axios'
github-repositories: 'microsoft/vscode,facebook/react'
pypi-packages: 'requests,numpy'
homebrew-formulas: 'git,node'
powershell-modules: 'PowerShellGet,PSReadLine'
postman-collections: '12345,67890'
go-modules: 'github.com/gin-gonic/gin,github.com/go-chi/chi'
json-output-path: 'stats.json' json-output-path: 'stats.json'
csv-output-path: 'stats.csv' csv-output-path: 'stats.csv'
report-output-path: 'docs/usage-report.md' report-output-path: 'docs/usage-report.md'
@@ -68,19 +80,17 @@ Last updated: 2025-07-29T18:53:52.619Z
- **Unique Packages**: 8 - **Unique Packages**: 8
- **Platforms Tracked**: npm, pypi, homebrew, go - **Platforms Tracked**: npm, pypi, homebrew, go
### Platform Totals
- **HOMEBREW**: 380,163 downloads (2 packages)
- **NPM**: 34,311 downloads (2 packages)
- **GO**: 33 downloads (2 packages)
### Top Packages ### Top Packages
1. **node** (homebrew) - 226,882 downloads 1. **node** (homebrew) - 226,882 downloads
2. **git** (homebrew) - 153,281 downloads 2. **git** (homebrew) - 153,281 downloads
3. **axios** (npm) - 18,397 downloads 3. **axios** (npm) - 18,397 downloads
4. **lodash** (npm) - 15,914 downloads 4. **lodash** (npm) - 15,914 downloads
5. **github.com/go-chi/chi** (go) - 33 downloads 5. **github.com/go-chi/chi** (go) - 33 downloads
### Recent Activity
- **node** (homebrew) - 226,882 downloads on 7/29/2025
- **git** (homebrew) - 153,281 downloads on 7/29/2025
- **axios** (npm) - 722 downloads on 7/29/2025
- **lodash** (npm) - 857 downloads on 7/29/2025
- **axios** (npm) - 936 downloads on 7/28/2025
<!-- USAGE_STATS_END --> <!-- USAGE_STATS_END -->
``` ```

View File

@@ -20,7 +20,13 @@ A comprehensive GitHub Action for tracking download statistics across multiple p
- name: Usage Statistics Tracker - name: Usage Statistics Tracker
uses: LukeHagar/usage-statistics@v1 uses: LukeHagar/usage-statistics@v1
with: with:
config: 'production' npm-packages: 'lodash,axios'
github-repositories: 'microsoft/vscode,facebook/react'
pypi-packages: 'requests,numpy'
homebrew-formulas: 'git,node'
powershell-modules: 'PowerShellGet,PSReadLine'
postman-collections: '12345,67890'
go-modules: 'github.com/gin-gonic/gin,github.com/go-chi/chi'
json-output-path: 'stats.json' json-output-path: 'stats.json'
csv-output-path: 'stats.csv' csv-output-path: 'stats.csv'
report-output-path: 'report.md' report-output-path: 'report.md'
@@ -50,7 +56,13 @@ bun test
| Parameter | Description | Required | Default | | Parameter | Description | Required | Default |
|-----------|-------------|----------|---------| |-----------|-------------|----------|---------|
| `config` | Configuration mode or JSON | No | `production` | | `npm-packages` | Comma-separated list of NPM packages | No | (empty) |
| `github-repositories` | Comma-separated list of GitHub repos (owner/repo) | No | (empty) |
| `pypi-packages` | Comma-separated list of PyPI packages | No | (empty) |
| `homebrew-formulas` | Comma-separated list of Homebrew formulas | No | (empty) |
| `powershell-modules` | Comma-separated list of PowerShell modules | No | (empty) |
| `postman-collections` | Comma-separated list of Postman collection IDs | No | (empty) |
| `go-modules` | Comma-separated list of Go modules | No | (empty) |
| `json-output-path` | Path for JSON output | No | `stats.json` | | `json-output-path` | Path for JSON output | No | `stats.json` |
| `csv-output-path` | Path for CSV output | No | (empty) | | `csv-output-path` | Path for CSV output | No | (empty) |
| `report-output-path` | Path for human-readable report | No | (empty) | | `report-output-path` | Path for human-readable report | No | (empty) |
@@ -61,30 +73,36 @@ bun test
| `commit-message` | Commit message for changes | No | `chore: update usage statistics [skip ci]` | | `commit-message` | Commit message for changes | No | `chore: update usage statistics [skip ci]` |
| `preview-mode` | Run with mock data | No | `false` | | `preview-mode` | Run with mock data | No | `false` |
### Configuration Modes ### Configuration Examples
#### Production Mode #### NPM Packages Only
```yaml ```yaml
- name: Usage Statistics Tracker - name: Usage Statistics Tracker
uses: LukeHagar/usage-statistics@v1 uses: LukeHagar/usage-statistics@v1
with: with:
config: 'production' npm-packages: 'lodash,axios,react'
``` ```
#### Development Mode #### GitHub Repositories Only
```yaml ```yaml
- name: Usage Statistics Tracker - name: Usage Statistics Tracker
uses: LukeHagar/usage-statistics@v1 uses: LukeHagar/usage-statistics@v1
with: with:
config: 'development' github-repositories: 'microsoft/vscode,facebook/react,vercel/next.js'
``` ```
#### Custom JSON Configuration #### Mixed Platform Configuration
```yaml ```yaml
- name: Usage Statistics Tracker - name: Usage Statistics Tracker
uses: LukeHagar/usage-statistics@v1 uses: LukeHagar/usage-statistics@v1
with: with:
config: '{"npmPackages": ["lodash", "axios"], "githubRepositories": ["microsoft/vscode"]}' npm-packages: 'lodash,axios'
github-repositories: 'microsoft/vscode,facebook/react'
pypi-packages: 'requests,numpy'
homebrew-formulas: 'git,node'
powershell-modules: 'PowerShellGet,PSReadLine'
postman-collections: '12345,67890'
go-modules: 'github.com/gin-gonic/gin,github.com/go-chi/chi'
``` ```
### Outputs ### Outputs
@@ -121,7 +139,13 @@ jobs:
- name: Usage Statistics Tracker - name: Usage Statistics Tracker
uses: LukeHagar/usage-statistics@v1 uses: LukeHagar/usage-statistics@v1
with: with:
config: 'production' npm-packages: 'lodash,axios'
github-repositories: 'microsoft/vscode,facebook/react'
pypi-packages: 'requests,numpy'
homebrew-formulas: 'git,node'
powershell-modules: 'PowerShellGet,PSReadLine'
postman-collections: '12345,67890'
go-modules: 'github.com/gin-gonic/gin,github.com/go-chi/chi'
json-output-path: 'stats.json' json-output-path: 'stats.json'
update-readme: 'true' update-readme: 'true'
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -141,7 +165,13 @@ jobs:
- name: Usage Statistics Tracker - name: Usage Statistics Tracker
uses: LukeHagar/usage-statistics@v1 uses: LukeHagar/usage-statistics@v1
with: with:
config: 'production' npm-packages: 'lodash,axios,react'
github-repositories: 'microsoft/vscode,facebook/react'
pypi-packages: 'requests,numpy'
homebrew-formulas: 'git,node'
powershell-modules: 'PowerShellGet,PSReadLine'
postman-collections: '12345,67890'
go-modules: 'github.com/gin-gonic/gin,github.com/go-chi/chi'
json-output-path: 'data/stats.json' json-output-path: 'data/stats.json'
csv-output-path: 'data/stats.csv' csv-output-path: 'data/stats.csv'
report-output-path: 'docs/usage-report.md' report-output-path: 'docs/usage-report.md'
@@ -172,7 +202,11 @@ jobs:
id: stats id: stats
uses: LukeHagar/usage-statistics@v1 uses: LukeHagar/usage-statistics@v1
with: with:
config: 'production' npm-packages: 'lodash,axios'
github-repositories: 'microsoft/vscode,facebook/react'
powershell-modules: 'PowerShellGet'
postman-collections: '12345'
go-modules: 'github.com/gin-gonic/gin'
json-output-path: 'stats.json' json-output-path: 'stats.json'
- name: Use Statistics Data - name: Use Statistics Data
@@ -198,19 +232,17 @@ Last updated: 2025-07-29T18:53:52.619Z
- **Unique Packages**: 8 - **Unique Packages**: 8
- **Platforms Tracked**: npm, pypi, homebrew, go - **Platforms Tracked**: npm, pypi, homebrew, go
### Platform Totals
- **HOMEBREW**: 380,163 downloads (2 packages)
- **NPM**: 34,311 downloads (2 packages)
- **GO**: 33 downloads (2 packages)
### Top Packages ### Top Packages
1. **node** (homebrew) - 226,882 downloads 1. **node** (homebrew) - 226,882 downloads
2. **git** (homebrew) - 153,281 downloads 2. **git** (homebrew) - 153,281 downloads
3. **axios** (npm) - 18,397 downloads 3. **axios** (npm) - 18,397 downloads
4. **lodash** (npm) - 15,914 downloads 4. **lodash** (npm) - 15,914 downloads
5. **github.com/go-chi/chi** (go) - 33 downloads 5. **github.com/go-chi/chi** (go) - 33 downloads
### Recent Activity
- **node** (homebrew) - 226,882 downloads on 7/29/2025
- **git** (homebrew) - 153,281 downloads on 7/29/2025
- **axios** (npm) - 722 downloads on 7/29/2025
- **lodash** (npm) - 857 downloads on 7/29/2025
- **axios** (npm) - 936 downloads on 7/28/2025
<!-- USAGE_STATS_END --> <!-- USAGE_STATS_END -->
``` ```
@@ -246,7 +278,6 @@ usage-statistics/
├── src/ ├── src/
│ ├── index.ts # Main library entry point │ ├── index.ts # Main library entry point
│ ├── action.ts # GitHub Action entry point │ ├── action.ts # GitHub Action entry point
│ ├── config.ts # Configuration management
│ ├── aggregator.ts # Statistics aggregation │ ├── aggregator.ts # Statistics aggregation
│ ├── types/ # TypeScript type definitions │ ├── types/ # TypeScript type definitions
│ ├── trackers/ # Platform-specific trackers │ ├── trackers/ # Platform-specific trackers

View File

@@ -3,11 +3,47 @@ description: 'Track download statistics across multiple platforms (NPM, GitHub,
author: 'LukeHagar' author: 'LukeHagar'
inputs: inputs:
# Configuration # NPM Configuration
config: npm-packages:
description: 'Configuration for tracking (development, production, test, or custom JSON)' description: 'Comma-separated list of NPM packages to track'
required: false required: false
default: 'production' default: ''
# GitHub Configuration
github-repositories:
description: 'Comma-separated list of GitHub repositories (format: owner/repo)'
required: false
default: ''
# PyPI Configuration
pypi-packages:
description: 'Comma-separated list of PyPI packages to track'
required: false
default: ''
# Homebrew Configuration
homebrew-formulas:
description: 'Comma-separated list of Homebrew formulas to track'
required: false
default: ''
# PowerShell Configuration
powershell-modules:
description: 'Comma-separated list of PowerShell modules to track'
required: false
default: ''
# Postman Configuration
postman-collections:
description: 'Comma-separated list of Postman collection IDs to track'
required: false
default: ''
# Go Configuration
go-modules:
description: 'Comma-separated list of Go modules to track'
required: false
default: ''
# Output paths # Output paths
json-output-path: json-output-path:
@@ -80,4 +116,4 @@ outputs:
runs: runs:
using: 'node20' using: 'node20'
main: 'dist/index.js' main: 'dist/action.js'

View File

@@ -16,7 +16,13 @@ jobs:
- name: Usage Statistics Tracker - name: Usage Statistics Tracker
uses: LukeHagar/usage-statistics@v1 uses: LukeHagar/usage-statistics@v1
with: with:
config: 'production' npm-packages: 'lodash,axios'
github-repositories: 'microsoft/vscode,facebook/react'
pypi-packages: 'requests,numpy'
homebrew-formulas: 'git,node'
powershell-modules: 'PowerShellGet,PSReadLine'
postman-collections: '12345,67890'
go-modules: 'github.com/gin-gonic/gin,github.com/go-chi/chi'
json-output-path: 'stats.json' json-output-path: 'stats.json'
csv-output-path: 'stats.csv' csv-output-path: 'stats.csv'
report-output-path: 'docs/usage-report.md' report-output-path: 'docs/usage-report.md'

View File

@@ -0,0 +1,59 @@
name: Usage Statistics with Input-Based Configuration
on:
schedule:
- cron: '0 0 * * *' # Daily at midnight
workflow_dispatch:
jobs:
update-stats:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Usage Statistics Tracker
uses: LukeHagar/usage-statistics@v1
with:
# NPM packages to track
npm-packages: 'lodash,axios,react,vue'
# GitHub repositories to track
github-repositories: 'microsoft/vscode,facebook/react,vercel/next.js'
# PyPI packages to track
pypi-packages: 'requests,numpy,pandas'
# Homebrew formulas to track
homebrew-formulas: 'git,node,postgresql'
# PowerShell modules to track
powershell-modules: 'PowerShellGet,PSReadLine'
# Go modules to track
go-modules: 'github.com/gin-gonic/gin,github.com/go-chi/chi'
# Output configuration
json-output-path: 'data/stats.json'
csv-output-path: 'data/stats.csv'
report-output-path: 'docs/usage-report.md'
# README integration
update-readme: 'true'
readme-path: 'README.md'
# API tokens
github-token: ${{ secrets.GITHUB_TOKEN }}
postman-api-key: ${{ secrets.POSTMAN_API_KEY }}
# Commit settings
commit-message: 'feat: update usage statistics with detailed report'
- name: Commit and push changes
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add data/stats.json data/stats.csv docs/usage-report.md README.md
git commit -m "chore: update usage statistics [skip ci]" || echo "No changes to commit"
git push

View File

@@ -3,12 +3,19 @@ import * as core from '@actions/core';
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
import * as path from 'path'; import * as path from 'path';
import { UsageStatisticsManager } from './index'; import { UsageStatisticsManager } from './index';
import { getConfig, validateConfig } from './config'; import type { TrackingConfig } from './types';
async function run() { async function run() {
try { try {
// Get inputs // Get inputs
const configInput = core.getInput('config'); const npmPackages = core.getInput('npm-packages');
const githubRepositories = core.getInput('github-repositories');
const pypiPackages = core.getInput('pypi-packages');
const homebrewFormulas = core.getInput('homebrew-formulas');
const powershellModules = core.getInput('powershell-modules');
const postmanCollections = core.getInput('postman-collections');
const goModules = core.getInput('go-modules');
const jsonOutputPath = core.getInput('json-output-path'); const jsonOutputPath = core.getInput('json-output-path');
const csvOutputPath = core.getInput('csv-output-path'); const csvOutputPath = core.getInput('csv-output-path');
const reportOutputPath = core.getInput('report-output-path'); const reportOutputPath = core.getInput('report-output-path');
@@ -27,31 +34,35 @@ async function run() {
process.env.POSTMAN_API_KEY = postmanApiKey; process.env.POSTMAN_API_KEY = postmanApiKey;
} }
// Get configuration // Build configuration from inputs
let trackingConfig; const trackingConfig: TrackingConfig = {
if (configInput === 'development' || configInput === 'production' || configInput === 'test') { enableLogging: true,
trackingConfig = getConfig(configInput as 'development' | 'production' | 'test'); updateInterval: 60 * 60 * 1000, // 1 hour
} else { npmPackages: npmPackages ? npmPackages.split(',').map(p => p.trim()).filter(p => p) : [],
// Try to parse as JSON githubRepos: githubRepositories ? githubRepositories.split(',').map(r => r.trim()).filter(r => r) : [],
try { pythonPackages: pypiPackages ? pypiPackages.split(',').map(p => p.trim()).filter(p => p) : [],
trackingConfig = JSON.parse(configInput); homebrewPackages: homebrewFormulas ? homebrewFormulas.split(',').map(f => f.trim()).filter(f => f) : [],
} catch (error) { powershellModules: powershellModules ? powershellModules.split(',').map(m => m.trim()).filter(m => m) : [],
core.setFailed(`Invalid config input: ${configInput}. Must be 'development', 'production', 'test', or valid JSON.`); postmanCollections: postmanCollections ? postmanCollections.split(',').map(c => c.trim()).filter(c => c) : [],
return; goModules: goModules ? goModules.split(',').map(m => m.trim()).filter(m => m) : []
} };
}
// Validate configuration // Validate that at least one platform has packages configured
try { const totalPackages = (trackingConfig.npmPackages?.length || 0) +
validateConfig(trackingConfig); (trackingConfig.githubRepos?.length || 0) +
} catch (error) { (trackingConfig.pythonPackages?.length || 0) +
core.setFailed(`Configuration validation failed: ${error}`); (trackingConfig.homebrewPackages?.length || 0) +
return; (trackingConfig.powershellModules?.length || 0) +
(trackingConfig.postmanCollections?.length || 0) +
(trackingConfig.goModules?.length || 0);
if (totalPackages === 0 && !previewMode) {
core.warning('No packages configured for tracking. Consider adding packages to track or enabling preview mode.');
} }
// Create manager // Create manager
const manager = new UsageStatisticsManager(trackingConfig); const manager = new UsageStatisticsManager(trackingConfig);
// Generate report // Generate report
let report; let report;
if (previewMode) { if (previewMode) {
@@ -61,10 +72,10 @@ async function run() {
core.info('📊 Generating comprehensive usage statistics report...'); core.info('📊 Generating comprehensive usage statistics report...');
report = await manager.generateComprehensiveReport(); report = await manager.generateComprehensiveReport();
} }
// Display report // Display report
await manager.displayReport(report); await manager.displayReport(report);
// Write JSON output // Write JSON output
if (jsonOutputPath) { if (jsonOutputPath) {
const jsonContent = JSON.stringify(report, null, 2); const jsonContent = JSON.stringify(report, null, 2);
@@ -112,36 +123,28 @@ async function run() {
} }
async function generateHumanReadableReport(report: any): Promise<string> { async function generateHumanReadableReport(report: any): Promise<string> {
let content = '# Usage Statistics Report\n\n'; let content = '# Usage Statistics Summary\n\n';
content += `Generated on: ${new Date().toISOString()}\n\n`; content += `Generated on: ${new Date().toISOString()}\n\n`;
// Summary // Overall Summary
content += '## Summary\n\n'; content += '## Overall Summary\n\n';
content += `- **Total Downloads**: ${report.totalDownloads.toLocaleString()}\n`; content += `- **Total Downloads**: ${report.totalDownloads.toLocaleString()}\n`;
content += `- **Unique Packages**: ${report.uniquePackages}\n`; content += `- **Unique Packages**: ${report.uniquePackages}\n`;
content += `- **Platforms Tracked**: ${report.platforms.join(', ')}\n\n`; content += `- **Platforms Tracked**: ${report.platforms.join(', ')}\n\n`;
// Platform Breakdown // Platform Totals
content += '## Platform Breakdown\n\n'; content += '## Platform Totals\n\n';
for (const [platform, data] of Object.entries(report.platformBreakdown)) { for (const [platform, data] of Object.entries(report.platformBreakdown)) {
content += `### ${platform.toUpperCase()}\n`; content += `### ${platform.toUpperCase()}\n`;
content += `- Downloads: ${data.totalDownloads.toLocaleString()}\n`; content += `- **Downloads**: ${data.totalDownloads.toLocaleString()}\n`;
content += `- Packages: ${data.uniquePackages}\n`; content += `- **Packages**: ${data.uniquePackages}\n\n`;
content += `- Package List: ${data.packages.join(', ')}\n\n`;
} }
// Top Packages // Package Rankings
content += '## Top Packages\n\n'; content += '## Package Rankings\n\n';
report.topPackages.slice(0, 10).forEach((pkg: any, index: number) => { report.topPackages.forEach((pkg: any, index: number) => {
content += `${index + 1}. **${pkg.name}** (${pkg.platform}) - ${pkg.downloads.toLocaleString()} downloads\n`; content += `${index + 1}. **${pkg.name}** (${pkg.platform}) - ${pkg.downloads.toLocaleString()} downloads\n`;
}); });
content += '\n';
// Recent Activity
content += '## Recent Activity\n\n';
report.recentActivity.slice(0, 5).forEach((activity: any) => {
content += `- **${activity.packageName}** (${activity.platform}) - ${activity.downloads.toLocaleString()} downloads on ${activity.timestamp.toLocaleDateString()}\n`;
});
return content; return content;
} }
@@ -163,14 +166,14 @@ Last updated: ${new Date().toISOString()}
- **Unique Packages**: ${report.uniquePackages} - **Unique Packages**: ${report.uniquePackages}
- **Platforms Tracked**: ${report.platforms.join(', ')} - **Platforms Tracked**: ${report.platforms.join(', ')}
### Top Packages ### Platform Totals
${report.topPackages.slice(0, 10).map((pkg: any, index: number) => ${Object.entries(report.platformBreakdown).map(([platform, data]: [string, any]) =>
`${index + 1}. **${pkg.name}** (${pkg.platform}) - ${pkg.downloads.toLocaleString()} downloads` `- **${platform.toUpperCase()}**: ${data.totalDownloads.toLocaleString()} downloads (${data.uniquePackages} packages)`
).join('\n')} ).join('\n')}
### Recent Activity ### Top Packages
${report.recentActivity.slice(0, 5).map((activity: any) => ${report.topPackages.map((pkg: any, index: number) =>
`- **${activity.packageName}** (${activity.platform}) - ${activity.downloads.toLocaleString()} downloads on ${activity.timestamp.toLocaleDateString()}` `${index + 1}. **${pkg.name}** (${pkg.platform}) - ${pkg.downloads.toLocaleString()} downloads`
).join('\n')} ).join('\n')}
`; `;

View File

@@ -17,7 +17,6 @@ export interface AggregatedStats {
totalDownloads: number; totalDownloads: number;
uniquePackages: number; uniquePackages: number;
platforms: string[]; platforms: string[];
timeRange: { start: Date; end: Date } | null;
platformBreakdown: Record<string, { platformBreakdown: Record<string, {
totalDownloads: number; totalDownloads: number;
uniquePackages: number; uniquePackages: number;
@@ -28,12 +27,6 @@ export interface AggregatedStats {
platform: string; platform: string;
downloads: number; downloads: number;
}>; }>;
recentActivity: Array<{
packageName: string;
platform: string;
downloads: number;
timestamp: Date;
}>;
} }
export class DownloadStatsAggregator implements DownloadStatsAggregator { export class DownloadStatsAggregator implements DownloadStatsAggregator {
@@ -64,17 +57,10 @@ export class DownloadStatsAggregator implements DownloadStatsAggregator {
}> = {}; }> = {};
const packageMap = new Map<string, { downloads: number; platform: string }>(); const packageMap = new Map<string, { downloads: number; platform: string }>();
const recentActivity: Array<{
packageName: string;
platform: string;
downloads: number;
timestamp: Date;
}> = [];
let totalDownloads = 0; let totalDownloads = 0;
let uniquePackages = 0; let uniquePackages = 0;
const platforms = new Set<string>(); const platforms = new Set<string>();
const timeRange = { start: new Date(), end: new Date(0) };
// Process each stat // Process each stat
for (const stat of stats) { for (const stat of stats) {
@@ -104,27 +90,8 @@ export class DownloadStatsAggregator implements DownloadStatsAggregator {
platformBreakdown[stat.platform].packages.push(stat.packageName); platformBreakdown[stat.platform].packages.push(stat.packageName);
platformBreakdown[stat.platform].uniquePackages++; platformBreakdown[stat.platform].uniquePackages++;
} }
// Track recent activity
recentActivity.push({
packageName: stat.packageName,
platform: stat.platform,
downloads: stat.downloadCount,
timestamp: stat.timestamp
});
// Update time range
if (stat.timestamp < timeRange.start) {
timeRange.start = stat.timestamp;
}
if (stat.timestamp > timeRange.end) {
timeRange.end = stat.timestamp;
}
} }
// Sort recent activity by timestamp
recentActivity.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
// Get top packages // Get top packages
const topPackages = Array.from(packageMap.entries()) const topPackages = Array.from(packageMap.entries())
.map(([key, data]) => ({ .map(([key, data]) => ({
@@ -132,17 +99,14 @@ export class DownloadStatsAggregator implements DownloadStatsAggregator {
platform: data.platform, platform: data.platform,
downloads: data.downloads downloads: data.downloads
})) }))
.sort((a, b) => b.downloads - a.downloads) .sort((a, b) => b.downloads - a.downloads);
.slice(0, 10);
return { return {
totalDownloads, totalDownloads,
uniquePackages, uniquePackages,
platforms: Array.from(platforms), platforms: Array.from(platforms),
timeRange: timeRange.start < timeRange.end ? timeRange : null,
platformBreakdown, platformBreakdown,
topPackages, topPackages
recentActivity: recentActivity.slice(0, 20)
}; };
} }

View File

@@ -1,112 +0,0 @@
/**
* Configuration for usage statistics tracking
* Customize this file to track your specific packages and tools
*/
import type { TrackingConfig } from './types';
export const defaultConfig: TrackingConfig = {
// NPM packages - Add your NPM package names here
npmPackages: [
'lodash',
'axios'
],
// GitHub repositories - Format: 'owner/repo'
githubRepos: [
'microsoft/vscode',
'facebook/react'
],
// PyPI packages - Add your Python package names here
pythonPackages: [
'requests',
'numpy'
],
// Homebrew formulae - Add your Homebrew package names here
homebrewPackages: [
'git',
'node'
],
// PowerShell modules - Add your PowerShell module names here
powershellModules: [
'PowerShellGet'
],
// Postman collections - Add your Postman collection IDs here
postmanCollections: [
// Note: These are example IDs - replace with real collection IDs
// For testing, we'll leave these empty to avoid errors
],
// Go modules - Add your Go module paths here
goModules: [
'github.com/gin-gonic/gin',
'github.com/go-chi/chi'
],
// Update interval in milliseconds (default: 1 hour)
updateInterval: 60 * 60 * 1000,
// Enable detailed logging
enableLogging: true
};
// Test configuration with minimal packages
export const testConfig: TrackingConfig = {
npmPackages: ['lodash'],
githubRepos: [],
pythonPackages: ['requests'],
homebrewPackages: ['git'],
powershellModules: [],
postmanCollections: [],
goModules: [],
updateInterval: 60 * 60 * 1000,
enableLogging: false
};
// Environment-specific configurations
export const developmentConfig: TrackingConfig = {
...defaultConfig,
enableLogging: true,
updateInterval: 5 * 60 * 1000, // 5 minutes for development
};
export const productionConfig: TrackingConfig = {
...defaultConfig,
enableLogging: false,
updateInterval: 60 * 60 * 1000, // 1 hour for production
};
// Helper function to get configuration based on environment
export function getConfig(environment: 'development' | 'production' | 'default' | 'test' = 'default'): TrackingConfig {
switch (environment) {
case 'development':
return developmentConfig;
case 'production':
return productionConfig;
case 'test':
return testConfig;
default:
return defaultConfig;
}
}
// Helper function to validate configuration
export function validateConfig(config: TrackingConfig): string[] {
const errors: string[] = [];
if (!config.npmPackages && !config.githubRepos && !config.pythonPackages &&
!config.homebrewPackages && !config.powershellModules &&
!config.postmanCollections && !config.goModules) {
errors.push('No packages configured for tracking. Please add packages to at least one platform.');
}
if (config.updateInterval && config.updateInterval < 60000) {
errors.push('Update interval should be at least 60 seconds to avoid rate limiting.');
}
return errors;
}

View File

@@ -1,12 +1,22 @@
import { describe, it, expect, beforeEach } from "bun:test"; import { describe, it, expect, beforeEach } from "bun:test";
import { UsageStatisticsManager } from "./index"; import { UsageStatisticsManager } from "./index";
import { getConfig } from "./config"; import type { TrackingConfig } from "./types";
describe("UsageStatisticsManager", () => { describe("UsageStatisticsManager", () => {
let manager: UsageStatisticsManager; let manager: UsageStatisticsManager;
beforeEach(() => { beforeEach(() => {
const config = getConfig('test'); const config: TrackingConfig = {
enableLogging: true,
updateInterval: 60 * 60 * 1000,
npmPackages: ['lodash', 'axios'],
githubRepos: ['microsoft/vscode', 'facebook/react'],
pythonPackages: ['requests', 'numpy'],
homebrewPackages: ['git', 'node'],
powershellModules: ['PowerShellGet'],
postmanCollections: [],
goModules: ['github.com/gin-gonic/gin', 'github.com/go-chi/chi']
};
manager = new UsageStatisticsManager(config); manager = new UsageStatisticsManager(config);
}); });
@@ -19,7 +29,7 @@ describe("UsageStatisticsManager", () => {
expect(report.uniquePackages).toBeGreaterThanOrEqual(0); expect(report.uniquePackages).toBeGreaterThanOrEqual(0);
expect(Array.isArray(report.platforms)).toBe(true); expect(Array.isArray(report.platforms)).toBe(true);
expect(Array.isArray(report.topPackages)).toBe(true); expect(Array.isArray(report.topPackages)).toBe(true);
expect(Array.isArray(report.recentActivity)).toBe(true); expect(report.topPackages.length).toBeGreaterThan(0);
expect(typeof report.platformBreakdown).toBe('object'); expect(typeof report.platformBreakdown).toBe('object');
}, 30000); // 30 second timeout }, 30000); // 30 second timeout
}); });
@@ -72,27 +82,23 @@ describe("UsageStatisticsManager", () => {
}); });
describe("Configuration", () => { describe("Configuration", () => {
it("should load development config", () => { it("should create valid config", () => {
const config = getConfig('development'); const config: TrackingConfig = {
enableLogging: true,
expect(config).toBeDefined(); updateInterval: 60 * 60 * 1000,
expect(config.enableLogging).toBe(true); npmPackages: ['lodash', 'axios'],
expect(config.updateInterval).toBe(5 * 60 * 1000); // 5 minutes githubRepos: ['microsoft/vscode'],
}); pythonPackages: ['requests'],
homebrewPackages: ['git'],
it("should load production config", () => { powershellModules: ['PowerShellGet'],
const config = getConfig('production'); postmanCollections: [],
goModules: ['github.com/gin-gonic/gin']
expect(config).toBeDefined(); };
expect(config.enableLogging).toBe(false);
expect(config.updateInterval).toBe(60 * 60 * 1000); // 1 hour
});
it("should load default config", () => {
const config = getConfig('default');
expect(config).toBeDefined(); expect(config).toBeDefined();
expect(config.enableLogging).toBe(true); expect(config.enableLogging).toBe(true);
expect(config.updateInterval).toBe(60 * 60 * 1000); // 1 hour expect(config.updateInterval).toBe(60 * 60 * 1000); // 1 hour
expect(config.npmPackages).toContain('lodash');
expect(config.githubRepos).toContain('microsoft/vscode');
}); });
}); });

View File

@@ -2,7 +2,6 @@
import type { TrackingConfig } from './types'; import type { TrackingConfig } from './types';
import { DownloadStatsAggregator } from './aggregator'; import { DownloadStatsAggregator } from './aggregator';
import type { AggregatedStats } from './aggregator'; import type { AggregatedStats } from './aggregator';
import { getConfig, validateConfig } from './config';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import path from 'path'; import path from 'path';
import { execSync } from 'node:child_process'; import { execSync } from 'node:child_process';
@@ -45,14 +44,9 @@ Last updated: ${new Date().toISOString()}
- **Platforms Tracked**: ${stats.platforms.join(', ')} - **Platforms Tracked**: ${stats.platforms.join(', ')}
### Top Packages ### Top Packages
${stats.topPackages.slice(0, 10).map((pkg, index) => ${stats.topPackages.map((pkg, index) =>
`${index + 1}. **${pkg.name}** (${pkg.platform}) - ${pkg.downloads.toLocaleString()} downloads` `${index + 1}. **${pkg.name}** (${pkg.platform}) - ${pkg.downloads.toLocaleString()} downloads`
).join('\n')} ).join('\n')}
### Recent Activity
${stats.recentActivity.slice(0, 5).map(activity =>
`- **${activity.packageName}** (${activity.platform}) - ${activity.downloads.toLocaleString()} downloads on ${activity.timestamp.toLocaleDateString()}`
).join('\n')}
`; `;
const startMarker = readmeContent.indexOf(STATS_MARKER_START); const startMarker = readmeContent.indexOf(STATS_MARKER_START);
@@ -80,8 +74,6 @@ async function gitCommitAndPush(files: string[], message: string) {
execSync(`git push`); execSync(`git push`);
} }
const trackingConfig: TrackingConfig = getConfig(process.env.NODE_ENV as 'development' | 'production' || 'default');
class UsageStatisticsManager { class UsageStatisticsManager {
private aggregator: DownloadStatsAggregator; private aggregator: DownloadStatsAggregator;
private lastUpdateTime: Date | null = null; private lastUpdateTime: Date | null = null;
@@ -110,9 +102,9 @@ class UsageStatisticsManager {
const report = await this.generateComprehensiveReport(); const report = await this.generateComprehensiveReport();
if (format === 'csv') { if (format === 'csv') {
const csvHeader = 'Platform,Package,Downloads,Last Updated\n'; const csvHeader = 'Platform,Package,Downloads\n';
const csvRows = report.topPackages.map(pkg => const csvRows = report.topPackages.map(pkg =>
`${pkg.platform},${pkg.name},${pkg.downloads},${new Date().toISOString()}` `${pkg.platform},${pkg.name},${pkg.downloads}`
).join('\n'); ).join('\n');
return csvHeader + csvRows; return csvHeader + csvRows;
} }
@@ -125,36 +117,27 @@ class UsageStatisticsManager {
} }
async displayReport(report: AggregatedStats) { async displayReport(report: AggregatedStats) {
console.log('🚀 Usage Statistics Report'); console.log('📊 Usage Statistics Summary');
console.log('==================================================\n'); console.log('==================================================\n');
console.log('📈 Summary:'); // Overall Summary
console.log('📈 Overall Summary:');
console.log(`Total Downloads: ${report.totalDownloads.toLocaleString()}`); console.log(`Total Downloads: ${report.totalDownloads.toLocaleString()}`);
console.log(`Unique Packages: ${report.uniquePackages}`); console.log(`Unique Packages: ${report.uniquePackages}`);
console.log(`Platforms Tracked: ${report.platforms.join(', ')}`); console.log(`Platforms Tracked: ${report.platforms.join(', ')}\n`);
if (report.timeRange) {
console.log(`Time Range: ${report.timeRange.start.toLocaleDateString()} to ${report.timeRange.end.toLocaleDateString()}\n`);
}
console.log('🏗️ Platform Breakdown:'); // Platform Totals
console.log('🏗️ Platform Totals:');
for (const [platform, data] of Object.entries(report.platformBreakdown)) { for (const [platform, data] of Object.entries(report.platformBreakdown)) {
console.log(` ${platform.toUpperCase()}:`); console.log(` ${platform.toUpperCase()}: ${data.totalDownloads.toLocaleString()} downloads (${data.uniquePackages} packages)`);
console.log(` Downloads: ${data.totalDownloads.toLocaleString()}`);
console.log(` Packages: ${data.uniquePackages}`);
console.log(` Package List: ${data.packages.join(', ')}`);
} }
console.log(); console.log('');
console.log('🏆 Top Packages:'); // Package Rankings
report.topPackages.slice(0, 10).forEach((pkg, index) => { console.log('🏆 Package Rankings:');
report.topPackages.forEach((pkg, index) => {
console.log(` ${index + 1}. ${pkg.name} (${pkg.platform}) - ${pkg.downloads.toLocaleString()} downloads`); console.log(` ${index + 1}. ${pkg.name} (${pkg.platform}) - ${pkg.downloads.toLocaleString()} downloads`);
}); });
console.log();
console.log('🕒 Recent Activity:');
report.recentActivity.slice(0, 5).forEach(activity => {
console.log(` ${activity.packageName} (${activity.platform}) - ${activity.downloads.toLocaleString()} downloads on ${activity.timestamp.toLocaleDateString()}`);
});
console.log('=================================================='); console.log('==================================================');
} }
@@ -167,41 +150,49 @@ class UsageStatisticsManager {
platform: 'npm', platform: 'npm',
packageName: 'lodash', packageName: 'lodash',
downloadCount: 1500000, downloadCount: 1500000,
timestamp: new Date(),
period: 'total' as const,
metadata: { version: '4.17.21' } metadata: { version: '4.17.21' }
}, },
{ {
platform: 'npm', platform: 'npm',
packageName: 'axios', packageName: 'axios',
downloadCount: 800000, downloadCount: 800000,
timestamp: new Date(),
period: 'total' as const,
metadata: { version: '1.6.0' } metadata: { version: '1.6.0' }
}, },
{ {
platform: 'github', platform: 'github',
packageName: 'microsoft/vscode', packageName: 'microsoft/vscode',
downloadCount: 500000, downloadCount: 500000,
timestamp: new Date(),
period: 'total' as const,
metadata: { release: 'v1.85.0' } metadata: { release: 'v1.85.0' }
}, },
{ {
platform: 'pypi', platform: 'pypi',
packageName: 'requests', packageName: 'requests',
downloadCount: 300000, downloadCount: 300000,
timestamp: new Date(),
period: 'total' as const,
metadata: { version: '2.31.0' } metadata: { version: '2.31.0' }
}, },
{ {
platform: 'homebrew', platform: 'homebrew',
packageName: 'git', packageName: 'git',
downloadCount: 250000, downloadCount: 250000,
timestamp: new Date(),
period: 'total' as const,
metadata: { version: '2.43.0' } metadata: { version: '2.43.0' }
},
{
platform: 'powershell',
packageName: 'PowerShellGet',
downloadCount: 120000,
metadata: { version: '2.2.5' }
},
{
platform: 'postman',
packageName: 'Postman Collection',
downloadCount: 75000,
metadata: { collectionId: '12345' }
},
{
platform: 'go',
packageName: 'github.com/gin-gonic/gin',
downloadCount: 45000,
metadata: { version: 'v1.9.1' }
} }
]; ];
@@ -214,15 +205,20 @@ class UsageStatisticsManager {
async function main() { async function main() {
console.log('🚀 Usage Statistics Tracker Starting...\n'); console.log('🚀 Usage Statistics Tracker Starting...\n');
// Validate configuration // Create a default configuration for CLI usage
try { const defaultConfig: TrackingConfig = {
validateConfig(trackingConfig); enableLogging: true,
} catch (error) { updateInterval: 60 * 60 * 1000, // 1 hour
console.error('❌ Configuration validation failed:', error); npmPackages: ['lodash', 'axios'],
process.exit(1); githubRepos: ['microsoft/vscode', 'facebook/react'],
} pythonPackages: ['requests', 'numpy'],
homebrewPackages: ['git', 'node'],
powershellModules: ['PowerShellGet'],
postmanCollections: [],
goModules: ['github.com/gin-gonic/gin', 'github.com/go-chi/chi']
};
const manager = new UsageStatisticsManager(trackingConfig); const manager = new UsageStatisticsManager(defaultConfig);
try { try {
// Check for preview mode // Check for preview mode
@@ -263,4 +259,4 @@ if (import.meta.main) {
main(); main();
} }
export { UsageStatisticsManager, trackingConfig }; export { UsageStatisticsManager };

View File

@@ -7,8 +7,6 @@ export interface BaseDownloadStats {
packageName: string; packageName: string;
version?: string; version?: string;
downloadCount: number; downloadCount: number;
timestamp: Date;
period?: 'daily' | 'weekly' | 'monthly' | 'total';
metadata?: Record<string, any>; metadata?: Record<string, any>;
} }
@@ -24,7 +22,6 @@ export interface DownloadStatsAggregator {
totalDownloads: number; totalDownloads: number;
uniquePackages: number; uniquePackages: number;
platforms: string[]; platforms: string[];
timeRange: { start: Date; end: Date } | null;
}; };
} }