Skip to content

Commit

Permalink
fix(cli): Mintlify & Readme migrator improvements. (#5745)
Browse files Browse the repository at this point in the history
* Migrator improvements.

* Add versions.yml

* us

* us
  • Loading branch information
eyw520 authored Jan 24, 2025
1 parent 18ff859 commit d8ab27a
Show file tree
Hide file tree
Showing 28 changed files with 6,786 additions and 60 deletions.
2 changes: 2 additions & 0 deletions packages/cli/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ function addInitCommand(cli: Argv<GlobalCliOptions>, cliContext: CliContext) {
await cliContext.runTask(async (context) => {
await initializeWithReadme({
readmeUrl: argv.readme,
organization: argv.organization ?? "fern",
taskContext: context,
versionOfCli: await getLatestVersionOfCli({ cliEnvironment: cliContext.environment })
});
Expand All @@ -267,6 +268,7 @@ function addInitCommand(cli: Argv<GlobalCliOptions>, cliContext: CliContext) {
await cliContext.runTask(async (taskContext) => {
await initializeWithMintlify({
pathToMintJson: argv.mintlify,
organization: argv.organization ?? "fern",
taskContext,
versionOfCli: await getLatestVersionOfCli({ cliEnvironment: cliContext.environment })
});
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/cli/versions.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
- changelogEntry:
- summary: |
Various improvements to the Mintlify and Readme importers, including better default styling
and spec imports for Mintlify migrations.
type: fix
irVersion: 55
version: 0.51.4

- changelogEntry:
- summary: |
The OpenAPI parser now prefers the JSON Content-Type variant over
Expand Down
8 changes: 7 additions & 1 deletion packages/cli/docs-importers/commons/src/FernDocsBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import { FernRegistry as CjsFdrSdk } from "@fern-fern/fdr-cjs-sdk";
* A builder utility to help
*/
export abstract class FernDocsBuilder {
public abstract addOpenAPI({ absolutePathToOpenAPI }: { absolutePathToOpenAPI: AbsoluteFilePath }): void;
public abstract addOpenAPI({
relativePathToOpenAPI,
absolutePathToOpenAPI
}: {
relativePathToOpenAPI: RelativeFilePath;
absolutePathToOpenAPI: AbsoluteFilePath;
}): void;

public abstract addMarkdownPage({
frontmatter,
Expand Down
51 changes: 41 additions & 10 deletions packages/cli/docs-importers/commons/src/FernDocsBuilderImpl.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { cp, mkdir, writeFile } from "fs/promises";
import yaml from "js-yaml";

import { DOCS_CONFIGURATION_FILENAME, FERN_DIRECTORY, docsYml } from "@fern-api/configuration";
import {
DOCS_CONFIGURATION_FILENAME,
FERN_DIRECTORY,
GENERATORS_CONFIGURATION_FILENAME,
docsYml,
generatorsYml
} from "@fern-api/configuration";
import { AbsoluteFilePath, RelativeFilePath, dirname, join } from "@fern-api/fs-utils";

import { FernRegistry as CjsFdrSdk } from "@fern-fern/fdr-cjs-sdk";
Expand All @@ -18,17 +24,26 @@ interface Asset {
}

export class FernDocsBuilderImpl extends FernDocsBuilder {
private absolutePathsToOpenAPI: AbsoluteFilePath[] = [];
private openApiSpecs: Record<RelativeFilePath, AbsoluteFilePath> = {};
private nonTabbedNavigation: NonTabbedNavigationBuilderImpl = new NonTabbedNavigationBuilderImpl();
private tabbedNavigation: Record<docsYml.RawSchemas.TabId, TabbedNavigationBuilderImpl> = {};
private markdownPages: Record<RelativeFilePath, MarkdownPage> = {};
private assets: Record<RelativeFilePath, Asset> = {};
private docsYml: docsYml.RawSchemas.DocsConfiguration = {
instances: []
};
private generatorsYml: generatorsYml.GeneratorsConfigurationSchema = {
api: { specs: [] as generatorsYml.ApiConfigurationV2SpecsSchema }
};

public addOpenAPI({ absolutePathToOpenAPI }: { absolutePathToOpenAPI: AbsoluteFilePath }): void {
this.absolutePathsToOpenAPI.push(absolutePathToOpenAPI);
public addOpenAPI({
relativePathToOpenAPI,
absolutePathToOpenAPI
}: {
relativePathToOpenAPI: RelativeFilePath;
absolutePathToOpenAPI: AbsoluteFilePath;
}): void {
this.openApiSpecs[relativePathToOpenAPI] = absolutePathToOpenAPI;
}

public addMarkdownPage({
Expand Down Expand Up @@ -119,21 +134,37 @@ export class FernDocsBuilderImpl extends FernDocsBuilder {
Object.entries(this.tabbedNavigation).forEach(([_, value]) => {
const tabbedItem: docsYml.RawSchemas.TabbedNavigationItem = {
tab: value.tabId,
layout: value.items
...(value.items.length > 0 ? { layout: value.items } : {})
};
tabbedNavigationItems.push(tabbedItem);
});
this.docsYml.navigation = tabbedNavigationItems;
} else if (this.nonTabbedNavigation != null) {
this.docsYml.navigation = this.nonTabbedNavigation.items;
}
if (Object.entries(this.openApiSpecs).length > 0 && this.generatorsYml?.api != null) {
if (typeof this.generatorsYml.api !== "string") {
this.generatorsYml.api = {
specs: Object.entries(this.openApiSpecs).map(([relativePath]) => ({
openapi: relativePath
}))
};
}
await writeFile(
join(absolutePathToFernDirectory, RelativeFilePath.of(GENERATORS_CONFIGURATION_FILENAME)),
yaml.dump(generatorsYml.serialization.GeneratorsConfigurationSchema.jsonOrThrow(this.generatorsYml))
);
await Promise.all(
Object.entries(this.openApiSpecs).map(async ([relativePath, absolutePath]) => {
const absolutePathToOpenAPI = join(absolutePathToFernDirectory, RelativeFilePath.of(relativePath));
await mkdir(dirname(absolutePathToOpenAPI), { recursive: true });
await cp(absolutePath, absolutePathToOpenAPI);
})
);
}

const absoluteFilePathToDocsYml = join(
absolutePathToFernDirectory,
RelativeFilePath.of(DOCS_CONFIGURATION_FILENAME)
);
await writeFile(
absoluteFilePathToDocsYml,
join(absolutePathToFernDirectory, RelativeFilePath.of(DOCS_CONFIGURATION_FILENAME)),
yaml.dump(docsYml.RawSchemas.Serializer.DocsConfiguration.jsonOrThrow(this.docsYml))
);

Expand Down
9 changes: 9 additions & 0 deletions packages/cli/docs-importers/commons/src/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { docsYml } from "@fern-api/configuration";

const DEFAULT_LAYOUT: docsYml.RawSchemas.LayoutConfig = {
searchbarPlacement: "header",
pageWidth: "full",
tabsPlacement: "header"
};

export { DEFAULT_LAYOUT };
2 changes: 2 additions & 0 deletions packages/cli/docs-importers/commons/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { DEFAULT_LAYOUT } from "./consts";
export { DocsImporter } from "./DocsImporter";
export { FernDocsBuilder, FernDocsNavigationBuilder } from "./FernDocsBuilder";
export { FernDocsBuilderImpl } from "./FernDocsBuilderImpl";
export type { TabInfo } from "./types";
7 changes: 7 additions & 0 deletions packages/cli/docs-importers/commons/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { FernDocsNavigationBuilder } from "./FernDocsBuilder";

export interface TabInfo {
name: string;
url: string;
navigationBuilder: FernDocsNavigationBuilder;
}
74 changes: 54 additions & 20 deletions packages/cli/docs-importers/mintlify/src/MintlifyImporter.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { readFile } from "fs/promises";

import { stripLeadingSlash } from "@fern-api/core-utils";
import { DocsImporter, FernDocsNavigationBuilder } from "@fern-api/docs-importer-commons";
import { FernDocsBuilder } from "@fern-api/docs-importer-commons";
import { DocsImporter, FernDocsBuilder, FernDocsNavigationBuilder, TabInfo } from "@fern-api/docs-importer-commons";
import { DEFAULT_LAYOUT } from "@fern-api/docs-importer-commons";
import { AbsoluteFilePath, RelativeFilePath, dirname, join } from "@fern-api/fs-utils";

import { convertColors } from "./convertColors";
import { convertLogo } from "./convertLogo";
import { convertNavigationItem } from "./convertNavigationItem";
import { MintJsonSchema, MintNavigationItem } from "./mintlify";
import { convertInstanceName } from "./utils/convertInstanceName";
import { getTabForMintItem } from "./utils/getTabForMintItem";

export declare namespace MintlifyImporter {
Expand All @@ -17,12 +18,6 @@ export declare namespace MintlifyImporter {
}
}

interface TabInfo {
name: string;
url: string;
navigationBuilder: FernDocsNavigationBuilder;
}

export class MintlifyImporter extends DocsImporter<MintlifyImporter.Args> {
private documentationTab: TabInfo | undefined = undefined;
private tabUrlToInfo: Record<string, TabInfo> = {};
Expand Down Expand Up @@ -52,15 +47,41 @@ export class MintlifyImporter extends DocsImporter<MintlifyImporter.Args> {
}
this.context.logger.debug("Converted color configuration");

for (const tab of mint.tabs ?? []) {
this.tabUrlToInfo[tab.url] = {
name: tab.name,
url: tab.url,
navigationBuilder: builder.getNavigationBuilder({
tabId: tab.url,
tabConfig: { slug: tab.url, displayName: tab.name }
})
};
builder.setLayout({ layout: DEFAULT_LAYOUT });

if (mint.tabs != null) {
for (const tab of mint.tabs ?? []) {
this.tabUrlToInfo[tab.url] = {
name: tab.name,
url: tab.url,
navigationBuilder: builder.getNavigationBuilder({
tabId: tab.url,
tabConfig: { slug: tab.url, displayName: tab.name }
})
};
}
} else if (mint.anchors != null) {
for (const anchor of mint.anchors) {
if ("url" in anchor && anchor.url != null && anchor.url.startsWith("https://")) {
this.tabUrlToInfo[anchor.url] = {
name: anchor.name,
url: anchor.url,
navigationBuilder: builder.getNavigationBuilder({
tabId: anchor.name,
tabConfig: { href: anchor.url, displayName: anchor.name }
})
};
} else if ("url" in anchor && anchor.url != null) {
this.tabUrlToInfo[anchor.url] = {
name: anchor.name,
url: anchor.url,
navigationBuilder: builder.getNavigationBuilder({
tabId: anchor.url,
tabConfig: { slug: anchor.url, displayName: anchor.name }
})
};
}
}
}

for (const mintItem of mint.navigation) {
Expand All @@ -83,6 +104,7 @@ export class MintlifyImporter extends DocsImporter<MintlifyImporter.Args> {
RelativeFilePath.of(stripLeadingSlash(mint.openapi))
);
builder.addOpenAPI({
relativePathToOpenAPI: RelativeFilePath.of(stripLeadingSlash(mint.openapi)),
absolutePathToOpenAPI
});
} else if (mint.openapi != null) {
Expand All @@ -92,16 +114,28 @@ export class MintlifyImporter extends DocsImporter<MintlifyImporter.Args> {
RelativeFilePath.of(stripLeadingSlash(openapi))
);
builder.addOpenAPI({
relativePathToOpenAPI: RelativeFilePath.of(stripLeadingSlash(openapi)),
absolutePathToOpenAPI
});
}
} else if (mint.anchors != null) {
for (const anchor of mint.anchors) {
if ("openapi" in anchor && anchor.openapi != null) {
const absolutePathToOpenAPI = join(
dirname(args.absolutePathToMintJson),
RelativeFilePath.of(stripLeadingSlash(anchor.openapi))
);
builder.addOpenAPI({
relativePathToOpenAPI: RelativeFilePath.of(stripLeadingSlash(anchor.openapi)),
absolutePathToOpenAPI
});
}
}
}
this.context.logger.debug("Imported OpenAPI specs");

// We're currently setting one instance so that clients can generate docs out of the box,
// but they'll have the option to add additional instances and custom domains later.
const instanceUrl = builder.setInstance({
companyName: mint.name
companyName: convertInstanceName(mint.name)
});
this.context.logger.debug(`Added instance ${instanceUrl} to docs.yml`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ describe("add-generator-groups", () => {
absolutePathToMintJson,
outputPath,
taskContext,
versionOfCli: "*"
versionOfCli: "*",
organization: "fern"
});
});
}
Expand Down
Loading

0 comments on commit d8ab27a

Please sign in to comment.