spec-up-t 1.6.3-beta.1 → 1.6.4
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/.github/copilot-instructions.md +2 -0
- package/assets/compiled/body.js +9 -7
- package/assets/compiled/head.css +6 -6
- package/assets/css/adjust-font-size.css +0 -4
- package/assets/css/download-pdf-docx.css +19 -20
- package/assets/css/header-navbar.css +0 -4
- package/assets/css/index.css +36 -0
- package/assets/css/terms-and-definitions.css +0 -1
- package/assets/css/validate-external-refs.css +198 -3
- package/assets/js/add-href-to-snapshot-link.js +11 -5
- package/assets/js/download-pdf-docx.js +20 -9
- package/assets/js/edit-term-buttons.js +71 -68
- package/assets/js/github-issues.js +27 -27
- package/assets/js/insert-irefs.js +1 -1
- package/assets/js/validate-external-refs.js +356 -7
- package/package.json +3 -3
- package/src/add-remove-xref-source.js +0 -5
- package/src/collect-external-references.test.js +98 -0
- package/src/install-from-boilerplate/boilerplate/gitignore +2 -2
- package/src/install-from-boilerplate/boilerplate/menu-wrapper.sh +19 -0
- package/src/install-from-boilerplate/boilerplate/specs.json +3 -6
- package/src/install-from-boilerplate/config-scripts-keys.js +1 -1
- package/src/install-from-boilerplate/config-system-files.js +1 -0
- package/src/install-from-boilerplate/custom-update.js +6 -3
- package/src/install-from-boilerplate/update-dependencies.js +105 -0
- package/src/pipeline/references/collect-external-references.js +12 -0
- package/src/pipeline/references/external-references-service.js +1 -1
- package/src/pipeline/rendering/render-spec-document.js +5 -1
- package/templates/template.html +43 -10
- package/src/health-check/destination-gitignore-checker.js +0 -414
- package/src/health-check/external-specs-checker.js +0 -287
- package/src/health-check/specs-configuration-checker.js +0 -387
- package/src/health-check/term-references-checker.js +0 -270
- package/src/health-check/terms-intro-checker.js +0 -82
- package/src/health-check/tref-term-checker.js +0 -549
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const https = require('https');
|
|
4
|
-
const url = require('url');
|
|
5
|
-
const Logger = require('../utils/logger');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Check if a given URL has correct GitHub Pages structure
|
|
9
|
-
* @param {string} urlStr - URL to check
|
|
10
|
-
* @returns {boolean} - Whether the URL has correct GitHub Pages structure
|
|
11
|
-
*/
|
|
12
|
-
function isValidGitHubPagesUrl(urlStr) {
|
|
13
|
-
try {
|
|
14
|
-
const parsedUrl = new URL(urlStr);
|
|
15
|
-
// GitHub Pages URLs are either username.github.io or github.io/repo-name
|
|
16
|
-
return (
|
|
17
|
-
parsedUrl.hostname === 'github.io' ||
|
|
18
|
-
parsedUrl.hostname.endsWith('.github.io')
|
|
19
|
-
);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
Logger.error(`Error validating GitHub Pages URL: ${error.message}`);
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Check if a given URL has correct GitHub repo structure
|
|
28
|
-
* @param {string} urlStr - URL to check
|
|
29
|
-
* @returns {boolean} - Whether the URL has correct GitHub repo structure
|
|
30
|
-
*/
|
|
31
|
-
function isValidGitHubRepoUrl(urlStr) {
|
|
32
|
-
try {
|
|
33
|
-
const parsedUrl = new URL(urlStr);
|
|
34
|
-
return (
|
|
35
|
-
parsedUrl.hostname === 'github.com' &&
|
|
36
|
-
parsedUrl.pathname.split('/').filter(Boolean).length >= 2
|
|
37
|
-
);
|
|
38
|
-
} catch (error) {
|
|
39
|
-
Logger.error(`Error validating GitHub repo URL: ${error.message}`);
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Check if URL exists (returns a valid response)
|
|
46
|
-
* @param {string} urlStr - URL to check
|
|
47
|
-
* @returns {Promise<boolean>} - Whether the URL exists
|
|
48
|
-
*/
|
|
49
|
-
function urlExists(urlStr) {
|
|
50
|
-
return new Promise((resolve) => {
|
|
51
|
-
try {
|
|
52
|
-
const parsedUrl = new URL(urlStr);
|
|
53
|
-
const options = {
|
|
54
|
-
method: 'HEAD',
|
|
55
|
-
host: parsedUrl.hostname,
|
|
56
|
-
path: parsedUrl.pathname + parsedUrl.search,
|
|
57
|
-
timeout: 5000
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const req = https.request(options, (res) => {
|
|
61
|
-
resolve(res.statusCode >= 200 && res.statusCode < 400);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
req.on('error', () => {
|
|
65
|
-
resolve(false);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
req.on('timeout', () => {
|
|
69
|
-
req.destroy();
|
|
70
|
-
resolve(false);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
req.end();
|
|
74
|
-
} catch (error) {
|
|
75
|
-
Logger.error(`URL Format Error: Invalid URL format for ${urlStr} - ${error.message}`);
|
|
76
|
-
resolve(false);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Check if specs.json file exists and read it
|
|
83
|
-
* @param {string} projectRoot - Root directory of the project
|
|
84
|
-
* @returns {Object} - Object containing results and specs data if found
|
|
85
|
-
*/
|
|
86
|
-
function checkAndReadSpecsFile(projectRoot) {
|
|
87
|
-
const specsPath = path.join(projectRoot, 'specs.json');
|
|
88
|
-
|
|
89
|
-
if (!fs.existsSync(specsPath)) {
|
|
90
|
-
return {
|
|
91
|
-
results: [{
|
|
92
|
-
name: 'Find specs.json file',
|
|
93
|
-
success: false,
|
|
94
|
-
details: 'specs.json file not found in project root'
|
|
95
|
-
}],
|
|
96
|
-
specs: null
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const specsContent = fs.readFileSync(specsPath, 'utf8');
|
|
102
|
-
const specs = JSON.parse(specsContent);
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
results: [{
|
|
106
|
-
name: 'Find specs.json file',
|
|
107
|
-
success: true,
|
|
108
|
-
details: 'specs.json file found'
|
|
109
|
-
}],
|
|
110
|
-
specs
|
|
111
|
-
};
|
|
112
|
-
} catch (error) {
|
|
113
|
-
return {
|
|
114
|
-
results: [{
|
|
115
|
-
name: 'Parse specs.json file',
|
|
116
|
-
success: false,
|
|
117
|
-
details: `❌ Error parsing specs.json: ${error.message}`
|
|
118
|
-
}],
|
|
119
|
-
specs: null
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Extract external specs from specs data
|
|
126
|
-
* @param {Object} specs - Specs data
|
|
127
|
-
* @returns {Object} - Object containing results and external specs if found
|
|
128
|
-
*/
|
|
129
|
-
function extractExternalSpecs(specs) {
|
|
130
|
-
if (!specs.specs || !Array.isArray(specs.specs) || !specs.specs.some(spec => spec.external_specs)) {
|
|
131
|
-
return {
|
|
132
|
-
results: [{
|
|
133
|
-
name: 'Find external_specs in specs.json',
|
|
134
|
-
success: false,
|
|
135
|
-
details: 'external_specs key not found in specs.json'
|
|
136
|
-
}],
|
|
137
|
-
externalSpecs: []
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const externalSpecs = [];
|
|
142
|
-
|
|
143
|
-
specs.specs.forEach(spec => {
|
|
144
|
-
if (spec.external_specs && Array.isArray(spec.external_specs)) {
|
|
145
|
-
externalSpecs.push(...spec.external_specs);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
results: [{
|
|
151
|
-
name: 'Find external_specs in specs.json',
|
|
152
|
-
success: true,
|
|
153
|
-
details: 'external_specs key found in specs.json'
|
|
154
|
-
}],
|
|
155
|
-
externalSpecs
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Check GitHub Pages URL for an external spec
|
|
161
|
-
* @param {Object} spec - External spec object
|
|
162
|
-
* @returns {Promise<Array>} - Array of check results
|
|
163
|
-
*/
|
|
164
|
-
async function checkGitHubPagesUrl(spec) {
|
|
165
|
-
const results = [];
|
|
166
|
-
|
|
167
|
-
if (!spec.gh_page) {
|
|
168
|
-
results.push({
|
|
169
|
-
name: `Check "${spec.external_spec}" gh_page URL`,
|
|
170
|
-
success: false,
|
|
171
|
-
details: 'gh_page URL is missing'
|
|
172
|
-
});
|
|
173
|
-
return results;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const isValidGhPage = isValidGitHubPagesUrl(spec.gh_page);
|
|
177
|
-
results.push({
|
|
178
|
-
name: `Check "${spec.external_spec}" gh_page URL structure`,
|
|
179
|
-
success: isValidGhPage,
|
|
180
|
-
details: isValidGhPage
|
|
181
|
-
? 'Valid GitHub Pages URL structure'
|
|
182
|
-
: `Invalid GitHub Pages URL structure: ${spec.gh_page}`
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
if (isValidGhPage) {
|
|
186
|
-
const ghPageExists = await urlExists(spec.gh_page);
|
|
187
|
-
results.push({
|
|
188
|
-
name: `Check "${spec.external_spec}" gh_page URL exists`,
|
|
189
|
-
success: ghPageExists,
|
|
190
|
-
details: ghPageExists
|
|
191
|
-
? 'GitHub Pages URL is accessible'
|
|
192
|
-
: `GitHub Pages URL is not accessible: ${spec.gh_page}`
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return results;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Check repository URL for an external spec
|
|
201
|
-
* @param {Object} spec - External spec object
|
|
202
|
-
* @returns {Promise<Array>} - Array of check results
|
|
203
|
-
*/
|
|
204
|
-
async function checkRepositoryUrl(spec) {
|
|
205
|
-
const results = [];
|
|
206
|
-
|
|
207
|
-
if (!spec.url) {
|
|
208
|
-
results.push({
|
|
209
|
-
name: `Check "${spec.external_spec}" repo URL`,
|
|
210
|
-
success: false,
|
|
211
|
-
details: 'Repository URL is missing'
|
|
212
|
-
});
|
|
213
|
-
return results;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const isValidRepoUrl = isValidGitHubRepoUrl(spec.url);
|
|
217
|
-
results.push({
|
|
218
|
-
name: `Check "${spec.external_spec}" repo URL structure`,
|
|
219
|
-
success: isValidRepoUrl,
|
|
220
|
-
details: isValidRepoUrl
|
|
221
|
-
? 'Valid GitHub repository URL structure'
|
|
222
|
-
: `Invalid GitHub repository URL structure: ${spec.url}`
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
if (isValidRepoUrl) {
|
|
226
|
-
const repoUrlExists = await urlExists(spec.url);
|
|
227
|
-
results.push({
|
|
228
|
-
name: `Check "${spec.external_spec}" repo URL exists`,
|
|
229
|
-
success: repoUrlExists,
|
|
230
|
-
details: repoUrlExists
|
|
231
|
-
? 'GitHub repository URL is accessible'
|
|
232
|
-
: `GitHub repository URL is not accessible: ${spec.url}`
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return results;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Check external specs in a specs.json file
|
|
241
|
-
* @param {string} projectRoot - Root directory of the project
|
|
242
|
-
* @returns {Promise<Array>} - Array of check results
|
|
243
|
-
*/
|
|
244
|
-
async function checkExternalSpecs(projectRoot) {
|
|
245
|
-
try {
|
|
246
|
-
// Check for and read specs.json file
|
|
247
|
-
const { results, specs } = checkAndReadSpecsFile(projectRoot);
|
|
248
|
-
|
|
249
|
-
if (!specs) {
|
|
250
|
-
return results;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Extract external specs from specs data
|
|
254
|
-
const { results: extractResults, externalSpecs } = extractExternalSpecs(specs);
|
|
255
|
-
|
|
256
|
-
// Combine results
|
|
257
|
-
const allResults = [...results, ...extractResults];
|
|
258
|
-
|
|
259
|
-
if (externalSpecs.length === 0) {
|
|
260
|
-
return allResults;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Check each external spec
|
|
264
|
-
for (const spec of externalSpecs) {
|
|
265
|
-
// Check GitHub Pages URL
|
|
266
|
-
const ghPageResults = await checkGitHubPagesUrl(spec);
|
|
267
|
-
allResults.push(...ghPageResults);
|
|
268
|
-
|
|
269
|
-
// Check repository URL
|
|
270
|
-
const repoUrlResults = await checkRepositoryUrl(spec);
|
|
271
|
-
allResults.push(...repoUrlResults);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return allResults;
|
|
275
|
-
} catch (error) {
|
|
276
|
-
Logger.error('Error checking external specs:', error);
|
|
277
|
-
return [{
|
|
278
|
-
name: 'External specs check',
|
|
279
|
-
success: false,
|
|
280
|
-
details: `Error: ${error.message}`
|
|
281
|
-
}];
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
module.exports = {
|
|
286
|
-
checkExternalSpecs
|
|
287
|
-
};
|
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Spec-Up-T specs.json Configuration Validator
|
|
3
|
-
*
|
|
4
|
-
* Validates specs.json configuration files by comparing project configurations
|
|
5
|
-
* against default templates to ensure proper setup and catch common issues.
|
|
6
|
-
*
|
|
7
|
-
* **Validation Flow:**
|
|
8
|
-
* 1. File existence check (project specs.json + default template)
|
|
9
|
-
* 2. Field categorization (required vs optional fields)
|
|
10
|
-
* 3. Required field validation (presence + configuration status)
|
|
11
|
-
* 4. Optional field validation (configuration warnings)
|
|
12
|
-
* 5. Unexpected field detection (typo prevention)
|
|
13
|
-
* 6. Summary report generation
|
|
14
|
-
*
|
|
15
|
-
* **Field Categories:**
|
|
16
|
-
* - **Required fields**: Must be present (e.g., title, author, source)
|
|
17
|
-
* - **Optional fields**: Can be omitted (e.g., logo, external_specs)
|
|
18
|
-
* - **Must-change fields**: Cannot use default values (title, author, etc.)
|
|
19
|
-
* - **Allow-default fields**: Can keep default values (spec_directory, etc.)
|
|
20
|
-
* - **Deprecated fields**: Legacy fields ignored during validation
|
|
21
|
-
*
|
|
22
|
-
* **Output:**
|
|
23
|
-
* Returns structured validation results with pass/fail/warning status,
|
|
24
|
-
* detailed messages, and actionable feedback for configuration improvements.
|
|
25
|
-
*
|
|
26
|
-
* @author Spec-Up-T Team
|
|
27
|
-
* @since 2025-06-06
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
const fs = require('fs');
|
|
31
|
-
const path = require('path');
|
|
32
|
-
const Logger = require('../utils/logger');
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Field descriptions for specs.json keys
|
|
36
|
-
*/
|
|
37
|
-
const fieldDescriptions = {
|
|
38
|
-
'title': 'Specification title',
|
|
39
|
-
'description': 'Specification description',
|
|
40
|
-
'author': 'Specification author',
|
|
41
|
-
'source': 'Source repository information',
|
|
42
|
-
'spec_directory': 'Directory containing specification content',
|
|
43
|
-
'spec_terms_directory': 'Directory containing term definitions',
|
|
44
|
-
'output_path': 'Output directory for generated files',
|
|
45
|
-
'markdown_paths': 'List of markdown files to include in the specification',
|
|
46
|
-
'logo': 'Logo URL',
|
|
47
|
-
'logo_link': 'Link to the logo',
|
|
48
|
-
'favicon': 'Favicon URL',
|
|
49
|
-
'external_specs': 'External specifications',
|
|
50
|
-
'katex': 'KaTeX math rendering configuration'
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Fields that can remain at their default values without being flagged
|
|
55
|
-
*/
|
|
56
|
-
const allowDefaultValueFields = [
|
|
57
|
-
'spec_directory',
|
|
58
|
-
'spec_terms_directory',
|
|
59
|
-
'output_path',
|
|
60
|
-
'katex',
|
|
61
|
-
'logo',
|
|
62
|
-
'logo_link',
|
|
63
|
-
'favicon'
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Fields that should fail if they're not modified from default values
|
|
68
|
-
*/
|
|
69
|
-
const mustChangeFields = [
|
|
70
|
-
'title',
|
|
71
|
-
'description',
|
|
72
|
-
'author',
|
|
73
|
-
'source'
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Known optional fields
|
|
78
|
-
*/
|
|
79
|
-
const knownOptionalFields = [
|
|
80
|
-
'logo',
|
|
81
|
-
'external_specs',
|
|
82
|
-
'logo_link',
|
|
83
|
-
'favicon',
|
|
84
|
-
'katex',
|
|
85
|
-
'spec_directory',
|
|
86
|
-
'spec_terms_directory',
|
|
87
|
-
'output_path',
|
|
88
|
-
'markdown_paths'
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Deprecated fields that should not be flagged as unexpected
|
|
93
|
-
*/
|
|
94
|
-
const deprecatedFields = [];
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Check if the files needed for configuration check exist
|
|
98
|
-
* @param {string} projectSpecsPath - Path to project specs.json
|
|
99
|
-
* @param {string} defaultSpecsPath - Path to default specs.json
|
|
100
|
-
* @returns {Array|null} - Check results or null if files exist
|
|
101
|
-
*/
|
|
102
|
-
function checkFilesExist(projectSpecsPath, defaultSpecsPath) {
|
|
103
|
-
if (!fs.existsSync(projectSpecsPath)) {
|
|
104
|
-
return [{
|
|
105
|
-
name: 'Find specs.json file',
|
|
106
|
-
success: false,
|
|
107
|
-
details: 'specs.json file not found in project root'
|
|
108
|
-
}];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (!fs.existsSync(defaultSpecsPath)) {
|
|
112
|
-
return [{
|
|
113
|
-
name: 'Find default specs.json template',
|
|
114
|
-
success: false,
|
|
115
|
-
details: 'Default specs.json template not found'
|
|
116
|
-
}];
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Get all valid field names (required + optional + deprecated). Creates a comprehensive list of all field names that should not be flagged as "unexpected"
|
|
124
|
-
* @param {Array} defaultSpecKeys - Keys from default specs
|
|
125
|
-
* @returns {Array} - All valid field names
|
|
126
|
-
*/
|
|
127
|
-
function getAllValidFields(defaultSpecKeys) {
|
|
128
|
-
// Get all field names from descriptions (these are the canonical field names)
|
|
129
|
-
const canonicalFields = Object.keys(fieldDescriptions);
|
|
130
|
-
|
|
131
|
-
// Combine with known optional fields and deprecated fields
|
|
132
|
-
const allValidFields = [
|
|
133
|
-
...canonicalFields,
|
|
134
|
-
...knownOptionalFields,
|
|
135
|
-
...deprecatedFields,
|
|
136
|
-
...defaultSpecKeys
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
// Remove duplicates
|
|
140
|
-
return [...new Set(allValidFields)];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Categorize fields into required and optional
|
|
145
|
-
* @param {Array} defaultSpecKeys - Keys from default specs
|
|
146
|
-
* @returns {Object} - Object containing required and optional fields
|
|
147
|
-
*/
|
|
148
|
-
function categorizeFields(defaultSpecKeys) {
|
|
149
|
-
const createFieldObject = key => ({
|
|
150
|
-
key,
|
|
151
|
-
description: fieldDescriptions[key] || `${key.replace(/_/g, ' ')} field`,
|
|
152
|
-
allowDefaultValue: allowDefaultValueFields.includes(key),
|
|
153
|
-
mustChange: mustChangeFields.includes(key)
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const requiredFields = defaultSpecKeys
|
|
157
|
-
.filter(key => !knownOptionalFields.includes(key))
|
|
158
|
-
.map(createFieldObject);
|
|
159
|
-
|
|
160
|
-
const optionalFields = defaultSpecKeys
|
|
161
|
-
.filter(key => knownOptionalFields.includes(key))
|
|
162
|
-
.map(createFieldObject);
|
|
163
|
-
|
|
164
|
-
return { requiredFields, optionalFields };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Process field validation results. Orchestrates the validation of all fields in the specs.json
|
|
169
|
-
* @param {Object} projectSpecs - Project specs object
|
|
170
|
-
* @param {Object} defaultSpecs - Default specs object
|
|
171
|
-
* @param {Array} defaultSpecKeys - Keys from default specs
|
|
172
|
-
* @returns {Object} - Object with results and missingRequiredKeys
|
|
173
|
-
*/
|
|
174
|
-
function processFieldValidation(projectSpecs, defaultSpecs, defaultSpecKeys) {
|
|
175
|
-
const { requiredFields, optionalFields } = categorizeFields(defaultSpecKeys);
|
|
176
|
-
|
|
177
|
-
const requiredResults = requiredFields.map(field => evaluateRequiredField(field, projectSpecs, defaultSpecs));
|
|
178
|
-
const optionalResults = optionalFields.map(field => evaluateOptionalField(field, projectSpecs, defaultSpecs));
|
|
179
|
-
|
|
180
|
-
const missingRequiredKeys = requiredResults
|
|
181
|
-
.filter(result => !result.success && result.details.includes('missing'))
|
|
182
|
-
.map((_, index) => requiredFields[index].key);
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
results: [...requiredResults, ...optionalResults],
|
|
186
|
-
missingRequiredKeys
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Check for unexpected fields in project specs
|
|
192
|
-
* @param {Object} projectSpecs - Project specs object
|
|
193
|
-
* @param {Array} defaultSpecKeys - Keys from default specs
|
|
194
|
-
* @returns {Array} - Array of unexpected field names
|
|
195
|
-
*/
|
|
196
|
-
function findUnexpectedFields(projectSpecs, defaultSpecKeys) {
|
|
197
|
-
const projectKeys = Object.keys(projectSpecs.specs?.[0] || {});
|
|
198
|
-
const allValidFields = getAllValidFields(defaultSpecKeys);
|
|
199
|
-
|
|
200
|
-
return projectKeys.filter(key => !allValidFields.includes(key));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Check if a field value has been configured
|
|
205
|
-
* @param {any} projectValue - Value from project specs
|
|
206
|
-
* @param {any} defaultValue - Value from default specs
|
|
207
|
-
* @returns {boolean} - True if configured
|
|
208
|
-
*/
|
|
209
|
-
function isFieldConfigured(projectValue, defaultValue) {
|
|
210
|
-
if (typeof projectValue === 'object') {
|
|
211
|
-
return JSON.stringify(projectValue) !== JSON.stringify(defaultValue);
|
|
212
|
-
}
|
|
213
|
-
return projectValue !== defaultValue;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Evaluate a field and generate result (unified for required/optional)
|
|
218
|
-
* @param {Object} field - Field definition
|
|
219
|
-
* @param {Object} projectSpecs - Project specs object
|
|
220
|
-
* @param {Object} defaultSpecs - Default specs object
|
|
221
|
-
* @param {boolean} isRequired - Whether field is required
|
|
222
|
-
* @returns {Object} - Check result
|
|
223
|
-
*/
|
|
224
|
-
function evaluateField(field, projectSpecs, defaultSpecs, isRequired) {
|
|
225
|
-
const hasField = projectSpecs.specs?.[0]?.hasOwnProperty(field.key);
|
|
226
|
-
|
|
227
|
-
if (!hasField) {
|
|
228
|
-
return {
|
|
229
|
-
name: `${field.description} configuration`,
|
|
230
|
-
success: !isRequired,
|
|
231
|
-
details: isRequired
|
|
232
|
-
? `Required "${field.key}" key is missing in specs.json`
|
|
233
|
-
: `Optional "${field.key}" key is not present (this is not required)`
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const projectValue = projectSpecs.specs[0][field.key];
|
|
238
|
-
const defaultValue = defaultSpecs.specs?.[0]?.[field.key];
|
|
239
|
-
const isConfigured = field.allowDefaultValue || isFieldConfigured(projectValue, defaultValue);
|
|
240
|
-
|
|
241
|
-
// Show warning when fields haven't been configured from their default values
|
|
242
|
-
const status = isConfigured ? undefined : 'warning';
|
|
243
|
-
|
|
244
|
-
const details = isConfigured
|
|
245
|
-
? (projectValue === defaultValue && field.allowDefaultValue)
|
|
246
|
-
? `Default value for ${field.description} is acceptable`
|
|
247
|
-
: `${field.description} has been changed from default`
|
|
248
|
-
: `${field.description} is still set to default value${mustChangeFields.includes(field.key) ? `: \"${defaultValue}\"` : ''}`;
|
|
249
|
-
|
|
250
|
-
return {
|
|
251
|
-
name: `${field.description} configuration`,
|
|
252
|
-
status,
|
|
253
|
-
success: true,
|
|
254
|
-
details
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Evaluate a required field and generate result
|
|
260
|
-
* @param {Object} field - Field definition
|
|
261
|
-
* @param {Object} projectSpecs - Project specs object
|
|
262
|
-
* @param {Object} defaultSpecs - Default specs object
|
|
263
|
-
* @returns {Object} - Check result
|
|
264
|
-
*/
|
|
265
|
-
function evaluateRequiredField(field, projectSpecs, defaultSpecs) {
|
|
266
|
-
return evaluateField(field, projectSpecs, defaultSpecs, true);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Evaluate an optional field and generate result
|
|
271
|
-
* @param {Object} field - Field definition
|
|
272
|
-
* @param {Object} projectSpecs - Project specs object
|
|
273
|
-
* @param {Object} defaultSpecs - Default specs object
|
|
274
|
-
* @returns {Object} - Check result
|
|
275
|
-
*/
|
|
276
|
-
function evaluateOptionalField(field, projectSpecs, defaultSpecs) {
|
|
277
|
-
return evaluateField(field, projectSpecs, defaultSpecs, false);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Generate summary results for the configuration check
|
|
282
|
-
* @param {Array} results - Existing check results
|
|
283
|
-
* @param {Array} missingRequiredKeys - List of missing required keys
|
|
284
|
-
* @param {Array} unexpectedKeys - List of unexpected keys
|
|
285
|
-
* @returns {Array} - Additional summary results
|
|
286
|
-
*/
|
|
287
|
-
function generateSummaryResults(results, missingRequiredKeys, unexpectedKeys) {
|
|
288
|
-
const summaryResults = [];
|
|
289
|
-
|
|
290
|
-
// Required fields summary
|
|
291
|
-
summaryResults.push({
|
|
292
|
-
name: 'Required fields check',
|
|
293
|
-
success: missingRequiredKeys.length === 0,
|
|
294
|
-
details: missingRequiredKeys.length > 0
|
|
295
|
-
? `Missing required fields: ${missingRequiredKeys.join(', ')}`
|
|
296
|
-
: 'All required fields are present'
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
// Unexpected fields check
|
|
300
|
-
if (unexpectedKeys.length > 0) {
|
|
301
|
-
summaryResults.push({
|
|
302
|
-
name: 'Unexpected fields check',
|
|
303
|
-
success: false,
|
|
304
|
-
details: `Found unexpected fields that might be typos: ${unexpectedKeys.join(', ')}`
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Overall configuration status
|
|
309
|
-
const fieldResults = results.filter(r =>
|
|
310
|
-
r.name.includes('configuration') && !r.name.includes('Overall')
|
|
311
|
-
);
|
|
312
|
-
|
|
313
|
-
const configuredItemsCount = fieldResults.filter(r => r.success).length;
|
|
314
|
-
const configurationPercentage = Math.round((configuredItemsCount / fieldResults.length) * 100);
|
|
315
|
-
|
|
316
|
-
summaryResults.push({
|
|
317
|
-
name: 'Overall configuration status',
|
|
318
|
-
success: configurationPercentage > 50 && missingRequiredKeys.length === 0,
|
|
319
|
-
details: `${configurationPercentage}% of specs.json has been configured (${configuredItemsCount}/${fieldResults.length} items)`
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
return summaryResults;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Load and parse configuration files
|
|
327
|
-
* @param {string} projectRoot - Root directory of the project
|
|
328
|
-
* @returns {Object} - Object containing parsed specs and file paths
|
|
329
|
-
*/
|
|
330
|
-
function loadConfigurationFiles(projectRoot) {
|
|
331
|
-
const projectSpecsPath = path.join(projectRoot, 'specs.json');
|
|
332
|
-
const defaultSpecsPath = path.join(
|
|
333
|
-
__dirname, '..', 'install-from-boilerplate', 'boilerplate', 'specs.json'
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
const fileCheckResults = checkFilesExist(projectSpecsPath, defaultSpecsPath);
|
|
337
|
-
if (fileCheckResults) {
|
|
338
|
-
return { error: fileCheckResults };
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
const projectSpecs = JSON.parse(fs.readFileSync(projectSpecsPath, 'utf8'));
|
|
342
|
-
const defaultSpecs = JSON.parse(fs.readFileSync(defaultSpecsPath, 'utf8'));
|
|
343
|
-
|
|
344
|
-
return { projectSpecs, defaultSpecs };
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Check if specs.json has been configured from default
|
|
349
|
-
* @param {string} projectRoot - Root directory of the project
|
|
350
|
-
* @returns {Promise<Array>} - Array of check results
|
|
351
|
-
*/
|
|
352
|
-
async function checkSpecsJsonConfiguration(projectRoot) {
|
|
353
|
-
try {
|
|
354
|
-
const { error, projectSpecs, defaultSpecs } = loadConfigurationFiles(projectRoot);
|
|
355
|
-
if (error) return error;
|
|
356
|
-
|
|
357
|
-
const results = [{
|
|
358
|
-
name: 'specs.json exists',
|
|
359
|
-
success: true,
|
|
360
|
-
details: 'Project specs.json file found'
|
|
361
|
-
}];
|
|
362
|
-
|
|
363
|
-
const defaultSpecKeys = Object.keys(defaultSpecs.specs?.[0] || {});
|
|
364
|
-
const { results: fieldResults, missingRequiredKeys } = processFieldValidation(
|
|
365
|
-
projectSpecs, defaultSpecs, defaultSpecKeys
|
|
366
|
-
);
|
|
367
|
-
|
|
368
|
-
results.push(...fieldResults);
|
|
369
|
-
|
|
370
|
-
const unexpectedKeys = findUnexpectedFields(projectSpecs, defaultSpecKeys);
|
|
371
|
-
const summaryResults = generateSummaryResults(results, missingRequiredKeys, unexpectedKeys);
|
|
372
|
-
|
|
373
|
-
return [...results, ...summaryResults];
|
|
374
|
-
|
|
375
|
-
} catch (error) {
|
|
376
|
-
Logger.error('Error checking specs.json configuration:', error);
|
|
377
|
-
return [{
|
|
378
|
-
name: 'specs.json configuration check',
|
|
379
|
-
success: false,
|
|
380
|
-
details: `Error: ${error.message}`
|
|
381
|
-
}];
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
module.exports = {
|
|
386
|
-
checkSpecsJsonConfiguration
|
|
387
|
-
};
|