Skip to content

Commit

Permalink
(fix): docs preview server is fault tolerant to invalid docs.yml fi…
Browse files Browse the repository at this point in the history
…les (#3648)
  • Loading branch information
dsinghvi authored May 19, 2024
1 parent 4570751 commit ae875bc
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 26 deletions.
16 changes: 13 additions & 3 deletions packages/cli/cli/src/commands/docs-dev/devDocsWorkspace.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { runPreviewServer } from "@fern-api/docs-preview";
import { Project } from "@fern-api/project-loader";
import { CliContext } from "../../cli-context/CliContext";
import { validateDocsWorkspaceAndLogIssues } from "../validate/validateDocsWorkspaceAndLogIssues";
import { validateDocsWorkspaceWithoutExiting } from "../validate/validateDocsWorkspaceAndLogIssues";

export async function previewDocsWorkspace({
loadProject,
Expand All @@ -24,13 +24,23 @@ export async function previewDocsWorkspace({
});

await cliContext.runTaskForWorkspace(docsWorkspace, async (context) => {
await validateDocsWorkspaceAndLogIssues({ workspace: docsWorkspace, context, logWarnings: false });

context.logger.info(`Starting server on port ${port}`);

await runPreviewServer({
initialProject: project,
reloadProject: loadProject,
validateProject: async (project) => {
const docsWorkspace = project.docsWorkspaces;
if (docsWorkspace == null) {
return;
}
await validateDocsWorkspaceWithoutExiting({
workspace: docsWorkspace,
context,
logWarnings: false,
logSummary: false
});
},
context,
port
});
Expand Down
8 changes: 6 additions & 2 deletions packages/cli/cli/src/commands/validate/logViolations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ export interface LogViolationsResponse {
export function logViolations({
context,
violations,
logWarnings
logWarnings,
logSummary = true
}: {
context: TaskContext;
violations: ValidationViolation[];
logWarnings: boolean;
logSummary?: boolean;
}): LogViolationsResponse {
const stats = getViolationStats(violations);
logViolationsSummary({ context, stats, logWarnings });
if (logSummary) {
logViolationsSummary({ context, stats, logWarnings });
}

violations.forEach((violation) => {
if (violation.severity === "error") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@ import { TaskContext } from "@fern-api/task-context";
import { DocsWorkspace } from "@fern-api/workspace-loader";
import { logViolations } from "./logViolations";

export async function validateDocsWorkspaceWithoutExiting({
workspace,
context,
logWarnings,
logSummary = true
}: {
workspace: DocsWorkspace;
context: TaskContext;
logWarnings: boolean;
logSummary?: boolean;
}): Promise<{ hasErrors: boolean }> {
const violations = await validateDocsWorkspace(workspace, context.logger);
const { hasErrors } = logViolations({ violations, context, logWarnings, logSummary });

return { hasErrors };
}

export async function validateDocsWorkspaceAndLogIssues({
workspace,
context,
Expand All @@ -12,8 +29,7 @@ export async function validateDocsWorkspaceAndLogIssues({
context: TaskContext;
logWarnings: boolean;
}): Promise<void> {
const violations = await validateDocsWorkspace(workspace, context.logger);
const { hasErrors } = logViolations({ violations, context, logWarnings });
const { hasErrors } = await validateDocsWorkspaceWithoutExiting({ workspace, context, logWarnings });

if (hasErrors) {
context.failAndThrow();
Expand Down
66 changes: 47 additions & 19 deletions packages/cli/docs-preview/src/runPreviewServer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { wrapWithHttps } from "@fern-api/docs-resolver";
import { DocsV2Read } from "@fern-api/fdr-sdk";
import { DocsV1Read, DocsV2Read } from "@fern-api/fdr-sdk";
import { dirname } from "@fern-api/fs-utils";
import { Project } from "@fern-api/project-loader";
import { TaskContext } from "@fern-api/task-context";
Expand All @@ -13,14 +13,31 @@ import { WebSocketServer, type WebSocket } from "ws";
import { downloadBundle, getPathToBundleFolder } from "./downloadLocalDocsBundle";
import { getPreviewDocsDefinition } from "./previewDocs";

const EMPTY_DOCS_DEFINITION: DocsV1Read.DocsDefinition = {
pages: {},
apis: {},
files: {},
filesV2: {},
config: {
navigation: {
items: []
}
},
search: {
type: "legacyMultiAlgoliaIndex"
}
};

export async function runPreviewServer({
initialProject,
reloadProject,
validateProject,
context,
port
}: {
initialProject: Project;
reloadProject: () => Promise<Project>;
validateProject: (project: Project) => Promise<void>;
context: TaskContext;
port: number;
}): Promise<void> {
Expand Down Expand Up @@ -53,29 +70,39 @@ export async function runPreviewServer({
wrapWithHttps(initialProject.docsWorkspaces?.config.instances[0]?.url ?? `localhost:${port}`)
);

let docsDefinition = await getPreviewDocsDefinition({
domain: instance.host,
project: initialProject,
context
});
let project = initialProject;
let docsDefinition: DocsV1Read.DocsDefinition | undefined;

const reloadDocsDefinition = async () => {
context.logger.info("Reloading project and docs");
context.logger.info("Reloading docs");
const startTime = Date.now();
const project = await reloadProject();
context.logger.info(`Reload project took ${Date.now() - startTime}ms`);
const newDocsDefinition = await getPreviewDocsDefinition({
domain: instance.host,
project,
context
});
context.logger.info(`Reload project and docs completed in ${Date.now() - startTime}ms`);
return newDocsDefinition;
try {
project = await reloadProject();
const newDocsDefinition = await getPreviewDocsDefinition({
domain: instance.host,
project,
context
});
context.logger.info(`Reload completed in ${Date.now() - startTime}ms`);
return newDocsDefinition;
} catch (err) {
context.logger.error("Failed to reload because of validation errors: ");
await validateProject(project);
return docsDefinition;
}
};

const watcher = new Watcher(absoluteFilePathToFern, { recursive: true, ignoreInitial: true });
// initialize docs definition
docsDefinition = await reloadDocsDefinition();

const watcher = new Watcher(absoluteFilePathToFern, {
recursive: true,
ignoreInitial: true,
debounce: 1000,
renameDetection: true
});
watcher.on("all", async (event: string, targetPath: string, _targetPathNext: string) => {
context.logger.debug(chalk.dim(`[${event}] ${targetPath}`));
context.logger.info(chalk.dim(`[${event}] ${targetPath}`));
// after the docsDefinition is reloaded, send a message to all connected clients to reload the page
const reloadedDocsDefinition = await reloadDocsDefinition();
if (reloadedDocsDefinition != null) {
Expand All @@ -92,7 +119,8 @@ export async function runPreviewServer({

app.post("/v2/registry/docs/load-with-url", async (_, res) => {
try {
const definition = docsDefinition;
// set to empty in case docsDefinition is null which happens when the initial docs definition is invalid
const definition = docsDefinition ?? EMPTY_DOCS_DEFINITION;
const response: DocsV2Read.LoadDocsForUrlResponse = {
baseUrl: {
domain: instance.host,
Expand Down

0 comments on commit ae875bc

Please sign in to comment.