Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions packages/nix/localDevShell/scripts/enhance-modules-json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/usr/bin/env node

// Purpose: Enhances the modules.json with filesystem content structure
// for both auto-generated and human-written documentation

const fs = require('fs');
const path = require('path');

// Get paths from environment or use defaults
const DOCS_VERSION_DIR = process.env.DOCS_VERSION_DIR || path.join(__dirname, '../../../website/src/content/docs/main');
const OUTPUT_DIR = path.join(DOCS_VERSION_DIR, 'modules');
const MODULES_JSON_PATH = path.join(DOCS_VERSION_DIR, 'modules.json');

// Read the existing modules.json generated by the bash script
function readExistingModulesJson() {
try {
const content = fs.readFileSync(MODULES_JSON_PATH, 'utf8');
return JSON.parse(content);
} catch (error) {
console.error('Error reading modules.json:', error);
return { modules: [] };
}
}

// Scan a module directory for manual documentation files
function scanModuleDirectory(moduleDir, moduleName) {
const content = { reference: null, guides: [], examples: [] };

if (!fs.existsSync(moduleDir)) {
return content;
}

const items = fs.readdirSync(moduleDir, { withFileTypes: true });

for (const item of items) {
if (item.name === 'reference' && item.isDirectory()) {
// Reference docs are auto-generated, just mark as present
content.reference = { path: '/reference', text: 'Reference' };
} else if (item.isFile() && item.name.endsWith('.mdx') && item.name !== 'index.mdx') {
// Human-written guides at the module root level
const baseName = path.basename(item.name, '.mdx');
const title = formatTitle(baseName);
content.guides.push({
path: `/${baseName}`,
text: title,
file: item.name
});
} else if (item.isDirectory() && item.name === 'examples') {
// Examples subdirectory
const examplesDir = path.join(moduleDir, 'examples');
const exampleFiles = fs.readdirSync(examplesDir, { withFileTypes: true })
.filter(file => file.isFile() && file.name.endsWith('.mdx'))
.map(file => {
const baseName = path.basename(file.name, '.mdx');
const title = formatTitle(baseName);
return {
path: `/examples/${baseName}`,
text: title,
file: file.name
};
});

if (exampleFiles.length > 0) {
content.examples = exampleFiles;
}
}
}

return content;
}

// Convert kebab-case to Title Case
function formatTitle(str) {
return str
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}

// Build the enhanced module structure
function buildEnhancedModuleStructure(module) {
const moduleDir = path.join(OUTPUT_DIR, module.module);
const content = scanModuleDirectory(moduleDir, module.module);

// Build the sub-navigation structure
const sub = [];

// Always add reference first if it exists
if (content.reference) {
sub.push(content.reference);
}

// Add guides
if (content.guides.length > 0) {
sub.push(...content.guides);
}

// Add examples as a submenu if they exist
if (content.examples.length > 0) {
sub.push({
path: '/examples',
text: 'Examples',
sub: content.examples
});
}

return {
...module,
sub: sub.length > 0 ? sub : undefined,
hasContent: sub.length > 0
};
}

// Main function
function enhanceModulesJson() {
console.log('Enhancing modules.json with filesystem content...');

const existingData = readExistingModulesJson();

// Enhance each module with content structure
const enhancedModules = existingData.modules.map(buildEnhancedModuleStructure);

// Update the JSON structure
const enhancedData = {
...existingData,
modules: enhancedModules,
generatedAt: new Date().toISOString(),
version: '2.0'
};

// Write the enhanced JSON back
fs.writeFileSync(MODULES_JSON_PATH, JSON.stringify(enhancedData, null, 2));

console.log(`Enhanced modules.json with ${enhancedModules.length} modules`);

// Log summary of content found
const modulesWithContent = enhancedModules.filter(m => m.hasContent);
const modulesWithGuides = enhancedModules.filter(m => m.sub?.some(s => s.file));
const modulesWithExamples = enhancedModules.filter(m => m.sub?.some(s => s.sub));

console.log(`- ${modulesWithContent.length} modules have additional content`);
console.log(`- ${modulesWithGuides.length} modules have human-written guides`);
console.log(`- ${modulesWithExamples.length} modules have examples`);
}

// Run if called directly
if (require.main === module) {
enhanceModulesJson();
}

module.exports = { enhanceModulesJson, scanModuleDirectory, formatTitle };
32 changes: 21 additions & 11 deletions packages/nix/localDevShell/scripts/generate-tf-docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@
# Purpose: Uses terraform-docs to create the markdown documentation
# for each terraform module for the public website
DOCS_VERSION_DIR="$REPO_ROOT/packages/website/src/content/docs/main"
OUTPUT_DIR="$DOCS_VERSION_DIR/reference/infrastructure-modules"
OUTPUT_DIR="$DOCS_VERSION_DIR/modules"

# Initialize an empty JSON object with a `modules` array
JSON=$(jq -n '{modules: []}')

# Remove the old docs
rm -rf "$OUTPUT_DIR/aws"
rm -rf "$OUTPUT_DIR/kubernetes"
rm -rf "$OUTPUT_DIR/authentik"
rm -rf "$OUTPUT_DIR/vault"
rm -rf "$OUTPUT_DIR/utility"
rm -rf "$OUTPUT_DIR/direct"
rm -rf "$OUTPUT_DIR/submodule"
# Function to clean only auto-generated content (reference folders)
function clean_autogenerated_content() {
local module_dir="$1"
if [ -d "$module_dir/reference" ]; then
rm -rf "$module_dir/reference"
fi
}

function skip_injected_variables() {
awk '
Expand Down Expand Up @@ -78,9 +77,16 @@ for d in "$TERRAFORM_MODULES_DIR"/*; do
# Append the directory name to the modules array in the JSON object
JSON=$(jq --arg module "$MODULE" --arg group "$GROUP" --arg type "$TYPE" '.modules += [{"module": $module, "type": $type, "group": $group}]' <<<"$JSON")

# Make the docs
DOCS_DIR="$OUTPUT_DIR/$TYPE/$GROUP/$MODULE"
# Make the docs in new flat structure with reference subfolder
MODULE_DIR="$OUTPUT_DIR/$MODULE"
DOCS_DIR="$MODULE_DIR/reference"

# Clean existing auto-generated content only
clean_autogenerated_content "$MODULE_DIR"

# Create the reference directory
mkdir -p "$DOCS_DIR"
# Generate the reference documentation
terraform-docs -c "$TERRAFORM_MODULES_DIR/.terraform-docs.yml" "$d" |
add_provider_links |
remove_version_header |
Expand All @@ -98,3 +104,7 @@ for d in "$TERRAFORM_MODULES_DIR"/*; do
done

echo "$JSON" >"$DOCS_VERSION_DIR/modules.json"

# Enhance the modules.json with filesystem content structure
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOCS_VERSION_DIR="$DOCS_VERSION_DIR" node "$SCRIPT_DIR/enhance-modules-json.js"
12 changes: 0 additions & 12 deletions packages/website/src/components/layouts/docs/util/makeModuleDir.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Enhanced module type with optional sub-navigation
interface EnhancedModule {
type: string;
group: string;
module: string;
sub?: Array<{
path: string;
text: string;
sub?: Array<{ path: string; text: string }>;
}>;
hasContent?: boolean;
}

export function makeModuleDir(
modules: Array<{ type: string; group: string; module: string }>,
group: string,
type: string,
) {
return modules
.filter((module) => module.group === group && module.type === type)
.map(({ module }) => ({
text: module,
path: `/${module}`,
}));
}

export function makeFlatModuleList(
modules: Array<EnhancedModule>,
) {
return modules
.map((module) => ({
text: module.module,
path: `/${module.module}`,
sub: module.sub, // Include sub-navigation if present
}))
.sort((a, b) => a.text.localeCompare(b.text));
}

// Create hierarchical module list grouped by type and group
export function makeHierarchicalModuleList(
modules: Array<EnhancedModule>,
) {
const grouped: Record<string, Record<string, EnhancedModule[]>> = {};

modules.forEach((module) => {
grouped[module.type][module.group].push(module);
});

return Object.entries(grouped).map(([type, groups]) => ({
text: formatTypeTitle(type),
path: `/${type}`,
sub: Object.entries(groups).map(([group, moduleList]) => ({
text: formatGroupTitle(group),
path: `/${type}/${group}`,
sub: moduleList
.sort((a, b) => a.module.localeCompare(b.module))
.map((module) => ({
text: module.module,
path: `/${module.module}`,
sub: module.sub,
})),
})),
}));
}

function formatTypeTitle(type: string): string {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id rather just have a static mapping with this as a fallback

return type.charAt(0).toUpperCase() + type.slice(1);
}

function formatGroupTitle(group: string): string {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isnt correct logic

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above. would rather have a static mapping with a fallback.

this fails for aws

return group
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
2 changes: 1 addition & 1 deletion packages/website/src/content/docs/edge/sideNavSections.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import { makeModuleDir } from "@/components/layouts/docs/util/makeModuleDir.ts";
import { makeModuleDir } from "@/components/layouts/docs/util/makeModuleList.ts";

import modules from "./modules.json";
import {
Expand Down
Loading