spec-up-t 1.1.54 → 1.2.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.
Files changed (52) hide show
  1. package/assets/compiled/body.js +59 -8
  2. package/assets/compiled/head.css +13 -12
  3. package/assets/css/adjust-font-size.css +11 -0
  4. package/assets/css/collapse-meta-info.css +27 -8
  5. package/assets/css/create-term-filter.css +11 -0
  6. package/assets/css/index.css +15 -0
  7. package/assets/css/prism.css +176 -153
  8. package/assets/css/prism.dark.css +157 -0
  9. package/assets/css/prism.default.css +180 -0
  10. package/assets/css/terms-and-definitions.1.css +223 -0
  11. package/assets/css/terms-and-definitions.css +214 -100
  12. package/assets/js/addAnchorsToTerms.js +1 -1
  13. package/assets/js/collapse-definitions.js +2 -1
  14. package/assets/js/collapse-meta-info.js +25 -11
  15. package/assets/js/create-term-filter.js +61 -0
  16. package/assets/js/horizontal-scroll-hint.js +159 -0
  17. package/assets/js/index.1.js +137 -0
  18. package/assets/js/index.js +2 -1
  19. package/assets/js/insert-trefs.js +122 -116
  20. package/assets/js/insert-xrefs.1.js +372 -0
  21. package/assets/js/{show-commit-hashes.js → insert-xrefs.js} +67 -7
  22. package/assets/js/prism.dark.js +24 -0
  23. package/assets/js/prism.default.js +23 -0
  24. package/assets/js/prism.js +4 -5
  25. package/assets/js/search.js +1 -1
  26. package/assets/js/toggle-dense-info.js +40 -0
  27. package/branches.md +4 -29
  28. package/index.js +397 -189
  29. package/index.new.js +662 -0
  30. package/package.json +1 -1
  31. package/src/asset-map.json +9 -5
  32. package/src/collect-external-references.js +16 -9
  33. package/src/collectExternalReferences/fetchTermsFromIndex.js +328 -0
  34. package/src/collectExternalReferences/processXTrefsData.js +73 -18
  35. package/src/create-pdf.js +385 -89
  36. package/src/health-check/external-specs-checker.js +207 -0
  37. package/src/health-check/output-gitignore-checker.js +261 -0
  38. package/src/health-check/specs-configuration-checker.js +274 -0
  39. package/src/health-check/term-references-checker.js +191 -0
  40. package/src/health-check/terms-intro-checker.js +81 -0
  41. package/src/health-check/tref-term-checker.js +463 -0
  42. package/src/health-check.js +445 -0
  43. package/src/install-from-boilerplate/config-scripts-keys.js +2 -0
  44. package/src/install-from-boilerplate/menu.sh +6 -3
  45. package/src/markdown-it-extensions.js +134 -103
  46. package/src/prepare-tref.js +61 -24
  47. package/src/utils/fetch.js +100 -0
  48. package/templates/template.html +12 -7
  49. package/assets/js/css-helper.js +0 -30
  50. package/src/collectExternalReferences/fetchTermsFromGitHubRepository.js +0 -232
  51. package/src/collectExternalReferences/fetchTermsFromGitHubRepository.test.js +0 -385
  52. package/src/collectExternalReferences/octokitClient.js +0 -96
@@ -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,191 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Extracts the spec name from a tref tag at the beginning of a markdown file
6
+ * @param {string} firstLine - The first line of a markdown file
7
+ * @returns {string|null} - The extracted spec name or null if not found
8
+ */
9
+ function extractSpecNameFromTref(firstLine) {
10
+ if (!firstLine.includes('[[tref:')) {
11
+ return null;
12
+ }
13
+
14
+ try {
15
+ // Extract content between [[tref: and the next comma
16
+ const match = firstLine.match(/\[\[tref:([^,]+)/);
17
+ if (match && match[1]) {
18
+ // Trim whitespace
19
+ return match[1].trim();
20
+ }
21
+ } catch (error) {
22
+ console.error('Error extracting spec name from tref:', error);
23
+ }
24
+
25
+ return null;
26
+ }
27
+
28
+ /**
29
+ * Check if all markdown files in spec terms directories have valid tref references
30
+ * @param {string} projectRoot - Root directory of the project
31
+ * @returns {Promise<Array>} - Array of check results
32
+ */
33
+ async function checkTermReferences(projectRoot) {
34
+ const results = [];
35
+
36
+ try {
37
+ const specsPath = path.join(projectRoot, 'specs.json');
38
+
39
+ // Check if specs.json exists
40
+ if (!fs.existsSync(specsPath)) {
41
+ return [{
42
+ name: 'Find specs.json file',
43
+ success: false,
44
+ details: 'specs.json file not found in project root'
45
+ }];
46
+ }
47
+
48
+ // Read specs.json
49
+ const specsContent = fs.readFileSync(specsPath, 'utf8');
50
+ const specs = JSON.parse(specsContent);
51
+
52
+ // Find all external specs
53
+ const externalSpecs = [];
54
+ let allSpecDirectories = [];
55
+
56
+ if (specs.specs && Array.isArray(specs.specs)) {
57
+ // Collect all external specs
58
+ specs.specs.forEach(spec => {
59
+ if (spec.external_specs && Array.isArray(spec.external_specs)) {
60
+ spec.external_specs.forEach(extSpec => {
61
+ if (extSpec.external_spec) {
62
+ externalSpecs.push(extSpec.external_spec);
63
+ }
64
+ });
65
+ }
66
+
67
+ // Collect term directories
68
+ if (spec.spec_directory && spec.spec_terms_directory) {
69
+ const termsDir = path.join(
70
+ projectRoot,
71
+ spec.spec_directory,
72
+ spec.spec_terms_directory
73
+ );
74
+ allSpecDirectories.push(termsDir);
75
+ }
76
+ });
77
+ }
78
+
79
+ if (externalSpecs.length === 0) {
80
+ results.push({
81
+ name: 'Find external specs',
82
+ success: false,
83
+ details: 'No external_spec entries found in specs.json'
84
+ });
85
+ } else {
86
+ results.push({
87
+ name: 'Find external specs',
88
+ success: true,
89
+ details: `Found ${externalSpecs.length} external specs: ${externalSpecs.join(', ')}`
90
+ });
91
+ }
92
+
93
+ if (allSpecDirectories.length === 0) {
94
+ results.push({
95
+ name: 'Find spec terms directories',
96
+ success: false,
97
+ details: 'No spec_directory/spec_terms_directory entries found in specs.json'
98
+ });
99
+ return results;
100
+ }
101
+
102
+ results.push({
103
+ name: 'Find spec terms directories',
104
+ success: true,
105
+ details: `Found ${allSpecDirectories.length} spec terms directories`
106
+ });
107
+
108
+ // Process all markdown files in all terms directories
109
+ for (const termsDir of allSpecDirectories) {
110
+ if (!fs.existsSync(termsDir)) {
111
+ results.push({
112
+ name: `Check terms directory: ${termsDir}`,
113
+ success: false,
114
+ details: `Terms directory does not exist: ${termsDir}`
115
+ });
116
+ continue;
117
+ }
118
+
119
+ // Find all markdown files in the terms directory
120
+ const markdownFiles = fs.readdirSync(termsDir)
121
+ .filter(file => path.extname(file) === '.md')
122
+ .map(file => path.join(termsDir, file));
123
+
124
+ if (markdownFiles.length === 0) {
125
+ results.push({
126
+ name: `Find markdown files in <code>${termsDir}</code>`,
127
+ success: false,
128
+ details: `No markdown files found in terms directory: ${termsDir}`
129
+ });
130
+ continue;
131
+ }
132
+
133
+ results.push({
134
+ name: `Find markdown files in <code>${termsDir}</code>`,
135
+ success: true,
136
+ details: `Found ${markdownFiles.length} markdown files`
137
+ });
138
+
139
+ // Check each markdown file
140
+ for (const mdFile of markdownFiles) {
141
+ try {
142
+ const content = fs.readFileSync(mdFile, 'utf8');
143
+ const firstLine = content.split('\n')[0];
144
+
145
+ if (!firstLine.includes('[[tref:')) {
146
+ // Skip this file as it doesn't contain a tref tag in the first line
147
+ continue;
148
+ }
149
+
150
+ const specName = extractSpecNameFromTref(firstLine);
151
+ if (!specName) {
152
+ results.push({
153
+ name: `Check tref in ${path.basename(mdFile)}`,
154
+ success: false,
155
+ details: `Could not extract spec name from tref tag in first line: "${firstLine}"`
156
+ });
157
+ continue;
158
+ }
159
+
160
+ const isValid = externalSpecs.includes(specName);
161
+ results.push({
162
+ name: `Check tref spec "${specName}" in <code>${path.basename(mdFile)}</code>`,
163
+ success: isValid,
164
+ details: isValid
165
+ ? `Valid external spec reference: ${specName}`
166
+ : `Invalid external spec reference: "${specName}" is not defined in external_specs`
167
+ });
168
+ } catch (error) {
169
+ results.push({
170
+ name: `Check file ${path.basename(mdFile)}`,
171
+ success: false,
172
+ details: `Error reading or processing file: ${error.message}`
173
+ });
174
+ }
175
+ }
176
+ }
177
+
178
+ return results;
179
+ } catch (error) {
180
+ console.error('Error checking term references:', error);
181
+ return [{
182
+ name: 'Term references check',
183
+ success: false,
184
+ details: `Error: ${error.message}`
185
+ }];
186
+ }
187
+ }
188
+
189
+ module.exports = {
190
+ checkTermReferences
191
+ };
@@ -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
+ };