spec-up-t 1.1.54 → 1.1.55

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,274 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Check if specs.json has been configured from default
6
+ * @param {string} projectRoot - Root directory of the project
7
+ * @returns {Promise<Array>} - Array of check results
8
+ */
9
+ async function checkSpecsJsonConfiguration(projectRoot) {
10
+ const results = [];
11
+
12
+ try {
13
+ // Path to the project's specs.json
14
+ const projectSpecsPath = path.join(projectRoot, 'specs.json');
15
+
16
+ // Path to the default boilerplate specs.json
17
+ const defaultSpecsPath = path.join(
18
+ __dirname,
19
+ '..',
20
+ 'install-from-boilerplate',
21
+ 'boilerplate',
22
+ 'specs.json'
23
+ );
24
+
25
+ // Check if project specs.json exists
26
+ if (!fs.existsSync(projectSpecsPath)) {
27
+ return [{
28
+ name: 'Find specs.json file',
29
+ success: false,
30
+ details: 'specs.json file not found in project root'
31
+ }];
32
+ }
33
+
34
+ // Check if default specs.json exists
35
+ if (!fs.existsSync(defaultSpecsPath)) {
36
+ return [{
37
+ name: 'Find default specs.json template',
38
+ success: false,
39
+ details: 'Default specs.json template not found'
40
+ }];
41
+ }
42
+
43
+ // Read both files
44
+ const projectSpecs = JSON.parse(fs.readFileSync(projectSpecsPath, 'utf8'));
45
+ const defaultSpecs = JSON.parse(fs.readFileSync(defaultSpecsPath, 'utf8'));
46
+
47
+ // Compare key parts to see if the file has been configured
48
+ results.push({
49
+ name: 'specs.json exists',
50
+ success: true,
51
+ details: 'Project specs.json file found'
52
+ });
53
+
54
+ // Dynamically extract field definitions from the default specs.json
55
+ const fieldDescriptions = {
56
+ 'title': 'Specification title',
57
+ 'description': 'Specification description',
58
+ 'author': 'Specification author',
59
+ 'source': 'Source repository information',
60
+ 'spec_directory': 'Directory containing specification content',
61
+ 'spec_terms_directory': 'Directory containing term definitions',
62
+ 'output_path': 'Output directory for generated files',
63
+ 'markdown_paths': 'List of markdown files to include in the specification',
64
+ 'logo': 'Logo URL',
65
+ 'logo_link': 'Link to the logo',
66
+ 'favicon': 'Favicon URL',
67
+ 'external_specs': 'External specifications',
68
+ 'katex': 'KaTeX math rendering configuration'
69
+ // Add more descriptions as needed, or create a more sophisticated lookup
70
+ };
71
+
72
+ // Define required and optional fields based on the default specs.json
73
+ const defaultSpecKeys = Object.keys(defaultSpecs.specs?.[0] || {});
74
+
75
+ // Known optional fields - this could be pulled from documentation if available
76
+ const knownOptionalFields = ['logo', 'external_specs', 'logo_link', 'favicon', 'katex'];
77
+
78
+ // Fields that can remain at their default values without being flagged
79
+ const allowDefaultValueFields = [
80
+ 'spec_directory',
81
+ 'spec_terms_directory',
82
+ 'output_path',
83
+ 'katex',
84
+ 'logo',
85
+ 'logo_link',
86
+ 'favicon'
87
+ // Add any other fields that should be allowed to have default values
88
+ // Add any new field here that should be allowed to have default values
89
+ ];
90
+
91
+ // Fields that should fail if they're not modified from default values
92
+ const mustChangeFields = [
93
+ 'title',
94
+ 'description',
95
+ 'author',
96
+ 'source'
97
+ ];
98
+
99
+ // Consider all keys in the template as required unless explicitly marked as optional
100
+ const requiredFields = defaultSpecKeys
101
+ .filter(key => !knownOptionalFields.includes(key))
102
+ .map(key => ({
103
+ key,
104
+ description: fieldDescriptions[key] || `${key.replace(/_/g, ' ')} field`,
105
+ allowDefaultValue: allowDefaultValueFields.includes(key),
106
+ mustChange: mustChangeFields.includes(key)
107
+ }));
108
+
109
+ const optionalFields = defaultSpecKeys
110
+ .filter(key => knownOptionalFields.includes(key))
111
+ .map(key => ({
112
+ key,
113
+ description: fieldDescriptions[key] || `${key.replace(/_/g, ' ')} field`,
114
+ allowDefaultValue: allowDefaultValueFields.includes(key)
115
+ }));
116
+
117
+ // Check each required field exists
118
+ const missingRequiredKeys = [];
119
+
120
+ for (const field of requiredFields) {
121
+ const hasField = projectSpecs.specs?.[0]?.hasOwnProperty(field.key);
122
+
123
+ if (!hasField) {
124
+ missingRequiredKeys.push(field.key);
125
+
126
+ results.push({
127
+ name: `${field.description} configuration`,
128
+ success: false,
129
+ details: `Required "${field.key}" key is missing in specs.json`
130
+ });
131
+ } else {
132
+ // Field exists, check if it's configured
133
+ const projectValue = projectSpecs.specs[0][field.key];
134
+ const defaultValue = defaultSpecs.specs?.[0]?.[field.key];
135
+ let isConfigured = false;
136
+
137
+ if (typeof projectValue === 'object') {
138
+ isConfigured = JSON.stringify(projectValue) !== JSON.stringify(defaultValue);
139
+ } else {
140
+ isConfigured = projectValue !== defaultValue;
141
+ }
142
+
143
+ // For fields that can keep their default values, we'll mark them as configured
144
+ if (field.allowDefaultValue) {
145
+ isConfigured = true;
146
+ }
147
+
148
+ // Determine if we should show warning or fail for unconfigured fields
149
+ let status = undefined;
150
+ let success = true;
151
+
152
+ if (!isConfigured) {
153
+ if (field.mustChange) {
154
+ // Must-change fields should fail if not configured
155
+ status = undefined; // No status means it shows as failure
156
+ success = false;
157
+ } else {
158
+ // Other fields should show a warning
159
+ status = 'warning';
160
+ success = true; // Still technically passes with a warning
161
+ }
162
+ }
163
+
164
+ results.push({
165
+ name: `${field.description} configuration`,
166
+ status: status,
167
+ success: success,
168
+ details: isConfigured
169
+ ? (projectValue === defaultValue && field.allowDefaultValue
170
+ ? `Default value for ${field.description} is acceptable`
171
+ : `${field.description} has been changed from default`)
172
+ : `${field.description} is still set to default value${field.key === 'title' || field.key === 'author' ? `: "${defaultValue}"` : ''}`
173
+ });
174
+ }
175
+ }
176
+
177
+ // Check optional fields
178
+ for (const field of optionalFields) {
179
+ const hasField = projectSpecs.specs?.[0]?.hasOwnProperty(field.key);
180
+
181
+ if (hasField) {
182
+ const projectValue = projectSpecs.specs[0][field.key];
183
+ const defaultValue = defaultSpecs.specs?.[0]?.[field.key];
184
+ let isConfigured = false;
185
+
186
+ if (typeof projectValue === 'object') {
187
+ isConfigured = JSON.stringify(projectValue) !== JSON.stringify(defaultValue);
188
+ } else {
189
+ isConfigured = projectValue !== defaultValue;
190
+ }
191
+
192
+ // For optional fields that can keep their default values, we'll mark them as configured
193
+ if (field.allowDefaultValue) {
194
+ isConfigured = true;
195
+ }
196
+
197
+ results.push({
198
+ name: `${field.description} configuration`,
199
+ status: isConfigured ? undefined : 'warning',
200
+ success: isConfigured || !isConfigured, // Always true for backward compatibility when using warning
201
+ details: isConfigured
202
+ ? (projectValue === defaultValue && field.allowDefaultValue
203
+ ? `Default value for ${field.description} is acceptable`
204
+ : `${field.description} has been changed from default`)
205
+ : `${field.description} is still set to default value`
206
+ });
207
+ } else {
208
+ results.push({
209
+ name: `${field.description} configuration`,
210
+ success: true,
211
+ details: `Optional "${field.key}" key is not present (this is not required)`
212
+ });
213
+ }
214
+ }
215
+
216
+ // Add a summary of missing required fields
217
+ if (missingRequiredKeys.length > 0) {
218
+ results.push({
219
+ name: 'Required fields check',
220
+ success: false,
221
+ details: `Missing required fields: ${missingRequiredKeys.join(', ')}`
222
+ });
223
+ } else {
224
+ results.push({
225
+ name: 'Required fields check',
226
+ success: true,
227
+ details: 'All required fields are present'
228
+ });
229
+ }
230
+
231
+ // Check if any fields exist that aren't in the standard template (could be typos)
232
+ const allStandardKeys = [...requiredFields, ...optionalFields].map(f => f.key);
233
+ const unexpectedKeys = Object.keys(projectSpecs.specs?.[0] || {})
234
+ .filter(key => !allStandardKeys.includes(key));
235
+
236
+ if (unexpectedKeys.length > 0) {
237
+ results.push({
238
+ name: 'Unexpected fields check',
239
+ success: false,
240
+ details: `Found unexpected fields that might be typos: ${unexpectedKeys.join(', ')}`
241
+ });
242
+ }
243
+
244
+ // Overall configuration status
245
+ // Count all fields that are present and configured
246
+ const fieldResults = results.filter(r =>
247
+ r.name.includes('configuration') &&
248
+ !r.name.includes('Overall')
249
+ );
250
+
251
+ const configuredItemsCount = fieldResults.filter(r => r.success).length;
252
+ const totalItems = fieldResults.length;
253
+ const configurationPercentage = Math.round((configuredItemsCount / totalItems) * 100);
254
+
255
+ results.push({
256
+ name: 'Overall configuration status',
257
+ success: configurationPercentage > 50 && missingRequiredKeys.length === 0,
258
+ details: `${configurationPercentage}% of specs.json has been configured (${configuredItemsCount}/${totalItems} items)`
259
+ });
260
+
261
+ return results;
262
+ } catch (error) {
263
+ console.error('Error checking specs.json configuration:', error);
264
+ return [{
265
+ name: 'specs.json configuration check',
266
+ success: false,
267
+ details: `Error: ${error.message}`
268
+ }];
269
+ }
270
+ }
271
+
272
+ module.exports = {
273
+ checkSpecsJsonConfiguration
274
+ };
@@ -0,0 +1,190 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const glob = require('glob');
4
+
5
+ /**
6
+ * Extracts the spec name from a tref tag at the beginning of a markdown file
7
+ * @param {string} firstLine - The first line of a markdown file
8
+ * @returns {string|null} - The extracted spec name or null if not found
9
+ */
10
+ function extractSpecNameFromTref(firstLine) {
11
+ if (!firstLine.includes('[[tref:')) {
12
+ return null;
13
+ }
14
+
15
+ try {
16
+ // Extract content between [[tref: and the next comma
17
+ const match = firstLine.match(/\[\[tref:([^,]+)/);
18
+ if (match && match[1]) {
19
+ // Trim whitespace
20
+ return match[1].trim();
21
+ }
22
+ } catch (error) {
23
+ console.error('Error extracting spec name from tref:', error);
24
+ }
25
+
26
+ return null;
27
+ }
28
+
29
+ /**
30
+ * Check if all markdown files in spec terms directories have valid tref references
31
+ * @param {string} projectRoot - Root directory of the project
32
+ * @returns {Promise<Array>} - Array of check results
33
+ */
34
+ async function checkTermReferences(projectRoot) {
35
+ const results = [];
36
+
37
+ try {
38
+ const specsPath = path.join(projectRoot, 'specs.json');
39
+
40
+ // Check if specs.json exists
41
+ if (!fs.existsSync(specsPath)) {
42
+ return [{
43
+ name: 'Find specs.json file',
44
+ success: false,
45
+ details: 'specs.json file not found in project root'
46
+ }];
47
+ }
48
+
49
+ // Read specs.json
50
+ const specsContent = fs.readFileSync(specsPath, 'utf8');
51
+ const specs = JSON.parse(specsContent);
52
+
53
+ // Find all external specs
54
+ const externalSpecs = [];
55
+ let allSpecDirectories = [];
56
+
57
+ if (specs.specs && Array.isArray(specs.specs)) {
58
+ // Collect all external specs
59
+ specs.specs.forEach(spec => {
60
+ if (spec.external_specs && Array.isArray(spec.external_specs)) {
61
+ spec.external_specs.forEach(extSpec => {
62
+ if (extSpec.external_spec) {
63
+ externalSpecs.push(extSpec.external_spec);
64
+ }
65
+ });
66
+ }
67
+
68
+ // Collect term directories
69
+ if (spec.spec_directory && spec.spec_terms_directory) {
70
+ const termsDir = path.join(
71
+ projectRoot,
72
+ spec.spec_directory,
73
+ spec.spec_terms_directory
74
+ );
75
+ allSpecDirectories.push(termsDir);
76
+ }
77
+ });
78
+ }
79
+
80
+ if (externalSpecs.length === 0) {
81
+ results.push({
82
+ name: 'Find external specs',
83
+ success: false,
84
+ details: 'No external_spec entries found in specs.json'
85
+ });
86
+ } else {
87
+ results.push({
88
+ name: 'Find external specs',
89
+ success: true,
90
+ details: `Found ${externalSpecs.length} external specs: ${externalSpecs.join(', ')}`
91
+ });
92
+ }
93
+
94
+ if (allSpecDirectories.length === 0) {
95
+ results.push({
96
+ name: 'Find spec terms directories',
97
+ success: false,
98
+ details: 'No spec_directory/spec_terms_directory entries found in specs.json'
99
+ });
100
+ return results;
101
+ }
102
+
103
+ results.push({
104
+ name: 'Find spec terms directories',
105
+ success: true,
106
+ details: `Found ${allSpecDirectories.length} spec terms directories`
107
+ });
108
+
109
+ // Process all markdown files in all terms directories
110
+ for (const termsDir of allSpecDirectories) {
111
+ if (!fs.existsSync(termsDir)) {
112
+ results.push({
113
+ name: `Check terms directory: ${termsDir}`,
114
+ success: false,
115
+ details: `Terms directory does not exist: ${termsDir}`
116
+ });
117
+ continue;
118
+ }
119
+
120
+ // Find all markdown files in the terms directory
121
+ const markdownFiles = glob.sync(path.join(termsDir, '*.md'));
122
+
123
+ if (markdownFiles.length === 0) {
124
+ results.push({
125
+ name: `Find markdown files in <code>${termsDir}</code>`,
126
+ success: false,
127
+ details: `No markdown files found in terms directory: ${termsDir}`
128
+ });
129
+ continue;
130
+ }
131
+
132
+ results.push({
133
+ name: `Find markdown files in <code>${termsDir}</code>`,
134
+ success: true,
135
+ details: `Found ${markdownFiles.length} markdown files`
136
+ });
137
+
138
+ // Check each markdown file
139
+ for (const mdFile of markdownFiles) {
140
+ try {
141
+ const content = fs.readFileSync(mdFile, 'utf8');
142
+ const firstLine = content.split('\n')[0];
143
+
144
+ if (!firstLine.includes('[[tref:')) {
145
+ // Skip this file as it doesn't contain a tref tag in the first line
146
+ continue;
147
+ }
148
+
149
+ const specName = extractSpecNameFromTref(firstLine);
150
+ if (!specName) {
151
+ results.push({
152
+ name: `Check tref in ${path.basename(mdFile)}`,
153
+ success: false,
154
+ details: `Could not extract spec name from tref tag in first line: "${firstLine}"`
155
+ });
156
+ continue;
157
+ }
158
+
159
+ const isValid = externalSpecs.includes(specName);
160
+ results.push({
161
+ name: `Check tref spec "${specName}" in <code>${path.basename(mdFile)}</code>`,
162
+ success: isValid,
163
+ details: isValid
164
+ ? `Valid external spec reference: ${specName}`
165
+ : `Invalid external spec reference: "${specName}" is not defined in external_specs`
166
+ });
167
+ } catch (error) {
168
+ results.push({
169
+ name: `Check file ${path.basename(mdFile)}`,
170
+ success: false,
171
+ details: `Error reading or processing file: ${error.message}`
172
+ });
173
+ }
174
+ }
175
+ }
176
+
177
+ return results;
178
+ } catch (error) {
179
+ console.error('Error checking term references:', error);
180
+ return [{
181
+ name: 'Term references check',
182
+ success: false,
183
+ details: `Error: ${error.message}`
184
+ }];
185
+ }
186
+ }
187
+
188
+ module.exports = {
189
+ checkTermReferences
190
+ };
@@ -0,0 +1,81 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Check if the terms-and-definitions-intro.md file exists in the spec directory
6
+ * @param {string} projectRoot - Root directory of the project
7
+ * @returns {Promise<Array>} - Array of check results
8
+ */
9
+ async function checkTermsIntroFile(projectRoot) {
10
+ const results = [];
11
+
12
+ try {
13
+ // Path to the project's specs.json
14
+ const specsPath = path.join(projectRoot, 'specs.json');
15
+
16
+ // Check if specs.json exists
17
+ if (!fs.existsSync(specsPath)) {
18
+ return [{
19
+ name: 'Find specs.json file',
20
+ success: false,
21
+ details: 'specs.json file not found in project root'
22
+ }];
23
+ }
24
+
25
+ results.push({
26
+ name: 'Find specs.json file',
27
+ success: true,
28
+ details: 'specs.json file found'
29
+ });
30
+
31
+ // Read specs.json to get the spec directory
32
+ const specsContent = fs.readFileSync(specsPath, 'utf8');
33
+ const specs = JSON.parse(specsContent);
34
+
35
+ // Get the spec_directory value
36
+ const specDir = specs.specs?.[0]?.spec_directory;
37
+
38
+ if (!specDir) {
39
+ results.push({
40
+ name: 'Find spec_directory field',
41
+ success: false,
42
+ details: 'spec_directory field not found in specs.json'
43
+ });
44
+ return results;
45
+ }
46
+
47
+ results.push({
48
+ name: 'Find spec_directory field',
49
+ success: true,
50
+ details: `spec_directory field found: "${specDir}"`
51
+ });
52
+
53
+ // Build the path to the terms-and-definitions-intro.md file
54
+ const specDirPath = path.resolve(projectRoot, specDir);
55
+ const termsIntroPath = path.join(specDirPath, 'terms-and-definitions-intro.md');
56
+
57
+ // Check if the terms-and-definitions-intro.md file exists
58
+ const termsIntroExists = fs.existsSync(termsIntroPath);
59
+
60
+ results.push({
61
+ name: 'Find terms-and-definitions-intro.md file',
62
+ success: termsIntroExists,
63
+ details: termsIntroExists
64
+ ? 'terms-and-definitions-intro.md file found in spec directory'
65
+ : `terms-and-definitions-intro.md file not found in ${specDirPath}`
66
+ });
67
+
68
+ return results;
69
+ } catch (error) {
70
+ console.error('Error checking terms-and-definitions-intro.md file:', error);
71
+ return [{
72
+ name: 'Terms intro file check',
73
+ success: false,
74
+ details: `Error: ${error.message}`
75
+ }];
76
+ }
77
+ }
78
+
79
+ module.exports = {
80
+ checkTermsIntroFile
81
+ };