@amiable-dev/docusaurus-plugin-stentorosaur 0.16.0 → 0.16.2

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.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * ADR-001: Client-Safe Data Source Resolver
3
+ *
4
+ * This module provides URL building utilities that can be safely imported
5
+ * in browser/client contexts. It does NOT import any Node.js-only modules
6
+ * like @docusaurus/utils-validation.
7
+ *
8
+ * For server-side validation and resolution, use data-source-resolver.ts.
9
+ *
10
+ * @see docs/adrs/ADR-001-configurable-data-fetching-strategies.md
11
+ */
12
+ import type { DataSource } from './types';
13
+ /**
14
+ * Build the fetch URL for a given DataSource configuration.
15
+ *
16
+ * This is the client-safe version that can be used in browser contexts.
17
+ * It assumes the DataSource has already been validated.
18
+ *
19
+ * @param dataSource - Validated DataSource configuration
20
+ * @returns URL string for fetching, or null for build-only strategy
21
+ */
22
+ export declare function buildFetchUrl(dataSource: DataSource): string | null;
23
+ //# sourceMappingURL=data-source-resolver.client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-source-resolver.client.d.ts","sourceRoot":"","sources":["../src/data-source-resolver.client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAU1C;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAsCnE"}
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ /**
3
+ * ADR-001: Client-Safe Data Source Resolver
4
+ *
5
+ * This module provides URL building utilities that can be safely imported
6
+ * in browser/client contexts. It does NOT import any Node.js-only modules
7
+ * like @docusaurus/utils-validation.
8
+ *
9
+ * For server-side validation and resolution, use data-source-resolver.ts.
10
+ *
11
+ * @see docs/adrs/ADR-001-configurable-data-fetching-strategies.md
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.buildFetchUrl = buildFetchUrl;
15
+ /**
16
+ * Default values for GitHub strategy
17
+ */
18
+ const GITHUB_DEFAULTS = {
19
+ branch: 'status-data',
20
+ path: 'current.json',
21
+ };
22
+ /**
23
+ * Build the fetch URL for a given DataSource configuration.
24
+ *
25
+ * This is the client-safe version that can be used in browser contexts.
26
+ * It assumes the DataSource has already been validated.
27
+ *
28
+ * @param dataSource - Validated DataSource configuration
29
+ * @returns URL string for fetching, or null for build-only strategy
30
+ */
31
+ function buildFetchUrl(dataSource) {
32
+ switch (dataSource.strategy) {
33
+ case 'github': {
34
+ const { owner, repo, branch = GITHUB_DEFAULTS.branch, path = GITHUB_DEFAULTS.path } = dataSource;
35
+ return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`;
36
+ }
37
+ case 'http': {
38
+ let url = dataSource.url;
39
+ // Append cache-busting parameter if enabled
40
+ if (dataSource.cacheBust) {
41
+ const timestamp = Date.now();
42
+ const separator = url.includes('?') ? '&' : '?';
43
+ url = `${url}${separator}t=${timestamp}`;
44
+ }
45
+ return url;
46
+ }
47
+ case 'static': {
48
+ // Use file:// protocol for local files
49
+ const path = dataSource.path;
50
+ if (path.startsWith('/')) {
51
+ return `file://${path}`;
52
+ }
53
+ return `file://${path}`;
54
+ }
55
+ case 'build-only':
56
+ return null;
57
+ default: {
58
+ // Exhaustive check
59
+ const _exhaustive = dataSource;
60
+ throw new Error(`Unknown strategy: ${_exhaustive.strategy}`);
61
+ }
62
+ }
63
+ }
@@ -15,7 +15,7 @@
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.useStatusData = useStatusData;
17
17
  const react_1 = require("react");
18
- const data_source_resolver_1 = require("../data-source-resolver");
18
+ const data_source_resolver_client_1 = require("../data-source-resolver.client");
19
19
  const data_source_validator_1 = require("../data-source-validator");
20
20
  /**
21
21
  * Custom hook for fetching status data based on DataSource configuration.
@@ -45,7 +45,7 @@ function useStatusData(options) {
45
45
  */
46
46
  const fetchData = (0, react_1.useCallback)(async () => {
47
47
  // Build fetch URL based on strategy
48
- let url = (0, data_source_resolver_1.buildFetchUrl)(dataSource);
48
+ let url = (0, data_source_resolver_client_1.buildFetchUrl)(dataSource);
49
49
  // Build-only strategy doesn't fetch
50
50
  if (url === null) {
51
51
  setLoading(false);
@@ -18,7 +18,7 @@ const IncidentHistory_1 = __importDefault(require("../IncidentHistory"));
18
18
  const MaintenanceList_1 = __importDefault(require("../Maintenance/MaintenanceList"));
19
19
  const PerformanceMetrics_1 = __importDefault(require("../PerformanceMetrics"));
20
20
  const version_1 = require("../../version");
21
- const data_source_resolver_1 = require("../../data-source-resolver");
21
+ const data_source_resolver_client_1 = require("../../data-source-resolver.client");
22
22
  const styles_module_css_1 = __importDefault(require("./styles.module.css"));
23
23
  function StatusPage({ statusData }) {
24
24
  const { items = [], incidents = [], maintenance = [], lastUpdated, showServices = true, showIncidents = true, showPerformanceMetrics = true, useDemoData = false, fetchUrl, dataSource, } = statusData || {};
@@ -31,7 +31,7 @@ function StatusPage({ statusData }) {
31
31
  const dataBaseUrl = (0, react_1.useMemo)(() => {
32
32
  if (dataSource) {
33
33
  // Build URL from dataSource, stripping the file part to get base URL
34
- const url = (0, data_source_resolver_1.buildFetchUrl)(dataSource);
34
+ const url = (0, data_source_resolver_client_1.buildFetchUrl)(dataSource);
35
35
  if (url) {
36
36
  // Remove file:// prefix for browser context
37
37
  const cleanUrl = url.startsWith('file://') ? url.replace('file://', '') : url;
@@ -17,7 +17,7 @@ const IncidentHistory_1 = __importDefault(require("../IncidentHistory"));
17
17
  const MaintenanceList_1 = __importDefault(require("../Maintenance/MaintenanceList"));
18
18
  const StatusBoard_1 = __importDefault(require("../StatusBoard"));
19
19
  const PerformanceMetrics_1 = __importDefault(require("../PerformanceMetrics"));
20
- const data_source_resolver_1 = require("../../data-source-resolver");
20
+ const data_source_resolver_client_1 = require("../../data-source-resolver.client");
21
21
  const styles_module_css_1 = __importDefault(require("./styles.module.css"));
22
22
  // Default configuration with all sections enabled
23
23
  const DEFAULT_CONFIG = {
@@ -42,7 +42,7 @@ function UptimeStatusPage({ statusData }) {
42
42
  const dataBaseUrl = (0, react_1.useMemo)(() => {
43
43
  if (dataSource) {
44
44
  // Build URL from dataSource, stripping the file part to get base URL
45
- const url = (0, data_source_resolver_1.buildFetchUrl)(dataSource);
45
+ const url = (0, data_source_resolver_client_1.buildFetchUrl)(dataSource);
46
46
  if (url) {
47
47
  // Remove file:// prefix for browser context
48
48
  const cleanUrl = url.startsWith('file://') ? url.replace('file://', '') : url;
package/lib/version.d.ts CHANGED
@@ -2,5 +2,5 @@
2
2
  * Plugin version - auto-generated during build
3
3
  * DO NOT EDIT MANUALLY - This file is generated from package.json
4
4
  */
5
- export declare const PLUGIN_VERSION = "0.16.0";
5
+ export declare const PLUGIN_VERSION = "0.16.2";
6
6
  //# sourceMappingURL=version.d.ts.map
package/lib/version.js CHANGED
@@ -5,4 +5,4 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.PLUGIN_VERSION = void 0;
8
- exports.PLUGIN_VERSION = '0.16.0';
8
+ exports.PLUGIN_VERSION = '0.16.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amiable-dev/docusaurus-plugin-stentorosaur",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "description": "A Docusaurus plugin for displaying status monitoring dashboard powered by GitHub Issues and Actions, similar to Upptime",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/plugin-status.d.ts",
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Calculate GitHub Actions minutes used per month for amiable-dev/amiable-docusaurus
5
+ *
6
+ * This script fetches workflow run data and calculates billable minutes.
7
+ *
8
+ * Usage:
9
+ * GITHUB_TOKEN=your_token node scripts/calculate-actions-minutes.js
10
+ *
11
+ * Options:
12
+ * --months N Number of months to analyze (default: 3)
13
+ * --verbose Show detailed output
14
+ */
15
+
16
+ const { Octokit } = require('@octokit/rest');
17
+
18
+ const OWNER = 'amiable-dev';
19
+ const REPO = 'amiable-docusaurus';
20
+
21
+ // GitHub Actions billing multipliers (for private repos)
22
+ // Public repos have unlimited free minutes
23
+ const BILLING_MULTIPLIERS = {
24
+ 'ubuntu-latest': 1, // Linux: 1x
25
+ 'ubuntu-20.04': 1,
26
+ 'ubuntu-22.04': 1,
27
+ 'windows-latest': 2, // Windows: 2x
28
+ 'windows-2019': 2,
29
+ 'windows-2022': 2,
30
+ 'macos-latest': 10, // macOS: 10x
31
+ 'macos-11': 10,
32
+ 'macos-12': 10,
33
+ 'macos-13': 10,
34
+ };
35
+
36
+ async function getWorkflowRuns(octokit, startDate, endDate) {
37
+ const runs = [];
38
+ let page = 1;
39
+ const perPage = 100;
40
+
41
+ console.log(`Fetching workflow runs from ${startDate.toISOString()} to ${endDate.toISOString()}...`);
42
+
43
+ while (true) {
44
+ const response = await octokit.actions.listWorkflowRunsForRepo({
45
+ owner: OWNER,
46
+ repo: REPO,
47
+ per_page: perPage,
48
+ page,
49
+ created: `${startDate.toISOString().split('T')[0]}..${endDate.toISOString().split('T')[0]}`,
50
+ });
51
+
52
+ runs.push(...response.data.workflow_runs);
53
+
54
+ if (response.data.workflow_runs.length < perPage) {
55
+ break;
56
+ }
57
+
58
+ page++;
59
+ }
60
+
61
+ return runs;
62
+ }
63
+
64
+ async function getJobsForRun(octokit, runId) {
65
+ const response = await octokit.actions.listJobsForWorkflowRun({
66
+ owner: OWNER,
67
+ repo: REPO,
68
+ run_id: runId,
69
+ });
70
+
71
+ return response.data.jobs;
72
+ }
73
+
74
+ function calculateJobDuration(job) {
75
+ if (!job.started_at || !job.completed_at) {
76
+ return 0;
77
+ }
78
+
79
+ const start = new Date(job.started_at);
80
+ const end = new Date(job.completed_at);
81
+ const durationMs = end - start;
82
+ const durationMinutes = Math.ceil(durationMs / 1000 / 60); // Round up to nearest minute
83
+
84
+ return durationMinutes;
85
+ }
86
+
87
+ function getRunnerMultiplier(runnerName) {
88
+ if (!runnerName) return 1;
89
+
90
+ for (const [key, multiplier] of Object.entries(BILLING_MULTIPLIERS)) {
91
+ if (runnerName.toLowerCase().includes(key.toLowerCase())) {
92
+ return multiplier;
93
+ }
94
+ }
95
+
96
+ return 1; // Default to Linux multiplier
97
+ }
98
+
99
+ async function analyzeMonthlyUsage(octokit, months = 3, verbose = false) {
100
+ const now = new Date();
101
+ const monthlyData = {};
102
+
103
+ // Initialize monthly buckets
104
+ for (let i = 0; i < months; i++) {
105
+ const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
106
+ const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
107
+ monthlyData[monthKey] = {
108
+ runs: 0,
109
+ jobs: 0,
110
+ totalMinutes: 0,
111
+ billableMinutes: 0,
112
+ workflows: {},
113
+ runners: {},
114
+ };
115
+ }
116
+
117
+ // Fetch workflow runs for the entire period
118
+ const startDate = new Date(now.getFullYear(), now.getMonth() - months + 1, 1);
119
+ const endDate = now;
120
+
121
+ const runs = await getWorkflowRuns(octokit, startDate, endDate);
122
+
123
+ console.log(`\nFound ${runs.length} workflow runs in the last ${months} months\n`);
124
+
125
+ // Process each run
126
+ for (const run of runs) {
127
+ const runDate = new Date(run.created_at);
128
+ const monthKey = `${runDate.getFullYear()}-${String(runDate.getMonth() + 1).padStart(2, '0')}`;
129
+
130
+ if (!monthlyData[monthKey]) continue;
131
+
132
+ monthlyData[monthKey].runs++;
133
+
134
+ // Get jobs for this run
135
+ const jobs = await getJobsForRun(octokit, run.id);
136
+
137
+ for (const job of jobs) {
138
+ const duration = calculateJobDuration(job);
139
+ const multiplier = getRunnerMultiplier(job.runner_name);
140
+ const billableMinutes = duration * multiplier;
141
+
142
+ monthlyData[monthKey].jobs++;
143
+ monthlyData[monthKey].totalMinutes += duration;
144
+ monthlyData[monthKey].billableMinutes += billableMinutes;
145
+
146
+ // Track by workflow
147
+ const workflowName = run.name || run.path;
148
+ if (!monthlyData[monthKey].workflows[workflowName]) {
149
+ monthlyData[monthKey].workflows[workflowName] = {
150
+ runs: 0,
151
+ minutes: 0,
152
+ billableMinutes: 0,
153
+ };
154
+ }
155
+ monthlyData[monthKey].workflows[workflowName].runs++;
156
+ monthlyData[monthKey].workflows[workflowName].minutes += duration;
157
+ monthlyData[monthKey].workflows[workflowName].billableMinutes += billableMinutes;
158
+
159
+ // Track by runner
160
+ const runnerType = job.runner_name || 'unknown';
161
+ if (!monthlyData[monthKey].runners[runnerType]) {
162
+ monthlyData[monthKey].runners[runnerType] = {
163
+ jobs: 0,
164
+ minutes: 0,
165
+ billableMinutes: 0,
166
+ };
167
+ }
168
+ monthlyData[monthKey].runners[runnerType].jobs++;
169
+ monthlyData[monthKey].runners[runnerType].minutes += duration;
170
+ monthlyData[monthKey].runners[runnerType].billableMinutes += billableMinutes;
171
+
172
+ if (verbose) {
173
+ console.log(` [${monthKey}] ${workflowName}: ${duration}min (${billableMinutes} billable) on ${runnerType}`);
174
+ }
175
+ }
176
+
177
+ // Add small delay to avoid rate limiting
178
+ await new Promise(resolve => setTimeout(resolve, 100));
179
+ }
180
+
181
+ return monthlyData;
182
+ }
183
+
184
+ function printReport(monthlyData) {
185
+ console.log('\n=== GitHub Actions Usage Report ===\n');
186
+ console.log(`Repository: ${OWNER}/${REPO}\n`);
187
+
188
+ const sortedMonths = Object.keys(monthlyData).sort().reverse();
189
+
190
+ let totalRuns = 0;
191
+ let totalJobs = 0;
192
+ let totalMinutes = 0;
193
+ let totalBillableMinutes = 0;
194
+
195
+ for (const month of sortedMonths) {
196
+ const data = monthlyData[month];
197
+ totalRuns += data.runs;
198
+ totalJobs += data.jobs;
199
+ totalMinutes += data.totalMinutes;
200
+ totalBillableMinutes += data.billableMinutes;
201
+
202
+ console.log(`Month: ${month}`);
203
+ console.log(` Workflow Runs: ${data.runs}`);
204
+ console.log(` Total Jobs: ${data.jobs}`);
205
+ console.log(` Actual Minutes: ${data.totalMinutes.toLocaleString()}`);
206
+ console.log(` Billable Minutes: ${data.billableMinutes.toLocaleString()}`);
207
+
208
+ // Top workflows
209
+ const topWorkflows = Object.entries(data.workflows)
210
+ .sort((a, b) => b[1].billableMinutes - a[1].billableMinutes)
211
+ .slice(0, 5);
212
+
213
+ if (topWorkflows.length > 0) {
214
+ console.log('\n Top Workflows:');
215
+ for (const [name, stats] of topWorkflows) {
216
+ console.log(` - ${name}: ${stats.runs} runs, ${stats.billableMinutes} billable minutes`);
217
+ }
218
+ }
219
+
220
+ // Runner breakdown
221
+ console.log('\n By Runner Type:');
222
+ for (const [runner, stats] of Object.entries(data.runners)) {
223
+ console.log(` - ${runner}: ${stats.jobs} jobs, ${stats.minutes} min (${stats.billableMinutes} billable)`);
224
+ }
225
+
226
+ console.log('\n');
227
+ }
228
+
229
+ console.log('=== Overall Totals ===');
230
+ console.log(`Total Runs: ${totalRuns}`);
231
+ console.log(`Total Jobs: ${totalJobs}`);
232
+ console.log(`Total Actual Minutes: ${totalMinutes.toLocaleString()}`);
233
+ console.log(`Total Billable Minutes: ${totalBillableMinutes.toLocaleString()}`);
234
+
235
+ if (totalRuns > 0) {
236
+ console.log(`\nAverage per Run: ${Math.round(totalMinutes / totalRuns)} minutes (${Math.round(totalBillableMinutes / totalRuns)} billable)`);
237
+ }
238
+ }
239
+
240
+ async function main() {
241
+ const token = process.env.GITHUB_TOKEN;
242
+ if (!token) {
243
+ console.error('Error: GITHUB_TOKEN environment variable is required');
244
+ console.error('Usage: GITHUB_TOKEN=your_token node scripts/calculate-actions-minutes.js');
245
+ process.exit(1);
246
+ }
247
+
248
+ // Parse command line arguments
249
+ const args = process.argv.slice(2);
250
+ let months = 3;
251
+ let verbose = false;
252
+
253
+ for (let i = 0; i < args.length; i++) {
254
+ if (args[i] === '--months' && args[i + 1]) {
255
+ months = parseInt(args[i + 1], 10);
256
+ i++;
257
+ } else if (args[i] === '--verbose') {
258
+ verbose = true;
259
+ }
260
+ }
261
+
262
+ const octokit = new Octokit({ auth: token });
263
+
264
+ try {
265
+ const monthlyData = await analyzeMonthlyUsage(octokit, months, verbose);
266
+ printReport(monthlyData);
267
+ } catch (error) {
268
+ console.error('Error:', error.message);
269
+ if (error.response) {
270
+ console.error('Response:', error.response.data);
271
+ }
272
+ process.exit(1);
273
+ }
274
+ }
275
+
276
+ main();
@@ -1 +1 @@
1
- {"root":["./src/annotation-utils.ts","./src/data-source-resolver.ts","./src/data-source-validator.ts","./src/demo-data.ts","./src/github-service.ts","./src/historical-data.ts","./src/index.ts","./src/label-utils.ts","./src/maintenance-utils.ts","./src/options.ts","./src/plugin-status.d.ts","./src/time-utils.ts","./src/types.ts","./src/version.ts","./src/hooks/index.ts","./src/hooks/useStatusData.ts","./src/notifications/simple-index.ts","./src/notifications/simple-notification-service.ts","./src/notifications/types.ts","./src/notifications/providers/discord-provider.ts","./src/notifications/providers/email-provider.ts","./src/notifications/providers/slack-provider.ts","./src/notifications/providers/telegram-provider.ts","./src/theme/css-modules.d.ts","./src/theme/theme-layout.d.ts","./src/theme/ChartPanel/index.tsx","./src/theme/IncidentHistory/index.tsx","./src/theme/Maintenance/MaintenanceItem/index.tsx","./src/theme/Maintenance/MaintenanceList/index.tsx","./src/theme/PerformanceMetrics/index.tsx","./src/theme/ResponseTimeChart/index.tsx","./src/theme/SLIChart/index.tsx","./src/theme/StatusBoard/index.tsx","./src/theme/StatusHistory/index.tsx","./src/theme/StatusItem/MiniHeatmap.tsx","./src/theme/StatusItem/index.tsx","./src/theme/StatusPage/index.tsx","./src/theme/UptimeChart/index.tsx","./src/theme/UptimeStatusPage/index.tsx","./src/theme/components/ExportButton.tsx","./src/theme/hooks/useChartExport.ts","./src/theme/hooks/useDataExport.ts","./src/utils/csv.ts","./src/utils/markdown.ts"],"version":"5.9.3"}
1
+ {"root":["./src/annotation-utils.ts","./src/data-source-resolver.client.ts","./src/data-source-resolver.ts","./src/data-source-validator.ts","./src/demo-data.ts","./src/github-service.ts","./src/historical-data.ts","./src/index.ts","./src/label-utils.ts","./src/maintenance-utils.ts","./src/options.ts","./src/plugin-status.d.ts","./src/time-utils.ts","./src/types.ts","./src/version.ts","./src/hooks/index.ts","./src/hooks/useStatusData.ts","./src/notifications/simple-index.ts","./src/notifications/simple-notification-service.ts","./src/notifications/types.ts","./src/notifications/providers/discord-provider.ts","./src/notifications/providers/email-provider.ts","./src/notifications/providers/slack-provider.ts","./src/notifications/providers/telegram-provider.ts","./src/theme/css-modules.d.ts","./src/theme/theme-layout.d.ts","./src/theme/ChartPanel/index.tsx","./src/theme/IncidentHistory/index.tsx","./src/theme/Maintenance/MaintenanceItem/index.tsx","./src/theme/Maintenance/MaintenanceList/index.tsx","./src/theme/PerformanceMetrics/index.tsx","./src/theme/ResponseTimeChart/index.tsx","./src/theme/SLIChart/index.tsx","./src/theme/StatusBoard/index.tsx","./src/theme/StatusHistory/index.tsx","./src/theme/StatusItem/MiniHeatmap.tsx","./src/theme/StatusItem/index.tsx","./src/theme/StatusPage/index.tsx","./src/theme/UptimeChart/index.tsx","./src/theme/UptimeStatusPage/index.tsx","./src/theme/components/ExportButton.tsx","./src/theme/hooks/useChartExport.ts","./src/theme/hooks/useDataExport.ts","./src/utils/csv.ts","./src/utils/markdown.ts"],"version":"5.9.3"}