spec-up-t 1.4.1-beta.3 → 1.5.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.
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Health Check Integration Script for spec-up-t
3
+ *
4
+ * This script integrates the spec-up-t-healthcheck tool to validate consuming projects.
5
+ * It runs FROM spec-up-t (menu option 7) but checks the CONSUMING project (current directory).
6
+ *
7
+ * Usage (from consuming project):
8
+ * npm run healthCheck
9
+ *
10
+ * Or via spec-up-t menu:
11
+ * npm run menu -> [7] Run health check
12
+ *
13
+ * Options:
14
+ * --format text|json|html Output format (default: html)
15
+ * --output <file> Output file path
16
+ * --checks <checks> Comma-separated list of specific checks
17
+ *
18
+ * Examples:
19
+ * node run-healthcheck.js
20
+ * node run-healthcheck.js --format json
21
+ * node run-healthcheck.js --format html --output health-report.html
22
+ */
23
+
24
+ const fs = require('fs').promises;
25
+ const path = require('path');
26
+
27
+ /**
28
+ * Parse command line arguments
29
+ * Handles both direct node invocations and npm script invocations
30
+ *
31
+ * @returns {Object} Parsed arguments
32
+ */
33
+ function parseArgs() {
34
+ const args = process.argv.slice(2);
35
+ const options = {
36
+ format: 'html', // Changed default to HTML for better user experience
37
+ output: null,
38
+ checks: null
39
+ };
40
+
41
+ for (let i = 0; i < args.length; i++) {
42
+ if (args[i] === '--format' && args[i + 1]) {
43
+ options.format = args[i + 1];
44
+ i++;
45
+ } else if (args[i] === '--output' && args[i + 1]) {
46
+ options.output = args[i + 1];
47
+ i++;
48
+ } else if (args[i] === '--checks' && args[i + 1]) {
49
+ options.checks = args[i + 1].split(',');
50
+ i++;
51
+ }
52
+ }
53
+
54
+ return options;
55
+ }
56
+
57
+ /**
58
+ * Main execution function
59
+ *
60
+ * Runs health checks on the CONSUMING PROJECT (current working directory).
61
+ * This script is part of spec-up-t but validates the project that uses spec-up-t.
62
+ */
63
+ async function main() {
64
+ try {
65
+ console.log('šŸ„ Spec-Up-T Health Check\n');
66
+ console.log('šŸ“ Checking consuming project in:', process.cwd());
67
+ console.log('');
68
+
69
+ // Parse command line arguments
70
+ const options = parseArgs();
71
+
72
+ // Import the health check tool
73
+ const {
74
+ createProvider,
75
+ runHealthChecks,
76
+ formatResultsAsText,
77
+ formatResultsAsJson,
78
+ formatResultsAsHtml
79
+ } = await import('spec-up-t-healthcheck');
80
+
81
+ // Create a provider for the CURRENT WORKING DIRECTORY (the consuming project)
82
+ const provider = createProvider(process.cwd());
83
+
84
+ // Run health checks on the consuming project
85
+ console.log('šŸ” Running health checks on consuming project...');
86
+ const healthCheckOptions = {};
87
+ if (options.checks) {
88
+ healthCheckOptions.checks = options.checks;
89
+ }
90
+
91
+ const results = await runHealthChecks(provider, healthCheckOptions);
92
+
93
+ // Display summary
94
+ console.log('\nšŸ“Š Health Check Summary:');
95
+ console.log(` Total Checks: ${results.summary.total}`);
96
+ console.log(` āœ… Passed: ${results.summary.passed}`);
97
+ console.log(` āŒ Failed: ${results.summary.failed}`);
98
+ console.log(` āš ļø Warnings: ${results.summary.warnings}`);
99
+ console.log(` ā­ļø Skipped: ${results.summary.skipped}`);
100
+ console.log(` šŸ“ˆ Health Score: ${results.summary.score}%\n`);
101
+
102
+ // Format results based on requested format
103
+ let output;
104
+ let defaultFilename;
105
+
106
+ switch (options.format) {
107
+ case 'json':
108
+ output = formatResultsAsJson(results);
109
+ defaultFilename = 'health-report.json';
110
+ break;
111
+
112
+ case 'html':
113
+ output = formatResultsAsHtml(results, {
114
+ title: 'Spec-Up-T Project Health Check',
115
+ repositoryUrl: results.provider.repoPath ?
116
+ `file://${results.provider.repoPath}` : undefined
117
+ });
118
+ defaultFilename = 'health-report.html';
119
+ break;
120
+
121
+ case 'text':
122
+ default:
123
+ output = formatResultsAsText(results);
124
+ defaultFilename = 'health-report.txt';
125
+ break;
126
+ }
127
+
128
+ // Determine output path
129
+ const outputPath = options.output || defaultFilename;
130
+
131
+ // Save to file
132
+ await fs.writeFile(outputPath, output);
133
+ console.log(`šŸ“„ Report saved to: ${outputPath}`);
134
+
135
+ // For HTML format, try to open in browser
136
+ if (options.format === 'html' && !options.output) {
137
+ try {
138
+ const { openHtmlFile } = require('./utils/file-opener');
139
+ await openHtmlFile(path.resolve(outputPath));
140
+ console.log('🌐 Opening report in browser...');
141
+ } catch (error) {
142
+ console.log('ā„¹ļø Tip: Open the report manually in your browser');
143
+ }
144
+ }
145
+
146
+ // For text format, also print to console
147
+ if (options.format === 'text') {
148
+ console.log('\n' + output);
149
+ }
150
+
151
+ // Exit with appropriate code
152
+ if (results.summary.hasErrors) {
153
+ console.error('\nāŒ Health check completed with errors');
154
+ console.error(' Please review the report for details');
155
+ process.exit(1);
156
+ } else if (results.summary.hasWarnings) {
157
+ console.warn('\nāš ļø Health check completed with warnings');
158
+ process.exit(0);
159
+ } else {
160
+ console.log('\nāœ… Health check completed successfully');
161
+ console.log(' All checks passed!');
162
+ process.exit(0);
163
+ }
164
+
165
+ } catch (error) {
166
+ console.error('\nāŒ Health check failed:', error.message);
167
+ console.error('\nDetails:', error.stack);
168
+ console.error('\nTroubleshooting:');
169
+ console.error(' - Ensure you are running this from a consuming project directory');
170
+ console.error(' - Check that spec-up-t-healthcheck is properly installed');
171
+ console.error(' - Verify the consuming project has a valid package.json');
172
+ process.exit(1);
173
+ }
174
+ }
175
+
176
+ // Run the script
177
+ main();
@@ -1,8 +1,12 @@
1
1
  const chalk = require('chalk');
2
+ const messageCollector = require('./message-collector');
2
3
 
3
4
  /**
4
5
  * Logger utility with color-coded console output
5
6
  * Provides consistent logging across the spec-up-t application
7
+ *
8
+ * All messages are automatically collected when message collection is active,
9
+ * allowing healthchecks and other tools to consume the output in JSON format.
6
10
  */
7
11
  class Logger {
8
12
  /**
@@ -10,6 +14,7 @@ class Logger {
10
14
  */
11
15
  static success(message, ...args) {
12
16
  console.log(chalk.green('āœ…'), chalk.green(message), ...args);
17
+ messageCollector.addMessage('success', message, args);
13
18
  }
14
19
 
15
20
  /**
@@ -17,6 +22,7 @@ class Logger {
17
22
  */
18
23
  static error(message, ...args) {
19
24
  console.log(chalk.red('āŒ'), chalk.red(message), ...args);
25
+ messageCollector.addMessage('error', message, args);
20
26
  }
21
27
 
22
28
  /**
@@ -24,6 +30,7 @@ class Logger {
24
30
  */
25
31
  static warn(message, ...args) {
26
32
  console.log(chalk.yellow('🟔'), chalk.yellow(message), ...args);
33
+ messageCollector.addMessage('warn', message, args);
27
34
  }
28
35
 
29
36
  /**
@@ -31,6 +38,7 @@ class Logger {
31
38
  */
32
39
  static info(message, ...args) {
33
40
  console.log(chalk.blue('šŸ“’'), chalk.blue(message), ...args);
41
+ messageCollector.addMessage('info', message, args);
34
42
  }
35
43
 
36
44
  /**
@@ -38,6 +46,7 @@ class Logger {
38
46
  */
39
47
  static process(message, ...args) {
40
48
  console.log(chalk.cyan('šŸ”„'), chalk.cyan(message), ...args);
49
+ messageCollector.addMessage('process', message, args);
41
50
  }
42
51
 
43
52
  /**
@@ -45,6 +54,7 @@ class Logger {
45
54
  */
46
55
  static debug(message, ...args) {
47
56
  console.log(chalk.gray('šŸ”'), chalk.gray(message), ...args);
57
+ messageCollector.addMessage('debug', message, args);
48
58
  }
49
59
 
50
60
  /**
@@ -52,6 +62,7 @@ class Logger {
52
62
  */
53
63
  static highlight(message, ...args) {
54
64
  console.log(chalk.blue('✨'), chalk.blue(message), ...args);
65
+ messageCollector.addMessage('highlight', message, args);
55
66
  }
56
67
 
57
68
  /**
@@ -59,6 +70,7 @@ class Logger {
59
70
  */
60
71
  static separator() {
61
72
  console.log(chalk.gray('═'.repeat(60)));
73
+ messageCollector.addMessage('separator', '═'.repeat(60), []);
62
74
  }
63
75
 
64
76
  /**
@@ -67,7 +79,9 @@ class Logger {
67
79
  static progress(current, total, message) {
68
80
  const percentage = Math.round((current / total) * 100);
69
81
  const bar = 'ā–ˆ'.repeat(Math.floor(percentage / 5)) + 'ā–‘'.repeat(20 - Math.floor(percentage / 5));
70
- console.log(chalk.cyan(`šŸ“Š [${bar}] ${percentage}% ${message}`));
82
+ const progressMessage = `[${bar}] ${percentage}% ${message}`;
83
+ console.log(chalk.cyan(`šŸ“Š ${progressMessage}`));
84
+ messageCollector.addMessage('progress', progressMessage, [current, total]);
71
85
  }
72
86
  }
73
87
 
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @file Message collector for capturing console output from menu operations
3
+ *
4
+ * This module wraps the Logger to intercept and store all console messages
5
+ * produced during menu [1] (render) and menu [4] (collect external references)
6
+ * operations, storing them in JSON format for consumption by healthchecks.
7
+ *
8
+ * The captured messages are stored in `.cache/console-messages.json` with
9
+ * structured metadata including:
10
+ * - timestamp: ISO timestamp of when the message was logged
11
+ * - type: success, error, warn, info, process, highlight, debug, separator
12
+ * - message: the actual message text
13
+ * - operation: which menu operation triggered this (render or collectExternalReferences)
14
+ */
15
+
16
+ const fs = require('fs-extra');
17
+ const path = require('path');
18
+
19
+ /**
20
+ * Message collection state
21
+ */
22
+ const messageStore = {
23
+ messages: [],
24
+ currentOperation: null,
25
+ isCollecting: false
26
+ };
27
+
28
+ /**
29
+ * Start collecting messages for a specific operation
30
+ * @param {string} operation - Operation name ('render' or 'collectExternalReferences')
31
+ */
32
+ function startCollecting(operation) {
33
+ messageStore.isCollecting = true;
34
+ messageStore.currentOperation = operation;
35
+ }
36
+
37
+ /**
38
+ * Stop collecting messages
39
+ */
40
+ function stopCollecting() {
41
+ messageStore.isCollecting = false;
42
+ messageStore.currentOperation = null;
43
+ }
44
+
45
+ /**
46
+ * Add a message to the collection
47
+ * @param {string} type - Message type (success, error, warn, etc.)
48
+ * @param {string} message - Message text
49
+ * @param {Array} args - Additional arguments passed to logger
50
+ */
51
+ function addMessage(type, message, args = []) {
52
+ if (!messageStore.isCollecting) {
53
+ return;
54
+ }
55
+
56
+ const messageEntry = {
57
+ timestamp: new Date().toISOString(),
58
+ type,
59
+ message: String(message),
60
+ operation: messageStore.currentOperation,
61
+ additionalData: args.length > 0 ? args.map(arg => String(arg)) : undefined
62
+ };
63
+
64
+ messageStore.messages.push(messageEntry);
65
+ }
66
+
67
+ /**
68
+ * Save collected messages to JSON file
69
+ * Each run creates a fresh file, replacing any existing messages.
70
+ *
71
+ * @param {string} [outputPath] - Optional custom output path
72
+ * @returns {Promise<string>} Path to the saved file
73
+ */
74
+ async function saveMessages(outputPath) {
75
+ const cacheDir = path.join(process.cwd(), '.cache');
76
+ const defaultPath = path.join(cacheDir, 'console-messages.json');
77
+ const filePath = outputPath || defaultPath;
78
+
79
+ await fs.ensureDir(path.dirname(filePath));
80
+
81
+ const output = {
82
+ metadata: {
83
+ generatedAt: new Date().toISOString(),
84
+ totalMessages: messageStore.messages.length,
85
+ operations: [...new Set(messageStore.messages.map(m => m.operation))],
86
+ messagesByType: messageStore.messages.reduce((acc, msg) => {
87
+ acc[msg.type] = (acc[msg.type] || 0) + 1;
88
+ return acc;
89
+ }, {})
90
+ },
91
+ messages: messageStore.messages
92
+ };
93
+
94
+ await fs.writeJson(filePath, output, { spaces: 2 });
95
+
96
+ return filePath;
97
+ }
98
+
99
+ /**
100
+ * Clear all collected messages
101
+ */
102
+ function clearMessages() {
103
+ messageStore.messages = [];
104
+ }
105
+
106
+ /**
107
+ * Get current messages (without saving)
108
+ * @returns {Array} Array of message objects
109
+ */
110
+ function getMessages() {
111
+ return [...messageStore.messages];
112
+ }
113
+
114
+ /**
115
+ * Get statistics about collected messages
116
+ * @returns {Object} Statistics object
117
+ */
118
+ function getStatistics() {
119
+ return {
120
+ total: messageStore.messages.length,
121
+ byType: messageStore.messages.reduce((acc, msg) => {
122
+ acc[msg.type] = (acc[msg.type] || 0) + 1;
123
+ return acc;
124
+ }, {}),
125
+ byOperation: messageStore.messages.reduce((acc, msg) => {
126
+ if (msg.operation) {
127
+ acc[msg.operation] = (acc[msg.operation] || 0) + 1;
128
+ }
129
+ return acc;
130
+ }, {}),
131
+ isCollecting: messageStore.isCollecting,
132
+ currentOperation: messageStore.currentOperation
133
+ };
134
+ }
135
+
136
+ module.exports = {
137
+ startCollecting,
138
+ stopCollecting,
139
+ addMessage,
140
+ saveMessages,
141
+ clearMessages,
142
+ getMessages,
143
+ getStatistics
144
+ };
@@ -8,7 +8,7 @@
8
8
  <meta name="author" content="${author}">
9
9
  <meta property="spec-up-t:github-repo-info" content="${githubRepoInfo}">
10
10
  <link rel="icon" href="${specFavicon}" type="image/x-icon">
11
- <meta name="generator" content="Spec-Up-T" />
11
+ <meta name="generator" content="Spec-Up-T">
12
12
  <title>${title}</title>
13
13
 
14
14
  ${assetsHead}
@@ -19,7 +19,7 @@
19
19
  <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.min.css" rel="stylesheet">
20
20
  </head>
21
21
 
22
- <body features="${features}">
22
+ <body>
23
23
  <!-- Skip to content link for accessibility -->
24
24
  <a href="#content" class="screen-reader-text">Skip to content</a>
25
25
 
@@ -128,7 +128,7 @@
128
128
  </button>
129
129
 
130
130
  <a id="logo" href="${specLogoLink}">
131
- <img class="d-print-none m-1" src="${specLogo}" alt="" />
131
+ <img class="d-print-none m-1" src="${specLogo}" alt="">
132
132
  </a>
133
133
 
134
134
  <!-- Spacer to push the following elements to the right -->
@@ -138,7 +138,7 @@
138
138
  <div class="d-flex align-items-center service-menu d-print-none">
139
139
 
140
140
  <!-- Settings side menu -->
141
- <button id="repo_settings" animate class="btn btn-sm btn-outline-secondary ms-2" type="button"
141
+ <button id="repo_settings" class="btn btn-sm btn-outline-secondary ms-2" type="button"
142
142
  data-bs-toggle="offcanvas" data-bs-target="#offcanvasSettings" aria-controls="offcanvasSettings">
143
143
  <svg class="bi" width="1em" height="1em" aria-hidden="true">
144
144
  <use href="#gear-wide-connected"></use>
@@ -249,7 +249,7 @@
249
249
 
250
250
  <div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasSettings" aria-labelledby="offcanvasSettingsLabel">
251
251
  <div class="offcanvas-header">
252
- <h5 class="offcanvas-title mt-0" id="offcanvasSettings">Settings</h5>
252
+ <h5 class="offcanvas-title mt-0" id="offcanvasSettingsLabel">Settings</h5>
253
253
  <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
254
254
  </div>
255
255
  <div class="offcanvas-body p-0">
@@ -259,7 +259,7 @@
259
259
  aria-label="Enter GitHub Token">
260
260
  Enter GitHub Token
261
261
  </button>
262
- <button id="repo_issues" issue-count animate class="btn btn-menu-item m-0 mb-1 border-start-0 border-end-0"
262
+ <button id="repo_issues" data-issue-count data-animate class="btn btn-menu-item m-0 mb-1 border-start-0 border-end-0"
263
263
  type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasIssues"
264
264
  aria-controls="offcanvasIssues">
265
265
  Issues