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,462 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { shouldProcessFile } = require('../utils/file-filter');
4
+
5
+ /**
6
+ * Extracts the spec name and term from a tref tag at the beginning of a markdown file
7
+ * @param {string} firstLine - The first line of a markdown file
8
+ * @returns {Object|null} - Object containing repo and term, or null if not found
9
+ */
10
+ function extractTrefInfo(firstLine) {
11
+ if (!firstLine.includes('[[tref:')) {
12
+ return null;
13
+ }
14
+
15
+ try {
16
+ // Extract content between [[tref: and ]]
17
+ const trefMatch = firstLine.match(/\[\[tref:([^\]]+)\]\]/);
18
+ if (!trefMatch || !trefMatch[1]) {
19
+ return null;
20
+ }
21
+
22
+ const trefContent = trefMatch[1].trim();
23
+
24
+ // Split by the first comma
25
+ const parts = trefContent.split(',');
26
+ if (parts.length < 2) {
27
+ return null;
28
+ }
29
+
30
+ // Extract repo and term
31
+ const repo = parts[0].trim();
32
+ const term = parts.slice(1).join(',').trim();
33
+
34
+ return { repo, term };
35
+ } catch (error) {
36
+ console.error('Error extracting tref info:', error);
37
+ return null;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Find all JSON cache files for repositories
43
+ * @param {string} cacheDir - Directory containing the cached files
44
+ * @returns {Array} - List of all JSON cache files
45
+ */
46
+ function findAllCacheFiles(cacheDir) {
47
+ if (!fs.existsSync(cacheDir)) {
48
+ return [];
49
+ }
50
+
51
+ try {
52
+ const files = fs.readdirSync(cacheDir)
53
+ .filter(file => file.endsWith('.json'))
54
+ .map(file => path.join(cacheDir, file));
55
+
56
+ return files;
57
+ } catch (error) {
58
+ console.error(`Error finding cache files:`, error);
59
+ return [];
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Checks if a term exists in a repository JSON cache file
65
+ * @param {string} filePath - Path to the cached repository file
66
+ * @param {string} term - Term to search for
67
+ * @returns {boolean} - Whether the term exists in the file
68
+ */
69
+ function termExistsInRepo(filePath, term) {
70
+ if (!fs.existsSync(filePath)) {
71
+ return false;
72
+ }
73
+
74
+ try {
75
+ const cacheData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
76
+
77
+ // Check if the file has a terms array
78
+ if (!cacheData || !cacheData.terms || !Array.isArray(cacheData.terms)) {
79
+ console.log(`Warning: Cache file ${filePath} has no terms array`);
80
+ return false;
81
+ }
82
+
83
+ // Case-insensitive search for the term
84
+ const termLower = term.toLowerCase();
85
+
86
+ // Check each term in the terms array
87
+ for (const termObj of cacheData.terms) {
88
+ // First check the 'term' property if it exists
89
+ if (termObj.term && termObj.term.toLowerCase() === termLower) {
90
+ return true;
91
+ }
92
+
93
+ // If there's a definition property, check if the term appears in it
94
+ if (termObj.definition) {
95
+ // Look for patterns like [[def: term]] or similar
96
+ const defMatch = termObj.definition.match(/\[\[def:\s*([^\],]+)/i);
97
+ if (defMatch && defMatch[1] && defMatch[1].trim().toLowerCase() === termLower) {
98
+ return true;
99
+ }
100
+ }
101
+ }
102
+
103
+ return false;
104
+ } catch (error) {
105
+ console.error(`Error checking if term exists in file ${filePath}:`, error);
106
+ return false;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Find which repo a cache file belongs to
112
+ * @param {string} filePath - Path to the cache file
113
+ * @param {Array} externalSpecs - List of external specs
114
+ * @returns {string|null} - The external_spec identifier or null
115
+ */
116
+ function findRepoForCacheFile(filePath, externalSpecs) {
117
+ try {
118
+ const cacheData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
119
+
120
+ if (!cacheData || !cacheData.repository) {
121
+ return null;
122
+ }
123
+
124
+ // Extract owner/repo from the repository field
125
+ const repoPath = cacheData.repository;
126
+
127
+ // Find matching external spec
128
+ for (const spec of externalSpecs) {
129
+ if (!spec.url) continue;
130
+
131
+ // Extract owner/repo from the URL
132
+ const match = spec.url.match(/github\.com\/([^\/]+\/[^\/]+)/i);
133
+ if (match && match[1] && repoPath.includes(match[1])) {
134
+ return spec.external_spec;
135
+ }
136
+ }
137
+
138
+ return null;
139
+ } catch (error) {
140
+ return null;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Checks for specified term in all available external repos
146
+ * @param {string} cacheDir - Directory containing the cached repository files
147
+ * @param {Array} externalSpecs - List of external specs from specs.json
148
+ * @param {string} currentRepo - The repository being checked (to exclude it)
149
+ * @param {string} term - Term to search for
150
+ * @returns {Array} - List of repositories where the term was found
151
+ */
152
+ function findTermInOtherRepos(cacheDir, externalSpecs, currentRepo, term) {
153
+ const reposWithTerm = [];
154
+
155
+ // Get all cache files
156
+ const cacheFiles = findAllCacheFiles(cacheDir);
157
+
158
+ for (const cacheFile of cacheFiles) {
159
+ // Check which repo this cache file belongs to
160
+ const repo = findRepoForCacheFile(cacheFile, externalSpecs);
161
+
162
+ // Skip if we couldn't determine which repo this is or if it's the current repo
163
+ if (!repo || repo === currentRepo) {
164
+ continue;
165
+ }
166
+
167
+ // Check if the term exists in this repo
168
+ if (termExistsInRepo(cacheFile, term)) {
169
+ reposWithTerm.push(repo);
170
+ }
171
+ }
172
+
173
+ return reposWithTerm;
174
+ }
175
+
176
+ /**
177
+ * Find cache file for a specific external repo
178
+ * @param {string} cacheDir - Cache directory
179
+ * @param {Object} specConfig - External spec configuration
180
+ * @returns {string|null} - Path to the cache file or null
181
+ */
182
+ function findCacheFileForRepo(cacheDir, specConfig) {
183
+ if (!fs.existsSync(cacheDir) || !specConfig || !specConfig.url) {
184
+ return null;
185
+ }
186
+
187
+ try {
188
+ // Extract owner and repo from URL
189
+ const match = specConfig.url.match(/github\.com\/([^\/]+)\/([^\/]+)/i);
190
+ if (!match || !match[1] || !match[2]) {
191
+ return null;
192
+ }
193
+
194
+ const owner = match[1];
195
+ const repo = match[2];
196
+
197
+ // Find all JSON files in the cache directory
198
+ const files = fs.readdirSync(cacheDir)
199
+ .filter(file => file.endsWith('.json'))
200
+ .map(file => ({
201
+ path: path.join(cacheDir, file),
202
+ name: file
203
+ }))
204
+ // Sort by timestamp descending (assuming timestamp is at the beginning of filename)
205
+ .sort((a, b) => {
206
+ const timestampA = parseInt(a.name.split('-')[0] || '0', 10);
207
+ const timestampB = parseInt(b.name.split('-')[0] || '0', 10);
208
+ return timestampB - timestampA;
209
+ });
210
+
211
+ // Find the most recent cache file for this repo
212
+ for (const file of files) {
213
+ if (file.name.includes(owner) && file.name.includes(repo)) {
214
+ return file.path;
215
+ }
216
+ }
217
+
218
+ return null;
219
+ } catch (error) {
220
+ console.error(`Error finding cache file for repo:`, error);
221
+ return null;
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Check if terms referenced via tref tags exist in the corresponding external repos
227
+ * @param {string} projectRoot - Root directory of the project
228
+ * @returns {Promise<Array>} - Array of check results
229
+ */
230
+ async function checkTrefTerms(projectRoot) {
231
+ const results = [];
232
+
233
+ try {
234
+ // Path to the project's specs.json
235
+ const specsPath = path.join(projectRoot, 'specs.json');
236
+
237
+ // Check if specs.json exists
238
+ if (!fs.existsSync(specsPath)) {
239
+ return [{
240
+ name: 'Find specs.json file',
241
+ success: false,
242
+ details: 'specs.json file not found in project root'
243
+ }];
244
+ }
245
+
246
+ results.push({
247
+ name: 'Find specs.json file',
248
+ success: true,
249
+ details: 'specs.json file found'
250
+ });
251
+
252
+ // Read specs.json to get the spec directory
253
+ const specsContent = fs.readFileSync(specsPath, 'utf8');
254
+ const specs = JSON.parse(specsContent);
255
+
256
+ // Get the external specs
257
+ if (!specs.specs || !Array.isArray(specs.specs)) {
258
+ results.push({
259
+ name: 'Find specs configuration',
260
+ success: false,
261
+ details: 'specs array not found in specs.json'
262
+ });
263
+ return results;
264
+ }
265
+
266
+ // Collect all external specs and spec directories
267
+ const externalSpecs = [];
268
+ const specDirs = [];
269
+
270
+ specs.specs.forEach(spec => {
271
+ if (spec.external_specs && Array.isArray(spec.external_specs)) {
272
+ externalSpecs.push(...spec.external_specs);
273
+ }
274
+
275
+ if (spec.spec_directory && spec.spec_terms_directory) {
276
+ const termsDir = path.join(
277
+ projectRoot,
278
+ spec.spec_directory,
279
+ spec.spec_terms_directory
280
+ );
281
+ specDirs.push(termsDir);
282
+ }
283
+ });
284
+
285
+ if (externalSpecs.length === 0) {
286
+ results.push({
287
+ name: 'Find external specs',
288
+ success: false,
289
+ details: 'No external specs found in specs.json'
290
+ });
291
+ return results;
292
+ }
293
+
294
+ results.push({
295
+ name: 'Find external specs',
296
+ success: true,
297
+ details: `Found ${externalSpecs.length} external specs`
298
+ });
299
+
300
+ if (specDirs.length === 0) {
301
+ results.push({
302
+ name: 'Find spec terms directories',
303
+ success: false,
304
+ details: 'No spec terms directories found'
305
+ });
306
+ return results;
307
+ }
308
+
309
+ results.push({
310
+ name: 'Find spec terms directories',
311
+ success: true,
312
+ details: `Found ${specDirs.length} spec terms directories`
313
+ });
314
+
315
+ // Path to the GitHub cache directory
316
+ const githubCacheDir = path.join(projectRoot, 'output', 'github-cache');
317
+
318
+ if (!fs.existsSync(githubCacheDir)) {
319
+ results.push({
320
+ name: 'Find GitHub cache directory',
321
+ success: false,
322
+ details: 'GitHub cache directory not found at output/github-cache. Terms in external repos cannot be verified.'
323
+ });
324
+ return results;
325
+ }
326
+
327
+ results.push({
328
+ name: 'Find GitHub cache directory',
329
+ success: true,
330
+ details: 'GitHub cache directory found'
331
+ });
332
+
333
+ // Find and check all markdown files in all spec directories
334
+ let totalFiles = 0;
335
+ let filesWithTref = 0;
336
+ let validTerms = 0;
337
+ let invalidTerms = 0;
338
+ let termsFoundInOtherRepos = 0;
339
+
340
+ for (const specDir of specDirs) {
341
+ if (!fs.existsSync(specDir)) {
342
+ continue;
343
+ }
344
+
345
+ // Get all markdown files
346
+ const allFiles = fs.readdirSync(specDir);
347
+ const markdownFiles = allFiles.filter(file => shouldProcessFile(file));
348
+
349
+ totalFiles += markdownFiles.length;
350
+
351
+ for (const file of markdownFiles) {
352
+ const filePath = path.join(specDir, file);
353
+
354
+ try {
355
+ const content = fs.readFileSync(filePath, 'utf8');
356
+ const lines = content.split('\n');
357
+ const firstLine = lines[0];
358
+
359
+ if (!firstLine.includes('[[tref:')) {
360
+ continue;
361
+ }
362
+
363
+ filesWithTref++;
364
+
365
+ // Extract repo and term information
366
+ const trefInfo = extractTrefInfo(firstLine);
367
+ if (!trefInfo) {
368
+ results.push({
369
+ name: `Parse tref in ${file}`,
370
+ success: false,
371
+ details: `Could not parse tref information from first line: "${firstLine}"`
372
+ });
373
+ continue;
374
+ }
375
+
376
+ const { repo, term } = trefInfo;
377
+
378
+ // Check if the referenced repo exists in external_specs
379
+ const specConfig = externalSpecs.find(spec => spec.external_spec === repo);
380
+ if (!specConfig) {
381
+ results.push({
382
+ name: `Check repo reference in ${file}`,
383
+ success: false,
384
+ details: `Referenced repo "${repo}" is not defined in external_specs`
385
+ });
386
+ continue;
387
+ }
388
+
389
+ // Find the cache file for this repo
390
+ const cacheFile = findCacheFileForRepo(githubCacheDir, specConfig);
391
+
392
+ if (!cacheFile) {
393
+ results.push({
394
+ name: `Find cache for repo "${repo}" referenced in ${file}`,
395
+ success: false,
396
+ details: `No cache file found for repo "${repo}". Unable to verify if term "${term}" exists.`
397
+ });
398
+ continue;
399
+ }
400
+
401
+ // Check if the term exists in the repo
402
+ const termExists = termExistsInRepo(cacheFile, term);
403
+
404
+ if (termExists) {
405
+ validTerms++;
406
+ results.push({
407
+ name: `Term "${term}" in repo "${repo}" (${file})`,
408
+ success: true,
409
+ details: `Term "${term}" found in repo "${repo}"`
410
+ });
411
+ } else {
412
+ // Check if the term exists in other repos
413
+ const otherRepos = findTermInOtherRepos(githubCacheDir, externalSpecs, repo, term);
414
+
415
+ if (otherRepos.length > 0) {
416
+ // Change: Show as warning (partial success) instead of failure
417
+ termsFoundInOtherRepos++;
418
+ results.push({
419
+ name: `Term "${term}" in repo "${repo}" (${file})`,
420
+ success: 'partial', // Use 'partial' to indicate a warning
421
+ details: `Warning: Term <code>${term}</code> NOT found in repo ${repo} but found in these repos: <code>${otherRepos.join(', ')}</code>. Consider updating the reference.`
422
+ });
423
+ } else {
424
+ invalidTerms++;
425
+ results.push({
426
+ name: `Term "${term}" in repo "${repo}" (${file})`,
427
+ success: false,
428
+ details: `Term <code>${term}</code> NOT found in repo <code>${repo}</code> and not found in any other external repos`
429
+ });
430
+ }
431
+ }
432
+ } catch (error) {
433
+ results.push({
434
+ name: `Process file ${file}`,
435
+ success: false,
436
+ details: `Error processing file: ${error.message}`
437
+ });
438
+ }
439
+ }
440
+ }
441
+
442
+ // Add summary results - modified to include terms found in other repos
443
+ results.push({
444
+ name: 'Term reference validation summary',
445
+ success: invalidTerms === 0,
446
+ details: `Processed ${totalFiles} files, found ${filesWithTref} with tref tags. ${validTerms} terms found correctly, ${termsFoundInOtherRepos} terms found in alternative repos (warnings), ${invalidTerms} terms missing.`
447
+ });
448
+
449
+ return results;
450
+ } catch (error) {
451
+ console.error('Error checking tref terms:', error);
452
+ return [{
453
+ name: 'Term reference validation check',
454
+ success: false,
455
+ details: `Error: ${error.message}`
456
+ }];
457
+ }
458
+ }
459
+
460
+ module.exports = {
461
+ checkTrefTerms
462
+ };