Skip to content

Commit

Permalink
feat: implement a simplified docs-v2 command
Browse files Browse the repository at this point in the history
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 #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.
  • Loading branch information
JohannesRudolph committed Aug 28, 2024
1 parent 8e43ec5 commit 834aa32
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 82 deletions.
97 changes: 17 additions & 80 deletions src/commands/foundation/docs.command.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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;
}
}
2 changes: 1 addition & 1 deletion src/docs/DocumentationRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand Down
3 changes: 2 additions & 1 deletion src/model/CollieHub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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);
}
}
18 changes: 18 additions & 0 deletions src/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}

0 comments on commit 834aa32

Please sign in to comment.