@stackql/provider-utils 0.5.4 → 0.5.6

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.4",
3
+ "version": "0.5.6",
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,222 @@ 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
+ } = options;
295
+
296
+ console.log(`documenting ${providerName} (v2)...`);
297
+
298
+ const docsDir = path.join(outputDir, `docs`);
299
+ const servicesDir = path.join(docsDir, `services`);
300
+
301
+ // Remove directory if it exists, then create it fresh
302
+ fs.existsSync(docsDir) && fs.rmSync(docsDir, { recursive: true, force: true });
303
+ fs.mkdirSync(servicesDir, { recursive: true });
304
+
305
+ // Check for provider data files
306
+ console.log(providerDataDir);
307
+ try {
308
+ const files = fs.readdirSync(providerDataDir);
309
+ console.log('Files in providerDataDir:', files);
310
+ } catch (err) {
311
+ console.error('Error reading providerDataDir:', err.message);
312
+ }
313
+
314
+ const headerContent1Path = path.join(providerDataDir, 'headerContent1.txt');
315
+ const headerContent2Path = path.join(providerDataDir, 'headerContent2.txt');
316
+
317
+ if (!fs.existsSync(headerContent1Path) || !fs.existsSync(headerContent2Path)) {
318
+ throw new Error(`Missing headerContent1.txt or headerContent2.txt in ${providerDataDir}`);
319
+ }
320
+
321
+ const headerContent1 = fs.readFileSync(headerContent1Path, 'utf8');
322
+ const headerContent2 = fs.readFileSync(headerContent2Path, 'utf8');
323
+
324
+ // Initialize counters
325
+ let servicesForIndex = [];
326
+ let totalServicesCount = 0;
327
+ let totalResourcesCount = 0;
328
+
329
+ // Process services
330
+ const serviceDir = path.join(providerDir, 'services');
331
+ console.log(`Processing services in ${serviceDir}...`);
332
+ const serviceFiles = fs.readdirSync(serviceDir).filter(file => path.extname(file) === '.yaml');
333
+
334
+ for (const file of serviceFiles) {
335
+ const serviceName = path.basename(file, '.yaml').replace(/-/g, '_');
336
+ console.log(`Processing service: ${serviceName}`);
337
+ servicesForIndex.push(serviceName);
338
+ const filePath = path.join(serviceDir, file);
339
+ totalServicesCount++;
340
+ const serviceFolder = `${servicesDir}/${serviceName}`;
341
+ await createDocsForServicev2(filePath, providerName, serviceName, serviceFolder, dereferenced);
342
+ }
343
+
344
+ console.log(`Processed ${totalServicesCount} services`);
345
+
346
+ // Count total resources
347
+ totalResourcesCount = fs.readdirSync(`${servicesDir}`, { withFileTypes: true })
348
+ .filter(dirent => dirent.isDirectory())
349
+ .map(dirent => fs.readdirSync(`${servicesDir}/${dirent.name}`).length)
350
+ .reduce((a, b) => a + b, 0);
351
+
352
+ console.log(`Processed ${totalResourcesCount} resources`);
353
+
354
+ // Create provider index
355
+ servicesForIndex = [...new Set(servicesForIndex)];
356
+ servicesForIndex.sort();
357
+
358
+ const half = Math.ceil(servicesForIndex.length / 2);
359
+ const firstColumnServices = servicesForIndex.slice(0, half);
360
+ const secondColumnServices = servicesForIndex.slice(half);
361
+
362
+ const indexContent = `${headerContent1}
363
+
364
+ :::info[Provider Summary]
365
+
366
+ total services: __${totalServicesCount}__
367
+ total resources: __${totalResourcesCount}__
368
+
369
+ :::
370
+
371
+ ${headerContent2}
372
+
373
+ ## Services
374
+ <div class="row">
375
+ <div class="providerDocColumn">
376
+ ${servicesToMarkdown(providerName, firstColumnServices)}
377
+ </div>
378
+ <div class="providerDocColumn">
379
+ ${servicesToMarkdown(providerName, secondColumnServices)}
380
+ </div>
381
+ </div>
382
+ `;
383
+
384
+ // Write index
385
+ const indexPath = path.join(docsDir, 'index.md');
386
+ fs.writeFileSync(indexPath, indexContent);
387
+ console.log(`Index file created at ${indexPath}`);
388
+
389
+ return {
390
+ totalServices: totalServicesCount,
391
+ totalResources: totalResourcesCount,
392
+ outputPath: docsDir
393
+ };
394
+ }
395
+
396
+ // v2 service processing - uses SchemaTable for collapsible nested fields
397
+ async function createDocsForServicev2(yamlFilePath, providerName, serviceName, serviceFolder, dereferenced = false) {
398
+
399
+ const data = yaml.load(fs.readFileSync(yamlFilePath, 'utf8'));
400
+
401
+ // Create a new SwaggerParser instance
402
+ let parser = new SwaggerParser();
403
+ const api = await parser.parse(yamlFilePath);
404
+ const ignorePaths = ["$.components.x-stackQL-resources"];
405
+ let dereferencedAPI;
406
+
407
+ if (dereferenced) {
408
+ // If API is already dereferenced, just use it as is
409
+ dereferencedAPI = api;
410
+ } else {
411
+ try {
412
+ // Only dereference and flatten if needed
413
+ dereferencedAPI = await SwaggerParser.dereference(api);
414
+ dereferencedAPI = await deno_openapi_dereferencer.flattenAllOf(dereferencedAPI);
415
+ } catch (error) {
416
+ console.error("error in dereferencing or flattening:", error);
417
+ }
418
+ }
419
+
420
+ // Create service directory
421
+ if (!fs.existsSync(serviceFolder)) {
422
+ fs.mkdirSync(serviceFolder, { recursive: true });
423
+ }
424
+
425
+ const resourcesObj = data.components['x-stackQL-resources'];
426
+
427
+ if (!resourcesObj) {
428
+ console.warn(`No resources found in ${yamlFilePath}`);
429
+ return;
430
+ }
431
+
432
+ const resources = [];
433
+ for (let resourceName in resourcesObj) {
434
+
435
+ let resourceData = resourcesObj[resourceName];
436
+ if (!resourceData.id) {
437
+ console.warn(`No 'id' defined for resource: ${resourceName} in service: ${serviceName}`);
438
+ continue;
439
+ }
440
+
441
+ const resourceDescription = resourceData.description || '';
442
+
443
+ // Determine if it's a View or a Resource
444
+ let resourceType = "Resource"; // Default type
445
+ if (resourceData.config?.views?.select) {
446
+ resourceType = "View";
447
+ }
448
+
449
+ resources.push({
450
+ name: resourceName,
451
+ description: resourceDescription,
452
+ type: resourceType,
453
+ resourceData,
454
+ dereferencedAPI,
455
+ });
456
+ }
457
+
458
+ // Process service index
459
+ const serviceIndexPath = path.join(serviceFolder, 'index.md');
460
+ const serviceIndexContent = await createServiceIndexContent(providerName, serviceName, resources);
461
+ fs.writeFileSync(serviceIndexPath, serviceIndexContent);
462
+
463
+ // Split into columns and process resources one by one
464
+ const halfLength = Math.ceil(resources.length / 2);
465
+ const firstColumn = resources.slice(0, halfLength);
466
+ const secondColumn = resources.slice(halfLength);
467
+
468
+ // Process each resource in first column
469
+ for (const resource of firstColumn) {
470
+ await processResourcev2(providerName, serviceFolder, serviceName, resource);
471
+ }
472
+
473
+ // Process each resource in second column
474
+ for (const resource of secondColumn) {
475
+ await processResourcev2(providerName, serviceFolder, serviceName, resource);
476
+ }
477
+
478
+ console.log(`Generated documentation (v2) for ${serviceName}`);
479
+ }
480
+
481
+ async function processResourcev2(providerName, serviceFolder, serviceName, resource) {
482
+ console.log(`Processing resource (v2): ${resource.name}`);
483
+
484
+ const resourceFolder = path.join(serviceFolder, resource.name);
485
+ if (!fs.existsSync(resourceFolder)) {
486
+ fs.mkdirSync(resourceFolder, { recursive: true });
487
+ }
488
+
489
+ const resourceIndexPath = path.join(resourceFolder, 'index.md');
490
+ const resourceIndexContent = await createResourceIndexContentv2(
491
+ providerName,
492
+ serviceName,
493
+ resource,
494
+ );
495
+ fs.writeFileSync(resourceIndexPath, resourceIndexContent);
496
+
497
+ // After writing the file, force garbage collection if available (optional)
498
+ if (global.gc) {
499
+ global.gc();
500
+ }
501
+ }
502
+
287
503
  // Function to convert services to markdown links
288
504
  function servicesToMarkdown(providerName, servicesList) {
289
505
  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}`);
@@ -135,20 +135,20 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
135
135
 
136
136
  // Get response and params using the same function as for SQL verbs
137
137
  const { respProps, respDescription, opDescription, requestBody } = getHttpOperationInfo(
138
- dereferencedAPI,
139
- resolvedPath,
140
- resolvedVerb,
141
- methodData.response.mediaType || '',
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,
@@ -157,16 +157,17 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
157
157
  requiredParams: requiredParams || {},
158
158
  optionalParams: optionalParams || {},
159
159
  requestBody: requestBody || {},
160
+ rawRespProps: respProps,
160
161
  };
161
-
162
+
162
163
  // Format and sort the properties using our helper functions
163
164
  const allProperties = formatProperties(respProps);
164
165
  sortAndAddProperties(methods[methodName], allProperties);
165
-
166
+
166
167
  console.info(`Processed exec method: ${methodName}`);
167
168
  }
168
169
  }
169
-
170
+
170
171
  return methods;
171
172
  }
172
173
 
@@ -180,22 +181,23 @@ export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sq
180
181
  const {requiredParams, optionalParams} = getHttpOperationParams(dereferencedAPI, path, httpVerb);
181
182
 
182
183
  // Initialize the method object with description and params
183
- methods[methodName] = {
184
+ methods[methodName] = {
184
185
  opDescription,
185
186
  respDescription,
186
187
  properties: {},
187
188
  requiredParams: requiredParams || {},
188
189
  optionalParams: optionalParams || {},
189
190
  requestBody: requestBody || {},
191
+ rawRespProps: respProps,
190
192
  };
191
-
193
+
192
194
  // Format and sort the properties using our helper functions
193
195
  const allProperties = formatProperties(respProps);
194
196
  sortAndAddProperties(methods[methodName], allProperties);
195
-
197
+
196
198
  console.info(`Processed method: ${methodName}`);
197
199
  }
198
-
200
+
199
201
  return methods;
200
202
  }
201
203
 
@@ -569,6 +571,81 @@ function getHttpRespBody(schema, objectKey) {
569
571
  }
570
572
  }
571
573
 
574
+ /**
575
+ * Recursively generates a nested JSON schema tree from response properties,
576
+ * suitable for use with the SchemaTable React component.
577
+ * Each node has: { name, type, description, children? }
578
+ * @param {Object} respProps - The raw response properties from the dereferenced API
579
+ * @param {number} depth - Current recursion depth
580
+ * @param {number} maxDepth - Maximum recursion depth to prevent infinite loops
581
+ * @returns {Array} Array of schema field objects
582
+ */
583
+ export function generateSchemaJsonFromProps(respProps, depth = 0, maxDepth = 4) {
584
+ if (depth >= maxDepth || !respProps || typeof respProps !== 'object') return [];
585
+
586
+ return Object.entries(respProps).map(([propName, prop]) => {
587
+ if (!prop) return null;
588
+
589
+ let propType = prop.type || 'object';
590
+ let propDesc = sanitizeHtml(prop.description || '');
591
+
592
+ // Add format info to type string if available
593
+ if (prop.format) {
594
+ propType += ` (${prop.format})`;
595
+ }
596
+
597
+ let children = [];
598
+
599
+ // Handle arrays - get children from items
600
+ if (prop.type === 'array' && prop.items) {
601
+ if (prop.items.properties) {
602
+ children = generateSchemaJsonFromProps(prop.items.properties, depth + 1, maxDepth);
603
+ }
604
+ }
605
+
606
+ // Handle objects - get children from properties
607
+ if (prop.type === 'object' && prop.properties) {
608
+ children = generateSchemaJsonFromProps(prop.properties, depth + 1, maxDepth);
609
+ }
610
+
611
+ return {
612
+ name: propName,
613
+ type: propType,
614
+ description: propDesc,
615
+ ...(children.length > 0 && { children })
616
+ };
617
+ }).filter(Boolean);
618
+ }
619
+
620
+ /**
621
+ * Sorts an array of schema field objects using the standard field priority:
622
+ * 1. Exact 'id' and 'name' fields
623
+ * 2. Fields ending with '_id'
624
+ * 3. Fields ending with '_name'
625
+ * 4. All other fields (alphabetical)
626
+ * @param {Array} fields - Array of schema field objects with 'name' property
627
+ * @returns {Array} Sorted array of schema field objects
628
+ */
629
+ export function sortSchemaFields(fields) {
630
+ if (!fields || fields.length === 0) return [];
631
+
632
+ const exactIdName = fields.filter(f => f.name === 'id' || f.name === 'name');
633
+ const idSuffix = fields.filter(f => f.name !== 'id' && f.name.endsWith('_id'));
634
+ const nameSuffix = fields.filter(f => f.name !== 'name' && f.name.endsWith('_name'));
635
+ const others = fields.filter(f =>
636
+ !exactIdName.includes(f) &&
637
+ !idSuffix.includes(f) &&
638
+ !nameSuffix.includes(f)
639
+ );
640
+
641
+ return [
642
+ ...exactIdName.sort((a, b) => a.name.localeCompare(b.name)),
643
+ ...idSuffix.sort((a, b) => a.name.localeCompare(b.name)),
644
+ ...nameSuffix.sort((a, b) => a.name.localeCompare(b.name)),
645
+ ...others.sort((a, b) => a.name.localeCompare(b.name)),
646
+ ];
647
+ }
648
+
572
649
  function getHttpOperationParams(dereferencedAPI, path, httpVerb) {
573
650
  const requiredParams = {};
574
651
  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';
@@ -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
+
@@ -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,19 @@ 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
+ ) {
30
+ // Generate each section of the documentation (v2 uses SchemaTable for fields)
31
+ const overviewContent = createOverviewSectionv2(resource.name, resource.type, resource.description, providerName, serviceName);
32
+ const fieldsContent = createFieldsSectionv2(resource.type, resource.resourceData, resource.dereferencedAPI);
33
+ const methodsContent = resource.type === 'Resource' ? createMethodsSection(resource.resourceData, resource.dereferencedAPI) : '';
34
+ const paramsContent = resource.type === 'Resource' ? createParamsSection(resource.resourceData, resource.dereferencedAPI) : '';
35
+ const examplesContent = resource.type === 'Resource' ? createExamplesSection(providerName, serviceName, resource.name, resource.resourceData, resource.dereferencedAPI) : '';
36
+
37
+ // Combine all sections into the final content
38
+ return `${overviewContent}${fieldsContent}${methodsContent}${paramsContent}${examplesContent}`;
39
+ }