From 834aa324439e25bcc322cca6431a2203d4b1f4d3 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Tue, 27 Aug 2024 22:24:00 +0200 Subject: [PATCH] feat: implement a simplified docs-v2 command the v2 approach for generating docs using collie leverages terragrunt with a foundation-level terraform module that builds the documentation. This approach is more versatile and more performant - uses terraform_remote_state to quickly download documentation_md output from all platform modules, which is much faster than any other approach we tried before (see https://github.com/meshcloud/collie-cli/pull/271) - the terraform module can freely implement templating, ie symlinking the documentation site generator templates to enable docs hot reloading - users have better control to work natively with the documentation generator of their choice, because logic is moved out of collie-cli The terraform module that we will make available for now unfortunately no longer has support for generating kit module documentation. However we have found that there's little value in generating kit module documentation outside of the collie-hub use case, as most cloud foundation teams want to generate documentation for their application teams and are content with re-viewing terraform module documentation directly off the README.md files in git. --- src/commands/foundation/docs.command.ts | 97 +++++-------------------- src/docs/DocumentationRepository.ts | 2 +- src/model/CollieHub.ts | 3 +- src/path.ts | 18 +++++ 4 files changed, 38 insertions(+), 82 deletions(-) diff --git a/src/commands/foundation/docs.command.ts b/src/commands/foundation/docs.command.ts index 214fe7c..5f6134c 100644 --- a/src/commands/foundation/docs.command.ts +++ b/src/commands/foundation/docs.command.ts @@ -1,21 +1,14 @@ -import * as fs from "std/fs"; import { CliApiFacadeFactory } from "../../api/CliApiFacadeFactory.ts"; import { Logger } from "../../cli/Logger.ts"; import { ProgressReporter } from "../../cli/ProgressReporter.ts"; -import { ComplianceControlRepository } from "../../compliance/ComplianceControlRepository.ts"; -import { ComplianceDocumentationGenerator } from "../../docs/ComplianceDocumentationGenerator.ts"; -import { DocumentationGenerator } from "../../docs/DocumentationGenerator.ts"; import { DocumentationRepository } from "../../docs/DocumentationRepository.ts"; -import { KitModuleDocumentationGenerator } from "../../docs/KitModuleDocumentationGenerator.ts"; -import { PlatformDocumentationGenerator } from "../../docs/PlatformDocumentationGenerator.ts"; -import { KitDependencyAnalyzer } from "../../kit/KitDependencyAnalyzer.ts"; -import { KitModuleRepository } from "../../kit/KitModuleRepository.ts"; import { CollieRepository } from "../../model/CollieRepository.ts"; import { FoundationRepository } from "../../model/FoundationRepository.ts"; import { ModelValidator } from "../../model/schemas/ModelValidator.ts"; import { GlobalCommandOptions } from "../GlobalCommandOptions.ts"; import { TopLevelCommand } from "../TopLevelCommand.ts"; import { getCurrentWorkingFoundation } from "../../cli/commandOptionsConventions.ts"; +import { exists } from "std/fs"; interface DocsCommandOptions { update?: boolean; @@ -89,59 +82,34 @@ export function registerDocsCmd(program: TopLevelCommand) { } async function updateDocumentation( - repo: CollieRepository, + _repo: CollieRepository, foundation: FoundationRepository, logger: Logger, ) { + const docsModulePath = foundation.resolvePath("docs"); + + if (!await exists(docsModulePath)) { + logger.error( + `Foundation-level docs module at "${docsModulePath}" does not exist.`, + ); + logger.tip( + "Import a starter docs module using 'collie kit import docs' command.", + ); + return; + } + const foundationProgress = new ProgressReporter( "generating docs", `foundation "${foundation.name}"`, logger, ); - const validator = new ModelValidator(logger); - const modules = await KitModuleRepository.load(repo, validator, logger); - const controls = await ComplianceControlRepository.load( - repo, - validator, - logger, - ); - const moduleDocumentation = new KitModuleDocumentationGenerator( - repo, - modules, - controls, - logger, - ); - - const complianceDocumentation = new ComplianceDocumentationGenerator( - repo, - logger, - ); - - const analyzer = new KitDependencyAnalyzer(repo, modules, logger); - const factory = new CliApiFacadeFactory(logger); const terragrunt = factory.buildTerragrunt(); - const platformDocumentation = new PlatformDocumentationGenerator( - repo, - foundation, - analyzer, - controls, - terragrunt, - logger, - ); - const docsRepo = new DocumentationRepository(foundation); - - await prepareSiteTemplate(docsRepo, repo, logger); - - const generator = new DocumentationGenerator( - moduleDocumentation, - complianceDocumentation, - platformDocumentation, - ); - - await generator.generateFoundationDocumentation(docsRepo); + await terragrunt.run(docsModulePath, { raw: ["apply"] }, { + autoApprove: true, + }); foundationProgress.done(); } @@ -171,34 +139,3 @@ async function buildDocumentation( await npm.run(["install"], { cwd: dir }); await npm.run(["run", "docs:build"], { cwd: dir }); } - -async function prepareSiteTemplate( - docsRepo: DocumentationRepository, - repo: CollieRepository, - logger: Logger, -) { - // TODO: throw if it doesn't work - const srcDir = repo.resolvePath("kit", "foundation", "docs", "template"); - - try { - await fs.copy(srcDir, docsRepo.resolvePath(), { overwrite: true }); - } catch (e) { - if (e instanceof Deno.errors.NotFound) { - logger.error( - (fmt) => - `could not find kit module with template for documentation site at ${ - fmt.kitPath( - srcDir, - ) - }`, - ); - - logger.tipCommand( - "This module is essential for documentation generation. To import this module run", - "kit import foundation/docs", - ); - Deno.exit(1); - } - throw e; - } -} diff --git a/src/docs/DocumentationRepository.ts b/src/docs/DocumentationRepository.ts index 617e69a..b124f9e 100644 --- a/src/docs/DocumentationRepository.ts +++ b/src/docs/DocumentationRepository.ts @@ -7,7 +7,7 @@ export class DocumentationRepository { // we use a "hidden" directory with a leading "." because terragrunt excludes hidden files and dirs // when building a terragrunt-cache folder, see https://terragrunt.gruntwork.io/docs/reference/config-blocks-and-attributes/#terraform "include_in_copy" // > By default, Terragrunt excludes hidden files and folders during the copy step. - private readonly docsRootDir = ".docs"; + private readonly docsRootDir = ".docs-v2"; private readonly docsContentDir = "docs"; constructor(private readonly foundation: FoundationRepository) {} diff --git a/src/model/CollieHub.ts b/src/model/CollieHub.ts index a3badb1..1326454 100644 --- a/src/model/CollieHub.ts +++ b/src/model/CollieHub.ts @@ -3,6 +3,7 @@ import { GitCliFacade } from "/api/git/GitCliFacade.ts"; import { CollieRepository } from "/model/CollieRepository.ts"; import { CollieConfig } from "./CollieConfig.ts"; import { Logger } from "../cli/Logger.ts"; +import { rimraf } from "../path.ts"; export class CollieHub { constructor( @@ -82,6 +83,6 @@ export class CollieHub { public async cleanHubClone() { const hubCacheDir = this.repo.resolvePath(...this.hubCacheDirPath); - await Deno.remove(hubCacheDir, { recursive: true }); + await rimraf(hubCacheDir); } } diff --git a/src/path.ts b/src/path.ts index fe674f1..8b86631 100644 --- a/src/path.ts +++ b/src/path.ts @@ -10,3 +10,21 @@ import * as posix from "std/path/posix"; export function convertToPosixPath(relativePath: string) { return relativePath.split(path.SEPARATOR).join(posix.SEPARATOR); } + +/** + * Removes a dir, including all its contents. Does not throw if the dir does not exist. + * Equivalent to `rm -rf dir` + * + * @param dir the directory to remove + */ +export async function rimraf(dir: string) { + try { + await Deno.remove(dir, { recursive: true }); + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + // ignore, dir is already removed + } else { + throw error; + } + } +}