spec-up-t 1.2.7 → 1.2.9

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.
@@ -1,5 +1,47 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ /**
4
+ * @fileoverview Spec-Up-T Health Check Tool
5
+ *
6
+ * This script performs comprehensive health checks on Spec-Up-T projects,
7
+ * validating configuration, external references, term definitions, and more.
8
+ * Generates an HTML report with detailed results and actionable feedback.
9
+ *
10
+ * @author Spec-Up-T Team
11
+ * @version 1.0.0
12
+ * @since 2025-06-06
13
+ */
14
+
15
+ /**
16
+ * @typedef {Object} HealthCheckResult
17
+ * @property {string} name - Name of the specific check
18
+ * @property {boolean|string} success - Success status (true, false, or 'partial')
19
+ * @property {string} [status] - Status override ('warning', 'pass', 'fail')
20
+ * @property {string} [details] - Additional details about the check result
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} HealthCheckSection
25
+ * @property {string} title - Title of the check section
26
+ * @property {HealthCheckResult[]} results - Array of individual check results
27
+ */
28
+
29
+ /**
30
+ * @typedef {Object} RepositoryInfo
31
+ * @property {string} host - Git hosting service (e.g., 'github')
32
+ * @property {string} account - Account/organization name
33
+ * @property {string} repo - Repository name
34
+ * @property {string} [branch] - Branch name
35
+ * @property {boolean} [verified] - Whether repository existence was verified
36
+ */
37
+
38
+ /**
39
+ * @typedef {Object} StatusDisplay
40
+ * @property {string} statusClass - CSS class for styling
41
+ * @property {string} statusIcon - HTML icon element
42
+ * @property {string} statusText - Display text for status
43
+ */
44
+
3
45
  const fs = require('fs');
4
46
  const path = require('path');
5
47
  const https = require('https');
@@ -13,7 +55,10 @@ const termsIntroChecker = require('./health-check/terms-intro-checker');
13
55
  const destinationGitignoreChecker = require('./health-check/destination-gitignore-checker');
14
56
  const trefTermChecker = require('./health-check/tref-term-checker');
15
57
 
16
- // Configuration
58
+ /**
59
+ * Directory where health check reports are generated
60
+ * @constant {string}
61
+ */
17
62
  const OUTPUT_DIR = path.join(process.cwd(), '.cache');
18
63
 
19
64
  // Create output directory if it doesn't exist
@@ -21,7 +66,21 @@ if (!fs.existsSync(OUTPUT_DIR)) {
21
66
  fs.mkdirSync(OUTPUT_DIR, { recursive: true });
22
67
  }
23
68
 
24
- // Helper function to read specs.json file
69
+ /**
70
+ * Retrieves repository information from specs.json file
71
+ *
72
+ * Reads the specs.json file to extract repository configuration including
73
+ * host, account, repo name, and branch. Falls back to default values if
74
+ * the file doesn't exist or is missing required fields.
75
+ *
76
+ * @async
77
+ * @function getRepoInfo
78
+ * @returns {Promise<RepositoryInfo>} Repository information object
79
+ *
80
+ * @example
81
+ * const repoInfo = await getRepoInfo();
82
+ * console.log(repoInfo.account); // 'blockchain-bird'
83
+ */
25
84
  async function getRepoInfo() {
26
85
  try {
27
86
  // Path to the default boilerplate specs.json
@@ -132,7 +191,24 @@ async function getRepoInfo() {
132
191
  };
133
192
  }
134
193
 
135
- // Helper function to check if a repository exists
194
+ /**
195
+ * Checks if a Git repository exists and is accessible
196
+ *
197
+ * Makes an HTTP HEAD request to verify repository existence without
198
+ * downloading the full repository content. Handles timeouts and errors gracefully.
199
+ *
200
+ * @function checkRepositoryExists
201
+ * @param {string} host - Git hosting service (e.g., 'github')
202
+ * @param {string} account - Account or organization name
203
+ * @param {string} repo - Repository name
204
+ * @returns {Promise<boolean>} True if repository exists and is accessible
205
+ *
206
+ * @example
207
+ * const exists = await checkRepositoryExists('github', 'blockchain-bird', 'spec-up-t');
208
+ * if (exists) {
209
+ * console.log('Repository is accessible');
210
+ * }
211
+ */
136
212
  function checkRepositoryExists(host, account, repo) {
137
213
  return new Promise((resolve) => {
138
214
  const url = `https://${host}.com/${account}/${repo}`;
@@ -163,7 +239,19 @@ function checkRepositoryExists(host, account, repo) {
163
239
  });
164
240
  }
165
241
 
166
- // Helper function to format current time for the filename
242
+ /**
243
+ * Formats current timestamp for use in filenames
244
+ *
245
+ * Generates a timestamp string that is safe to use in filenames by
246
+ * replacing special characters with hyphens. Format: YYYY-MM-DD-HH-mm-ssZ
247
+ *
248
+ * @function getFormattedTimestamp
249
+ * @returns {string} Formatted timestamp string suitable for filenames
250
+ *
251
+ * @example
252
+ * const timestamp = getFormattedTimestamp();
253
+ * console.log(timestamp); // "2025-06-06-14-30-25Z"
254
+ */
167
255
  function getFormattedTimestamp() {
168
256
  const now = new Date();
169
257
  return now.toISOString()
@@ -172,12 +260,37 @@ function getFormattedTimestamp() {
172
260
  .replace(/Z/g, 'Z');
173
261
  }
174
262
 
175
- // Helper function to generate a human-readable timestamp for display
263
+ /**
264
+ * Generates a human-readable timestamp for display in reports
265
+ *
266
+ * Creates a localized timestamp string for display purposes,
267
+ * using the system's default locale and timezone.
268
+ *
269
+ * @function getHumanReadableTimestamp
270
+ * @returns {string} Human-readable timestamp string
271
+ *
272
+ * @example
273
+ * const readable = getHumanReadableTimestamp();
274
+ * console.log(readable); // "6/6/2025, 2:30:25 PM"
275
+ */
176
276
  function getHumanReadableTimestamp() {
177
277
  return new Date().toLocaleString();
178
278
  }
179
279
 
180
- // Helper function to determine status display parameters based on result
280
+ /**
281
+ * Determines status display parameters based on check result
282
+ *
283
+ * Analyzes check results to determine appropriate CSS classes,
284
+ * icons, and text for visual status representation in the HTML report.
285
+ *
286
+ * @function getStatusDisplay
287
+ * @param {HealthCheckResult} result - Check result object
288
+ * @returns {StatusDisplay} Status display configuration
289
+ *
290
+ * @example
291
+ * const display = getStatusDisplay({ success: true });
292
+ * console.log(display.statusText); // "Pass"
293
+ */
181
294
  function getStatusDisplay(result) {
182
295
  if (result.status === 'warning' || result.success === 'partial') {
183
296
  // Warning status
@@ -203,7 +316,34 @@ function getStatusDisplay(result) {
203
316
  }
204
317
  }
205
318
 
206
- // Main function to run all checks and generate the report
319
+ /**
320
+ * Main function to run all health checks and generate the report
321
+ *
322
+ * Orchestrates the execution of all available health check modules,
323
+ * collects results, and generates a comprehensive HTML report.
324
+ * Handles errors gracefully and ensures proper cleanup.
325
+ *
326
+ * @async
327
+ * @function runHealthCheck
328
+ * @throws {Error} When health check execution fails
329
+ *
330
+ * @description
331
+ * The function performs the following checks:
332
+ * - Term reference checks in external specs
333
+ * - External specs URL validation
334
+ * - Term references validation
335
+ * - specs.json configuration validation
336
+ * - Terms introduction file validation
337
+ * - .gitignore destination directory check
338
+ *
339
+ * @example
340
+ * try {
341
+ * await runHealthCheck();
342
+ * console.log('Health check completed successfully');
343
+ * } catch (error) {
344
+ * console.error('Health check failed:', error);
345
+ * }
346
+ */
207
347
  async function runHealthCheck() {
208
348
  console.log('Running health checks...');
209
349
 
@@ -264,7 +404,27 @@ async function runHealthCheck() {
264
404
  }
265
405
  }
266
406
 
267
- // Generate HTML report
407
+ /**
408
+ * Generates and opens an HTML health check report
409
+ *
410
+ * Creates a comprehensive HTML report with all health check results,
411
+ * saves it to the cache directory, and opens it in the default browser.
412
+ * The report includes interactive features like filtering and status indicators.
413
+ *
414
+ * @async
415
+ * @function generateReport
416
+ * @param {HealthCheckSection[]} checkResults - Array of health check result objects
417
+ * @throws {Error} When report generation or file operations fail
418
+ *
419
+ * @example
420
+ * const results = [
421
+ * {
422
+ * title: 'Configuration Check',
423
+ * results: [{ name: 'specs.json', success: true, details: 'Valid' }]
424
+ * }
425
+ * ];
426
+ * await generateReport(results);
427
+ */
268
428
  async function generateReport(checkResults) {
269
429
  const timestamp = getFormattedTimestamp();
270
430
  // Get repository information from specs.json
@@ -289,7 +449,26 @@ async function generateReport(checkResults) {
289
449
  }
290
450
  }
291
451
 
292
- // Generate HTML content
452
+ /**
453
+ * Generates HTML content for the health check report
454
+ *
455
+ * Creates a complete HTML document with Bootstrap styling, interactive features,
456
+ * and comprehensive health check results. Includes repository verification,
457
+ * status filtering, and detailed result tables.
458
+ *
459
+ * @function generateHtmlReport
460
+ * @param {HealthCheckSection[]} checkResults - Array of health check result sections
461
+ * @param {string} timestamp - Human-readable timestamp for the report
462
+ * @param {RepositoryInfo} repoInfo - Repository information object
463
+ * @returns {string} Complete HTML document as string
464
+ *
465
+ * @example
466
+ * const html = generateHtmlReport(results, '6/6/2025, 2:30:25 PM', {
467
+ * host: 'github',
468
+ * account: 'blockchain-bird',
469
+ * repo: 'spec-up-t'
470
+ * });
471
+ */
293
472
  function generateHtmlReport(checkResults, timestamp, repoInfo) {
294
473
  let resultsHtml = '';
295
474
 
@@ -457,5 +636,15 @@ function generateHtmlReport(checkResults, timestamp, repoInfo) {
457
636
  `;
458
637
  }
459
638
 
460
- // Run the health check
639
+ /**
640
+ * Script execution entry point
641
+ *
642
+ * Immediately executes the health check when this script is run directly.
643
+ * This allows the script to be used as a standalone command-line tool.
644
+ *
645
+ * @example
646
+ * // Run from command line:
647
+ * // node src/health-check.js
648
+ * // npm run healthCheck
649
+ */
461
650
  runHealthCheck();
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { ESCAPED_PLACEHOLDER } = require('./escape-handler');
4
+
3
5
  /**
4
6
  * Configuration for custom template syntax [[example]] used throughout the markdown parsing
5
7
  * These constants define how template markers are identified and processed
@@ -64,6 +66,12 @@ module.exports = function (md, templates = {}) {
64
66
  md.inline.ruler.after('emphasis', 'templates', function templates_ruler(state, silent) {
65
67
  // Get the current parsing position
66
68
  var start = state.pos;
69
+
70
+ // Check if we're at an escaped placeholder - if so, skip processing
71
+ if (state.src.slice(start, start + ESCAPED_PLACEHOLDER.length) === ESCAPED_PLACEHOLDER) {
72
+ return false;
73
+ }
74
+
67
75
  // Check if we're at a template opening marker
68
76
  let prefix = state.src.slice(start, start + levels);
69
77
  if (prefix !== openString) return false;
@@ -1,340 +0,0 @@
1
- /**
2
- * @file fetchTermsFromIndex.js
3
- * @description Fetches terms and definitions from external repository's index.html
4
- * @author Generated with assistance from GitHub Copilot
5
- * @version 1.0.0
6
- * @since 2025-04-15
7
- */
8
-
9
- const fs = require('fs');
10
- const path = require('path');
11
- const { JSDOM } = require('jsdom');
12
- const axios = require('axios');
13
- const { addPath, getPath, getAllPaths } = require('../../config/paths');
14
- const crypto = require('crypto');
15
-
16
- // Directory to store cached files
17
- const CACHE_DIR = getPath('githubcache');
18
-
19
- /**
20
- * Generates a cache key based on repository information
21
- * @param {string} owner - Repository owner
22
- * @param {string} repo - Repository name
23
- * @returns {string} - Cache key
24
- */
25
- function generateCacheKey(owner, repo) {
26
- const input = `${owner}-${repo}-index`;
27
- return crypto.createHash('md5').update(input).digest('hex');
28
- }
29
-
30
- /**
31
- * Checks if a cached version exists and is valid
32
- * @param {string} cacheKey - Cache key
33
- * @param {object} options - Options object
34
- * @param {number} options.cacheTTL - Time-to-live for cache in milliseconds (default: 24 hours)
35
- * @returns {object|null} - Cached data or null if not found or expired
36
- * @example
37
- * const cacheTTL = options.cacheTTL || 24 * 60 * 60 * 1000; // Default: 24 hours
38
- */
39
- function getFromCache(cacheKey, options = {}) {
40
- const cachePath = path.join(CACHE_DIR, `${cacheKey}.json`);
41
- // Use default 24h TTL instead of 0
42
- const cacheTTL = options.cacheTTL || 24 * 60 * 60 * 1000;
43
-
44
- // Early return if file doesn't exist
45
- if (!fs.existsSync(cachePath)) {
46
- return null;
47
- }
48
-
49
- // Try to read and parse the cache file
50
- let cacheData;
51
- try {
52
- cacheData = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
53
- } catch (error) {
54
- console.log(`Error reading cache for key: ${cacheKey}`);
55
- return null;
56
- }
57
-
58
- // Check if cache has timestamp and is not expired
59
- const cacheTime = new Date(cacheData.timestamp).getTime();
60
- const currentTime = new Date().getTime();
61
- const isExpired = currentTime - cacheTime > cacheTTL;
62
-
63
- if (isExpired) {
64
- console.log(`Cache expired for key: ${cacheKey}`);
65
- return null;
66
- }
67
-
68
- console.log(`Using cached data for key: ${cacheKey}`);
69
- return cacheData;
70
- }
71
-
72
- /**
73
- * Saves data to cache
74
- * @param {string} cacheKey - Cache key
75
- * @param {object} data - Data to cache
76
- */
77
- function saveToCache(cacheKey, data) {
78
- const cachePath = path.join(CACHE_DIR, `${cacheKey}.json`);
79
- const cacheData = {
80
- timestamp: new Date().toISOString(),
81
- ...data
82
- };
83
- fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2));
84
- console.log(`Saved to cache: ${cacheKey}`);
85
- }
86
-
87
- /**
88
- * Fetches the latest commit hash for a specific file in a repository
89
- * @param {string} token - GitHub API Token
90
- * @param {string} owner - Repository owner
91
- * @param {string} repo - Repository name
92
- * @param {string} filePath - Path to the file in the repository
93
- * @param {object} headers - Request headers
94
- * @returns {string|null} - Latest commit hash or null if error
95
- */
96
- async function getFileCommitHash(token, owner, repo, filePath, headers) {
97
- try {
98
- // Normalize the file path to ensure it doesn't have leading slash
99
- const normalizedPath = filePath.replace(/^\//, '');
100
-
101
- // Construct API URL to get commits for the specific file
102
- const commitsUrl = `https://api.github.com/repos/${owner}/${repo}/commits?path=${normalizedPath}&per_page=1`;
103
- console.log(`Fetching latest commit for file: ${commitsUrl}`);
104
-
105
- const response = await axios.get(commitsUrl, { headers });
106
-
107
- if (response.status !== 200 || !response.data || response.data.length === 0) {
108
- console.log(`❌ Could not find commit information for ${filePath}`);
109
- return null;
110
- }
111
-
112
- // Return the SHA of the latest commit
113
- return response.data[0].sha;
114
- } catch (error) {
115
- console.error(`❌ Error fetching commit hash: ${error.message}`);
116
- return null;
117
- }
118
- }
119
-
120
- /**
121
- * Fetches all terms and definitions from a repository's index.html
122
- * @param {string} token - GitHub API Token
123
- * @param {string} owner - Repository owner
124
- * @param {string} repo - Repository name
125
- * @param {object} options - Additional options
126
- * @returns {object|null} - Object containing all terms or null if error
127
- */
128
- async function fetchAllTermsFromIndex(token, owner, repo, options = {}) {
129
- try {
130
- // Generate cache key based on repo information
131
- const cacheKey = generateCacheKey(owner, repo);
132
- let cachedData = null;
133
-
134
- // Check cache first if caching is enabled
135
- if (options.cache !== false) {
136
- cachedData = getFromCache(cacheKey, options);
137
- if (cachedData) {
138
- return cachedData;
139
- }
140
- }
141
-
142
- // Configure headers for GitHub API
143
- const headers = {};
144
- if (token) {
145
- headers['Authorization'] = `token ${token}`;
146
- }
147
-
148
- // Get the specs.json content from the repository to find the output_path
149
- const specsJsonUrl = `https://api.github.com/repos/${owner}/${repo}/contents/specs.json`;
150
- console.log(`Fetching specs.json from: ${specsJsonUrl}`);
151
-
152
- // Fetch specs.json content
153
- const specsJsonResponse = await axios.get(specsJsonUrl, { headers });
154
- if (specsJsonResponse.status !== 200) {
155
- console.log(`❌ Could not find specs.json in repository ${owner}/${repo}`);
156
- return null;
157
- }
158
-
159
- // Decode specs.json content from base64
160
- const specsJsonContent = Buffer.from(specsJsonResponse.data.content, 'base64').toString('utf8');
161
- const specsJson = JSON.parse(specsJsonContent);
162
-
163
- // Get the output_path from specs.json
164
- const outputPath = specsJson.specs[0].output_path;
165
- if (!outputPath) {
166
- console.log(`❌ No output_path found in specs.json for repository ${owner}/${repo}`);
167
- return null;
168
- }
169
-
170
- // Fix: Properly normalize the output path to ensure it doesn't have leading "./" or trailing "/"
171
- const normalizedOutputPath = outputPath.replace(/^\.\//, '').replace(/\/$/, '');
172
-
173
- // Create the path to the index.html file
174
- const indexHtmlPath = `${normalizedOutputPath}/index.html`;
175
-
176
- // Fetch the index.html content with properly constructed URL
177
- const indexHtmlUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main/${indexHtmlPath}`;
178
- console.log(`Fetching index.html from: ${indexHtmlUrl}`);
179
-
180
- const indexHtmlResponse = await axios.get(indexHtmlUrl, { headers });
181
- if (indexHtmlResponse.status !== 200) {
182
- console.log(`❌ Could not find index.html at ${indexHtmlUrl}`);
183
- return null;
184
- }
185
-
186
- // Get the commit hash for the index.html file
187
- const commitHash = await getFileCommitHash(token, owner, repo, indexHtmlPath, headers);
188
- if (!commitHash) {
189
- console.log(`⚠️ Could not get commit hash for index.html, continuing without it`);
190
- }
191
-
192
- const htmlContent = indexHtmlResponse.data;
193
-
194
- // Parse HTML using JSDOM
195
- const dom = new JSDOM(htmlContent);
196
- const document = dom.window.document;
197
-
198
- // Find all term definition lists with class "terms-and-definitions-list"
199
- const termDlList = document.querySelector('dl.terms-and-definitions-list');
200
- if (!termDlList) {
201
- console.log(`❌ No terms-and-definitions-list found in ${indexHtmlUrl}`);
202
- return null;
203
- }
204
-
205
- // Extract all terms and definitions
206
- const terms = [];
207
- let dtElements = termDlList.querySelectorAll('dt');
208
-
209
- dtElements.forEach(dt => {
210
- // Find the term span that starts with id="term:
211
- const termSpan = dt.querySelector('span[id^="term:"]');
212
- if (!termSpan) return;
213
-
214
- // Get the term text (all text content, excluding nested spans)
215
- let termText = '';
216
- for (let node of termSpan.childNodes) {
217
- if (node.nodeType === dom.window.Node.TEXT_NODE) {
218
- termText += node.textContent.trim();
219
- }
220
- }
221
-
222
- // If no text found, try to get the full text content
223
- if (!termText) {
224
- termText = termSpan.textContent.trim();
225
- }
226
-
227
- // Skip empty terms
228
- if (!termText) return;
229
-
230
- // Find all corresponding definition elements
231
- let dds = [];
232
- let nextElement = dt.nextElementSibling;
233
-
234
- // Collect all consecutive <dd> elements until we reach another <dt>
235
- while (nextElement && nextElement.tagName.toLowerCase() === 'dd') {
236
- dds.push(nextElement.outerHTML);
237
- nextElement = nextElement.nextElementSibling;
238
- }
239
-
240
- terms.push({
241
- term: termText,
242
- definition: dds.join('\n')
243
- });
244
- });
245
-
246
- // Store all terms in a JSON file with timestamp
247
- const timestamp = Date.now();
248
- const outputDir = path.join(CACHE_DIR);
249
- if (!fs.existsSync(outputDir)) {
250
- fs.mkdirSync(outputDir, { recursive: true });
251
- }
252
-
253
- // Create output filename with timestamp
254
- const outputFileName = `${timestamp}-${owner}-${repo}-terms.json`;
255
- const outputFilePath = path.join(outputDir, outputFileName);
256
-
257
- // Create the result object
258
- const result = {
259
- timestamp,
260
- repository: `${owner}/${repo}`,
261
- terms,
262
- sha: commitHash, // Use the commit hash of the index.html file
263
- avatarUrl: null,
264
- outputFileName
265
- };
266
-
267
- // Save all terms to file
268
- fs.writeFileSync(outputFilePath, JSON.stringify(result, null, 2));
269
- console.log(`✅ Saved ${terms.length} terms to ${outputFilePath}`);
270
-
271
- // Save to cache if enabled
272
- if (options.cache !== false) {
273
- saveToCache(cacheKey, result);
274
- }
275
-
276
- return result;
277
-
278
- } catch (error) {
279
- if (error.response) {
280
- if (error.response.status === 404) {
281
- console.log(`❌ Resource not found: ${error.config.url}`);
282
- } else if (error.response.status === 403 && error.response.headers['x-ratelimit-remaining'] === '0') {
283
- const resetTime = new Date(parseInt(error.response.headers['x-ratelimit-reset']) * 1000);
284
- console.error(`❌ GitHub API rate limit exceeded. Try again after ${resetTime.toLocaleString()}`);
285
- } else {
286
- console.error(`❌ Error fetching data: ${error.response.status} ${error.response.statusText}`);
287
- }
288
- } else {
289
- console.error(`❌ Error fetching term: ${error.message}`);
290
- }
291
- return null;
292
- }
293
- }
294
-
295
- /**
296
- * Fetches a specific term from repository's index.html
297
- * This is a wrapper that uses fetchAllTermsFromIndex for efficiency
298
- * @param {string} token - GitHub API Token
299
- * @param {string} term - The specific term to look for
300
- * @param {string} owner - Repository owner
301
- * @param {string} repo - Repository name
302
- * @param {string} termsDir - Directory containing term definitions (not used in this implementation)
303
- * @param {object} options - Additional options
304
- * @returns {object|null} - Found term data or null if not found
305
- */
306
- async function fetchTermsFromIndex(token, term, owner, repo, termsDir, options = {}) {
307
- // First get all terms from the repository (which is cached)
308
- const allTermsData = await fetchAllTermsFromIndex(token, owner, repo, options);
309
-
310
- if (!allTermsData || !allTermsData.terms) {
311
- return null;
312
- }
313
-
314
- // Find the specific term
315
- const foundTerm = allTermsData.terms.find(t => t.term.toLowerCase() === term.toLowerCase());
316
-
317
- if (foundTerm) {
318
- console.log(`Found term '${term}' in repository ${owner}/${repo}`);
319
- return {
320
- term: foundTerm.term,
321
- content: foundTerm.definition,
322
- sha: allTermsData.sha,
323
- repository: {
324
- owner: {
325
- login: owner,
326
- avatar_url: allTermsData.avatarUrl
327
- },
328
- name: repo
329
- }
330
- };
331
- } else {
332
- console.log(`❌ Term "${term}" not found in repository ${owner}/${repo}`);
333
- return null;
334
- }
335
- }
336
-
337
- module.exports = {
338
- fetchTermsFromIndex,
339
- fetchAllTermsFromIndex // Export the function to fetch all terms as well
340
- };