@stackql/provider-utils 0.5.5 → 0.5.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackql/provider-utils",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "Utilities for building StackQL providers from OpenAPI specifications.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -3,7 +3,7 @@
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
5
  import yaml from 'js-yaml';
6
- import { createResourceIndexContent } from './resource-content.js';
6
+ import { createResourceIndexContent, createResourceIndexContentv2 } from './resource-content.js';
7
7
  import SwaggerParser from '@apidevtools/swagger-parser';
8
8
  import * as deno_openapi_dereferencer from "@stackql/deno-openapi-dereferencer";
9
9
 
@@ -284,6 +284,224 @@ function generateResourceLinks(providerName, serviceName, resources) {
284
284
  return resourceLinks.join('<br />\n');
285
285
  }
286
286
 
287
+ export async function generateDocsv2(options) {
288
+ const {
289
+ providerName,
290
+ providerDir, // e.g., 'output/src/heroku/v00.00.00000'
291
+ outputDir, // e.g., 'website'
292
+ providerDataDir, // e.g., 'config/provider-data'
293
+ dereferenced = false,
294
+ succinct = false, // use summary instead of description for method/example descriptions
295
+ } = options;
296
+
297
+ console.log(`documenting ${providerName} (v2)...`);
298
+
299
+ const docsDir = path.join(outputDir, `docs`);
300
+ const servicesDir = path.join(docsDir, `services`);
301
+
302
+ // Remove directory if it exists, then create it fresh
303
+ fs.existsSync(docsDir) && fs.rmSync(docsDir, { recursive: true, force: true });
304
+ fs.mkdirSync(servicesDir, { recursive: true });
305
+
306
+ // Check for provider data files
307
+ console.log(providerDataDir);
308
+ try {
309
+ const files = fs.readdirSync(providerDataDir);
310
+ console.log('Files in providerDataDir:', files);
311
+ } catch (err) {
312
+ console.error('Error reading providerDataDir:', err.message);
313
+ }
314
+
315
+ const headerContent1Path = path.join(providerDataDir, 'headerContent1.txt');
316
+ const headerContent2Path = path.join(providerDataDir, 'headerContent2.txt');
317
+
318
+ if (!fs.existsSync(headerContent1Path) || !fs.existsSync(headerContent2Path)) {
319
+ throw new Error(`Missing headerContent1.txt or headerContent2.txt in ${providerDataDir}`);
320
+ }
321
+
322
+ const headerContent1 = fs.readFileSync(headerContent1Path, 'utf8');
323
+ const headerContent2 = fs.readFileSync(headerContent2Path, 'utf8');
324
+
325
+ // Initialize counters
326
+ let servicesForIndex = [];
327
+ let totalServicesCount = 0;
328
+ let totalResourcesCount = 0;
329
+
330
+ // Process services
331
+ const serviceDir = path.join(providerDir, 'services');
332
+ console.log(`Processing services in ${serviceDir}...`);
333
+ const serviceFiles = fs.readdirSync(serviceDir).filter(file => path.extname(file) === '.yaml');
334
+
335
+ for (const file of serviceFiles) {
336
+ const serviceName = path.basename(file, '.yaml').replace(/-/g, '_');
337
+ console.log(`Processing service: ${serviceName}`);
338
+ servicesForIndex.push(serviceName);
339
+ const filePath = path.join(serviceDir, file);
340
+ totalServicesCount++;
341
+ const serviceFolder = `${servicesDir}/${serviceName}`;
342
+ await createDocsForServicev2(filePath, providerName, serviceName, serviceFolder, dereferenced, succinct);
343
+ }
344
+
345
+ console.log(`Processed ${totalServicesCount} services`);
346
+
347
+ // Count total resources
348
+ totalResourcesCount = fs.readdirSync(`${servicesDir}`, { withFileTypes: true })
349
+ .filter(dirent => dirent.isDirectory())
350
+ .map(dirent => fs.readdirSync(`${servicesDir}/${dirent.name}`).length)
351
+ .reduce((a, b) => a + b, 0);
352
+
353
+ console.log(`Processed ${totalResourcesCount} resources`);
354
+
355
+ // Create provider index
356
+ servicesForIndex = [...new Set(servicesForIndex)];
357
+ servicesForIndex.sort();
358
+
359
+ const half = Math.ceil(servicesForIndex.length / 2);
360
+ const firstColumnServices = servicesForIndex.slice(0, half);
361
+ const secondColumnServices = servicesForIndex.slice(half);
362
+
363
+ const indexContent = `${headerContent1}
364
+
365
+ :::info[Provider Summary]
366
+
367
+ total services: __${totalServicesCount}__
368
+ total resources: __${totalResourcesCount}__
369
+
370
+ :::
371
+
372
+ ${headerContent2}
373
+
374
+ ## Services
375
+ <div class="row">
376
+ <div class="providerDocColumn">
377
+ ${servicesToMarkdown(providerName, firstColumnServices)}
378
+ </div>
379
+ <div class="providerDocColumn">
380
+ ${servicesToMarkdown(providerName, secondColumnServices)}
381
+ </div>
382
+ </div>
383
+ `;
384
+
385
+ // Write index
386
+ const indexPath = path.join(docsDir, 'index.md');
387
+ fs.writeFileSync(indexPath, indexContent);
388
+ console.log(`Index file created at ${indexPath}`);
389
+
390
+ return {
391
+ totalServices: totalServicesCount,
392
+ totalResources: totalResourcesCount,
393
+ outputPath: docsDir
394
+ };
395
+ }
396
+
397
+ // v2 service processing - uses SchemaTable for collapsible nested fields
398
+ async function createDocsForServicev2(yamlFilePath, providerName, serviceName, serviceFolder, dereferenced = false, succinct = false) {
399
+
400
+ const data = yaml.load(fs.readFileSync(yamlFilePath, 'utf8'));
401
+
402
+ // Create a new SwaggerParser instance
403
+ let parser = new SwaggerParser();
404
+ const api = await parser.parse(yamlFilePath);
405
+ const ignorePaths = ["$.components.x-stackQL-resources"];
406
+ let dereferencedAPI;
407
+
408
+ if (dereferenced) {
409
+ // If API is already dereferenced, just use it as is
410
+ dereferencedAPI = api;
411
+ } else {
412
+ try {
413
+ // Only dereference and flatten if needed
414
+ dereferencedAPI = await SwaggerParser.dereference(api);
415
+ dereferencedAPI = await deno_openapi_dereferencer.flattenAllOf(dereferencedAPI);
416
+ } catch (error) {
417
+ console.error("error in dereferencing or flattening:", error);
418
+ }
419
+ }
420
+
421
+ // Create service directory
422
+ if (!fs.existsSync(serviceFolder)) {
423
+ fs.mkdirSync(serviceFolder, { recursive: true });
424
+ }
425
+
426
+ const resourcesObj = data.components['x-stackQL-resources'];
427
+
428
+ if (!resourcesObj) {
429
+ console.warn(`No resources found in ${yamlFilePath}`);
430
+ return;
431
+ }
432
+
433
+ const resources = [];
434
+ for (let resourceName in resourcesObj) {
435
+
436
+ let resourceData = resourcesObj[resourceName];
437
+ if (!resourceData.id) {
438
+ console.warn(`No 'id' defined for resource: ${resourceName} in service: ${serviceName}`);
439
+ continue;
440
+ }
441
+
442
+ const resourceDescription = resourceData.description || '';
443
+
444
+ // Determine if it's a View or a Resource
445
+ let resourceType = "Resource"; // Default type
446
+ if (resourceData.config?.views?.select) {
447
+ resourceType = "View";
448
+ }
449
+
450
+ resources.push({
451
+ name: resourceName,
452
+ description: resourceDescription,
453
+ type: resourceType,
454
+ resourceData,
455
+ dereferencedAPI,
456
+ });
457
+ }
458
+
459
+ // Process service index
460
+ const serviceIndexPath = path.join(serviceFolder, 'index.md');
461
+ const serviceIndexContent = await createServiceIndexContent(providerName, serviceName, resources);
462
+ fs.writeFileSync(serviceIndexPath, serviceIndexContent);
463
+
464
+ // Split into columns and process resources one by one
465
+ const halfLength = Math.ceil(resources.length / 2);
466
+ const firstColumn = resources.slice(0, halfLength);
467
+ const secondColumn = resources.slice(halfLength);
468
+
469
+ // Process each resource in first column
470
+ for (const resource of firstColumn) {
471
+ await processResourcev2(providerName, serviceFolder, serviceName, resource, succinct);
472
+ }
473
+
474
+ // Process each resource in second column
475
+ for (const resource of secondColumn) {
476
+ await processResourcev2(providerName, serviceFolder, serviceName, resource, succinct);
477
+ }
478
+
479
+ console.log(`Generated documentation (v2) for ${serviceName}`);
480
+ }
481
+
482
+ async function processResourcev2(providerName, serviceFolder, serviceName, resource, succinct = false) {
483
+ console.log(`Processing resource (v2): ${resource.name}`);
484
+
485
+ const resourceFolder = path.join(serviceFolder, resource.name);
486
+ if (!fs.existsSync(resourceFolder)) {
487
+ fs.mkdirSync(resourceFolder, { recursive: true });
488
+ }
489
+
490
+ const resourceIndexPath = path.join(resourceFolder, 'index.md');
491
+ const resourceIndexContent = await createResourceIndexContentv2(
492
+ providerName,
493
+ serviceName,
494
+ resource,
495
+ succinct,
496
+ );
497
+ fs.writeFileSync(resourceIndexPath, resourceIndexContent);
498
+
499
+ // After writing the file, force garbage collection if available (optional)
500
+ if (global.gc) {
501
+ global.gc();
502
+ }
503
+ }
504
+
287
505
  // Function to convert services to markdown links
288
506
  function servicesToMarkdown(providerName, servicesList) {
289
507
  return servicesList.map(service => `<a href="/services/${service}/">${service}</a><br />`).join('\n');
@@ -90,7 +90,7 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
90
90
  // Get all SQL verb methods
91
91
  const allSqlMethodNames = new Set();
92
92
  const sqlVerbTypes = ['select', 'insert', 'update', 'delete', 'replace'];
93
-
93
+
94
94
  for (const verb of sqlVerbTypes) {
95
95
  if (resourceData.sqlVerbs[verb] && resourceData.sqlVerbs[verb].length > 0) {
96
96
  for (const method of resourceData.sqlVerbs[verb]) {
@@ -99,32 +99,32 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
99
99
  }
100
100
  }
101
101
  }
102
-
102
+
103
103
  // Process each method that's not in any SQL verb
104
104
  for (const [methodName, methodData] of Object.entries(resourceData.methods)) {
105
105
  if (!allSqlMethodNames.has(methodName)) {
106
106
  const { path, httpVerb, mediaType, openAPIDocKey } = methodData.operation;
107
107
  let resolvedPath = path;
108
108
  let resolvedVerb = httpVerb;
109
-
109
+
110
110
  // If operation uses $ref, resolve it
111
111
  if (methodData.operation.$ref) {
112
112
  const refPath = methodData.operation.$ref;
113
-
113
+
114
114
  // Extract the path and verb from the $ref
115
115
  // The path format is typically '#/paths/~1api~1v2~1accounts~1{name}:undrop/post'
116
116
  const pathMatch = refPath.match(/#\/paths\/(.+)\/([^/]+)$/);
117
-
117
+
118
118
  if (pathMatch && pathMatch.length === 3) {
119
119
  // Replace the escaped characters in the path
120
120
  let path = pathMatch[1]
121
121
  .replace(/~1/g, '/') // Replace ~1 with /
122
122
  .replace(/~0/g, '~') // Replace ~0 with ~ if needed
123
-
123
+
124
124
  // Don't modify path parts with special characters like ':undrop'
125
125
  resolvedPath = path;
126
126
  resolvedVerb = pathMatch[2];
127
-
127
+
128
128
  console.log(`Resolved path: ${resolvedPath}, verb: ${resolvedVerb}`);
129
129
  } else {
130
130
  console.warn(`Could not parse $ref path: ${refPath}`);
@@ -134,39 +134,41 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
134
134
  }
135
135
 
136
136
  // Get response and params using the same function as for SQL verbs
137
- const { respProps, respDescription, opDescription, requestBody } = getHttpOperationInfo(
138
- dereferencedAPI,
139
- resolvedPath,
140
- resolvedVerb,
141
- methodData.response.mediaType || '',
137
+ const { respProps, respDescription, opDescription, opSummary, requestBody } = getHttpOperationInfo(
138
+ dereferencedAPI,
139
+ resolvedPath,
140
+ resolvedVerb,
141
+ methodData.response.mediaType || '',
142
142
  methodData.response.openAPIDocKey || '200',
143
143
  ''
144
144
  );
145
-
145
+
146
146
  const { requiredParams, optionalParams } = getHttpOperationParams(
147
- dereferencedAPI,
148
- resolvedPath,
147
+ dereferencedAPI,
148
+ resolvedPath,
149
149
  resolvedVerb
150
150
  );
151
-
151
+
152
152
  // Initialize the method with the same structure as SQL methods
153
153
  methods[methodName] = {
154
154
  opDescription,
155
+ opSummary,
155
156
  respDescription,
156
157
  properties: {},
157
158
  requiredParams: requiredParams || {},
158
159
  optionalParams: optionalParams || {},
159
160
  requestBody: requestBody || {},
161
+ rawRespProps: respProps,
160
162
  };
161
-
163
+
162
164
  // Format and sort the properties using our helper functions
163
165
  const allProperties = formatProperties(respProps);
164
166
  sortAndAddProperties(methods[methodName], allProperties);
165
-
167
+
166
168
  console.info(`Processed exec method: ${methodName}`);
167
169
  }
168
170
  }
169
-
171
+
170
172
  return methods;
171
173
  }
172
174
 
@@ -176,26 +178,28 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
176
178
 
177
179
  for (const thisMethod of resourceData.sqlVerbs[sqlVerb]) {
178
180
  const {path, httpVerb, mediaType, openAPIDocKey, objectKey, methodName} = getHttpOperationForSqlVerb(thisMethod.$ref, resourceData);
179
- const {respProps, respDescription, opDescription, requestBody} = getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAPIDocKey, objectKey);
181
+ const {respProps, respDescription, opDescription, opSummary, requestBody} = getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAPIDocKey, objectKey);
180
182
  const {requiredParams, optionalParams} = getHttpOperationParams(dereferencedAPI, path, httpVerb);
181
183
 
182
184
  // Initialize the method object with description and params
183
- methods[methodName] = {
185
+ methods[methodName] = {
184
186
  opDescription,
187
+ opSummary,
185
188
  respDescription,
186
189
  properties: {},
187
190
  requiredParams: requiredParams || {},
188
191
  optionalParams: optionalParams || {},
189
192
  requestBody: requestBody || {},
193
+ rawRespProps: respProps,
190
194
  };
191
-
195
+
192
196
  // Format and sort the properties using our helper functions
193
197
  const allProperties = formatProperties(respProps);
194
198
  sortAndAddProperties(methods[methodName], allProperties);
195
-
199
+
196
200
  console.info(`Processed method: ${methodName}`);
197
201
  }
198
-
202
+
199
203
  return methods;
200
204
  }
201
205
 
@@ -381,8 +385,9 @@ function getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAP
381
385
  throw new Error(`HTTP verb '${httpVerb}' not found for path '${path}'`);
382
386
  }
383
387
 
384
- // Get operation description
388
+ // Get operation description and summary
385
389
  const opDescription = (dereferencedAPI.paths[path][httpVerb].description || '');
390
+ const opSummary = (dereferencedAPI.paths[path][httpVerb].summary || '');
386
391
 
387
392
  // Extract request body if it exists
388
393
  let requestBody = {};
@@ -472,19 +477,21 @@ function getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAP
472
477
  respProps: {},
473
478
  respDescription: '',
474
479
  opDescription,
480
+ opSummary,
475
481
  requestBody
476
482
  };
477
483
  }
478
-
484
+
479
485
  // Check if there's a content section with the mediaType
480
486
  const responseObj = dereferencedAPI.paths[path][httpVerb].responses[openAPIDocKey];
481
-
487
+
482
488
  // If no content or no mediaType in the response, return empty properties
483
489
  if (!responseObj.content || !mediaType || !responseObj.content[mediaType] || !responseObj.content[mediaType].schema) {
484
490
  return {
485
491
  respProps: {},
486
492
  respDescription: responseObj.description || '',
487
493
  opDescription,
494
+ opSummary,
488
495
  requestBody
489
496
  };
490
497
  }
@@ -497,6 +504,7 @@ function getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAP
497
504
  respProps,
498
505
  respDescription: responseObj.description ? responseObj.description : respDescription,
499
506
  opDescription,
507
+ opSummary,
500
508
  requestBody
501
509
  };
502
510
  }
@@ -569,6 +577,81 @@ function getHttpRespBody(schema, objectKey) {
569
577
  }
570
578
  }
571
579
 
580
+ /**
581
+ * Recursively generates a nested JSON schema tree from response properties,
582
+ * suitable for use with the SchemaTable React component.
583
+ * Each node has: { name, type, description, children? }
584
+ * @param {Object} respProps - The raw response properties from the dereferenced API
585
+ * @param {number} depth - Current recursion depth
586
+ * @param {number} maxDepth - Maximum recursion depth to prevent infinite loops
587
+ * @returns {Array} Array of schema field objects
588
+ */
589
+ export function generateSchemaJsonFromProps(respProps, depth = 0, maxDepth = 4) {
590
+ if (depth >= maxDepth || !respProps || typeof respProps !== 'object') return [];
591
+
592
+ return Object.entries(respProps).map(([propName, prop]) => {
593
+ if (!prop) return null;
594
+
595
+ let propType = prop.type || 'object';
596
+ let propDesc = sanitizeHtml(prop.description || '');
597
+
598
+ // Add format info to type string if available
599
+ if (prop.format) {
600
+ propType += ` (${prop.format})`;
601
+ }
602
+
603
+ let children = [];
604
+
605
+ // Handle arrays - get children from items
606
+ if (prop.type === 'array' && prop.items) {
607
+ if (prop.items.properties) {
608
+ children = generateSchemaJsonFromProps(prop.items.properties, depth + 1, maxDepth);
609
+ }
610
+ }
611
+
612
+ // Handle objects - get children from properties
613
+ if (prop.type === 'object' && prop.properties) {
614
+ children = generateSchemaJsonFromProps(prop.properties, depth + 1, maxDepth);
615
+ }
616
+
617
+ return {
618
+ name: propName,
619
+ type: propType,
620
+ description: propDesc,
621
+ ...(children.length > 0 && { children })
622
+ };
623
+ }).filter(Boolean);
624
+ }
625
+
626
+ /**
627
+ * Sorts an array of schema field objects using the standard field priority:
628
+ * 1. Exact 'id' and 'name' fields
629
+ * 2. Fields ending with '_id'
630
+ * 3. Fields ending with '_name'
631
+ * 4. All other fields (alphabetical)
632
+ * @param {Array} fields - Array of schema field objects with 'name' property
633
+ * @returns {Array} Sorted array of schema field objects
634
+ */
635
+ export function sortSchemaFields(fields) {
636
+ if (!fields || fields.length === 0) return [];
637
+
638
+ const exactIdName = fields.filter(f => f.name === 'id' || f.name === 'name');
639
+ const idSuffix = fields.filter(f => f.name !== 'id' && f.name.endsWith('_id'));
640
+ const nameSuffix = fields.filter(f => f.name !== 'name' && f.name.endsWith('_name'));
641
+ const others = fields.filter(f =>
642
+ !exactIdName.includes(f) &&
643
+ !idSuffix.includes(f) &&
644
+ !nameSuffix.includes(f)
645
+ );
646
+
647
+ return [
648
+ ...exactIdName.sort((a, b) => a.name.localeCompare(b.name)),
649
+ ...idSuffix.sort((a, b) => a.name.localeCompare(b.name)),
650
+ ...nameSuffix.sort((a, b) => a.name.localeCompare(b.name)),
651
+ ...others.sort((a, b) => a.name.localeCompare(b.name)),
652
+ ];
653
+ }
654
+
572
655
  function getHttpOperationParams(dereferencedAPI, path, httpVerb) {
573
656
  const requiredParams = {};
574
657
  const optionalParams = {};
@@ -1,5 +1,5 @@
1
1
  // src/docgen/index.js
2
2
 
3
3
  // Export all documentation generation functions
4
- export { generateDocs } from './generator.js';
5
- export { createResourceIndexContent } from './resource-content.js';
4
+ export { generateDocs, generateDocsv2 } from './generator.js';
5
+ export { createResourceIndexContent, createResourceIndexContentv2 } from './resource-content.js';
@@ -4,7 +4,7 @@ import {
4
4
  sanitizeHtml,
5
5
  } from '../../helpers.js';
6
6
 
7
- export function createDeleteExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI) {
7
+ export function createDeleteExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct = false) {
8
8
  const deleteMethods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, 'delete');
9
9
 
10
10
  // if there are no delete methods, return empty content
@@ -30,7 +30,7 @@ export function createDeleteExamples(providerName, serviceName, resourceName, re
30
30
  content += '<TabItem value="' + methodName + '">\n\n';
31
31
 
32
32
  // Add method description
33
- const opDescription = methodDetails.opDescription || 'No description available.';
33
+ const opDescription = (succinct && methodDetails.opSummary) ? methodDetails.opSummary : (methodDetails.opDescription || 'No description available.');
34
34
  content += sanitizeHtml(opDescription);
35
35
 
36
36
  // Create SQL example
@@ -4,7 +4,7 @@ import {
4
4
  sanitizeHtml
5
5
  } from '../../helpers.js';
6
6
 
7
- export function createExecExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI) {
7
+ export function createExecExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct = false) {
8
8
  const execMethods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, 'exec');
9
9
 
10
10
  // if there are no exec methods, return empty content
@@ -30,7 +30,7 @@ export function createExecExamples(providerName, serviceName, resourceName, reso
30
30
  content += '<TabItem value="' + methodName + '">\n\n';
31
31
 
32
32
  // Add method description
33
- const opDescription = methodDetails.opDescription || methodDetails.respDescription || 'No description available.';
33
+ const opDescription = (succinct && methodDetails.opSummary) ? methodDetails.opSummary : (methodDetails.opDescription || methodDetails.respDescription || 'No description available.');
34
34
  content += sanitizeHtml(opDescription);
35
35
 
36
36
  // Create SQL example
@@ -4,7 +4,7 @@ import {
4
4
  sanitizeHtml
5
5
  } from '../../helpers.js';
6
6
 
7
- export function createInsertExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI) {
7
+ export function createInsertExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct = false) {
8
8
  const insertMethods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, 'insert');
9
9
 
10
10
  // if there are no insert methods, return empty content
@@ -34,9 +34,8 @@ export function createInsertExamples(providerName, serviceName, resourceName, re
34
34
  content += '<TabItem value="' + methodName + '">\n\n';
35
35
 
36
36
  // Add method description
37
- const opDescription = methodDetails.opDescription || 'No description available.';
37
+ const opDescription = (succinct && methodDetails.opSummary) ? methodDetails.opSummary : (methodDetails.opDescription || 'No description available.');
38
38
  content += sanitizeHtml(opDescription);
39
- // content += methodDetails.opDescription || 'No description available.';
40
39
 
41
40
  // Create SQL example
42
41
  content += '\n\n```sql\nINSERT INTO ' + providerName + '.' + serviceName + '.' + resourceName + ' (\n';
@@ -4,7 +4,7 @@ import {
4
4
  sanitizeHtml
5
5
  } from '../../helpers.js';
6
6
 
7
- export function createSelectExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI) {
7
+ export function createSelectExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct = false) {
8
8
  const selectMethods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, 'select');
9
9
 
10
10
  // if there are no select methods, return empty content
@@ -30,7 +30,7 @@ export function createSelectExamples(providerName, serviceName, resourceName, re
30
30
  content += '<TabItem value="' + methodName + '">\n\n';
31
31
  // content += methodDetails.opDescription || 'No description available.';
32
32
  // Add method description
33
- const opDescription = methodDetails.opDescription || methodDetails.respDescription || 'No description available.';
33
+ const opDescription = (succinct && methodDetails.opSummary) ? methodDetails.opSummary : (methodDetails.opDescription || methodDetails.respDescription || 'No description available.');
34
34
  content += sanitizeHtml(opDescription);
35
35
 
36
36
  // Create SQL example
@@ -4,7 +4,7 @@ import {
4
4
  sanitizeHtml
5
5
  } from '../../helpers.js';
6
6
 
7
- export function createUpdateExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, isReplace = false) {
7
+ export function createUpdateExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, isReplace = false, succinct = false) {
8
8
  const updateMethods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, isReplace ? 'replace' : 'update');
9
9
 
10
10
  // if there are no update methods, return empty content
@@ -34,10 +34,8 @@ export function createUpdateExamples(providerName, serviceName, resourceName, re
34
34
  Object.entries(updateMethods).forEach(([methodName, methodDetails]) => {
35
35
  content += '<TabItem value="' + methodName + '">\n\n';
36
36
 
37
- // // Add method description
38
- // content += methodDetails.opDescription || methodDetails.respDescription || 'No description available.';
39
37
  // Add method description
40
- const opDescription = methodDetails.opDescription || 'No description available.';
38
+ const opDescription = (succinct && methodDetails.opSummary) ? methodDetails.opSummary : (methodDetails.opDescription || 'No description available.');
41
39
  content += sanitizeHtml(opDescription);
42
40
 
43
41
  // Create SQL example
@@ -6,26 +6,26 @@ import { createUpdateExamples } from './examples/update-example.js';
6
6
  import { createDeleteExamples } from './examples/delete-example.js';
7
7
  import { createExecExamples } from './examples/exec-example.js';
8
8
 
9
- export function createExamplesSection(providerName, serviceName, resourceName, resourceData, dereferencedAPI) {
9
+ export function createExamplesSection(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct = false) {
10
10
  let content = '';
11
-
11
+
12
12
  // Add SELECT examples
13
- content += createSelectExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI);
13
+ content += createSelectExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct);
14
14
 
15
15
  // Add INSERT examples
16
- content += createInsertExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI);
17
-
16
+ content += createInsertExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct);
17
+
18
18
  // Add UPDATE examples
19
- content += createUpdateExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, false);
20
-
19
+ content += createUpdateExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, false, succinct);
20
+
21
21
  // Add REPLACE examples
22
- content += createUpdateExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, true);
22
+ content += createUpdateExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, true, succinct);
23
23
 
24
24
  // Add DELETE examples
25
- content += createDeleteExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI);
25
+ content += createDeleteExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct);
26
26
 
27
27
  // Add EXEC examples
28
- content += createExecExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI);
28
+ content += createExecExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI, succinct);
29
29
 
30
30
  return content;
31
31
  }
@@ -1,7 +1,9 @@
1
1
  // src/docgen/resource/fields.js
2
- import {
3
- getSqlMethodsWithOrderedFields,
2
+ import {
3
+ getSqlMethodsWithOrderedFields,
4
4
  sanitizeHtml,
5
+ generateSchemaJsonFromProps,
6
+ sortSchemaFields,
5
7
  } from '../helpers.js';
6
8
  import { docView } from './view.js';
7
9
 
@@ -100,3 +102,62 @@ export function createFieldsSection(resourceType, resourceData, dereferencedAPI)
100
102
  return content;
101
103
  }
102
104
 
105
+ export function createFieldsSectionv2(resourceType, resourceData, dereferencedAPI) {
106
+ let content = '## Fields\n\n';
107
+
108
+ if (resourceType === 'Resource') {
109
+
110
+ content += 'The following fields are returned by `SELECT` queries:\n\n';
111
+
112
+ // Use the reusable function to get methods with ordered fields
113
+ const methods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, 'select');
114
+
115
+ if (Object.keys(methods).length > 0) {
116
+ const methodNames = Object.keys(methods);
117
+
118
+ const tabValues = methodNames.map(methodName => {
119
+ return `{ label: '${methodName}', value: '${methodName}' }`;
120
+ }).join(',\n ');
121
+
122
+ content += `<Tabs
123
+ defaultValue="${methodNames[0]}"
124
+ values={[
125
+ ${tabValues}
126
+ ]}
127
+ >\n`;
128
+
129
+ for (const methodName of methodNames) {
130
+ const methodData = methods[methodName];
131
+
132
+ content += `<TabItem value="${methodName}">\n\n`;
133
+
134
+ // Add the method description if available and not in the meaningless list
135
+ if (methodData.respDescription &&
136
+ !meaninglessDescriptions.includes(methodData.respDescription.trim().toLowerCase()) &&
137
+ methodData.respDescription.trim().length > 0) {
138
+ content += `${sanitizeHtml(methodData.respDescription)}\n\n`;
139
+ }
140
+
141
+ // Build nested schema JSON from raw response props and sort fields
142
+ const schemaFields = generateSchemaJsonFromProps(methodData.rawRespProps);
143
+ const sortedFields = sortSchemaFields(schemaFields);
144
+
145
+ content += `<SchemaTable fields={${JSON.stringify(sortedFields, null, 2)}} />\n`;
146
+
147
+ content += `</TabItem>\n`;
148
+ }
149
+
150
+ content += `</Tabs>\n`;
151
+ } else {
152
+ content += `${mdCodeAnchor}SELECT${mdCodeAnchor} not supported for this resource, use ${mdCodeAnchor}SHOW METHODS${mdCodeAnchor} to view available operations for the resource.\n\n`;
153
+ }
154
+
155
+ } else {
156
+ // its a view
157
+ console.log(`processing view : ${resourceData.name}...`)
158
+ content += docView(resourceData);
159
+ }
160
+
161
+ return content;
162
+ }
163
+
@@ -22,7 +22,7 @@ const getRequiredBodyParams = (methodDetails, accessType) => {
22
22
  }
23
23
  };
24
24
 
25
- export function createMethodsSection(resourceData, dereferencedAPI) {
25
+ export function createMethodsSection(resourceData, dereferencedAPI, succinct = false) {
26
26
 
27
27
  let content = `\n## Methods\n\n`;
28
28
 
@@ -80,7 +80,7 @@ export function createMethodsSection(resourceData, dereferencedAPI) {
80
80
  <td><CopyableCode code="${accessType}" /></td>
81
81
  <td>${requiredParamsStr}</td>
82
82
  <td>${optionalParamsStr}</td>
83
- <td>${sanitizeHtml(methodDetails.opDescription)}</td>
83
+ <td>${sanitizeHtml(succinct && methodDetails.opSummary ? methodDetails.opSummary : methodDetails.opDescription)}</td>
84
84
  </tr>`;
85
85
  }
86
86
  };
@@ -41,3 +41,43 @@ content += `
41
41
  `;
42
42
  return content;
43
43
  }
44
+
45
+ export function createOverviewSectionv2(resourceName, resourceType, resourceDescription, providerName, serviceName) {
46
+
47
+ let content = `---
48
+ title: ${resourceName}
49
+ hide_title: false
50
+ hide_table_of_contents: false
51
+ keywords:
52
+ - ${resourceName}
53
+ - ${serviceName}
54
+ - ${providerName}
55
+ - infrastructure-as-code
56
+ - configuration-as-data
57
+ - cloud inventory
58
+ description: Query, deploy and manage ${providerName} resources using SQL
59
+ custom_edit_url: null
60
+ image: /img/stackql-${providerName}-provider-featured-image.png
61
+ ---
62
+
63
+ import CopyableCode from '@site/src/components/CopyableCode/CopyableCode';
64
+ import Tabs from '@theme/Tabs';
65
+ import TabItem from '@theme/TabItem';
66
+ import SchemaTable from '@site/src/components/SchemaTable/SchemaTable';
67
+
68
+ `;
69
+
70
+ content += resourceDescription ? resourceDescription : `Creates, updates, deletes, gets or lists ${getIndefiniteArticle(resourceName)} <code>${resourceName}</code> resource.`;
71
+
72
+ content += `
73
+
74
+ ## Overview
75
+ <table><tbody>
76
+ <tr><td><b>Name</b></td><td><code>${resourceName}</code></td></tr>
77
+ <tr><td><b>Type</b></td><td>${resourceType}</td></tr>
78
+ <tr><td><b>Id</b></td><td><CopyableCode code="${providerName}.${serviceName}.${resourceName}" /></td></tr>
79
+ </tbody></table>
80
+
81
+ `;
82
+ return content;
83
+ }
@@ -1,14 +1,14 @@
1
1
  // src/docgen/resource-content.js
2
2
 
3
- import { createOverviewSection } from './resource/overview.js';
4
- import { createFieldsSection } from './resource/fields.js';
3
+ import { createOverviewSection, createOverviewSectionv2 } from './resource/overview.js';
4
+ import { createFieldsSection, createFieldsSectionv2 } from './resource/fields.js';
5
5
  import { createMethodsSection } from './resource/methods.js';
6
6
  import { createParamsSection } from './resource/parameters.js';
7
7
  import { createExamplesSection } from './resource/examples.js';
8
-
8
+
9
9
  export async function createResourceIndexContent(
10
- providerName,
11
- serviceName,
10
+ providerName,
11
+ serviceName,
12
12
  resource,
13
13
  ) {
14
14
  // Generate each section of the documentation
@@ -21,3 +21,20 @@ export async function createResourceIndexContent(
21
21
  // Combine all sections into the final content
22
22
  return `${overviewContent}${fieldsContent}${methodsContent}${paramsContent}${examplesContent}`;
23
23
  }
24
+
25
+ export async function createResourceIndexContentv2(
26
+ providerName,
27
+ serviceName,
28
+ resource,
29
+ succinct = false,
30
+ ) {
31
+ // Generate each section of the documentation (v2 uses SchemaTable for fields)
32
+ const overviewContent = createOverviewSectionv2(resource.name, resource.type, resource.description, providerName, serviceName);
33
+ const fieldsContent = createFieldsSectionv2(resource.type, resource.resourceData, resource.dereferencedAPI);
34
+ const methodsContent = resource.type === 'Resource' ? createMethodsSection(resource.resourceData, resource.dereferencedAPI, succinct) : '';
35
+ const paramsContent = resource.type === 'Resource' ? createParamsSection(resource.resourceData, resource.dereferencedAPI) : '';
36
+ const examplesContent = resource.type === 'Resource' ? createExamplesSection(providerName, serviceName, resource.name, resource.resourceData, resource.dereferencedAPI, succinct) : '';
37
+
38
+ // Combine all sections into the final content
39
+ return `${overviewContent}${fieldsContent}${methodsContent}${paramsContent}${examplesContent}`;
40
+ }