diff --git a/bin/generate-docs.js b/bin/generate-docs.js
index 152cfd150..54ec4f34f 100644
--- a/bin/generate-docs.js
+++ b/bin/generate-docs.js
@@ -1,1712 +1,1712 @@
-import fs from 'fs';
-import path from 'path';
-import yaml from 'js-yaml';
-import pluralize from 'pluralize';
-import indefinite from 'indefinite';
-import { exec } from 'child_process';
-import { fileURLToPath } from 'url';
-import { dirname } from 'path';
-
-// ES module equivalents for __dirname and __filename
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-//
-// update ...
-//
-const providerName = 'awscc';
-const providerDirName = 'awscc'; // Directory name differs from provider name
-const staticServices = [];
-const nativeServices = ['cloud_control', 'tagging'];
-// AWS global services where us-east-1 is the canonical region for Cloud Control API
-const globalOnlyServices = ['iam', 'route53', 'organizations', 'cloudfront', 'globalaccelerator', 'shield', 'budgets'];
-
-function getRegionExample(serviceName) {
- return globalOnlyServices.includes(serviceName) ? 'us-east-1' : '{{ region }}';
-}
-const websiteDir = '../website';
-const providerDevDir = '../provider-dev';
-const openAPIdir = '../openapi';
-
-const sqlCodeBlockStart = '```sql';
-const jsonCodeBlockStart = '```json';
-const codeBlockEnd = '```';
-const mdCodeAnchor = "`";
-
-async function createDocsForService(yamlFilePath) {
- const data = yaml.load(fs.readFileSync(yamlFilePath, 'utf8'));
-
- const serviceName = path.basename(yamlFilePath, '.yaml');
-
- if(staticServices.includes(serviceName)) {
- console.log(`Skipping ${serviceName}`);
- return;
- }
-
- const serviceFolder = path.join(__dirname, `${websiteDir}/docs/services`, serviceName);
-
- // Create service directory under generated-docs
- if (!fs.existsSync(serviceFolder)) {
- fs.mkdirSync(serviceFolder, { recursive: true });
- }
-
- let resourcesObj = data.components['x-stackQL-resources'];
-
- if (!resourcesObj) {
- console.warn(`No resources found in ${yamlFilePath}`);
- return;
- }
-
- const resources = [];
- for (let resourceName in resourcesObj) {
- let resourceData = resourcesObj[resourceName];
-
- // Check if 'id' exists in 'resourceData'
- if (!resourceData.id) {
- console.warn(`No 'id' defined for resource: ${resourceName} in service: ${serviceName}`);
- return;
- }
-
- // Create the markdown content for the resource
- const schemaName = resourceData['x-cfn-schema-name'] || '';
- const schemaTypeName = resourceData['x-cfn-type-name'] || '';
- const methodType = resourceData['x-type'];
- const identifiers = resourceData['x-identifiers'] || [];
- const schema = data.components.schemas[schemaName] || {};
- const componentsSchemas = data.components.schemas;
-
- const servicePaths = data.paths || {};
- const serviceResources = data.components['x-stackQL-resources'] || {};
- resources.push({ name: resourceName, type: methodType, identifiers: identifiers, schemaName, schema, componentsSchemas, servicePaths, serviceResources, resourceData, schemaTypeName });
- }
-
- // Build map of _list_only resources to their base resources
- const listOnlyMap = {};
- for (const resource of resources) {
- if (resource.name.endsWith('_list_only')) {
- const baseName = resource.name.replace('_list_only', '');
- listOnlyMap[baseName] = resource;
- }
- }
-
- // Filter out _list_only resources - they will be collapsed into base resource docs
- const filteredResources = resources.filter(r => !r.name.endsWith('_list_only'));
-
- // Create index.md for the service
- const serviceIndexPath = path.join(serviceFolder, 'index.md');
- const serviceIndexContent = await createServiceIndexContent(serviceName, filteredResources);
- fs.writeFileSync(serviceIndexPath, serviceIndexContent);
-
- // Divide resources into two columns
- const halfLength = Math.ceil(filteredResources.length / 2);
- const firstColumn = filteredResources.slice(0, halfLength);
- const secondColumn = filteredResources.slice(halfLength);
-
- // Create resource subfolders and index.md for each resource
- firstColumn.forEach((resource) => {
- const resourceFolder = path.join(serviceFolder, resource.name);
- if (!fs.existsSync(resourceFolder)) {
- fs.mkdirSync(resourceFolder, { recursive: true });
- }
-
- const resourceIndexPath = path.join(resourceFolder, 'index.md');
- const resourceIndexContent = createResourceIndexContent(serviceName, resource.name, resource.type, resource.identifiers, resource.schemaName, resource.schema, resource.componentsSchemas, resource.servicePaths, resource.serviceResources, resource.resourceData, resource.schemaTypeName, listOnlyMap[resource.name]);
- fs.writeFileSync(resourceIndexPath, resourceIndexContent);
- });
-
- secondColumn.forEach((resource) => {
- const resourceFolder = path.join(serviceFolder, resource.name);
- if (!fs.existsSync(resourceFolder)) {
- fs.mkdirSync(resourceFolder, { recursive: true });
- }
-
- const resourceIndexPath = path.join(resourceFolder, 'index.md');
- const resourceIndexContent = createResourceIndexContent(serviceName, resource.name, resource.type, resource.identifiers, resource.schemaName, resource.schema, resource.componentsSchemas, resource.servicePaths, resource.serviceResources, resource.resourceData, resource.schemaTypeName, listOnlyMap[resource.name]);
-
- try {
- fs.writeFileSync(resourceIndexPath, resourceIndexContent);
- } catch (error) {
- console.error('Error writing file:');
- console.error('Path:', resourceIndexPath);
- console.error('Content type:', typeof resourceIndexContent);
- console.error('Content:', resourceIndexContent);
- console.error('Error:', error);
- }
- });
-
- console.log(`Generated documentation for ${serviceName}`);
-}
-
-async function createServiceIndexContent(serviceName, resources) {
- const totalResources = resources.length; // Calculate the total resources
-
- totalResourcesCount += totalResources; // Update global resource count
-
- // Sort resources alphabetically by name
- resources.sort((a, b) => a.name.localeCompare(b.name));
-
- const halfLength = Math.ceil(totalResources / 2);
- const firstColumnResources = resources.slice(0, halfLength);
- const secondColumnResources = resources.slice(halfLength);
-
- // Generate the HTML for resource links in the first column
- const firstColumnLinks = generateResourceLinks(serviceName, firstColumnResources);
-
- // Generate the HTML for resource links in the second column
- const secondColumnLinks = generateResourceLinks(serviceName, secondColumnResources);
-
- // Create the markdown content for the service index
- // You can customize this content as needed
- return `---
-title: ${serviceName}
-hide_title: false
-hide_table_of_contents: false
-keywords:
- - ${serviceName}
- - aws
- - stackql
- - infrastructure-as-code
- - configuration-as-data
- - cloud inventory
-description: Query, deploy and manage AWS resources using SQL
-custom_edit_url: null
-image: /img/stackql-aws-provider-featured-image.png
----
-
-The ${serviceName} service documentation.
-
-:::info Service Summary
-
-
-
-total resources: ${totalResources}
-
-
-
-:::
-
-## Resources
-
-
-${firstColumnLinks}
-
-
-${secondColumnLinks}
-
-
`;
-}
-
-function generateResourceLinks(serviceName, resources) {
- // Generate resource links for the service index
- const resourceLinks = resources.map((resource) => {
- return `${resource.name}`;
- });
- return resourceLinks.join('
\n');
-}
-
-function getReturningClause() {
- const progressEventFields = [
- 'ErrorCode',
- 'EventTime',
- 'Identifier',
- 'Operation',
- 'OperationStatus',
- 'RequestToken',
- 'ResourceModel',
- 'RetryAfter',
- 'StatusMessage',
- 'TypeName',
- ];
- return `RETURNING\n ${progressEventFields.join(",\n ")}\n`;
-}
-
-function createAdditionalParametersSection(serviceName, resourceData) {
- if (nativeServices.includes(serviceName)) {
- return '';
- }
- const hasInsert = !!resourceData.methods?.create_resource;
- const hasUpdate = !!resourceData.methods?.update_resource;
- const hasDelete = !!resourceData.methods?.delete_resource;
- if (!hasInsert && !hasUpdate && !hasDelete) {
- return '';
- }
- return `
-## Additional Parameters
-
-Mutable resources in the Cloud Control provider support additional optional parameters which can be supplied with \`INSERT\`, \`UPDATE\`, or \`DELETE\` operations. These include:
-
-| Parameter | Description |
-|-----------|-------------|
-| | A unique identifier to ensure the idempotency of the resource request.
This allows the provider to accurately distinguish between retries and new requests.
A client token is valid for 36 hours once used.
After that, a resource request with the same client token is treated as a new request.
If you do not specify a client token, one is generated for inclusion in the request. |
-| | The ARN of the IAM role used to perform this resource operation.
The role specified must have the permissions required for this operation.
If you do not specify a role, a temporary session is created using your AWS user credentials. |
-| | For private resource types, the type version to use in this resource operation.
If you do not specify a resource version, the default version is used. |
-`;
-}
-
-function createDeleteExample(serviceName, resourceName, resourceData, thisSchema, allSchemas) {
- const deleteOperation = resourceData.methods?.delete_resource;
- if (!deleteOperation) {
- return '';
- }
-
- return `\n## ${mdCodeAnchor}DELETE${mdCodeAnchor} example
-
-${sqlCodeBlockStart}
-/*+ delete */
-DELETE FROM ${providerName}.${serviceName}.${resourceName}
-WHERE
- Identifier = '${resourceData['x-identifiers'].map(id => `{{ ${toSnakeCase(fixCamelCaseIssues(id))} }}`).join('|')}' AND
- region = '${getRegionExample(serviceName)}'
-${getReturningClause()};
-${codeBlockEnd}
-`;
-}
-
-function createUpdateExample(serviceName, resourceName, resourceData, thisSchema, allSchemas) {
- const updateOperation = resourceData.methods?.update_resource;
- if (!updateOperation) {
- return '';
- }
-
- const readOnlyProps = (thisSchema['x-read-only-properties'] || []).map(p => p.split('/')[0]);
- const createOnlyProps = (thisSchema['x-create-only-properties'] || []).map(p => p.split('/')[0]);
- const excludeProps = new Set([...readOnlyProps, ...createOnlyProps]);
-
- const updatableProps = Object.keys(thisSchema.properties || {}).filter(p => !excludeProps.has(p));
-
- if (updatableProps.length === 0) {
- return '';
- }
-
- const patchFields = updatableProps.map(p => ` "${p}": ${toSnakeCase(fixCamelCaseIssues(p))}`).join(',\n');
- const identifierValues = (resourceData['x-identifiers'] || []).map(id => `{{ ${toSnakeCase(fixCamelCaseIssues(id))} }}`).join('|');
-
- return `\n## ${mdCodeAnchor}UPDATE${mdCodeAnchor} example
-
-Use the following StackQL query and manifest file to update a ${pluralize.singular(resourceName)} resource, using [__${mdCodeAnchor}stack-deploy${mdCodeAnchor}__](https://pypi.org/project/stack-deploy/).
-
-${sqlCodeBlockStart}
-/*+ update */
-UPDATE ${providerName}.${serviceName}.${resourceName}
-SET PatchDocument = string('{{ {
-${patchFields}
-} | generate_patch_document }}')
-WHERE
- region = '{{ region }}' AND
- Identifier = '${identifierValues}'
-${getReturningClause()};
-${codeBlockEnd}
-`;
-}
-
-function createInsertExample(serviceName, resourceName, resourceData, thisSchema, allSchemas) {
- const createOperation = resourceData.methods?.create_resource;
- if (!createOperation) {
- return '';
- }
-
- function resolveRef(ref, visitedRefs) {
- const path = ref.replace('#/components/schemas/', '');
- if (visitedRefs.has(path)) {
- console.log(`Cyclic reference detected for ${path}, skipping expansion.`);
- return null; // Return null or some placeholder to avoid infinite recursion
- }
- visitedRefs.add(path);
- return allSchemas[path];
- }
-
- function resolveType(schema, requiredOnly, visitedRefs = new Set()) {
- if (!schema) return null;
-
- if (schema.type) {
- switch (schema.type) {
- case 'string':
- return 'string';
- case 'integer':
- return 0;
- case 'boolean':
- return false;
- case 'array':
- if (schema.items) {
- return [resolveType(schema.items.$ref ? resolveRef(schema.items.$ref, visitedRefs) : schema.items, requiredOnly, visitedRefs)];
- }
- return [];
- case 'object':
- if (schema.properties) {
- let obj = {};
- Object.keys(schema.properties).forEach(prop => {
- if(schema['x-read-only-properties'] && schema['x-read-only-properties'].includes(prop)){
- return null;
- }
- if (requiredOnly && schema['x-required-properties'] && !schema['x-required-properties'].includes(prop)) {
- return null;
- }
- obj[prop] = resolveType(schema.properties[prop].$ref ? resolveRef(schema.properties[prop].$ref, visitedRefs) : schema.properties[prop], requiredOnly, visitedRefs);
- });
- return obj;
- }
- return {};
- default:
- return null;
- }
- } else if (schema.$ref) {
- return resolveType(resolveRef(schema.$ref, visitedRefs), requiredOnly, visitedRefs);
- }
- return null;
- }
-
- function getObjectDetails(inputObject, detailType) {
- const keys = Object.keys(inputObject);
- const values = Object.values(inputObject);
-
- switch (detailType) {
- case 'keys':
- return keys;
- case 'values':
- return values;
- default:
- throw new Error('Invalid detail type requested. Use "keys" or "values".');
- }
- }
-
- function templateObjectValues(obj) {
- const templatedObj = JSON.parse(JSON.stringify(obj), (key, value) => {
- const snakeKey = key ? toSnakeCase(fixCamelCaseIssues(key)) : key;
- if (typeof value === 'string') {
- return `{{ ${snakeKey} }}`;
- }
- if (typeof value === 'boolean' || typeof value === 'number') {
- return `{{ ${snakeKey} }}`;
- }
- if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'string') {
- return [`{{ ${snakeKey}[0] }}`];
- }
- return value;
- });
- return templatedObj
- }
-
- const templateObject = {
- version: 1,
- name: "stack name",
- description: "stack description",
- providers: [
- "aws"
- ],
- globals: [{
- name: "region",
- value: "{{ vars.AWS_REGION }}"
- }],
- resources: [],
- };
-
- function transformDataToResource(templateData, resourceName, isRoot = true) {
- if (!templateData) return null;
-
- if (isRoot) {
- // If it's the root object, return a structured resource
- return {
- name: `${pluralize.singular(resourceName)}`,
- props: Object.entries(templateData).map(([key, value]) => ({
- name: toSnakeCase(fixCamelCaseIssues(key)),
- value: transformDataToResource(value, resourceName, false)
- }))
- };
- } else if (Array.isArray(templateData)) {
- // If it's an array, transform each element
- return templateData.map(item => transformDataToResource(item, resourceName, false));
- } else if (typeof templateData === 'object' && templateData !== null) {
- // For object values, recursively call transformation for each property
- const transformedObject = {};
- for (const [key, value] of Object.entries(templateData)) {
- transformedObject[toSnakeCase(fixCamelCaseIssues(key))] = transformDataToResource(value, resourceName, false);
- }
- return transformedObject;
- } else {
- // For primitive values, simply return the value
- return templateData;
- }
- };
-
- templateObject.resources.push(transformDataToResource(templateObjectValues(resolveType(thisSchema, false)), resourceName));
-
- const requiredKeys = getObjectDetails(resolveType(thisSchema, true), 'keys');
- const allKeys = getObjectDetails(resolveType(thisSchema, false), 'keys');
- const requiredSnakeKeys = requiredKeys.map(k => toSnakeCase(fixCamelCaseIssues(k)));
- const allSnakeKeys = allKeys.map(k => toSnakeCase(fixCamelCaseIssues(k)));
-
- // Escape backticks and ${} in the YAML to prevent template literal issues in JSX
- const manifestYaml = yaml.dump(templateObject, { lineWidth: -1 }).trimEnd();
- const escapedManifestYaml = manifestYaml.replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
-
- return `\n## ${mdCodeAnchor}INSERT${mdCodeAnchor} example
-
-Use the following StackQL query and manifest file to create a new ${pluralize.singular(resourceName)} resource, using [__${mdCodeAnchor}stack-deploy${mdCodeAnchor}__](https://pypi.org/project/stack-deploy/).
-
-
-
-
-${sqlCodeBlockStart}
-/*+ create */
-INSERT INTO ${providerName}.${serviceName}.${resourceName} (
- ${requiredKeys.join(",\n ")},
- region
-)
-SELECT
- '{{ ${requiredSnakeKeys.join(" }}',\n '{{ ")} }}',
- '{{ region }}'
-${getReturningClause()};
-${codeBlockEnd}
-
-
-
-${sqlCodeBlockStart}
-/*+ create */
-INSERT INTO ${providerName}.${serviceName}.${resourceName} (
- ${allKeys.join(",\n ")},
- region
-)
-SELECT
- '{{ ${allSnakeKeys.join(" }}',\n '{{ ")} }}',
- '{{ region }}'
-${getReturningClause()};
-${codeBlockEnd}
-
-
-
-{\`${escapedManifestYaml}\`}
-
-
-`;
-}
-
-// Resolve the response schema for a schema-less native resource (e.g. tagging).
-// Follows: select sqlVerb $ref → method → operation $ref → path → POST response 200 → schema.
-// If the method has an objectKey (e.g. $.ResourceTagMappingList), unwraps the array items schema.
-function resolveNativeResponseSchema(resourceData, servicePaths, componentsSchemas) {
- try {
- const selectRefs = resourceData?.sqlVerbs?.select;
- if (!selectRefs || !selectRefs.length) return null;
-
- const methodRef = selectRefs[0]?.$ref;
- if (!methodRef) return null;
-
- // $ref is like '#/components/x-stackQL-resources/report_creation/methods/describe_report_creation'
- const methodName = methodRef.split('/').pop();
- const method = resourceData.methods?.[methodName];
- if (!method) return null;
-
- const opRef = method.operation?.$ref;
- if (!opRef) return null;
-
- // opRef is like '#/paths/~1?..../post' — extract path key and HTTP verb
- const opRefParts = opRef.replace('#/paths/', '').split('/');
- const pathKey = decodeURIComponent(opRefParts.slice(0, -1).join('/').replace(/~1/g, '/').replace(/~0/g, '~'));
- const httpVerb = opRefParts[opRefParts.length - 1];
-
- const operation = servicePaths?.[pathKey]?.[httpVerb];
- if (!operation) return null;
-
- // Get the 200 response schema ref
- const responseContent = operation.responses?.['200']?.content;
- if (!responseContent) return null;
- const mediaType = Object.keys(responseContent)[0];
- const schemaRef = responseContent[mediaType]?.schema?.$ref;
- if (!schemaRef) return null;
-
- const responseSchemaName = schemaRef.split('/').pop();
- let responseSchema = componentsSchemas[responseSchemaName];
- if (!responseSchema) return null;
-
- // If the method has an objectKey like $.ResourceTagMappingList, resolve that property's items
- const objectKey = method.response?.objectKey;
- if (objectKey) {
- const propName = objectKey.replace(/^\$\./, '');
- const propDef = responseSchema.properties?.[propName];
- if (propDef) {
- let itemsRef = propDef.items?.$ref || propDef.allOf?.[0]?.items?.$ref;
- // Handle allOf wrapping a $ref to an array type
- if (!itemsRef && propDef.allOf) {
- for (const entry of propDef.allOf) {
- const resolved = entry.$ref ? componentsSchemas[entry.$ref.split('/').pop()] : entry;
- if (resolved?.items?.$ref) { itemsRef = resolved.items.$ref; break; }
- if (resolved?.items) { return resolved.items; }
- }
- }
- if (itemsRef) {
- const itemSchemaName = itemsRef.split('/').pop();
- return componentsSchemas[itemSchemaName] || null;
- }
- }
- }
-
- return responseSchema;
- } catch (e) {
- return null;
- }
-}
-
-function generateNativeSelectExample(sqlExampleSelect, sqlExampleListCols, sqlExampleFrom, sqlExampleListWhere) {
- if (!sqlExampleListWhere) return '';
- return `## ${mdCodeAnchor}SELECT${mdCodeAnchor} examples\n\n${sqlCodeBlockStart}\n${sqlExampleSelect}\n${sqlExampleListCols}\n${sqlExampleFrom}\n${sqlExampleListWhere}\n${codeBlockEnd}`;
-}
-
-function createResourceIndexContent(serviceName, resourceName, resourceType, resourceIdentifiers, schemaName, schema, componentsSchemas, servicePaths, serviceResources, resourceData, schemaTypeName, listOnlyResource) {
- // Create the markdown content for each resource index
- // resourceType is x-type, one of 'cloud_control' (cloud control), 'native' (native resource), 'view' (SQL view)
-
- let sqlExampleWhere;
- let sqlExampleListWhere;
- let sqlExampleGetWhere;
- let permissionsHeadingMarkdown ='';
- let permissionsBylineMarkdown = '';
- let permissionsMarkdown = '';
- let hasList = false;
- let hasGet = false;
- let isSelectable = true;
- let sqlVerbsList = [];
-
- const singularResourceName = pluralize.singular(resourceName);
- const listOnlyResourceName = listOnlyResource ? listOnlyResource.name : null;
-
- const moreInfoCaption = `For more information, see ${schemaTypeName}.`;
-
- const requiredParamsMap = {
- create_resource: (schema['x-required-properties'] ? schema['x-required-properties'].join(', ') + ', region' : 'region'),
- delete_resource: "Identifier, region",
- update_resource: "Identifier, PatchDocument, region",
- };
-
- const sqlExampleSelect = `SELECT`;
- const sqlExampleFrom = `FROM ${providerName}.${serviceName}.${resourceName}`;
-
- sqlExampleWhere = `WHERE region = '${getRegionExample(serviceName)}'`;
-
- let fields = resourceIdentifiers;
-
- let resourceDescription = `Creates, updates, deletes or gets ${indefinite(singularResourceName, { articleOnly: true })} ${singularResourceName} resource or lists ${resourceName} in a region`;
-
- if (resourceName.endsWith('_tags')) {
- const cleanedResourceName = resourceName.replace('_tags', '');
- resourceDescription = `Expands all tag keys and values for ${pluralize.plural(cleanedResourceName)} in a region`;
- }
-
- if(resourceName.endsWith('_list_only')){
- const cleanedResourceName = resourceName.replace('_list_only', '');
- resourceDescription = `Lists ${cleanedResourceName} in a region or regions, for all properties use ${cleanedResourceName}`;
- }
-
- function getSqlVerbsFromResourceData(resourceData) {
- // Extract SQL verbs directly from the resource data instead of calling StackQL
- const sqlVerbsList = [];
-
- if (resourceData.sqlVerbs) {
- Object.entries(resourceData.sqlVerbs).forEach(([sqlVerbName, methodRefs]) => {
- if (methodRefs && Array.isArray(methodRefs) && methodRefs.length > 0) {
- methodRefs.forEach(methodRef => {
- if (methodRef && methodRef.$ref) {
- const methodName = methodRef.$ref.split('/').pop();
- // Use a simplified required params mapping for native resources
- let requiredParams = 'region';
-
- // Map common method patterns to their required parameters
- if (methodName.includes('list')) {
- requiredParams = 'region';
- } else if (methodName.includes('get') || methodName.includes('describe')) {
- requiredParams = 'Identifier, region';
- } else if (methodName.includes('create')) {
- requiredParams = 'Identifier, region';
- } else if (methodName.includes('update') || methodName.includes('modify')) {
- requiredParams = 'Identifier, PatchDocument, region';
- } else if (methodName.includes('delete')) {
- requiredParams = 'Identifier, region';
- }
-
- sqlVerbsList.push({
- sqlVerbName,
- methodName,
- requiredParams
- });
- }
- });
- }
- });
- }
-
- return sqlVerbsList;
- }
-
- if (resourceType === 'view') {
- // isPlural = false;
- fields = schema.properties;
- resourceDescription = schema.description;
- if(schema['x-example-where-clause']){
- sqlExampleWhere = schema['x-example-where-clause'];
- }
-
- sqlVerbsList.push({
- sqlVerbName: 'select',
- methodName: 'view',
- requiredParams: 'region'
- });
- }
-
- if (resourceType === 'native') {
- const rawSchema = resourceData['x-cfn-schema-name'] && componentsSchemas[resourceData['x-cfn-schema-name']];
-
- // Unwrap array schemas (e.g. ResourceRequestStatusSummaries, ResourceDescriptions)
- // to access the actual item properties
- let resolvedNativeSchema = rawSchema || null;
- if (resolvedNativeSchema?.type === 'array' && resolvedNativeSchema?.items?.$ref) {
- const itemsSchemaName = resolvedNativeSchema.items.$ref.split('/').pop();
- resolvedNativeSchema = componentsSchemas[itemsSchemaName] || resolvedNativeSchema;
- }
-
- const hasSchemaName = !!rawSchema;
- const hasProperties = !!(resolvedNativeSchema?.properties && Object.keys(resolvedNativeSchema.properties).length > 0);
-
- if (resourceData['x-example-where-clause']) {
- sqlExampleWhere = resourceData['x-example-where-clause'];
- } else if (hasSchemaName && !hasProperties) {
- // Named-schema native with no resolvable properties and no where clause — not selectable
- isSelectable = false;
- }
- // Schema-less native (e.g. tagging) with no x-example-where-clause keeps
- // isSelectable = true and uses the default WHERE region = 'us-east-1'
-
- if (isSelectable) {
- if (hasSchemaName) {
- // Standard native resource with an explicit schema (e.g. cloud_control)
- fields = resolvedNativeSchema.properties;
- schema = resolvedNativeSchema;
- resourceDescription = resolvedNativeSchema.description || rawSchema.description;
- } else {
- // Schema-less native resource (e.g. tagging) — resolve fields from the
- // select method's response schema via the path reference
- const resolvedSchema = resolveNativeResponseSchema(resourceData, servicePaths, componentsSchemas);
- if (resolvedSchema) {
- fields = resolvedSchema.properties || {};
- schema = resolvedSchema;
- }
- }
- }
-
- if (!resourceDescription) {
- if ('x-description' in resourceData) {
- resourceDescription = resourceData['x-description'];
- }
- }
-
- const verbsToPush = getSqlVerbsFromResourceData(resourceData);
- verbsToPush.forEach(({ sqlVerbName, methodName, requiredParams }) => {
- sqlVerbsList.push({
- sqlVerbName,
- methodName,
- requiredParams
- });
- });
-
- // Set hasList if any select sqlVerb was registered
- if (isSelectable && sqlVerbsList.some(v => v.sqlVerbName === 'select')) {
- hasList = true;
- sqlExampleListWhere = `WHERE\n ${sqlExampleWhere.replace(/^WHERE\s+/, '')};`;
- }
- }
-
- if (resourceType === 'cloud_control_view') {
-
- let viewResourceName = resourceName;
-
- if (resourceName.endsWith('_tags')) {
- viewResourceName = pluralize.plural(resourceName.replace('_tags', ''));
- }
-
- if(resourceName.endsWith('_list_only')){
- viewResourceName = resourceName.replace('_list_only', '');
- }
-
- // permissions
- permissionsHeadingMarkdown = '## Permissions\n';
- permissionsBylineMarkdown = `For permissions required to operate on the ${resourceName} resource, see ${viewResourceName}\n`;
-
- sqlVerbsList.push({
- sqlVerbName: 'select',
- methodName: 'list_resources',
- requiredParams: 'region'
- });
-
- hasList = true;
- sqlExampleListWhere = `WHERE\n ${sqlExampleWhere.replace(/^WHERE\s+/, '')};`;
-
- }
-
- // Custom view resources (vw_ prefix, x-type: custom_view)
- // These use config.docs.fields for field definitions and config.docs.requiredParams for WHERE clause
- if (resourceType === 'custom_view') {
- const docsConfig = resourceData?.config?.docs;
- const docFields = docsConfig?.fields || [];
- const requiredParams = docsConfig?.requiredParams || [];
-
- // Build a synthetic schema.properties from config.docs.fields for field display
- // Exclude 'region' as it is auto-added by the doc generator
- const nonRegionFields = docFields.filter(f => f.name !== 'region');
- const syntheticProperties = {};
- for (const field of nonRegionFields) {
- syntheticProperties[field.name] = {
- type: field.type || 'string',
- description: field.description || '',
- };
- }
- // fields as array of field names (used by getColumns with isList=true)
- fields = nonRegionFields.map(f => f.name);
- schema = { properties: syntheticProperties };
-
- resourceDescription = `Custom view of ${resourceName.replace(/^vw_/, '')} resources in a region`;
-
- sqlVerbsList.push({
- sqlVerbName: 'select',
- methodName: 'view',
- requiredParams: requiredParams.length > 0 ? requiredParams.map(p => p.name).join(', ') : 'region'
- });
-
- hasList = true;
-
- // Build WHERE clause from requiredParams
- if (requiredParams.length > 0) {
- const whereParts = requiredParams.map(p => `${p.name} = '{{ ${p.name} }}'`);
- sqlExampleWhere = `WHERE ${whereParts.join(' AND\n ')}`;
- }
-
- sqlExampleListWhere = `WHERE\n ${sqlExampleWhere.replace(/^WHERE\s+/, '')};`;
- }
-
- // DDL view exception: only for native services (cloud_control, tagging)
- // These views have no x-type but have config.views.select.ddl with a SELECT * FROM
- if (!resourceType && nativeServices.includes(serviceName) && resourceData?.config?.views?.select?.ddl) {
- const ddl = resourceData.config.views.select.ddl;
-
- // Extract the base resource name from "select * from awscc.."
- const fromMatch = ddl.match(/from\s+\w+\.\w+\.(\w+)/i);
- const baseResourceName = fromMatch ? fromMatch[1] : null;
-
- if (baseResourceName) {
- // Find the base resource's schema name and resolve its fields
- const baseResource = serviceResources?.[baseResourceName];
- const baseSchemaName = baseResource?.['x-cfn-schema-name'];
- const baseSchema = baseSchemaName ? componentsSchemas[baseSchemaName] : null;
-
- // If the base schema is an array, unwrap to the items schema for fields
- let resolvedBaseSchema = baseSchema;
- if (baseSchema?.type === 'array' && baseSchema?.items?.$ref) {
- const itemsSchemaName = baseSchema.items.$ref.split('/').pop();
- resolvedBaseSchema = componentsSchemas[itemsSchemaName] || baseSchema;
- }
-
- if (resolvedBaseSchema?.properties) {
- fields = resolvedBaseSchema.properties;
- schema = resolvedBaseSchema;
- }
- }
-
- resourceDescription = `View of ${baseResourceName || resourceName} filtered by the SQL WHERE clause; see ${baseResourceName || resourceName} for the full resource.`;
-
- sqlVerbsList.push({
- sqlVerbName: 'select',
- methodName: 'view',
- requiredParams: 'region'
- });
-
- hasList = true;
- sqlExampleListWhere = `WHERE\n ${sqlExampleWhere.replace(/^WHERE\s+/, '')};`;
- }
-
- if (resourceType === 'cloud_control') {
-
- // permissions
- permissionsHeadingMarkdown = schema?.['x-required-permissions'] ? '## Permissions\n' : '';
- permissionsBylineMarkdown = schema?.['x-required-permissions'] ? `To operate on the ${resourceName} resource, the following permissions are required:\n` : '';
-
- const permissionTabs = Object.entries(schema?.['x-required-permissions'] ?? {})
- .map(([permissionType, permissions]) => {
- const includedTypes = ['create', 'list', 'delete', 'read', 'update'];
-
- if (includedTypes.includes(permissionType)) {
- // update flags
- if (permissionType === 'list') {
- hasList = true;
- }
- if (permissionType === 'read') {
- hasGet = true;
- }
-
- if(!hasList && !hasGet){
- isSelectable = false;
- }
-
- if (permissions.length === 0) {
- return null;
- }
-
- const label = permissionType.charAt(0).toUpperCase() + permissionType.slice(1);
- const permissionsList = permissions.join(",\n");
-
- return { value: permissionType, label, permissionsList };
- } else {
- return null;
- }
- })
- .filter(section => section !== null);
-
- if (permissionTabs.length > 0) {
- const tabValues = permissionTabs.map(t => ` { label: '${t.label}', value: '${t.value}', },`).join('\n');
- const tabItems = permissionTabs.map(t => `
-
-${jsonCodeBlockStart}
-${t.permissionsList}
-${codeBlockEnd}
-
-`).join('\n');
-
- permissionsMarkdown = `
-${tabItems}
-`;
- } else {
- permissionsMarkdown = '';
- }
-
- // covers non select ops for cc resources
- sqlVerbsList = Object.entries(resourceData.sqlVerbs).map(([key, value]) => {
- if (value && Array.isArray(value) && value.length > 0 && typeof value[0].$ref == 'string') {
- return {
- sqlVerbName: key,
- methodName: value[0].$ref.split('/').pop(),
- requiredParams: requiredParamsMap[value[0].$ref.split('/').pop()]
- };
- }});
-
- if(hasList){
- sqlVerbsList.push({
- sqlVerbName: 'select',
- methodName: 'list_resources',
- requiredParams: 'region'
- });
- }
-
- if(hasGet){
- sqlVerbsList.push({
- sqlVerbName: 'select',
- methodName: 'get_resource',
- requiredParams: 'Identifier, region'
- });
- }
-
- const identifierValues = resourceIdentifiers.map(id => `{{ ${toSnakeCase(fixCamelCaseIssues(id))} }}`).join('|');
- const identifierClause = `Identifier = '${identifierValues}'`;
-
- sqlExampleListWhere = `WHERE\n ${sqlExampleWhere.replace(/^WHERE\s+/, '')};`;
- sqlExampleGetWhere = `WHERE\n ${sqlExampleWhere.replace(/^WHERE\s+/, '')} AND\n ${identifierClause};`;
-
- }
-
- sqlVerbsList = sqlVerbsList.filter(Boolean);
-
- // if resourceDescription is an empty string or undefined, error out
- if (!resourceDescription) {
- console.error(`Resource description is missing for ${serviceName}.${resourceName}`);
- return;
- }
-
- const sqlExampleListCols = getColumns(fields, true, resourceType === 'native', false);
- const sqlExampleGetCols = getColumns(schema.properties, false, resourceType === 'native', false);
- const sqlExampleGetTagsCols = getColumns(schema.properties, false, resourceType === 'native', true);
-
- // Compute list_only fields for tabbed display
- const listOnlyFields = listOnlyResource
- ? generateSchemaJsonForResource(serviceName, listOnlyResourceName, 'cloud_control_view', schema, componentsSchemas, sqlExampleListCols)
- : null;
- const listOnlyFrom = listOnlyResourceName
- ? `FROM ${providerName}.${serviceName}.${listOnlyResourceName}`
- : null;
-
- // Build the Fields section
- let fieldsSection;
- if (!(isSelectable || hasList || hasGet)) {
- fieldsSection = 'SELECT operation not supported for this resource.';
- } else if (listOnlyResource && hasGet) {
- const getFields = JSON.stringify(generateSchemaJsonForResource(serviceName, resourceName, resourceType, schema, componentsSchemas, sqlExampleListCols), null, 2);
- const listFields = JSON.stringify(listOnlyFields, null, 2);
- fieldsSection = `
-
-
-
-
-
-
-
-
-`;
- } else {
- fieldsSection = ``;
- }
-
- return `---
-title: ${resourceName}
-hide_title: false
-hide_table_of_contents: false
-keywords:
- - ${resourceName}
- - ${serviceName}
- - aws
- - stackql
- - infrastructure-as-code
- - configuration-as-data
- - cloud inventory
-description: Query, deploy and manage AWS resources using SQL
-custom_edit_url: null
-image: /img/stackql-aws-provider-featured-image.png
----
-
-import CodeBlock from '@theme/CodeBlock';
-import CopyableCode from '@site/src/components/CopyableCode/CopyableCode';
-import Tabs from '@theme/Tabs';
-import TabItem from '@theme/TabItem';
-import SchemaTable from '@site/src/components/SchemaTable/SchemaTable';
-
-${resourceDescription}
-
-## Overview
-
-
-| Name | ${resourceName} |
-| Type | Resource |
-${schema.description ? `| Description | ${wrapInDetails(cleanDescription(schema.description))} |
` : `| Description | ${resourceName} |
`}
-| Id | |
-
-
-
-## Fields
-${fieldsSection}
-${schemaName && resourceType === 'cloud_control' ? `\n${moreInfoCaption}\n` : ''}
-## Methods
-${generateMethodsTable(serviceName, resourceName, sqlVerbsList, listOnlyResourceName)}
-
-${resourceType !== 'native' ? generateSelectExamples(resourceName, hasList, hasGet, sqlExampleSelect, sqlExampleListCols, sqlExampleFrom, sqlExampleListWhere, sqlExampleGetCols, sqlExampleGetTagsCols, sqlExampleGetWhere, listOnlyResourceName, listOnlyFrom): generateNativeSelectExample(sqlExampleSelect, sqlExampleListCols, sqlExampleFrom, sqlExampleListWhere)}
-${resourceType !== 'native' ? createInsertExample(serviceName, resourceName, resourceData, schema, componentsSchemas): ''}
-${resourceType !== 'native' ? createUpdateExample(serviceName, resourceName, resourceData, schema, componentsSchemas): ''}
-${resourceType !== 'native' ? createDeleteExample(serviceName, resourceName, resourceData, schema, componentsSchemas): ''}
-${resourceType !== 'native' ? createAdditionalParametersSection(serviceName, resourceData): ''}
-${permissionsHeadingMarkdown}
-${permissionsBylineMarkdown}
-${permissionsMarkdown}`;
-}
-
-function getDescFromRef(ref, components) {
- // Assuming ref is a string like '#/components/schemas/Arn'
- const schemaname = ref.split('/').pop();
- let schema = components[schemaname];
- return schema.description;
-}
-
-function getTypeFromRef(ref, components) {
- // Assuming ref is a string like '#/components/schemas/Arn'
- const schemaname = ref.split('/').pop();
- let schema = components[schemaname];
- return schema.type;
-}
-
-function cleanDescription(description) {
- if (!description) return '';
-
- // Step 1: Convert markdown inline elements to HTML.
- // Order: double-backtick before single, links before bare brackets, bold before italic.
- let result = description;
- result = result.replace(/``([^`]+?)``/g, '$1');
- result = result.replace(/`([^`]+?)`/g, '$1');
- result = result.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, '$1');
- result = result.replace(/\*\*([^*]+?)\*\*/g, '$1');
- // *italic* — must not greedily consume across line boundaries
- result = result.replace(/\*([^*\n]+?)\*/g, '$1');
-
- // Step 2: Process line structure — split on newlines, trim indent, convert
- // ordered list items (1. text) to , group consecutive into ,
- // and join remaining lines with
(blank lines become paragraph breaks).
- const lines = result.split(/\r?\n/);
- const processed = [];
- let inList = false;
-
- for (const rawLine of lines) {
- const line = rawLine.trim();
-
- if (/^\d+\.\s+/.test(line)) {
- // Ordered list item → bullet -
- if (!inList) {
- processed.push('
');
- inList = true;
- }
- processed.push('- ' + line.replace(/^\d+\.\s+/, '') + '
');
- } else {
- if (inList) {
- processed.push('
');
- inList = false;
- }
- if (line === '') {
- // Blank line — skip (lists and text lines already handle their own spacing)
- } else {
- // Non-empty non-list line — append with a single
separator
- const prev = processed.length > 0 ? processed[processed.length - 1] : '';
- if (processed.length > 0 &&
- !prev.endsWith('')) {
- processed.push('
');
- }
- processed.push(line);
- }
- }
- }
- if (inList) processed.push('
');
- result = processed.join('');
-
- // Step 3: Protect all HTML tags from MDX escaping using placeholders.
- const htmlTags = [];
- result = result.replace(/<[^>]+>/g, (tag) => {
- htmlTags.push(tag);
- return `HTMLTAG${htmlTags.length - 1}END`;
- });
-
- // Step 4: Escape only MDX-breaking chars in plain text.
- result = result
- .replace(/{/g, '{')
- .replace(/}/g, '}');
-
- // Step 5: Restore HTML tags.
- result = result.replace(/HTMLTAG(\d+)END/g, (_, i) => htmlTags[parseInt(i)]);
-
- return result.trim();
-}
-
-function wrapInDetails(html) {
- const breakIndex = html.indexOf('
');
- if (breakIndex === -1) return html;
- const firstLine = html.substring(0, breakIndex);
- const rest = html.substring(breakIndex + 6); // skip '
'
- if (!rest.trim()) return html;
- return `${firstLine}
${rest} `;
-}
-
-function toSnakeCase(str) {
- return str.replace(/[\w]([A-Z])/g, (m) => m[0] + "_" + m[1]).toLowerCase();
-}
-
-function fixCamelCaseIssues(propertyName) {
- const replacements = {
- DB: "Db",
- ARN: "Arn",
- WAN: "Wan",
- URL: "Url",
- VPC: "Vpc",
- DNS: "Dns",
- IP: "Ip",
- IAM: "Iam",
- TLS: "Tls",
- SSM: "Ssm",
- CNAME: "Cname",
- SQ: "Sq",
- ML: "Ml",
- SKU: "Sku",
- KMS: "Kms",
- OK: "Ok",
- CA: "Ca",
- ID: "Id",
- TTL: "Ttl",
- URI: "Uri",
- LB: "Lb",
- SNS: "Sns",
- AWS: "Aws",
- ACL: "Acl",
- AZ: "Az",
- API: "Api",
- MSK: "Msk",
- ECS: "Ecs",
- EBS: "Ebs",
- CWE: "Cwe",
- SSE: "Sse",
- HPO: "Hpo",
- LF: "Lf",
- WAL: "Wal",
- OAuth: "Oauth",
- };
-
- let updatedPropertyName = propertyName;
-
- for (const [key, value] of Object.entries(replacements)) {
- const regex = new RegExp(key, 'g');
- updatedPropertyName = updatedPropertyName.replace(regex, value);
- }
-
- return updatedPropertyName;
-}
-
-function generateTableRow(propName, type, descriptionCleaned, isCustom = false, nestedHtml = '') {
- if(isCustom){
- return ` | ${type} | ${descriptionCleaned}${nestedHtml} |
\n`;
- } else {
- return ` | ${type} | ${descriptionCleaned}${nestedHtml} |
\n`;
- }
-}
-
-function generateSchemaJson(schema, componentsSchemas, depth = 0, maxDepth = 4, visitedRefs = new Set()) {
- if (depth >= maxDepth || !schema?.properties) return [];
-
- return Object.entries(schema.properties).map(([propName, prop]) => {
- let propType = prop.type || '';
- let propDesc = '';
- let children = [];
-
- // Resolve $ref
- let resolvedSchema = prop;
- if (prop.$ref) {
- const refName = prop.$ref.split('/').pop();
- if (visitedRefs.has(refName)) return null; // Prevent cycles
- visitedRefs.add(refName);
- resolvedSchema = componentsSchemas[refName] || {};
- propType = propType || resolvedSchema.type || 'object';
- propDesc = resolvedSchema.description || '';
- }
-
- // Handle allOf
- if (prop.allOf) {
- resolvedSchema = {};
- prop.allOf.forEach(item => {
- if (item.$ref) {
- const refName = item.$ref.split('/').pop();
- if (!visitedRefs.has(refName)) {
- visitedRefs.add(refName);
- Object.assign(resolvedSchema, componentsSchemas[refName] || {});
- }
- } else {
- Object.assign(resolvedSchema, item);
- }
- });
- propType = propType || resolvedSchema.type || 'object';
- propDesc = propDesc || resolvedSchema.description || '';
- }
-
- if (prop.description) propDesc = prop.description;
- propDesc = wrapInDetails(cleanDescription(propDesc));
-
- // Handle arrays - get children from items
- if ((propType === 'array' || resolvedSchema.type === 'array') && (resolvedSchema.items || prop.items)) {
- let itemsSchema = resolvedSchema.items || prop.items;
- if (itemsSchema.$ref) {
- const refName = itemsSchema.$ref.split('/').pop();
- if (!visitedRefs.has(refName)) {
- visitedRefs.add(refName);
- itemsSchema = componentsSchemas[refName] || {};
- }
- }
- if (itemsSchema.properties) {
- children = generateSchemaJson(itemsSchema, componentsSchemas, depth + 1, maxDepth, new Set(visitedRefs));
- }
- }
-
- // Handle objects - get children from properties
- if ((propType === 'object' || resolvedSchema.type === 'object') && resolvedSchema.properties) {
- children = generateSchemaJson(resolvedSchema, componentsSchemas, depth + 1, maxDepth, new Set(visitedRefs));
- }
-
- const fieldName = toSnakeCase(fixCamelCaseIssues(propName));
-
- return {
- name: fieldName,
- type: propType || 'object',
- description: propDesc,
- ...(children.length > 0 && { children })
- };
- }).filter(Boolean);
-}
-
-function generateSchemaJsonForResource(serviceName, resourceName, resourceType, schema, componentsSchemas, sqlExampleListCols) {
- const isTags = resourceName.endsWith('_tags');
- const isListOnly = resourceName.endsWith('_list_only');
-
- let fields = generateSchemaJson(schema, componentsSchemas, 0, 4);
-
- // Filter for list_only resources
- if (isListOnly) {
- fields = fields.filter(f => sqlExampleListCols.includes(f.name));
- }
-
- // Filter out Tags for _tags resources
- if (isTags) {
- fields = fields.filter(f => f.name !== 'tags');
- fields.push({ name: 'tag_key', type: 'string', description: 'Tag key.' });
- fields.push({ name: 'tag_value', type: 'string', description: 'Tag value.' });
- }
-
- // Add region
- fields.push({ name: 'region', type: 'string', description: 'AWS region.' });
-
- console.log(`Generated schema JSON for ${serviceName}.${resourceName}`);
- return fields;
-}
-
-// function generateNestedPropertiesHtml(propSchema, componentsSchemas, depth = 0, maxDepth = 3) {
-// if (depth >= maxDepth || !propSchema) return '';
-
-// let schema = propSchema;
-
-// // Resolve $ref if present
-// if (propSchema.$ref) {
-// const schemaName = propSchema.$ref.split('/').pop();
-// schema = componentsSchemas[schemaName];
-// if (!schema) return '';
-// }
-
-// // Handle allOf
-// if (propSchema.allOf) {
-// schema = {};
-// propSchema.allOf.forEach(item => {
-// if (item.$ref) {
-// const refSchema = componentsSchemas[item.$ref.split('/').pop()];
-// Object.assign(schema, refSchema);
-// } else {
-// Object.assign(schema, item);
-// }
-// });
-// }
-
-// // For arrays, get items schema
-// if (schema.type === 'array' && schema.items) {
-// let itemsSchema = schema.items;
-// if (schema.items.$ref) {
-// const schemaName = schema.items.$ref.split('/').pop();
-// itemsSchema = componentsSchemas[schemaName];
-// }
-// if (itemsSchema && (itemsSchema.type === 'object' || itemsSchema.properties)) {
-// return generateNestedPropertiesHtml(itemsSchema, componentsSchemas, depth, maxDepth);
-// }
-// return '';
-// }
-
-// // For objects with properties
-// if (schema.properties && Object.keys(schema.properties).length > 0) {
-// let rows = '';
-// for (const [nestedPropName, nestedProp] of Object.entries(schema.properties)) {
-// let nestedType = nestedProp.type || '';
-// let nestedDesc = '';
-
-// if (nestedProp.$ref) {
-// const refSchema = componentsSchemas[nestedProp.$ref.split('/').pop()];
-// if (refSchema) {
-// nestedType = nestedType || refSchema.type || 'object';
-// nestedDesc = refSchema.description || '';
-// }
-// }
-// if (nestedProp.description) {
-// nestedDesc = nestedProp.description;
-// }
-
-// nestedDesc = cleanDescription(nestedDesc);
-
-// // Recurse for nested objects/arrays
-// const nestedHtml = generateNestedPropertiesHtml(nestedProp, componentsSchemas, depth + 1, maxDepth);
-
-// rows += `${toSnakeCase(fixCamelCaseIssues(nestedPropName))} | ${nestedType} | ${nestedDesc}${nestedHtml} |
`;
-// }
-
-// // Return minified HTML - no newlines
-// return `Show properties
| Property | Type | Description |
${rows}
`;
-// }
-
-// return '';
-// }
-
-function getColumns(properties, isList, isCustom = false, is_tags = false){
- let columns = ' region,';
- if (isList) {
- if(Array.isArray(properties)){
- properties.forEach(propName => {
- if(isCustom){
- columns += `\n ${propName},`;
- } else {
- columns += `\n ${toSnakeCase(fixCamelCaseIssues(propName))},`;
- }
- })
- }
- } else {
- for (let propName in properties) {
- if(isCustom){
- columns += `\n ${propName},`;
- } else {
- if(propName === 'Tags' && is_tags){
- continue;
- }
- columns += `\n ${toSnakeCase(fixCamelCaseIssues(propName))},`;
- }
- }
- }
-
- if(is_tags){
- columns += `\n tag_key,`;
- columns += `\n tag_value,`;
- }
-
- columns = columns.slice(0, -1);
- return columns;
-}
-
-// function generateFieldsTable(serviceName, resourceName, resourceType, schemaName, schema, componentsSchemas, sqlExampleListCols) {
-
-// let isTags = false;
-// if(resourceName.endsWith('_tags')){
-// isTags = true;
-// }
-
-// let isListOnly = false;
-// if(resourceName.endsWith('_list_only')){
-// isListOnly = true;
-// }
-
-// let fieldsTable = '';
-// console.log(`Generating fields table for ${serviceName}.${resourceName}`);
-// for (let propName in schema.properties) {
-
-// if(isListOnly){
-// if(!sqlExampleListCols.includes(toSnakeCase(fixCamelCaseIssues(propName)))){
-// continue;
-// }
-// }
-
-// if(isTags && propName === 'Tags'){
-// continue;
-// }
-
-// let propDesc = '';
-// let propType = '';
-// const prop = schema.properties[propName];
-
-// if('description' in prop){
-// propDesc = cleanDescription(prop.description);
-// }
-// if('type' in prop){
-// propType = prop.type;
-// }
-// if (prop.hasOwnProperty('$ref')) {
-// !propType ? propType = getTypeFromRef(prop['$ref'], componentsSchemas) : null;
-// !propDesc ? propDesc = cleanDescription(getDescFromRef(prop['$ref'], componentsSchemas)) : null;
-// } else if (prop.hasOwnProperty('allOf')) {
-// let allOfArray = prop.allOf;
-// let combinedObject = {};
-// allOfArray.forEach(item => {
-// Object.assign(combinedObject, item);
-// });
-// !propType ? propType = combinedObject.type : null;
-// !propDesc ? propDesc = combinedObject.description : null;
-// if(combinedObject.hasOwnProperty('$ref')){
-// !propType ? propType = getTypeFromRef(combinedObject['$ref'], componentsSchemas) : null;
-// !propDesc ? propDesc = cleanDescription(getDescFromRef(combinedObject['$ref'], componentsSchemas)) : null;
-// }
-// }
-
-// // Generate expandable nested properties for objects and arrays
-// let nestedHtml = '';
-// if (propType === 'object' || propType === 'array') {
-// nestedHtml = generateNestedPropertiesHtml(prop, componentsSchemas, 0, 3);
-// }
-
-// fieldsTable += generateTableRow(propName, propType, propDesc, false, nestedHtml);
-// }
-
-// if(isTags){
-// fieldsTable += generateTableRow('tag_key', 'string', 'Tag key.');
-// fieldsTable += generateTableRow('tag_value', 'string', 'Tag value.');
-// }
-
-// fieldsTable += generateTableRow('region', 'string', 'AWS region.');
-
-// return fieldsTable;
-// }
-
-function generateSelectExamples(resourceName, hasList, hasGet, sqlExampleSelect, sqlExampleListCols, sqlExampleFrom, sqlExampleListWhere, sqlExampleGetCols, sqlExampleGetTagsCols, sqlExampleGetWhere, listOnlyResourceName, listOnlyFrom){
- let returnString = '';
- if(hasList || hasGet){
- returnString = `## ${mdCodeAnchor}SELECT${mdCodeAnchor} examples\n`;
- } else {
- return returnString;
- }
-
- // Tabbed select for cloud_control resources with both get and list_only
- if(listOnlyResourceName && hasGet && hasList){
- const singularResourceName = pluralize.singular(resourceName);
- const getDesc = `Gets all properties from an individual ${singularResourceName}.`;
- const listDesc = `Lists all ${resourceName} in a region.`;
- const listFromClause = listOnlyFrom || sqlExampleFrom;
-
- returnString += `\n
-
-
-${getDesc}
-${sqlCodeBlockStart}
-${sqlExampleSelect}
-${sqlExampleGetCols}
-${sqlExampleFrom}
-${sqlExampleGetWhere}
-${codeBlockEnd}
-
-
-
-${listDesc}
-${sqlCodeBlockStart}
-${sqlExampleSelect}
-${sqlExampleListCols}
-${listFromClause}
-${sqlExampleListWhere}
-${codeBlockEnd}
-
-`;
- return returnString;
- }
-
- if(hasList){
- let listDesc = `Lists all ${resourceName.replace('_list_only','')} in a region.`;
-
- if(resourceName.startsWith('vw_')){
- listDesc = `Lists all ${resourceName} in a region.`;
- returnString += `${listDesc}\n${sqlCodeBlockStart}\n${sqlExampleSelect}\n${sqlExampleListCols}\n${sqlExampleFrom}\n${sqlExampleListWhere}\n${codeBlockEnd}`;
- } else if(resourceName.endsWith('_list_only')){
- returnString += `${listDesc}\n${sqlCodeBlockStart}\n${sqlExampleSelect}\n${sqlExampleListCols}\n${sqlExampleFrom}\n${sqlExampleListWhere}\n${codeBlockEnd}`;
- } else if(resourceName.endsWith('_tags')){
- listDesc = `Expands tags for all ${pluralize.plural(resourceName.replace('_tags',''))} in a region.`;
- returnString += `${listDesc}\n${sqlCodeBlockStart}\n${sqlExampleSelect}\n${sqlExampleGetTagsCols}\n${sqlExampleFrom}\n${sqlExampleListWhere}\n${codeBlockEnd}`;
- }
- }
-
- if(hasGet){
- const singularResourceName = pluralize.singular(resourceName);
- const getDesc = `Gets all properties from an individual ${singularResourceName}.`;
- returnString += `\n${getDesc}\n${sqlCodeBlockStart}\n${sqlExampleSelect}\n${sqlExampleGetCols}\n${sqlExampleFrom}\n${sqlExampleGetWhere}\n${codeBlockEnd}`;
- }
-
- return returnString;
-}
-
-function generateMethodsTable(serviceName, resourceName, sqlVerbsList, listOnlyResourceName) {
- const hasResourceCol = !!listOnlyResourceName;
-
- let html = `
-
-
-
- | Name | ${hasResourceCol ? '\n Resource | ' : ''}
- Accessible by |
- Required Params |
-
`;
-
- sqlVerbsList.forEach(item => {
- const itemResource = (item.methodName === 'list_resources' && listOnlyResourceName)
- ? listOnlyResourceName
- : resourceName;
- html += `
-
- | ${hasResourceCol ? `\n ${itemResource} | ` : ''}
- ${item.sqlVerbName.toUpperCase()} |
- |
-
`;
- });
-
- html += `
-
-
`;
-
- return html;
-}
-
-function clearAndCreateDocsDir(directory = path.join(__dirname, `${websiteDir}/docs`)) {
- if (fs.existsSync(directory)) {
- console.log(`Clearing existing docs directory: ${directory}`);
- fs.readdirSync(directory).forEach((file) => {
- const currentPath = path.join(directory, file);
- if (fs.lstatSync(currentPath).isDirectory()) {
- // Skip directories listed in staticServices
- if (staticServices.includes(file)) {
- console.log("Skipping directory:", currentPath);
- return; // Skip this directory and its contents
- }
- // Recurse if it's a directory and not in staticServices
- clearAndCreateDocsDir(currentPath);
- // After recursion, check if the directory is empty before attempting to remove it
- if (fs.readdirSync(currentPath).length === 0) {
- fs.rmdirSync(currentPath); // Remove the directory once it's empty
- }
- } else {
- // Delete the file if it's not a directory and not the MDX file
- fs.unlinkSync(currentPath);
- }
- });
- } else {
- console.log("Docs directory not found, creating:", directory);
- }
-
- // Ensure the directory exists
- if (!fs.existsSync(directory)) {
- fs.mkdirSync(directory, { recursive: true });
- console.log("Created docs directory:", directory);
- }
-}
-
-function clearMdDocsDir(directory = `${websiteDir}/docs`) {
- if (fs.existsSync(directory)) {
- fs.readdirSync(directory).forEach((file) => {
- const currentPath = path.join(directory, file);
- if (fs.lstatSync(currentPath).isDirectory()) {
- // Skip directories listed in staticServices
- if (staticServices.includes(file)) {
- console.log("Skipping directory:", currentPath);
- return; // Skip this directory and its contents
- }
- // Recurse if it's a directory and not in staticServices
- clearMdDocsDir(currentPath);
- // After recursion, check if the directory is empty before attempting to remove it
- if (fs.readdirSync(currentPath).length === 0) {
- fs.rmdirSync(currentPath); // Remove the directory once it's empty
- }
- } else {
- // Delete the file if it's not a directory and not the MDX file
- fs.unlinkSync(currentPath);
- }
- });
- } else {
- console.log("Directory not found:", directory);
- }
-}
-
-function countResourceDirectories(serviceDir) {
- return fs.readdirSync(serviceDir, { withFileTypes: true })
- .filter(dirent => dirent.isDirectory()).length;
-}
-
-//
-// Main
-//
-
-// Clear and recreate the docs directory with absolute path
-const docsPath = path.join(__dirname, `${websiteDir}/docs`);
-clearAndCreateDocsDir(docsPath);
-
-// delete index.md if it exists (using absolute path)
-const indexMdPath = path.join(__dirname, `${websiteDir}/docs/index.md`);
-if (fs.existsSync(indexMdPath)) {
- fs.unlinkSync(indexMdPath);
- console.log(`Deleted existing index.md: ${indexMdPath}`);
-}
-
-// init provider index
-let servicesForIndex = [];
-
-// Static header content
-const headerContent1 = fs.readFileSync(path.join(__dirname,`${providerDevDir}/docgen/provider-data/headerContent1.txt`), 'utf8');
-const headerContent2 = fs.readFileSync(path.join(__dirname,`${providerDevDir}/docgen/provider-data/headerContent2.txt`), 'utf8');
-
-// Initialize counters for services and resources
-let totalServicesCount = 0;
-let totalResourcesCount = 0;
-
-//
-// Iterate over each yaml file in the output directory
-// and generate documentation for each service
-//
-async function main() {
- const files = fs.readdirSync(path.join(__dirname, `${openAPIdir}/src/${providerDirName}/v00.00.00000/services`));
-
- for (const file of files) {
- if (path.extname(file) === '.yaml') {
- const serviceName = path.basename(file, '.yaml');
- servicesForIndex.push(serviceName);
- totalServicesCount++; // Increment service counter
- await createDocsForService(path.join(__dirname, `${openAPIdir}/src/${providerDirName}/v00.00.00000/services`, file));
- }
- }
-
- //
- // get data for provider index
- //
-
- // add non cloud control services
- servicesForIndex = servicesForIndex.concat(staticServices);
-
- // make sure servicesForIndex is unique
- servicesForIndex = [...new Set(servicesForIndex)];
-
- // add services to index
- servicesForIndex.sort();
-
- // Splitting services into two columns
- const half = Math.ceil(servicesForIndex.length / 2);
- const firstColumnServices = servicesForIndex.slice(0, half);
- const secondColumnServices = servicesForIndex.slice(half);
-
- // Function to convert services to markdown links
- function servicesToMarkdown(servicesList) {
- return servicesList.map(service => `${service}
`).join('\n');
- }
-
- // Create the content for the index file
- const indexContent = `${headerContent1}
-
-:::info Provider Summary
-
-
-
-total services: ${totalServicesCount}
-total resources: ${totalResourcesCount}
-
-
-
-:::
-
-${headerContent2}
-
-## Services
-
-
-${servicesToMarkdown(firstColumnServices)}
-
-
-${servicesToMarkdown(secondColumnServices)}
-
-
-`;
-
- // Write the index file
- const indexPath = path.join(__dirname, `${websiteDir}/docs/index.md`);
- fs.writeFileSync(indexPath, indexContent);
-
- console.log(`Index file created at ${indexPath}`);
-}
-
-// Run the main function
-main().catch(console.error);
+import fs from 'fs';
+import path from 'path';
+import yaml from 'js-yaml';
+import pluralize from 'pluralize';
+import indefinite from 'indefinite';
+import { exec } from 'child_process';
+import { fileURLToPath } from 'url';
+import { dirname } from 'path';
+
+// ES module equivalents for __dirname and __filename
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+//
+// update ...
+//
+const providerName = 'awscc';
+const providerDirName = 'awscc'; // Directory name differs from provider name
+const staticServices = [];
+const nativeServices = ['cloud_control', 'tagging'];
+// AWS global services where us-east-1 is the canonical region for Cloud Control API
+const globalOnlyServices = ['iam', 'route53', 'organizations', 'cloudfront', 'globalaccelerator', 'shield', 'budgets'];
+
+function getRegionExample(serviceName) {
+ return globalOnlyServices.includes(serviceName) ? 'us-east-1' : '{{ region }}';
+}
+const websiteDir = '../website';
+const providerDevDir = '../provider-dev';
+const openAPIdir = '../openapi';
+
+const sqlCodeBlockStart = '```sql';
+const jsonCodeBlockStart = '```json';
+const codeBlockEnd = '```';
+const mdCodeAnchor = "`";
+
+async function createDocsForService(yamlFilePath) {
+ const data = yaml.load(fs.readFileSync(yamlFilePath, 'utf8'));
+
+ const serviceName = path.basename(yamlFilePath, '.yaml');
+
+ if(staticServices.includes(serviceName)) {
+ console.log(`Skipping ${serviceName}`);
+ return;
+ }
+
+ const serviceFolder = path.join(__dirname, `${websiteDir}/docs/services`, serviceName);
+
+ // Create service directory under generated-docs
+ if (!fs.existsSync(serviceFolder)) {
+ fs.mkdirSync(serviceFolder, { recursive: true });
+ }
+
+ let resourcesObj = data.components['x-stackQL-resources'];
+
+ if (!resourcesObj) {
+ console.warn(`No resources found in ${yamlFilePath}`);
+ return;
+ }
+
+ const resources = [];
+ for (let resourceName in resourcesObj) {
+ let resourceData = resourcesObj[resourceName];
+
+ // Check if 'id' exists in 'resourceData'
+ if (!resourceData.id) {
+ console.warn(`No 'id' defined for resource: ${resourceName} in service: ${serviceName}`);
+ return;
+ }
+
+ // Create the markdown content for the resource
+ const schemaName = resourceData['x-cfn-schema-name'] || '';
+ const schemaTypeName = resourceData['x-cfn-type-name'] || '';
+ const methodType = resourceData['x-type'];
+ const identifiers = resourceData['x-identifiers'] || [];
+ const schema = data.components.schemas[schemaName] || {};
+ const componentsSchemas = data.components.schemas;
+
+ const servicePaths = data.paths || {};
+ const serviceResources = data.components['x-stackQL-resources'] || {};
+ resources.push({ name: resourceName, type: methodType, identifiers: identifiers, schemaName, schema, componentsSchemas, servicePaths, serviceResources, resourceData, schemaTypeName });
+ }
+
+ // Build map of _list_only resources to their base resources
+ const listOnlyMap = {};
+ for (const resource of resources) {
+ if (resource.name.endsWith('_list_only')) {
+ const baseName = resource.name.replace('_list_only', '');
+ listOnlyMap[baseName] = resource;
+ }
+ }
+
+ // Filter out _list_only resources - they will be collapsed into base resource docs
+ const filteredResources = resources.filter(r => !r.name.endsWith('_list_only'));
+
+ // Create index.md for the service
+ const serviceIndexPath = path.join(serviceFolder, 'index.md');
+ const serviceIndexContent = await createServiceIndexContent(serviceName, filteredResources);
+ fs.writeFileSync(serviceIndexPath, serviceIndexContent);
+
+ // Divide resources into two columns
+ const halfLength = Math.ceil(filteredResources.length / 2);
+ const firstColumn = filteredResources.slice(0, halfLength);
+ const secondColumn = filteredResources.slice(halfLength);
+
+ // Create resource subfolders and index.md for each resource
+ firstColumn.forEach((resource) => {
+ const resourceFolder = path.join(serviceFolder, resource.name);
+ if (!fs.existsSync(resourceFolder)) {
+ fs.mkdirSync(resourceFolder, { recursive: true });
+ }
+
+ const resourceIndexPath = path.join(resourceFolder, 'index.md');
+ const resourceIndexContent = createResourceIndexContent(serviceName, resource.name, resource.type, resource.identifiers, resource.schemaName, resource.schema, resource.componentsSchemas, resource.servicePaths, resource.serviceResources, resource.resourceData, resource.schemaTypeName, listOnlyMap[resource.name]);
+ fs.writeFileSync(resourceIndexPath, resourceIndexContent);
+ });
+
+ secondColumn.forEach((resource) => {
+ const resourceFolder = path.join(serviceFolder, resource.name);
+ if (!fs.existsSync(resourceFolder)) {
+ fs.mkdirSync(resourceFolder, { recursive: true });
+ }
+
+ const resourceIndexPath = path.join(resourceFolder, 'index.md');
+ const resourceIndexContent = createResourceIndexContent(serviceName, resource.name, resource.type, resource.identifiers, resource.schemaName, resource.schema, resource.componentsSchemas, resource.servicePaths, resource.serviceResources, resource.resourceData, resource.schemaTypeName, listOnlyMap[resource.name]);
+
+ try {
+ fs.writeFileSync(resourceIndexPath, resourceIndexContent);
+ } catch (error) {
+ console.error('Error writing file:');
+ console.error('Path:', resourceIndexPath);
+ console.error('Content type:', typeof resourceIndexContent);
+ console.error('Content:', resourceIndexContent);
+ console.error('Error:', error);
+ }
+ });
+
+ console.log(`Generated documentation for ${serviceName}`);
+}
+
+async function createServiceIndexContent(serviceName, resources) {
+ const totalResources = resources.length; // Calculate the total resources
+
+ totalResourcesCount += totalResources; // Update global resource count
+
+ // Sort resources alphabetically by name
+ resources.sort((a, b) => a.name.localeCompare(b.name));
+
+ const halfLength = Math.ceil(totalResources / 2);
+ const firstColumnResources = resources.slice(0, halfLength);
+ const secondColumnResources = resources.slice(halfLength);
+
+ // Generate the HTML for resource links in the first column
+ const firstColumnLinks = generateResourceLinks(serviceName, firstColumnResources);
+
+ // Generate the HTML for resource links in the second column
+ const secondColumnLinks = generateResourceLinks(serviceName, secondColumnResources);
+
+ // Create the markdown content for the service index
+ // You can customize this content as needed
+ return `---
+title: ${serviceName}
+hide_title: false
+hide_table_of_contents: false
+keywords:
+ - ${serviceName}
+ - aws
+ - stackql
+ - infrastructure-as-code
+ - configuration-as-data
+ - cloud inventory
+description: Query, deploy and manage AWS resources using SQL
+custom_edit_url: null
+image: /img/stackql-aws-provider-featured-image.png
+---
+
+The ${serviceName} service documentation.
+
+:::info[Service Summary]
+
+
+
+total resources: ${totalResources}
+
+
+
+:::
+
+## Resources
+
+
+${firstColumnLinks}
+
+
+${secondColumnLinks}
+
+
`;
+}
+
+function generateResourceLinks(serviceName, resources) {
+ // Generate resource links for the service index
+ const resourceLinks = resources.map((resource) => {
+ return `${resource.name}`;
+ });
+ return resourceLinks.join('
\n');
+}
+
+function getReturningClause() {
+ const progressEventFields = [
+ 'ErrorCode',
+ 'EventTime',
+ 'Identifier',
+ 'Operation',
+ 'OperationStatus',
+ 'RequestToken',
+ 'ResourceModel',
+ 'RetryAfter',
+ 'StatusMessage',
+ 'TypeName',
+ ];
+ return `RETURNING\n ${progressEventFields.join(",\n ")}\n`;
+}
+
+function createAdditionalParametersSection(serviceName, resourceData) {
+ if (nativeServices.includes(serviceName)) {
+ return '';
+ }
+ const hasInsert = !!resourceData.methods?.create_resource;
+ const hasUpdate = !!resourceData.methods?.update_resource;
+ const hasDelete = !!resourceData.methods?.delete_resource;
+ if (!hasInsert && !hasUpdate && !hasDelete) {
+ return '';
+ }
+ return `
+## Additional Parameters
+
+Mutable resources in the Cloud Control provider support additional optional parameters which can be supplied with \`INSERT\`, \`UPDATE\`, or \`DELETE\` operations. These include:
+
+| Parameter | Description |
+|-----------|-------------|
+| | A unique identifier to ensure the idempotency of the resource request.
This allows the provider to accurately distinguish between retries and new requests.
A client token is valid for 36 hours once used.
After that, a resource request with the same client token is treated as a new request.
If you do not specify a client token, one is generated for inclusion in the request. |
+| | The ARN of the IAM role used to perform this resource operation.
The role specified must have the permissions required for this operation.
If you do not specify a role, a temporary session is created using your AWS user credentials. |
+| | For private resource types, the type version to use in this resource operation.
If you do not specify a resource version, the default version is used. |
+`;
+}
+
+function createDeleteExample(serviceName, resourceName, resourceData, thisSchema, allSchemas) {
+ const deleteOperation = resourceData.methods?.delete_resource;
+ if (!deleteOperation) {
+ return '';
+ }
+
+ return `\n## ${mdCodeAnchor}DELETE${mdCodeAnchor} example
+
+${sqlCodeBlockStart}
+/*+ delete */
+DELETE FROM ${providerName}.${serviceName}.${resourceName}
+WHERE
+ Identifier = '${resourceData['x-identifiers'].map(id => `{{ ${toSnakeCase(fixCamelCaseIssues(id))} }}`).join('|')}' AND
+ region = '${getRegionExample(serviceName)}'
+${getReturningClause()};
+${codeBlockEnd}
+`;
+}
+
+function createUpdateExample(serviceName, resourceName, resourceData, thisSchema, allSchemas) {
+ const updateOperation = resourceData.methods?.update_resource;
+ if (!updateOperation) {
+ return '';
+ }
+
+ const readOnlyProps = (thisSchema['x-read-only-properties'] || []).map(p => p.split('/')[0]);
+ const createOnlyProps = (thisSchema['x-create-only-properties'] || []).map(p => p.split('/')[0]);
+ const excludeProps = new Set([...readOnlyProps, ...createOnlyProps]);
+
+ const updatableProps = Object.keys(thisSchema.properties || {}).filter(p => !excludeProps.has(p));
+
+ if (updatableProps.length === 0) {
+ return '';
+ }
+
+ const patchFields = updatableProps.map(p => ` "${p}": ${toSnakeCase(fixCamelCaseIssues(p))}`).join(',\n');
+ const identifierValues = (resourceData['x-identifiers'] || []).map(id => `{{ ${toSnakeCase(fixCamelCaseIssues(id))} }}`).join('|');
+
+ return `\n## ${mdCodeAnchor}UPDATE${mdCodeAnchor} example
+
+Use the following StackQL query and manifest file to update a ${pluralize.singular(resourceName)} resource, using [__${mdCodeAnchor}stack-deploy${mdCodeAnchor}__](https://pypi.org/project/stack-deploy/).
+
+${sqlCodeBlockStart}
+/*+ update */
+UPDATE ${providerName}.${serviceName}.${resourceName}
+SET PatchDocument = string('{{ {
+${patchFields}
+} | generate_patch_document }}')
+WHERE
+ region = '{{ region }}' AND
+ Identifier = '${identifierValues}'
+${getReturningClause()};
+${codeBlockEnd}
+`;
+}
+
+function createInsertExample(serviceName, resourceName, resourceData, thisSchema, allSchemas) {
+ const createOperation = resourceData.methods?.create_resource;
+ if (!createOperation) {
+ return '';
+ }
+
+ function resolveRef(ref, visitedRefs) {
+ const path = ref.replace('#/components/schemas/', '');
+ if (visitedRefs.has(path)) {
+ console.log(`Cyclic reference detected for ${path}, skipping expansion.`);
+ return null; // Return null or some placeholder to avoid infinite recursion
+ }
+ visitedRefs.add(path);
+ return allSchemas[path];
+ }
+
+ function resolveType(schema, requiredOnly, visitedRefs = new Set()) {
+ if (!schema) return null;
+
+ if (schema.type) {
+ switch (schema.type) {
+ case 'string':
+ return 'string';
+ case 'integer':
+ return 0;
+ case 'boolean':
+ return false;
+ case 'array':
+ if (schema.items) {
+ return [resolveType(schema.items.$ref ? resolveRef(schema.items.$ref, visitedRefs) : schema.items, requiredOnly, visitedRefs)];
+ }
+ return [];
+ case 'object':
+ if (schema.properties) {
+ let obj = {};
+ Object.keys(schema.properties).forEach(prop => {
+ if(schema['x-read-only-properties'] && schema['x-read-only-properties'].includes(prop)){
+ return null;
+ }
+ if (requiredOnly && schema['x-required-properties'] && !schema['x-required-properties'].includes(prop)) {
+ return null;
+ }
+ obj[prop] = resolveType(schema.properties[prop].$ref ? resolveRef(schema.properties[prop].$ref, visitedRefs) : schema.properties[prop], requiredOnly, visitedRefs);
+ });
+ return obj;
+ }
+ return {};
+ default:
+ return null;
+ }
+ } else if (schema.$ref) {
+ return resolveType(resolveRef(schema.$ref, visitedRefs), requiredOnly, visitedRefs);
+ }
+ return null;
+ }
+
+ function getObjectDetails(inputObject, detailType) {
+ const keys = Object.keys(inputObject);
+ const values = Object.values(inputObject);
+
+ switch (detailType) {
+ case 'keys':
+ return keys;
+ case 'values':
+ return values;
+ default:
+ throw new Error('Invalid detail type requested. Use "keys" or "values".');
+ }
+ }
+
+ function templateObjectValues(obj) {
+ const templatedObj = JSON.parse(JSON.stringify(obj), (key, value) => {
+ const snakeKey = key ? toSnakeCase(fixCamelCaseIssues(key)) : key;
+ if (typeof value === 'string') {
+ return `{{ ${snakeKey} }}`;
+ }
+ if (typeof value === 'boolean' || typeof value === 'number') {
+ return `{{ ${snakeKey} }}`;
+ }
+ if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'string') {
+ return [`{{ ${snakeKey}[0] }}`];
+ }
+ return value;
+ });
+ return templatedObj
+ }
+
+ const templateObject = {
+ version: 1,
+ name: "stack name",
+ description: "stack description",
+ providers: [
+ "aws"
+ ],
+ globals: [{
+ name: "region",
+ value: "{{ vars.AWS_REGION }}"
+ }],
+ resources: [],
+ };
+
+ function transformDataToResource(templateData, resourceName, isRoot = true) {
+ if (!templateData) return null;
+
+ if (isRoot) {
+ // If it's the root object, return a structured resource
+ return {
+ name: `${pluralize.singular(resourceName)}`,
+ props: Object.entries(templateData).map(([key, value]) => ({
+ name: toSnakeCase(fixCamelCaseIssues(key)),
+ value: transformDataToResource(value, resourceName, false)
+ }))
+ };
+ } else if (Array.isArray(templateData)) {
+ // If it's an array, transform each element
+ return templateData.map(item => transformDataToResource(item, resourceName, false));
+ } else if (typeof templateData === 'object' && templateData !== null) {
+ // For object values, recursively call transformation for each property
+ const transformedObject = {};
+ for (const [key, value] of Object.entries(templateData)) {
+ transformedObject[toSnakeCase(fixCamelCaseIssues(key))] = transformDataToResource(value, resourceName, false);
+ }
+ return transformedObject;
+ } else {
+ // For primitive values, simply return the value
+ return templateData;
+ }
+ };
+
+ templateObject.resources.push(transformDataToResource(templateObjectValues(resolveType(thisSchema, false)), resourceName));
+
+ const requiredKeys = getObjectDetails(resolveType(thisSchema, true), 'keys');
+ const allKeys = getObjectDetails(resolveType(thisSchema, false), 'keys');
+ const requiredSnakeKeys = requiredKeys.map(k => toSnakeCase(fixCamelCaseIssues(k)));
+ const allSnakeKeys = allKeys.map(k => toSnakeCase(fixCamelCaseIssues(k)));
+
+ // Escape backticks and ${} in the YAML to prevent template literal issues in JSX
+ const manifestYaml = yaml.dump(templateObject, { lineWidth: -1 }).trimEnd();
+ const escapedManifestYaml = manifestYaml.replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
+
+ return `\n## ${mdCodeAnchor}INSERT${mdCodeAnchor} example
+
+Use the following StackQL query and manifest file to create a new ${pluralize.singular(resourceName)} resource, using [__${mdCodeAnchor}stack-deploy${mdCodeAnchor}__](https://pypi.org/project/stack-deploy/).
+
+
+
+
+${sqlCodeBlockStart}
+/*+ create */
+INSERT INTO ${providerName}.${serviceName}.${resourceName} (
+ ${requiredKeys.join(",\n ")},
+ region
+)
+SELECT
+ '{{ ${requiredSnakeKeys.join(" }}',\n '{{ ")} }}',
+ '{{ region }}'
+${getReturningClause()};
+${codeBlockEnd}
+
+
+
+${sqlCodeBlockStart}
+/*+ create */
+INSERT INTO ${providerName}.${serviceName}.${resourceName} (
+ ${allKeys.join(",\n ")},
+ region
+)
+SELECT
+ '{{ ${allSnakeKeys.join(" }}',\n '{{ ")} }}',
+ '{{ region }}'
+${getReturningClause()};
+${codeBlockEnd}
+
+
+
+{\`${escapedManifestYaml}\`}
+
+
+`;
+}
+
+// Resolve the response schema for a schema-less native resource (e.g. tagging).
+// Follows: select sqlVerb $ref → method → operation $ref → path → POST response 200 → schema.
+// If the method has an objectKey (e.g. $.ResourceTagMappingList), unwraps the array items schema.
+function resolveNativeResponseSchema(resourceData, servicePaths, componentsSchemas) {
+ try {
+ const selectRefs = resourceData?.sqlVerbs?.select;
+ if (!selectRefs || !selectRefs.length) return null;
+
+ const methodRef = selectRefs[0]?.$ref;
+ if (!methodRef) return null;
+
+ // $ref is like '#/components/x-stackQL-resources/report_creation/methods/describe_report_creation'
+ const methodName = methodRef.split('/').pop();
+ const method = resourceData.methods?.[methodName];
+ if (!method) return null;
+
+ const opRef = method.operation?.$ref;
+ if (!opRef) return null;
+
+ // opRef is like '#/paths/~1?..../post' — extract path key and HTTP verb
+ const opRefParts = opRef.replace('#/paths/', '').split('/');
+ const pathKey = decodeURIComponent(opRefParts.slice(0, -1).join('/').replace(/~1/g, '/').replace(/~0/g, '~'));
+ const httpVerb = opRefParts[opRefParts.length - 1];
+
+ const operation = servicePaths?.[pathKey]?.[httpVerb];
+ if (!operation) return null;
+
+ // Get the 200 response schema ref
+ const responseContent = operation.responses?.['200']?.content;
+ if (!responseContent) return null;
+ const mediaType = Object.keys(responseContent)[0];
+ const schemaRef = responseContent[mediaType]?.schema?.$ref;
+ if (!schemaRef) return null;
+
+ const responseSchemaName = schemaRef.split('/').pop();
+ let responseSchema = componentsSchemas[responseSchemaName];
+ if (!responseSchema) return null;
+
+ // If the method has an objectKey like $.ResourceTagMappingList, resolve that property's items
+ const objectKey = method.response?.objectKey;
+ if (objectKey) {
+ const propName = objectKey.replace(/^\$\./, '');
+ const propDef = responseSchema.properties?.[propName];
+ if (propDef) {
+ let itemsRef = propDef.items?.$ref || propDef.allOf?.[0]?.items?.$ref;
+ // Handle allOf wrapping a $ref to an array type
+ if (!itemsRef && propDef.allOf) {
+ for (const entry of propDef.allOf) {
+ const resolved = entry.$ref ? componentsSchemas[entry.$ref.split('/').pop()] : entry;
+ if (resolved?.items?.$ref) { itemsRef = resolved.items.$ref; break; }
+ if (resolved?.items) { return resolved.items; }
+ }
+ }
+ if (itemsRef) {
+ const itemSchemaName = itemsRef.split('/').pop();
+ return componentsSchemas[itemSchemaName] || null;
+ }
+ }
+ }
+
+ return responseSchema;
+ } catch (e) {
+ return null;
+ }
+}
+
+function generateNativeSelectExample(sqlExampleSelect, sqlExampleListCols, sqlExampleFrom, sqlExampleListWhere) {
+ if (!sqlExampleListWhere) return '';
+ return `## ${mdCodeAnchor}SELECT${mdCodeAnchor} examples\n\n${sqlCodeBlockStart}\n${sqlExampleSelect}\n${sqlExampleListCols}\n${sqlExampleFrom}\n${sqlExampleListWhere}\n${codeBlockEnd}`;
+}
+
+function createResourceIndexContent(serviceName, resourceName, resourceType, resourceIdentifiers, schemaName, schema, componentsSchemas, servicePaths, serviceResources, resourceData, schemaTypeName, listOnlyResource) {
+ // Create the markdown content for each resource index
+ // resourceType is x-type, one of 'cloud_control' (cloud control), 'native' (native resource), 'view' (SQL view)
+
+ let sqlExampleWhere;
+ let sqlExampleListWhere;
+ let sqlExampleGetWhere;
+ let permissionsHeadingMarkdown ='';
+ let permissionsBylineMarkdown = '';
+ let permissionsMarkdown = '';
+ let hasList = false;
+ let hasGet = false;
+ let isSelectable = true;
+ let sqlVerbsList = [];
+
+ const singularResourceName = pluralize.singular(resourceName);
+ const listOnlyResourceName = listOnlyResource ? listOnlyResource.name : null;
+
+ const moreInfoCaption = `For more information, see ${schemaTypeName}.`;
+
+ const requiredParamsMap = {
+ create_resource: (schema['x-required-properties'] ? schema['x-required-properties'].join(', ') + ', region' : 'region'),
+ delete_resource: "Identifier, region",
+ update_resource: "Identifier, PatchDocument, region",
+ };
+
+ const sqlExampleSelect = `SELECT`;
+ const sqlExampleFrom = `FROM ${providerName}.${serviceName}.${resourceName}`;
+
+ sqlExampleWhere = `WHERE region = '${getRegionExample(serviceName)}'`;
+
+ let fields = resourceIdentifiers;
+
+ let resourceDescription = `Creates, updates, deletes or gets ${indefinite(singularResourceName, { articleOnly: true })} ${singularResourceName} resource or lists ${resourceName} in a region`;
+
+ if (resourceName.endsWith('_tags')) {
+ const cleanedResourceName = resourceName.replace('_tags', '');
+ resourceDescription = `Expands all tag keys and values for ${pluralize.plural(cleanedResourceName)} in a region`;
+ }
+
+ if(resourceName.endsWith('_list_only')){
+ const cleanedResourceName = resourceName.replace('_list_only', '');
+ resourceDescription = `Lists ${cleanedResourceName} in a region or regions, for all properties use ${cleanedResourceName}`;
+ }
+
+ function getSqlVerbsFromResourceData(resourceData) {
+ // Extract SQL verbs directly from the resource data instead of calling StackQL
+ const sqlVerbsList = [];
+
+ if (resourceData.sqlVerbs) {
+ Object.entries(resourceData.sqlVerbs).forEach(([sqlVerbName, methodRefs]) => {
+ if (methodRefs && Array.isArray(methodRefs) && methodRefs.length > 0) {
+ methodRefs.forEach(methodRef => {
+ if (methodRef && methodRef.$ref) {
+ const methodName = methodRef.$ref.split('/').pop();
+ // Use a simplified required params mapping for native resources
+ let requiredParams = 'region';
+
+ // Map common method patterns to their required parameters
+ if (methodName.includes('list')) {
+ requiredParams = 'region';
+ } else if (methodName.includes('get') || methodName.includes('describe')) {
+ requiredParams = 'Identifier, region';
+ } else if (methodName.includes('create')) {
+ requiredParams = 'Identifier, region';
+ } else if (methodName.includes('update') || methodName.includes('modify')) {
+ requiredParams = 'Identifier, PatchDocument, region';
+ } else if (methodName.includes('delete')) {
+ requiredParams = 'Identifier, region';
+ }
+
+ sqlVerbsList.push({
+ sqlVerbName,
+ methodName,
+ requiredParams
+ });
+ }
+ });
+ }
+ });
+ }
+
+ return sqlVerbsList;
+ }
+
+ if (resourceType === 'view') {
+ // isPlural = false;
+ fields = schema.properties;
+ resourceDescription = schema.description;
+ if(schema['x-example-where-clause']){
+ sqlExampleWhere = schema['x-example-where-clause'];
+ }
+
+ sqlVerbsList.push({
+ sqlVerbName: 'select',
+ methodName: 'view',
+ requiredParams: 'region'
+ });
+ }
+
+ if (resourceType === 'native') {
+ const rawSchema = resourceData['x-cfn-schema-name'] && componentsSchemas[resourceData['x-cfn-schema-name']];
+
+ // Unwrap array schemas (e.g. ResourceRequestStatusSummaries, ResourceDescriptions)
+ // to access the actual item properties
+ let resolvedNativeSchema = rawSchema || null;
+ if (resolvedNativeSchema?.type === 'array' && resolvedNativeSchema?.items?.$ref) {
+ const itemsSchemaName = resolvedNativeSchema.items.$ref.split('/').pop();
+ resolvedNativeSchema = componentsSchemas[itemsSchemaName] || resolvedNativeSchema;
+ }
+
+ const hasSchemaName = !!rawSchema;
+ const hasProperties = !!(resolvedNativeSchema?.properties && Object.keys(resolvedNativeSchema.properties).length > 0);
+
+ if (resourceData['x-example-where-clause']) {
+ sqlExampleWhere = resourceData['x-example-where-clause'];
+ } else if (hasSchemaName && !hasProperties) {
+ // Named-schema native with no resolvable properties and no where clause — not selectable
+ isSelectable = false;
+ }
+ // Schema-less native (e.g. tagging) with no x-example-where-clause keeps
+ // isSelectable = true and uses the default WHERE region = 'us-east-1'
+
+ if (isSelectable) {
+ if (hasSchemaName) {
+ // Standard native resource with an explicit schema (e.g. cloud_control)
+ fields = resolvedNativeSchema.properties;
+ schema = resolvedNativeSchema;
+ resourceDescription = resolvedNativeSchema.description || rawSchema.description;
+ } else {
+ // Schema-less native resource (e.g. tagging) — resolve fields from the
+ // select method's response schema via the path reference
+ const resolvedSchema = resolveNativeResponseSchema(resourceData, servicePaths, componentsSchemas);
+ if (resolvedSchema) {
+ fields = resolvedSchema.properties || {};
+ schema = resolvedSchema;
+ }
+ }
+ }
+
+ if (!resourceDescription) {
+ if ('x-description' in resourceData) {
+ resourceDescription = resourceData['x-description'];
+ }
+ }
+
+ const verbsToPush = getSqlVerbsFromResourceData(resourceData);
+ verbsToPush.forEach(({ sqlVerbName, methodName, requiredParams }) => {
+ sqlVerbsList.push({
+ sqlVerbName,
+ methodName,
+ requiredParams
+ });
+ });
+
+ // Set hasList if any select sqlVerb was registered
+ if (isSelectable && sqlVerbsList.some(v => v.sqlVerbName === 'select')) {
+ hasList = true;
+ sqlExampleListWhere = `WHERE\n ${sqlExampleWhere.replace(/^WHERE\s+/, '')};`;
+ }
+ }
+
+ if (resourceType === 'cloud_control_view') {
+
+ let viewResourceName = resourceName;
+
+ if (resourceName.endsWith('_tags')) {
+ viewResourceName = pluralize.plural(resourceName.replace('_tags', ''));
+ }
+
+ if(resourceName.endsWith('_list_only')){
+ viewResourceName = resourceName.replace('_list_only', '');
+ }
+
+ // permissions
+ permissionsHeadingMarkdown = '## Permissions\n';
+ permissionsBylineMarkdown = `For permissions required to operate on the ${resourceName} resource, see ${viewResourceName}\n`;
+
+ sqlVerbsList.push({
+ sqlVerbName: 'select',
+ methodName: 'list_resources',
+ requiredParams: 'region'
+ });
+
+ hasList = true;
+ sqlExampleListWhere = `WHERE\n ${sqlExampleWhere.replace(/^WHERE\s+/, '')};`;
+
+ }
+
+ // Custom view resources (vw_ prefix, x-type: custom_view)
+ // These use config.docs.fields for field definitions and config.docs.requiredParams for WHERE clause
+ if (resourceType === 'custom_view') {
+ const docsConfig = resourceData?.config?.docs;
+ const docFields = docsConfig?.fields || [];
+ const requiredParams = docsConfig?.requiredParams || [];
+
+ // Build a synthetic schema.properties from config.docs.fields for field display
+ // Exclude 'region' as it is auto-added by the doc generator
+ const nonRegionFields = docFields.filter(f => f.name !== 'region');
+ const syntheticProperties = {};
+ for (const field of nonRegionFields) {
+ syntheticProperties[field.name] = {
+ type: field.type || 'string',
+ description: field.description || '',
+ };
+ }
+ // fields as array of field names (used by getColumns with isList=true)
+ fields = nonRegionFields.map(f => f.name);
+ schema = { properties: syntheticProperties };
+
+ resourceDescription = `Custom view of ${resourceName.replace(/^vw_/, '')} resources in a region`;
+
+ sqlVerbsList.push({
+ sqlVerbName: 'select',
+ methodName: 'view',
+ requiredParams: requiredParams.length > 0 ? requiredParams.map(p => p.name).join(', ') : 'region'
+ });
+
+ hasList = true;
+
+ // Build WHERE clause from requiredParams
+ if (requiredParams.length > 0) {
+ const whereParts = requiredParams.map(p => `${p.name} = '{{ ${p.name} }}'`);
+ sqlExampleWhere = `WHERE ${whereParts.join(' AND\n ')}`;
+ }
+
+ sqlExampleListWhere = `WHERE\n ${sqlExampleWhere.replace(/^WHERE\s+/, '')};`;
+ }
+
+ // DDL view exception: only for native services (cloud_control, tagging)
+ // These views have no x-type but have config.views.select.ddl with a SELECT * FROM
+ if (!resourceType && nativeServices.includes(serviceName) && resourceData?.config?.views?.select?.ddl) {
+ const ddl = resourceData.config.views.select.ddl;
+
+ // Extract the base resource name from "select * from awscc.."
+ const fromMatch = ddl.match(/from\s+\w+\.\w+\.(\w+)/i);
+ const baseResourceName = fromMatch ? fromMatch[1] : null;
+
+ if (baseResourceName) {
+ // Find the base resource's schema name and resolve its fields
+ const baseResource = serviceResources?.[baseResourceName];
+ const baseSchemaName = baseResource?.['x-cfn-schema-name'];
+ const baseSchema = baseSchemaName ? componentsSchemas[baseSchemaName] : null;
+
+ // If the base schema is an array, unwrap to the items schema for fields
+ let resolvedBaseSchema = baseSchema;
+ if (baseSchema?.type === 'array' && baseSchema?.items?.$ref) {
+ const itemsSchemaName = baseSchema.items.$ref.split('/').pop();
+ resolvedBaseSchema = componentsSchemas[itemsSchemaName] || baseSchema;
+ }
+
+ if (resolvedBaseSchema?.properties) {
+ fields = resolvedBaseSchema.properties;
+ schema = resolvedBaseSchema;
+ }
+ }
+
+ resourceDescription = `View of ${baseResourceName || resourceName} filtered by the SQL WHERE clause; see ${baseResourceName || resourceName} for the full resource.`;
+
+ sqlVerbsList.push({
+ sqlVerbName: 'select',
+ methodName: 'view',
+ requiredParams: 'region'
+ });
+
+ hasList = true;
+ sqlExampleListWhere = `WHERE\n ${sqlExampleWhere.replace(/^WHERE\s+/, '')};`;
+ }
+
+ if (resourceType === 'cloud_control') {
+
+ // permissions
+ permissionsHeadingMarkdown = schema?.['x-required-permissions'] ? '## Permissions\n' : '';
+ permissionsBylineMarkdown = schema?.['x-required-permissions'] ? `To operate on the ${resourceName} resource, the following permissions are required:\n` : '';
+
+ const permissionTabs = Object.entries(schema?.['x-required-permissions'] ?? {})
+ .map(([permissionType, permissions]) => {
+ const includedTypes = ['create', 'list', 'delete', 'read', 'update'];
+
+ if (includedTypes.includes(permissionType)) {
+ // update flags
+ if (permissionType === 'list') {
+ hasList = true;
+ }
+ if (permissionType === 'read') {
+ hasGet = true;
+ }
+
+ if(!hasList && !hasGet){
+ isSelectable = false;
+ }
+
+ if (permissions.length === 0) {
+ return null;
+ }
+
+ const label = permissionType.charAt(0).toUpperCase() + permissionType.slice(1);
+ const permissionsList = permissions.join(",\n");
+
+ return { value: permissionType, label, permissionsList };
+ } else {
+ return null;
+ }
+ })
+ .filter(section => section !== null);
+
+ if (permissionTabs.length > 0) {
+ const tabValues = permissionTabs.map(t => ` { label: '${t.label}', value: '${t.value}', },`).join('\n');
+ const tabItems = permissionTabs.map(t => `
+
+${jsonCodeBlockStart}
+${t.permissionsList}
+${codeBlockEnd}
+
+`).join('\n');
+
+ permissionsMarkdown = `
+${tabItems}
+`;
+ } else {
+ permissionsMarkdown = '';
+ }
+
+ // covers non select ops for cc resources
+ sqlVerbsList = Object.entries(resourceData.sqlVerbs).map(([key, value]) => {
+ if (value && Array.isArray(value) && value.length > 0 && typeof value[0].$ref == 'string') {
+ return {
+ sqlVerbName: key,
+ methodName: value[0].$ref.split('/').pop(),
+ requiredParams: requiredParamsMap[value[0].$ref.split('/').pop()]
+ };
+ }});
+
+ if(hasList){
+ sqlVerbsList.push({
+ sqlVerbName: 'select',
+ methodName: 'list_resources',
+ requiredParams: 'region'
+ });
+ }
+
+ if(hasGet){
+ sqlVerbsList.push({
+ sqlVerbName: 'select',
+ methodName: 'get_resource',
+ requiredParams: 'Identifier, region'
+ });
+ }
+
+ const identifierValues = resourceIdentifiers.map(id => `{{ ${toSnakeCase(fixCamelCaseIssues(id))} }}`).join('|');
+ const identifierClause = `Identifier = '${identifierValues}'`;
+
+ sqlExampleListWhere = `WHERE\n ${sqlExampleWhere.replace(/^WHERE\s+/, '')};`;
+ sqlExampleGetWhere = `WHERE\n ${sqlExampleWhere.replace(/^WHERE\s+/, '')} AND\n ${identifierClause};`;
+
+ }
+
+ sqlVerbsList = sqlVerbsList.filter(Boolean);
+
+ // if resourceDescription is an empty string or undefined, error out
+ if (!resourceDescription) {
+ console.error(`Resource description is missing for ${serviceName}.${resourceName}`);
+ return;
+ }
+
+ const sqlExampleListCols = getColumns(fields, true, resourceType === 'native', false);
+ const sqlExampleGetCols = getColumns(schema.properties, false, resourceType === 'native', false);
+ const sqlExampleGetTagsCols = getColumns(schema.properties, false, resourceType === 'native', true);
+
+ // Compute list_only fields for tabbed display
+ const listOnlyFields = listOnlyResource
+ ? generateSchemaJsonForResource(serviceName, listOnlyResourceName, 'cloud_control_view', schema, componentsSchemas, sqlExampleListCols)
+ : null;
+ const listOnlyFrom = listOnlyResourceName
+ ? `FROM ${providerName}.${serviceName}.${listOnlyResourceName}`
+ : null;
+
+ // Build the Fields section
+ let fieldsSection;
+ if (!(isSelectable || hasList || hasGet)) {
+ fieldsSection = 'SELECT operation not supported for this resource.';
+ } else if (listOnlyResource && hasGet) {
+ const getFields = JSON.stringify(generateSchemaJsonForResource(serviceName, resourceName, resourceType, schema, componentsSchemas, sqlExampleListCols), null, 2);
+ const listFields = JSON.stringify(listOnlyFields, null, 2);
+ fieldsSection = `
+
+
+
+
+
+
+
+
+`;
+ } else {
+ fieldsSection = ``;
+ }
+
+ return `---
+title: ${resourceName}
+hide_title: false
+hide_table_of_contents: false
+keywords:
+ - ${resourceName}
+ - ${serviceName}
+ - aws
+ - stackql
+ - infrastructure-as-code
+ - configuration-as-data
+ - cloud inventory
+description: Query, deploy and manage AWS resources using SQL
+custom_edit_url: null
+image: /img/stackql-aws-provider-featured-image.png
+---
+
+import CodeBlock from '@theme/CodeBlock';
+import CopyableCode from '@site/src/components/CopyableCode/CopyableCode';
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import SchemaTable from '@site/src/components/SchemaTable/SchemaTable';
+
+${resourceDescription}
+
+## Overview
+
+
+| Name | ${resourceName} |
+| Type | Resource |
+${schema.description ? `| Description | ${wrapInDetails(cleanDescription(schema.description))} |
` : `| Description | ${resourceName} |
`}
+| Id | |
+
+
+
+## Fields
+${fieldsSection}
+${schemaName && resourceType === 'cloud_control' ? `\n${moreInfoCaption}\n` : ''}
+## Methods
+${generateMethodsTable(serviceName, resourceName, sqlVerbsList, listOnlyResourceName)}
+
+${resourceType !== 'native' ? generateSelectExamples(resourceName, hasList, hasGet, sqlExampleSelect, sqlExampleListCols, sqlExampleFrom, sqlExampleListWhere, sqlExampleGetCols, sqlExampleGetTagsCols, sqlExampleGetWhere, listOnlyResourceName, listOnlyFrom): generateNativeSelectExample(sqlExampleSelect, sqlExampleListCols, sqlExampleFrom, sqlExampleListWhere)}
+${resourceType !== 'native' ? createInsertExample(serviceName, resourceName, resourceData, schema, componentsSchemas): ''}
+${resourceType !== 'native' ? createUpdateExample(serviceName, resourceName, resourceData, schema, componentsSchemas): ''}
+${resourceType !== 'native' ? createDeleteExample(serviceName, resourceName, resourceData, schema, componentsSchemas): ''}
+${resourceType !== 'native' ? createAdditionalParametersSection(serviceName, resourceData): ''}
+${permissionsHeadingMarkdown}
+${permissionsBylineMarkdown}
+${permissionsMarkdown}`;
+}
+
+function getDescFromRef(ref, components) {
+ // Assuming ref is a string like '#/components/schemas/Arn'
+ const schemaname = ref.split('/').pop();
+ let schema = components[schemaname];
+ return schema.description;
+}
+
+function getTypeFromRef(ref, components) {
+ // Assuming ref is a string like '#/components/schemas/Arn'
+ const schemaname = ref.split('/').pop();
+ let schema = components[schemaname];
+ return schema.type;
+}
+
+function cleanDescription(description) {
+ if (!description) return '';
+
+ // Step 1: Convert markdown inline elements to HTML.
+ // Order: double-backtick before single, links before bare brackets, bold before italic.
+ let result = description;
+ result = result.replace(/``([^`]+?)``/g, '$1');
+ result = result.replace(/`([^`]+?)`/g, '$1');
+ result = result.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, '$1');
+ result = result.replace(/\*\*([^*]+?)\*\*/g, '$1');
+ // *italic* — must not greedily consume across line boundaries
+ result = result.replace(/\*([^*\n]+?)\*/g, '$1');
+
+ // Step 2: Process line structure — split on newlines, trim indent, convert
+ // ordered list items (1. text) to , group consecutive into ,
+ // and join remaining lines with
(blank lines become paragraph breaks).
+ const lines = result.split(/\r?\n/);
+ const processed = [];
+ let inList = false;
+
+ for (const rawLine of lines) {
+ const line = rawLine.trim();
+
+ if (/^\d+\.\s+/.test(line)) {
+ // Ordered list item → bullet -
+ if (!inList) {
+ processed.push('
');
+ inList = true;
+ }
+ processed.push('- ' + line.replace(/^\d+\.\s+/, '') + '
');
+ } else {
+ if (inList) {
+ processed.push('
');
+ inList = false;
+ }
+ if (line === '') {
+ // Blank line — skip (lists and text lines already handle their own spacing)
+ } else {
+ // Non-empty non-list line — append with a single
separator
+ const prev = processed.length > 0 ? processed[processed.length - 1] : '';
+ if (processed.length > 0 &&
+ !prev.endsWith('')) {
+ processed.push('
');
+ }
+ processed.push(line);
+ }
+ }
+ }
+ if (inList) processed.push('
');
+ result = processed.join('');
+
+ // Step 3: Protect all HTML tags from MDX escaping using placeholders.
+ const htmlTags = [];
+ result = result.replace(/<[^>]+>/g, (tag) => {
+ htmlTags.push(tag);
+ return `HTMLTAG${htmlTags.length - 1}END`;
+ });
+
+ // Step 4: Escape only MDX-breaking chars in plain text.
+ result = result
+ .replace(/{/g, '{')
+ .replace(/}/g, '}');
+
+ // Step 5: Restore HTML tags.
+ result = result.replace(/HTMLTAG(\d+)END/g, (_, i) => htmlTags[parseInt(i)]);
+
+ return result.trim();
+}
+
+function wrapInDetails(html) {
+ const breakIndex = html.indexOf('
');
+ if (breakIndex === -1) return html;
+ const firstLine = html.substring(0, breakIndex);
+ const rest = html.substring(breakIndex + 6); // skip '
'
+ if (!rest.trim()) return html;
+ return `${firstLine}
${rest} `;
+}
+
+function toSnakeCase(str) {
+ return str.replace(/[\w]([A-Z])/g, (m) => m[0] + "_" + m[1]).toLowerCase();
+}
+
+function fixCamelCaseIssues(propertyName) {
+ const replacements = {
+ DB: "Db",
+ ARN: "Arn",
+ WAN: "Wan",
+ URL: "Url",
+ VPC: "Vpc",
+ DNS: "Dns",
+ IP: "Ip",
+ IAM: "Iam",
+ TLS: "Tls",
+ SSM: "Ssm",
+ CNAME: "Cname",
+ SQ: "Sq",
+ ML: "Ml",
+ SKU: "Sku",
+ KMS: "Kms",
+ OK: "Ok",
+ CA: "Ca",
+ ID: "Id",
+ TTL: "Ttl",
+ URI: "Uri",
+ LB: "Lb",
+ SNS: "Sns",
+ AWS: "Aws",
+ ACL: "Acl",
+ AZ: "Az",
+ API: "Api",
+ MSK: "Msk",
+ ECS: "Ecs",
+ EBS: "Ebs",
+ CWE: "Cwe",
+ SSE: "Sse",
+ HPO: "Hpo",
+ LF: "Lf",
+ WAL: "Wal",
+ OAuth: "Oauth",
+ };
+
+ let updatedPropertyName = propertyName;
+
+ for (const [key, value] of Object.entries(replacements)) {
+ const regex = new RegExp(key, 'g');
+ updatedPropertyName = updatedPropertyName.replace(regex, value);
+ }
+
+ return updatedPropertyName;
+}
+
+function generateTableRow(propName, type, descriptionCleaned, isCustom = false, nestedHtml = '') {
+ if(isCustom){
+ return ` | ${type} | ${descriptionCleaned}${nestedHtml} |
\n`;
+ } else {
+ return ` | ${type} | ${descriptionCleaned}${nestedHtml} |
\n`;
+ }
+}
+
+function generateSchemaJson(schema, componentsSchemas, depth = 0, maxDepth = 4, visitedRefs = new Set()) {
+ if (depth >= maxDepth || !schema?.properties) return [];
+
+ return Object.entries(schema.properties).map(([propName, prop]) => {
+ let propType = prop.type || '';
+ let propDesc = '';
+ let children = [];
+
+ // Resolve $ref
+ let resolvedSchema = prop;
+ if (prop.$ref) {
+ const refName = prop.$ref.split('/').pop();
+ if (visitedRefs.has(refName)) return null; // Prevent cycles
+ visitedRefs.add(refName);
+ resolvedSchema = componentsSchemas[refName] || {};
+ propType = propType || resolvedSchema.type || 'object';
+ propDesc = resolvedSchema.description || '';
+ }
+
+ // Handle allOf
+ if (prop.allOf) {
+ resolvedSchema = {};
+ prop.allOf.forEach(item => {
+ if (item.$ref) {
+ const refName = item.$ref.split('/').pop();
+ if (!visitedRefs.has(refName)) {
+ visitedRefs.add(refName);
+ Object.assign(resolvedSchema, componentsSchemas[refName] || {});
+ }
+ } else {
+ Object.assign(resolvedSchema, item);
+ }
+ });
+ propType = propType || resolvedSchema.type || 'object';
+ propDesc = propDesc || resolvedSchema.description || '';
+ }
+
+ if (prop.description) propDesc = prop.description;
+ propDesc = wrapInDetails(cleanDescription(propDesc));
+
+ // Handle arrays - get children from items
+ if ((propType === 'array' || resolvedSchema.type === 'array') && (resolvedSchema.items || prop.items)) {
+ let itemsSchema = resolvedSchema.items || prop.items;
+ if (itemsSchema.$ref) {
+ const refName = itemsSchema.$ref.split('/').pop();
+ if (!visitedRefs.has(refName)) {
+ visitedRefs.add(refName);
+ itemsSchema = componentsSchemas[refName] || {};
+ }
+ }
+ if (itemsSchema.properties) {
+ children = generateSchemaJson(itemsSchema, componentsSchemas, depth + 1, maxDepth, new Set(visitedRefs));
+ }
+ }
+
+ // Handle objects - get children from properties
+ if ((propType === 'object' || resolvedSchema.type === 'object') && resolvedSchema.properties) {
+ children = generateSchemaJson(resolvedSchema, componentsSchemas, depth + 1, maxDepth, new Set(visitedRefs));
+ }
+
+ const fieldName = toSnakeCase(fixCamelCaseIssues(propName));
+
+ return {
+ name: fieldName,
+ type: propType || 'object',
+ description: propDesc,
+ ...(children.length > 0 && { children })
+ };
+ }).filter(Boolean);
+}
+
+function generateSchemaJsonForResource(serviceName, resourceName, resourceType, schema, componentsSchemas, sqlExampleListCols) {
+ const isTags = resourceName.endsWith('_tags');
+ const isListOnly = resourceName.endsWith('_list_only');
+
+ let fields = generateSchemaJson(schema, componentsSchemas, 0, 4);
+
+ // Filter for list_only resources
+ if (isListOnly) {
+ fields = fields.filter(f => sqlExampleListCols.includes(f.name));
+ }
+
+ // Filter out Tags for _tags resources
+ if (isTags) {
+ fields = fields.filter(f => f.name !== 'tags');
+ fields.push({ name: 'tag_key', type: 'string', description: 'Tag key.' });
+ fields.push({ name: 'tag_value', type: 'string', description: 'Tag value.' });
+ }
+
+ // Add region
+ fields.push({ name: 'region', type: 'string', description: 'AWS region.' });
+
+ console.log(`Generated schema JSON for ${serviceName}.${resourceName}`);
+ return fields;
+}
+
+// function generateNestedPropertiesHtml(propSchema, componentsSchemas, depth = 0, maxDepth = 3) {
+// if (depth >= maxDepth || !propSchema) return '';
+
+// let schema = propSchema;
+
+// // Resolve $ref if present
+// if (propSchema.$ref) {
+// const schemaName = propSchema.$ref.split('/').pop();
+// schema = componentsSchemas[schemaName];
+// if (!schema) return '';
+// }
+
+// // Handle allOf
+// if (propSchema.allOf) {
+// schema = {};
+// propSchema.allOf.forEach(item => {
+// if (item.$ref) {
+// const refSchema = componentsSchemas[item.$ref.split('/').pop()];
+// Object.assign(schema, refSchema);
+// } else {
+// Object.assign(schema, item);
+// }
+// });
+// }
+
+// // For arrays, get items schema
+// if (schema.type === 'array' && schema.items) {
+// let itemsSchema = schema.items;
+// if (schema.items.$ref) {
+// const schemaName = schema.items.$ref.split('/').pop();
+// itemsSchema = componentsSchemas[schemaName];
+// }
+// if (itemsSchema && (itemsSchema.type === 'object' || itemsSchema.properties)) {
+// return generateNestedPropertiesHtml(itemsSchema, componentsSchemas, depth, maxDepth);
+// }
+// return '';
+// }
+
+// // For objects with properties
+// if (schema.properties && Object.keys(schema.properties).length > 0) {
+// let rows = '';
+// for (const [nestedPropName, nestedProp] of Object.entries(schema.properties)) {
+// let nestedType = nestedProp.type || '';
+// let nestedDesc = '';
+
+// if (nestedProp.$ref) {
+// const refSchema = componentsSchemas[nestedProp.$ref.split('/').pop()];
+// if (refSchema) {
+// nestedType = nestedType || refSchema.type || 'object';
+// nestedDesc = refSchema.description || '';
+// }
+// }
+// if (nestedProp.description) {
+// nestedDesc = nestedProp.description;
+// }
+
+// nestedDesc = cleanDescription(nestedDesc);
+
+// // Recurse for nested objects/arrays
+// const nestedHtml = generateNestedPropertiesHtml(nestedProp, componentsSchemas, depth + 1, maxDepth);
+
+// rows += `${toSnakeCase(fixCamelCaseIssues(nestedPropName))} | ${nestedType} | ${nestedDesc}${nestedHtml} |
`;
+// }
+
+// // Return minified HTML - no newlines
+// return `Show properties
| Property | Type | Description |
${rows}
`;
+// }
+
+// return '';
+// }
+
+function getColumns(properties, isList, isCustom = false, is_tags = false){
+ let columns = ' region,';
+ if (isList) {
+ if(Array.isArray(properties)){
+ properties.forEach(propName => {
+ if(isCustom){
+ columns += `\n ${propName},`;
+ } else {
+ columns += `\n ${toSnakeCase(fixCamelCaseIssues(propName))},`;
+ }
+ })
+ }
+ } else {
+ for (let propName in properties) {
+ if(isCustom){
+ columns += `\n ${propName},`;
+ } else {
+ if(propName === 'Tags' && is_tags){
+ continue;
+ }
+ columns += `\n ${toSnakeCase(fixCamelCaseIssues(propName))},`;
+ }
+ }
+ }
+
+ if(is_tags){
+ columns += `\n tag_key,`;
+ columns += `\n tag_value,`;
+ }
+
+ columns = columns.slice(0, -1);
+ return columns;
+}
+
+// function generateFieldsTable(serviceName, resourceName, resourceType, schemaName, schema, componentsSchemas, sqlExampleListCols) {
+
+// let isTags = false;
+// if(resourceName.endsWith('_tags')){
+// isTags = true;
+// }
+
+// let isListOnly = false;
+// if(resourceName.endsWith('_list_only')){
+// isListOnly = true;
+// }
+
+// let fieldsTable = '';
+// console.log(`Generating fields table for ${serviceName}.${resourceName}`);
+// for (let propName in schema.properties) {
+
+// if(isListOnly){
+// if(!sqlExampleListCols.includes(toSnakeCase(fixCamelCaseIssues(propName)))){
+// continue;
+// }
+// }
+
+// if(isTags && propName === 'Tags'){
+// continue;
+// }
+
+// let propDesc = '';
+// let propType = '';
+// const prop = schema.properties[propName];
+
+// if('description' in prop){
+// propDesc = cleanDescription(prop.description);
+// }
+// if('type' in prop){
+// propType = prop.type;
+// }
+// if (prop.hasOwnProperty('$ref')) {
+// !propType ? propType = getTypeFromRef(prop['$ref'], componentsSchemas) : null;
+// !propDesc ? propDesc = cleanDescription(getDescFromRef(prop['$ref'], componentsSchemas)) : null;
+// } else if (prop.hasOwnProperty('allOf')) {
+// let allOfArray = prop.allOf;
+// let combinedObject = {};
+// allOfArray.forEach(item => {
+// Object.assign(combinedObject, item);
+// });
+// !propType ? propType = combinedObject.type : null;
+// !propDesc ? propDesc = combinedObject.description : null;
+// if(combinedObject.hasOwnProperty('$ref')){
+// !propType ? propType = getTypeFromRef(combinedObject['$ref'], componentsSchemas) : null;
+// !propDesc ? propDesc = cleanDescription(getDescFromRef(combinedObject['$ref'], componentsSchemas)) : null;
+// }
+// }
+
+// // Generate expandable nested properties for objects and arrays
+// let nestedHtml = '';
+// if (propType === 'object' || propType === 'array') {
+// nestedHtml = generateNestedPropertiesHtml(prop, componentsSchemas, 0, 3);
+// }
+
+// fieldsTable += generateTableRow(propName, propType, propDesc, false, nestedHtml);
+// }
+
+// if(isTags){
+// fieldsTable += generateTableRow('tag_key', 'string', 'Tag key.');
+// fieldsTable += generateTableRow('tag_value', 'string', 'Tag value.');
+// }
+
+// fieldsTable += generateTableRow('region', 'string', 'AWS region.');
+
+// return fieldsTable;
+// }
+
+function generateSelectExamples(resourceName, hasList, hasGet, sqlExampleSelect, sqlExampleListCols, sqlExampleFrom, sqlExampleListWhere, sqlExampleGetCols, sqlExampleGetTagsCols, sqlExampleGetWhere, listOnlyResourceName, listOnlyFrom){
+ let returnString = '';
+ if(hasList || hasGet){
+ returnString = `## ${mdCodeAnchor}SELECT${mdCodeAnchor} examples\n`;
+ } else {
+ return returnString;
+ }
+
+ // Tabbed select for cloud_control resources with both get and list_only
+ if(listOnlyResourceName && hasGet && hasList){
+ const singularResourceName = pluralize.singular(resourceName);
+ const getDesc = `Gets all properties from an individual ${singularResourceName}.`;
+ const listDesc = `Lists all ${resourceName} in a region.`;
+ const listFromClause = listOnlyFrom || sqlExampleFrom;
+
+ returnString += `\n
+
+
+${getDesc}
+${sqlCodeBlockStart}
+${sqlExampleSelect}
+${sqlExampleGetCols}
+${sqlExampleFrom}
+${sqlExampleGetWhere}
+${codeBlockEnd}
+
+
+
+${listDesc}
+${sqlCodeBlockStart}
+${sqlExampleSelect}
+${sqlExampleListCols}
+${listFromClause}
+${sqlExampleListWhere}
+${codeBlockEnd}
+
+`;
+ return returnString;
+ }
+
+ if(hasList){
+ let listDesc = `Lists all ${resourceName.replace('_list_only','')} in a region.`;
+
+ if(resourceName.startsWith('vw_')){
+ listDesc = `Lists all ${resourceName} in a region.`;
+ returnString += `${listDesc}\n${sqlCodeBlockStart}\n${sqlExampleSelect}\n${sqlExampleListCols}\n${sqlExampleFrom}\n${sqlExampleListWhere}\n${codeBlockEnd}`;
+ } else if(resourceName.endsWith('_list_only')){
+ returnString += `${listDesc}\n${sqlCodeBlockStart}\n${sqlExampleSelect}\n${sqlExampleListCols}\n${sqlExampleFrom}\n${sqlExampleListWhere}\n${codeBlockEnd}`;
+ } else if(resourceName.endsWith('_tags')){
+ listDesc = `Expands tags for all ${pluralize.plural(resourceName.replace('_tags',''))} in a region.`;
+ returnString += `${listDesc}\n${sqlCodeBlockStart}\n${sqlExampleSelect}\n${sqlExampleGetTagsCols}\n${sqlExampleFrom}\n${sqlExampleListWhere}\n${codeBlockEnd}`;
+ }
+ }
+
+ if(hasGet){
+ const singularResourceName = pluralize.singular(resourceName);
+ const getDesc = `Gets all properties from an individual ${singularResourceName}.`;
+ returnString += `\n${getDesc}\n${sqlCodeBlockStart}\n${sqlExampleSelect}\n${sqlExampleGetCols}\n${sqlExampleFrom}\n${sqlExampleGetWhere}\n${codeBlockEnd}`;
+ }
+
+ return returnString;
+}
+
+function generateMethodsTable(serviceName, resourceName, sqlVerbsList, listOnlyResourceName) {
+ const hasResourceCol = !!listOnlyResourceName;
+
+ let html = `
+
+
+
+ | Name | ${hasResourceCol ? '\n Resource | ' : ''}
+ Accessible by |
+ Required Params |
+
`;
+
+ sqlVerbsList.forEach(item => {
+ const itemResource = (item.methodName === 'list_resources' && listOnlyResourceName)
+ ? listOnlyResourceName
+ : resourceName;
+ html += `
+
+ | ${hasResourceCol ? `\n ${itemResource} | ` : ''}
+ ${item.sqlVerbName.toUpperCase()} |
+ |
+
`;
+ });
+
+ html += `
+
+
`;
+
+ return html;
+}
+
+function clearAndCreateDocsDir(directory = path.join(__dirname, `${websiteDir}/docs`)) {
+ if (fs.existsSync(directory)) {
+ console.log(`Clearing existing docs directory: ${directory}`);
+ fs.readdirSync(directory).forEach((file) => {
+ const currentPath = path.join(directory, file);
+ if (fs.lstatSync(currentPath).isDirectory()) {
+ // Skip directories listed in staticServices
+ if (staticServices.includes(file)) {
+ console.log("Skipping directory:", currentPath);
+ return; // Skip this directory and its contents
+ }
+ // Recurse if it's a directory and not in staticServices
+ clearAndCreateDocsDir(currentPath);
+ // After recursion, check if the directory is empty before attempting to remove it
+ if (fs.readdirSync(currentPath).length === 0) {
+ fs.rmdirSync(currentPath); // Remove the directory once it's empty
+ }
+ } else {
+ // Delete the file if it's not a directory and not the MDX file
+ fs.unlinkSync(currentPath);
+ }
+ });
+ } else {
+ console.log("Docs directory not found, creating:", directory);
+ }
+
+ // Ensure the directory exists
+ if (!fs.existsSync(directory)) {
+ fs.mkdirSync(directory, { recursive: true });
+ console.log("Created docs directory:", directory);
+ }
+}
+
+function clearMdDocsDir(directory = `${websiteDir}/docs`) {
+ if (fs.existsSync(directory)) {
+ fs.readdirSync(directory).forEach((file) => {
+ const currentPath = path.join(directory, file);
+ if (fs.lstatSync(currentPath).isDirectory()) {
+ // Skip directories listed in staticServices
+ if (staticServices.includes(file)) {
+ console.log("Skipping directory:", currentPath);
+ return; // Skip this directory and its contents
+ }
+ // Recurse if it's a directory and not in staticServices
+ clearMdDocsDir(currentPath);
+ // After recursion, check if the directory is empty before attempting to remove it
+ if (fs.readdirSync(currentPath).length === 0) {
+ fs.rmdirSync(currentPath); // Remove the directory once it's empty
+ }
+ } else {
+ // Delete the file if it's not a directory and not the MDX file
+ fs.unlinkSync(currentPath);
+ }
+ });
+ } else {
+ console.log("Directory not found:", directory);
+ }
+}
+
+function countResourceDirectories(serviceDir) {
+ return fs.readdirSync(serviceDir, { withFileTypes: true })
+ .filter(dirent => dirent.isDirectory()).length;
+}
+
+//
+// Main
+//
+
+// Clear and recreate the docs directory with absolute path
+const docsPath = path.join(__dirname, `${websiteDir}/docs`);
+clearAndCreateDocsDir(docsPath);
+
+// delete index.md if it exists (using absolute path)
+const indexMdPath = path.join(__dirname, `${websiteDir}/docs/index.md`);
+if (fs.existsSync(indexMdPath)) {
+ fs.unlinkSync(indexMdPath);
+ console.log(`Deleted existing index.md: ${indexMdPath}`);
+}
+
+// init provider index
+let servicesForIndex = [];
+
+// Static header content
+const headerContent1 = fs.readFileSync(path.join(__dirname,`${providerDevDir}/docgen/provider-data/headerContent1.txt`), 'utf8');
+const headerContent2 = fs.readFileSync(path.join(__dirname,`${providerDevDir}/docgen/provider-data/headerContent2.txt`), 'utf8');
+
+// Initialize counters for services and resources
+let totalServicesCount = 0;
+let totalResourcesCount = 0;
+
+//
+// Iterate over each yaml file in the output directory
+// and generate documentation for each service
+//
+async function main() {
+ const files = fs.readdirSync(path.join(__dirname, `${openAPIdir}/src/${providerDirName}/v00.00.00000/services`));
+
+ for (const file of files) {
+ if (path.extname(file) === '.yaml') {
+ const serviceName = path.basename(file, '.yaml');
+ servicesForIndex.push(serviceName);
+ totalServicesCount++; // Increment service counter
+ await createDocsForService(path.join(__dirname, `${openAPIdir}/src/${providerDirName}/v00.00.00000/services`, file));
+ }
+ }
+
+ //
+ // get data for provider index
+ //
+
+ // add non cloud control services
+ servicesForIndex = servicesForIndex.concat(staticServices);
+
+ // make sure servicesForIndex is unique
+ servicesForIndex = [...new Set(servicesForIndex)];
+
+ // add services to index
+ servicesForIndex.sort();
+
+ // Splitting services into two columns
+ const half = Math.ceil(servicesForIndex.length / 2);
+ const firstColumnServices = servicesForIndex.slice(0, half);
+ const secondColumnServices = servicesForIndex.slice(half);
+
+ // Function to convert services to markdown links
+ function servicesToMarkdown(servicesList) {
+ return servicesList.map(service => `${service}
`).join('\n');
+ }
+
+ // Create the content for the index file
+ const indexContent = `${headerContent1}
+
+:::info[Provider Summary]
+
+
+
+total services: ${totalServicesCount}
+total resources: ${totalResourcesCount}
+
+
+
+:::
+
+${headerContent2}
+
+## Services
+
+
+${servicesToMarkdown(firstColumnServices)}
+
+
+${servicesToMarkdown(secondColumnServices)}
+
+
+`;
+
+ // Write the index file
+ const indexPath = path.join(__dirname, `${websiteDir}/docs/index.md`);
+ fs.writeFileSync(indexPath, indexContent);
+
+ console.log(`Index file created at ${indexPath}`);
+}
+
+// Run the main function
+main().catch(console.error);
diff --git a/website/docs/index.md b/website/docs/index.md
index ac0218507..4a2a44f86 100644
--- a/website/docs/index.md
+++ b/website/docs/index.md
@@ -25,7 +25,7 @@ For the native AWS provider see the [__`aws`__](https://aws-provider.stackql.io/
:::
-:::info Provider Summary
+:::info[Provider Summary]
diff --git a/website/docs/services/accessanalyzer/index.md b/website/docs/services/accessanalyzer/index.md
index 0c0fee6fd..8605dd7d7 100644
--- a/website/docs/services/accessanalyzer/index.md
+++ b/website/docs/services/accessanalyzer/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The accessanalyzer service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/acmpca/index.md b/website/docs/services/acmpca/index.md
index 8b226459b..62945b784 100644
--- a/website/docs/services/acmpca/index.md
+++ b/website/docs/services/acmpca/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The acmpca service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/aiops/index.md b/website/docs/services/aiops/index.md
index 9b2f5b051..eac4baff3 100644
--- a/website/docs/services/aiops/index.md
+++ b/website/docs/services/aiops/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The aiops service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/amazonmq/index.md b/website/docs/services/amazonmq/index.md
index f5af18e04..93e7cc321 100644
--- a/website/docs/services/amazonmq/index.md
+++ b/website/docs/services/amazonmq/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The amazonmq service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/amplify/index.md b/website/docs/services/amplify/index.md
index 175eb4dbe..3e5451be6 100644
--- a/website/docs/services/amplify/index.md
+++ b/website/docs/services/amplify/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The amplify service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/amplifyuibuilder/index.md b/website/docs/services/amplifyuibuilder/index.md
index 16f9f24b5..35d0c9750 100644
--- a/website/docs/services/amplifyuibuilder/index.md
+++ b/website/docs/services/amplifyuibuilder/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The amplifyuibuilder service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/apigateway/index.md b/website/docs/services/apigateway/index.md
index 31a9d84ce..be65ddcb1 100644
--- a/website/docs/services/apigateway/index.md
+++ b/website/docs/services/apigateway/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The apigateway service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/apigatewayv2/index.md b/website/docs/services/apigatewayv2/index.md
index 65cdcb46f..4413c5191 100644
--- a/website/docs/services/apigatewayv2/index.md
+++ b/website/docs/services/apigatewayv2/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The apigatewayv2 service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/appconfig/index.md b/website/docs/services/appconfig/index.md
index e5c08fbf2..25f6ce4cd 100644
--- a/website/docs/services/appconfig/index.md
+++ b/website/docs/services/appconfig/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The appconfig service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/appflow/index.md b/website/docs/services/appflow/index.md
index 3a903719f..a542c3735 100644
--- a/website/docs/services/appflow/index.md
+++ b/website/docs/services/appflow/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The appflow service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/appintegrations/index.md b/website/docs/services/appintegrations/index.md
index f1e4db7ec..ad9a6f128 100644
--- a/website/docs/services/appintegrations/index.md
+++ b/website/docs/services/appintegrations/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The appintegrations service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/applicationautoscaling/index.md b/website/docs/services/applicationautoscaling/index.md
index cec14be25..d6d263f6e 100644
--- a/website/docs/services/applicationautoscaling/index.md
+++ b/website/docs/services/applicationautoscaling/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The applicationautoscaling service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/applicationinsights/index.md b/website/docs/services/applicationinsights/index.md
index 6b9d92d02..64c3caf58 100644
--- a/website/docs/services/applicationinsights/index.md
+++ b/website/docs/services/applicationinsights/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The applicationinsights service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/applicationsignals/index.md b/website/docs/services/applicationsignals/index.md
index e505a3e33..a7f78c560 100644
--- a/website/docs/services/applicationsignals/index.md
+++ b/website/docs/services/applicationsignals/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The applicationsignals service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/apprunner/index.md b/website/docs/services/apprunner/index.md
index 1173ec8fd..6704ffc08 100644
--- a/website/docs/services/apprunner/index.md
+++ b/website/docs/services/apprunner/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The apprunner service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/appstream/index.md b/website/docs/services/appstream/index.md
index b6780c0c8..dad3797a4 100644
--- a/website/docs/services/appstream/index.md
+++ b/website/docs/services/appstream/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The appstream service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/appsync/index.md b/website/docs/services/appsync/index.md
index aed1a4067..bc6f5cdd1 100644
--- a/website/docs/services/appsync/index.md
+++ b/website/docs/services/appsync/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The appsync service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/apptest/index.md b/website/docs/services/apptest/index.md
index 7849fe8d9..f1ea10478 100644
--- a/website/docs/services/apptest/index.md
+++ b/website/docs/services/apptest/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The apptest service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/aps/index.md b/website/docs/services/aps/index.md
index 6d24c12f5..35738f146 100644
--- a/website/docs/services/aps/index.md
+++ b/website/docs/services/aps/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The aps service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/arcregionswitch/index.md b/website/docs/services/arcregionswitch/index.md
index 1f536f860..bb9e21da4 100644
--- a/website/docs/services/arcregionswitch/index.md
+++ b/website/docs/services/arcregionswitch/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The arcregionswitch service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/arczonalshift/index.md b/website/docs/services/arczonalshift/index.md
index 4dee61b46..b5bc1487d 100644
--- a/website/docs/services/arczonalshift/index.md
+++ b/website/docs/services/arczonalshift/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The arczonalshift service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/athena/index.md b/website/docs/services/athena/index.md
index 909a461aa..c93e7720e 100644
--- a/website/docs/services/athena/index.md
+++ b/website/docs/services/athena/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The athena service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/auditmanager/index.md b/website/docs/services/auditmanager/index.md
index bff05476c..b9fdb3b02 100644
--- a/website/docs/services/auditmanager/index.md
+++ b/website/docs/services/auditmanager/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The auditmanager service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/autoscaling/index.md b/website/docs/services/autoscaling/index.md
index fc162ac83..bdc3468da 100644
--- a/website/docs/services/autoscaling/index.md
+++ b/website/docs/services/autoscaling/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The autoscaling service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/b2bi/index.md b/website/docs/services/b2bi/index.md
index 9d862f2c7..e8b5265de 100644
--- a/website/docs/services/b2bi/index.md
+++ b/website/docs/services/b2bi/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The b2bi service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/backup/index.md b/website/docs/services/backup/index.md
index 8ca669339..4579c0aa0 100644
--- a/website/docs/services/backup/index.md
+++ b/website/docs/services/backup/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The backup service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/backupgateway/index.md b/website/docs/services/backupgateway/index.md
index 8c2320cf6..7d95e4b99 100644
--- a/website/docs/services/backupgateway/index.md
+++ b/website/docs/services/backupgateway/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The backupgateway service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/batch/index.md b/website/docs/services/batch/index.md
index a5b248c6b..cfa9fee13 100644
--- a/website/docs/services/batch/index.md
+++ b/website/docs/services/batch/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The batch service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/bcmdataexports/index.md b/website/docs/services/bcmdataexports/index.md
index 1ba7b9e76..35b4cf04d 100644
--- a/website/docs/services/bcmdataexports/index.md
+++ b/website/docs/services/bcmdataexports/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The bcmdataexports service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/bedrock/index.md b/website/docs/services/bedrock/index.md
index dbcafc736..c282535d2 100644
--- a/website/docs/services/bedrock/index.md
+++ b/website/docs/services/bedrock/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The bedrock service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/billing/index.md b/website/docs/services/billing/index.md
index 903097a2c..2184b66e1 100644
--- a/website/docs/services/billing/index.md
+++ b/website/docs/services/billing/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The billing service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/billingconductor/index.md b/website/docs/services/billingconductor/index.md
index 244c17d5d..0a11630d8 100644
--- a/website/docs/services/billingconductor/index.md
+++ b/website/docs/services/billingconductor/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The billingconductor service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/budgets/index.md b/website/docs/services/budgets/index.md
index 40a36b3b9..b2317869d 100644
--- a/website/docs/services/budgets/index.md
+++ b/website/docs/services/budgets/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The budgets service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/cassandra/index.md b/website/docs/services/cassandra/index.md
index df5953c2b..6f5de4928 100644
--- a/website/docs/services/cassandra/index.md
+++ b/website/docs/services/cassandra/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The cassandra service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/ce/index.md b/website/docs/services/ce/index.md
index 14a14e250..1a242f260 100644
--- a/website/docs/services/ce/index.md
+++ b/website/docs/services/ce/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The ce service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/certificatemanager/index.md b/website/docs/services/certificatemanager/index.md
index a5e8b69ed..9bd5a88a5 100644
--- a/website/docs/services/certificatemanager/index.md
+++ b/website/docs/services/certificatemanager/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The certificatemanager service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/chatbot/index.md b/website/docs/services/chatbot/index.md
index 2f4c0a4dd..d238748e7 100644
--- a/website/docs/services/chatbot/index.md
+++ b/website/docs/services/chatbot/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The chatbot service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/cleanrooms/index.md b/website/docs/services/cleanrooms/index.md
index 291a34fc8..052958d4b 100644
--- a/website/docs/services/cleanrooms/index.md
+++ b/website/docs/services/cleanrooms/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The cleanrooms service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/cleanroomsml/index.md b/website/docs/services/cleanroomsml/index.md
index 46a92ccf8..4e7677b45 100644
--- a/website/docs/services/cleanroomsml/index.md
+++ b/website/docs/services/cleanroomsml/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The cleanroomsml service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/cloud_control/index.md b/website/docs/services/cloud_control/index.md
index d9307dfa5..2c9823c47 100644
--- a/website/docs/services/cloud_control/index.md
+++ b/website/docs/services/cloud_control/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The cloud_control service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/cloudformation/index.md b/website/docs/services/cloudformation/index.md
index 2949e3c3c..f1a368b52 100644
--- a/website/docs/services/cloudformation/index.md
+++ b/website/docs/services/cloudformation/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The cloudformation service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/cloudfront/index.md b/website/docs/services/cloudfront/index.md
index e10c54047..45a06800b 100644
--- a/website/docs/services/cloudfront/index.md
+++ b/website/docs/services/cloudfront/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The cloudfront service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/cloudtrail/index.md b/website/docs/services/cloudtrail/index.md
index 8446ae187..45483eab0 100644
--- a/website/docs/services/cloudtrail/index.md
+++ b/website/docs/services/cloudtrail/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The cloudtrail service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/cloudwatch/index.md b/website/docs/services/cloudwatch/index.md
index 7c4f1738d..f54bc3daf 100644
--- a/website/docs/services/cloudwatch/index.md
+++ b/website/docs/services/cloudwatch/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The cloudwatch service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/codeartifact/index.md b/website/docs/services/codeartifact/index.md
index f4a6128f7..2f2592100 100644
--- a/website/docs/services/codeartifact/index.md
+++ b/website/docs/services/codeartifact/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The codeartifact service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/codebuild/index.md b/website/docs/services/codebuild/index.md
index 654a56738..edf79e988 100644
--- a/website/docs/services/codebuild/index.md
+++ b/website/docs/services/codebuild/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The codebuild service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/codeconnections/index.md b/website/docs/services/codeconnections/index.md
index fbc6d8498..f6220b90f 100644
--- a/website/docs/services/codeconnections/index.md
+++ b/website/docs/services/codeconnections/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The codeconnections service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/codedeploy/index.md b/website/docs/services/codedeploy/index.md
index e27b64f3e..7b88c20b9 100644
--- a/website/docs/services/codedeploy/index.md
+++ b/website/docs/services/codedeploy/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The codedeploy service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/codeguruprofiler/index.md b/website/docs/services/codeguruprofiler/index.md
index 9c65ef101..2eea08a75 100644
--- a/website/docs/services/codeguruprofiler/index.md
+++ b/website/docs/services/codeguruprofiler/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The codeguruprofiler service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/codegurureviewer/index.md b/website/docs/services/codegurureviewer/index.md
index b86566135..47d38670d 100644
--- a/website/docs/services/codegurureviewer/index.md
+++ b/website/docs/services/codegurureviewer/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The codegurureviewer service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/codepipeline/index.md b/website/docs/services/codepipeline/index.md
index 808a61ec2..68250a07f 100644
--- a/website/docs/services/codepipeline/index.md
+++ b/website/docs/services/codepipeline/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The codepipeline service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/codestarconnections/index.md b/website/docs/services/codestarconnections/index.md
index 9a4c6080d..df817ec55 100644
--- a/website/docs/services/codestarconnections/index.md
+++ b/website/docs/services/codestarconnections/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The codestarconnections service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/codestarnotifications/index.md b/website/docs/services/codestarnotifications/index.md
index 0e5391b71..be34302a9 100644
--- a/website/docs/services/codestarnotifications/index.md
+++ b/website/docs/services/codestarnotifications/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The codestarnotifications service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/cognito/index.md b/website/docs/services/cognito/index.md
index 2766eeb68..23594279f 100644
--- a/website/docs/services/cognito/index.md
+++ b/website/docs/services/cognito/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The cognito service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/comprehend/index.md b/website/docs/services/comprehend/index.md
index 16a990f11..e5f94e350 100644
--- a/website/docs/services/comprehend/index.md
+++ b/website/docs/services/comprehend/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The comprehend service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/config/index.md b/website/docs/services/config/index.md
index 879af2525..26605bb8d 100644
--- a/website/docs/services/config/index.md
+++ b/website/docs/services/config/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The config service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/connect/index.md b/website/docs/services/connect/index.md
index a93ea85a3..0af975064 100644
--- a/website/docs/services/connect/index.md
+++ b/website/docs/services/connect/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The connect service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/connectcampaigns/index.md b/website/docs/services/connectcampaigns/index.md
index 8377be3f6..ff4b253c1 100644
--- a/website/docs/services/connectcampaigns/index.md
+++ b/website/docs/services/connectcampaigns/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The connectcampaigns service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/connectcampaignsv2/index.md b/website/docs/services/connectcampaignsv2/index.md
index bc3a9f0f2..482718da2 100644
--- a/website/docs/services/connectcampaignsv2/index.md
+++ b/website/docs/services/connectcampaignsv2/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The connectcampaignsv2 service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/controltower/index.md b/website/docs/services/controltower/index.md
index 9dcc55e14..18f474b90 100644
--- a/website/docs/services/controltower/index.md
+++ b/website/docs/services/controltower/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The controltower service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/cur/index.md b/website/docs/services/cur/index.md
index 840c505d0..bcbc0beb5 100644
--- a/website/docs/services/cur/index.md
+++ b/website/docs/services/cur/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The cur service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/customerprofiles/index.md b/website/docs/services/customerprofiles/index.md
index cd5ae55e5..fd8f79915 100644
--- a/website/docs/services/customerprofiles/index.md
+++ b/website/docs/services/customerprofiles/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The customerprofiles service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/databrew/index.md b/website/docs/services/databrew/index.md
index f8cdda386..ead6822ad 100644
--- a/website/docs/services/databrew/index.md
+++ b/website/docs/services/databrew/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The databrew service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/datapipeline/index.md b/website/docs/services/datapipeline/index.md
index 2fb12a145..6f2e2903d 100644
--- a/website/docs/services/datapipeline/index.md
+++ b/website/docs/services/datapipeline/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The datapipeline service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/datasync/index.md b/website/docs/services/datasync/index.md
index bb168b7a4..fa0bfa266 100644
--- a/website/docs/services/datasync/index.md
+++ b/website/docs/services/datasync/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The datasync service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/datazone/index.md b/website/docs/services/datazone/index.md
index 99418f48d..742741e10 100644
--- a/website/docs/services/datazone/index.md
+++ b/website/docs/services/datazone/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The datazone service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/deadline/index.md b/website/docs/services/deadline/index.md
index ce8ddf5db..1457fbf3d 100644
--- a/website/docs/services/deadline/index.md
+++ b/website/docs/services/deadline/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The deadline service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/detective/index.md b/website/docs/services/detective/index.md
index d79ebd75b..4655f1467 100644
--- a/website/docs/services/detective/index.md
+++ b/website/docs/services/detective/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The detective service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/devopsguru/index.md b/website/docs/services/devopsguru/index.md
index a597e519e..cd63726b6 100644
--- a/website/docs/services/devopsguru/index.md
+++ b/website/docs/services/devopsguru/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The devopsguru service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/directoryservice/index.md b/website/docs/services/directoryservice/index.md
index 52714d585..764bb7027 100644
--- a/website/docs/services/directoryservice/index.md
+++ b/website/docs/services/directoryservice/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The directoryservice service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/dms/index.md b/website/docs/services/dms/index.md
index bb9631208..dc4be996d 100644
--- a/website/docs/services/dms/index.md
+++ b/website/docs/services/dms/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The dms service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/docdbelastic/index.md b/website/docs/services/docdbelastic/index.md
index d93879b56..92f98ed54 100644
--- a/website/docs/services/docdbelastic/index.md
+++ b/website/docs/services/docdbelastic/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The docdbelastic service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/dsql/index.md b/website/docs/services/dsql/index.md
index a3ed85aa7..d46029a4d 100644
--- a/website/docs/services/dsql/index.md
+++ b/website/docs/services/dsql/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The dsql service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/dynamodb/index.md b/website/docs/services/dynamodb/index.md
index c113fbf9e..b470d6cc8 100644
--- a/website/docs/services/dynamodb/index.md
+++ b/website/docs/services/dynamodb/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The dynamodb service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/ec2/index.md b/website/docs/services/ec2/index.md
index 57bec86de..99eedabe5 100644
--- a/website/docs/services/ec2/index.md
+++ b/website/docs/services/ec2/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The ec2 service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/ecr/index.md b/website/docs/services/ecr/index.md
index 0ee1fb9dd..713ea49ae 100644
--- a/website/docs/services/ecr/index.md
+++ b/website/docs/services/ecr/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The ecr service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/ecs/index.md b/website/docs/services/ecs/index.md
index 1d7c009d9..9c351ec32 100644
--- a/website/docs/services/ecs/index.md
+++ b/website/docs/services/ecs/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The ecs service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/efs/index.md b/website/docs/services/efs/index.md
index 1aa59803f..a4a98dfb3 100644
--- a/website/docs/services/efs/index.md
+++ b/website/docs/services/efs/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The efs service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/eks/index.md b/website/docs/services/eks/index.md
index debdd3141..5241ba4fe 100644
--- a/website/docs/services/eks/index.md
+++ b/website/docs/services/eks/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The eks service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/elasticache/index.md b/website/docs/services/elasticache/index.md
index 5bbe6d01b..5a5856a05 100644
--- a/website/docs/services/elasticache/index.md
+++ b/website/docs/services/elasticache/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The elasticache service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/elasticbeanstalk/index.md b/website/docs/services/elasticbeanstalk/index.md
index ca33107d7..af52e27dd 100644
--- a/website/docs/services/elasticbeanstalk/index.md
+++ b/website/docs/services/elasticbeanstalk/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The elasticbeanstalk service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/elasticloadbalancingv2/index.md b/website/docs/services/elasticloadbalancingv2/index.md
index 0f754218b..51273f952 100644
--- a/website/docs/services/elasticloadbalancingv2/index.md
+++ b/website/docs/services/elasticloadbalancingv2/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The elasticloadbalancingv2 service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/emr/index.md b/website/docs/services/emr/index.md
index f7f285e40..5155cd98d 100644
--- a/website/docs/services/emr/index.md
+++ b/website/docs/services/emr/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The emr service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/emrcontainers/index.md b/website/docs/services/emrcontainers/index.md
index e647adc57..1b54d7938 100644
--- a/website/docs/services/emrcontainers/index.md
+++ b/website/docs/services/emrcontainers/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The emrcontainers service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/emrserverless/index.md b/website/docs/services/emrserverless/index.md
index 4f80c8ba6..b7b5c1167 100644
--- a/website/docs/services/emrserverless/index.md
+++ b/website/docs/services/emrserverless/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The emrserverless service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/entityresolution/index.md b/website/docs/services/entityresolution/index.md
index e9b003043..e8c139cf5 100644
--- a/website/docs/services/entityresolution/index.md
+++ b/website/docs/services/entityresolution/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The entityresolution service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/events/index.md b/website/docs/services/events/index.md
index b0b80d13e..867220ebe 100644
--- a/website/docs/services/events/index.md
+++ b/website/docs/services/events/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The events service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/eventschemas/index.md b/website/docs/services/eventschemas/index.md
index 281ff9373..d63b12354 100644
--- a/website/docs/services/eventschemas/index.md
+++ b/website/docs/services/eventschemas/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The eventschemas service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/evidently/index.md b/website/docs/services/evidently/index.md
index 056080813..f25c41cdb 100644
--- a/website/docs/services/evidently/index.md
+++ b/website/docs/services/evidently/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The evidently service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/evs/index.md b/website/docs/services/evs/index.md
index fe9b98daf..89c382108 100644
--- a/website/docs/services/evs/index.md
+++ b/website/docs/services/evs/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The evs service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/finspace/index.md b/website/docs/services/finspace/index.md
index 70a066aed..e2263b44b 100644
--- a/website/docs/services/finspace/index.md
+++ b/website/docs/services/finspace/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The finspace service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/fis/index.md b/website/docs/services/fis/index.md
index 627872a29..0d84458e5 100644
--- a/website/docs/services/fis/index.md
+++ b/website/docs/services/fis/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The fis service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/fms/index.md b/website/docs/services/fms/index.md
index 2d7089988..1e94140f2 100644
--- a/website/docs/services/fms/index.md
+++ b/website/docs/services/fms/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The fms service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/forecast/index.md b/website/docs/services/forecast/index.md
index d14e44540..b53b83a3d 100644
--- a/website/docs/services/forecast/index.md
+++ b/website/docs/services/forecast/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The forecast service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/frauddetector/index.md b/website/docs/services/frauddetector/index.md
index e58844a92..5eb750c31 100644
--- a/website/docs/services/frauddetector/index.md
+++ b/website/docs/services/frauddetector/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The frauddetector service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/fsx/index.md b/website/docs/services/fsx/index.md
index 965a372aa..29bcd6d33 100644
--- a/website/docs/services/fsx/index.md
+++ b/website/docs/services/fsx/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The fsx service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/gamelift/index.md b/website/docs/services/gamelift/index.md
index cf322ac74..f73db90b0 100644
--- a/website/docs/services/gamelift/index.md
+++ b/website/docs/services/gamelift/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The gamelift service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/globalaccelerator/index.md b/website/docs/services/globalaccelerator/index.md
index d6900e764..50919ab60 100644
--- a/website/docs/services/globalaccelerator/index.md
+++ b/website/docs/services/globalaccelerator/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The globalaccelerator service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/glue/index.md b/website/docs/services/glue/index.md
index f9bb50db8..214ad896d 100644
--- a/website/docs/services/glue/index.md
+++ b/website/docs/services/glue/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The glue service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/grafana/index.md b/website/docs/services/grafana/index.md
index 9c4e8ba68..4f4e56805 100644
--- a/website/docs/services/grafana/index.md
+++ b/website/docs/services/grafana/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The grafana service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/greengrassv2/index.md b/website/docs/services/greengrassv2/index.md
index a8192c3b3..3eb22b05d 100644
--- a/website/docs/services/greengrassv2/index.md
+++ b/website/docs/services/greengrassv2/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The greengrassv2 service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/groundstation/index.md b/website/docs/services/groundstation/index.md
index 3127c7f05..956a6757c 100644
--- a/website/docs/services/groundstation/index.md
+++ b/website/docs/services/groundstation/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The groundstation service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/guardduty/index.md b/website/docs/services/guardduty/index.md
index 4468f5360..8332e21f3 100644
--- a/website/docs/services/guardduty/index.md
+++ b/website/docs/services/guardduty/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The guardduty service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/healthimaging/index.md b/website/docs/services/healthimaging/index.md
index 61c2ec4f6..2835b50bf 100644
--- a/website/docs/services/healthimaging/index.md
+++ b/website/docs/services/healthimaging/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The healthimaging service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/healthlake/index.md b/website/docs/services/healthlake/index.md
index 2328f4d40..84d881998 100644
--- a/website/docs/services/healthlake/index.md
+++ b/website/docs/services/healthlake/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The healthlake service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/iam/index.md b/website/docs/services/iam/index.md
index 53726d427..2bd2dcca0 100644
--- a/website/docs/services/iam/index.md
+++ b/website/docs/services/iam/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The iam service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/identitystore/index.md b/website/docs/services/identitystore/index.md
index 6540d679d..f69990d98 100644
--- a/website/docs/services/identitystore/index.md
+++ b/website/docs/services/identitystore/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The identitystore service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/imagebuilder/index.md b/website/docs/services/imagebuilder/index.md
index e0dde23d4..14864451f 100644
--- a/website/docs/services/imagebuilder/index.md
+++ b/website/docs/services/imagebuilder/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The imagebuilder service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/inspector/index.md b/website/docs/services/inspector/index.md
index 65e60ee90..b8bbabb8b 100644
--- a/website/docs/services/inspector/index.md
+++ b/website/docs/services/inspector/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The inspector service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/inspectorv2/index.md b/website/docs/services/inspectorv2/index.md
index c89c8ab4d..1ba20263c 100644
--- a/website/docs/services/inspectorv2/index.md
+++ b/website/docs/services/inspectorv2/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The inspectorv2 service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/internetmonitor/index.md b/website/docs/services/internetmonitor/index.md
index dda3e24c0..dcc66614e 100644
--- a/website/docs/services/internetmonitor/index.md
+++ b/website/docs/services/internetmonitor/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The internetmonitor service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/invoicing/index.md b/website/docs/services/invoicing/index.md
index 3eaf9d8ac..04dc9278f 100644
--- a/website/docs/services/invoicing/index.md
+++ b/website/docs/services/invoicing/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The invoicing service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/iot/index.md b/website/docs/services/iot/index.md
index bda8d6be9..1cde7c612 100644
--- a/website/docs/services/iot/index.md
+++ b/website/docs/services/iot/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The iot service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/iotanalytics/index.md b/website/docs/services/iotanalytics/index.md
index 1146fb23f..b417d7a5e 100644
--- a/website/docs/services/iotanalytics/index.md
+++ b/website/docs/services/iotanalytics/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The iotanalytics service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/iotcoredeviceadvisor/index.md b/website/docs/services/iotcoredeviceadvisor/index.md
index b6fee0a6f..a1de5b3cd 100644
--- a/website/docs/services/iotcoredeviceadvisor/index.md
+++ b/website/docs/services/iotcoredeviceadvisor/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The iotcoredeviceadvisor service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/iotevents/index.md b/website/docs/services/iotevents/index.md
index fb516a806..ee0944332 100644
--- a/website/docs/services/iotevents/index.md
+++ b/website/docs/services/iotevents/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The iotevents service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/iotfleetwise/index.md b/website/docs/services/iotfleetwise/index.md
index f11d59bff..0a153dd9f 100644
--- a/website/docs/services/iotfleetwise/index.md
+++ b/website/docs/services/iotfleetwise/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The iotfleetwise service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/iotsitewise/index.md b/website/docs/services/iotsitewise/index.md
index 61a9c5a7c..32f2ee992 100644
--- a/website/docs/services/iotsitewise/index.md
+++ b/website/docs/services/iotsitewise/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The iotsitewise service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/iottwinmaker/index.md b/website/docs/services/iottwinmaker/index.md
index e0eba1179..7c5ee029e 100644
--- a/website/docs/services/iottwinmaker/index.md
+++ b/website/docs/services/iottwinmaker/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The iottwinmaker service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/iotwireless/index.md b/website/docs/services/iotwireless/index.md
index e9f2000ef..db2353fdf 100644
--- a/website/docs/services/iotwireless/index.md
+++ b/website/docs/services/iotwireless/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The iotwireless service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/ivs/index.md b/website/docs/services/ivs/index.md
index d076184f9..e0f583789 100644
--- a/website/docs/services/ivs/index.md
+++ b/website/docs/services/ivs/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The ivs service documentation.
-:::info Service Summary
+:::info[Service Summary]
diff --git a/website/docs/services/ivschat/index.md b/website/docs/services/ivschat/index.md
index b54939b0f..a34e3e581 100644
--- a/website/docs/services/ivschat/index.md
+++ b/website/docs/services/ivschat/index.md
@@ -16,7 +16,7 @@ image: /img/stackql-aws-provider-featured-image.png
The ivschat service documentation.
-:::info Service Summary
+:::info[Service Summary]