Skip to content

Commit

Permalink
(feat, local preview): setup dynamic local preview (#3634)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsinghvi authored May 19, 2024
1 parent b77c168 commit 724f29b
Show file tree
Hide file tree
Showing 18 changed files with 272 additions and 12 deletions.
47 changes: 46 additions & 1 deletion .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
3 changes: 3 additions & 0 deletions packages/cli/cli/.env-cmdrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = {
LOCAL_STORAGE_FOLDER: ".fern",
POSTHOG_API_KEY: process.env.POSTHOG_API_KEY,
DOCS_DOMAIN_SUFFIX: "docs.buildwithfern.com",
DOCS_PREVIEW_BUCKET: 'https://prod-local-preview-bundle2.s3.amazonaws.com/'
},
dev: {
AUTH0_DOMAIN: "fern-dev.us.auth0.com",
Expand All @@ -20,6 +21,7 @@ module.exports = {
LOCAL_STORAGE_FOLDER: ".fern-dev",
POSTHOG_API_KEY: null,
DOCS_DOMAIN_SUFFIX: "dev.docs.buildwithfern.com",
DOCS_PREVIEW_BUCKET: 'https://dev2-local-preview-bundle2.s3.amazonaws.com/'
},
local: {
AUTH0_DOMAIN: "fern-dev.us.auth0.com",
Expand All @@ -31,5 +33,6 @@ module.exports = {
LOCAL_STORAGE_FOLDER: ".fern-local",
POSTHOG_API_KEY: null,
DOCS_DOMAIN_SUFFIX: "dev.docs.buildwithfern.com",
DOCS_PREVIEW_BUCKET: 'https://dev2-local-preview-bundle2.s3.amazonaws.com/'
},
};
3 changes: 2 additions & 1 deletion packages/cli/cli/.mrlint.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"VENUS_AUDIENCE",
"LOCAL_STORAGE_FOLDER",
"POSTHOG_API_KEY",
"DOCS_DOMAIN_SUFFIX"
"DOCS_DOMAIN_SUFFIX",
"DOCS_PREVIEW_BUCKET"
]
},
"plugins": {
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/cli/build.dev.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ async function main() {
"process.env.VENUS_AUDIENCE": getEnvironmentVariable("VENUS_AUDIENCE"),
"process.env.LOCAL_STORAGE_FOLDER": getEnvironmentVariable("LOCAL_STORAGE_FOLDER"),
"process.env.POSTHOG_API_KEY": getEnvironmentVariable("POSTHOG_API_KEY"),
"process.env.DOCS_DOMAIN_SUFFIX": getEnvironmentVariable("DOCS_DOMAIN_SUFFIX")
"process.env.DOCS_DOMAIN_SUFFIX": getEnvironmentVariable("DOCS_DOMAIN_SUFFIX"),
"process.env.DOCS_PREVIEW_BUCKET": getEnvironmentVariable("DOCS_PREVIEW_BUCKET")
}
};

Expand Down
3 changes: 2 additions & 1 deletion packages/cli/cli/build.local.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ async function main() {
"process.env.VENUS_AUDIENCE": getEnvironmentVariable("VENUS_AUDIENCE"),
"process.env.LOCAL_STORAGE_FOLDER": getEnvironmentVariable("LOCAL_STORAGE_FOLDER"),
"process.env.POSTHOG_API_KEY": getEnvironmentVariable("POSTHOG_API_KEY"),
"process.env.DOCS_DOMAIN_SUFFIX": getEnvironmentVariable("DOCS_DOMAIN_SUFFIX")
"process.env.DOCS_DOMAIN_SUFFIX": getEnvironmentVariable("DOCS_DOMAIN_SUFFIX"),
"process.env.DOCS_PREVIEW_BUCKET": getEnvironmentVariable("DOCS_PREVIEW_BUCKET")
}
};

Expand Down
3 changes: 2 additions & 1 deletion packages/cli/cli/build.prod.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ async function main() {
"process.env.VENUS_AUDIENCE": getEnvironmentVariable("VENUS_AUDIENCE"),
"process.env.LOCAL_STORAGE_FOLDER": getEnvironmentVariable("LOCAL_STORAGE_FOLDER"),
"process.env.POSTHOG_API_KEY": getEnvironmentVariable("POSTHOG_API_KEY"),
"process.env.DOCS_DOMAIN_SUFFIX": getEnvironmentVariable("DOCS_DOMAIN_SUFFIX")
"process.env.DOCS_DOMAIN_SUFFIX": getEnvironmentVariable("DOCS_DOMAIN_SUFFIX"),
"process.env.DOCS_PREVIEW_BUCKET": getEnvironmentVariable("DOCS_PREVIEW_BUCKET")
}
};

Expand Down
6 changes: 3 additions & 3 deletions packages/cli/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ async function tryRunCli(cliContext: CliContext) {
addLoginCommand(cli, cliContext);
addFormatCommand(cli, cliContext);
addWriteDefinitionCommand(cli, cliContext);
addPreviewCommand(cli, cliContext);
addDocsPreviewCommand(cli, cliContext);
addMockCommand(cli, cliContext);
addWriteOverridesCommand(cli, cliContext);
addTestCommand(cli, cliContext);
Expand Down Expand Up @@ -882,9 +882,9 @@ function addWriteDefinitionCommand(cli: Argv<GlobalCliOptions>, cliContext: CliC
);
}

function addPreviewCommand(cli: Argv<GlobalCliOptions>, cliContext: CliContext) {
function addDocsPreviewCommand(cli: Argv<GlobalCliOptions>, cliContext: CliContext) {
cli.command(
"preview",
"docs preview",
false, // hide from help message
(yargs) => yargs,
async () => {
Expand Down
8 changes: 7 additions & 1 deletion packages/cli/docs-preview/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,25 @@
"@fern-api/fdr-sdk": "0.94.3-b6c3996ce",
"@fern-api/fs-utils": "workspace:*",
"@fern-api/ir-generator": "workspace:*",
"@fern-api/logger": "workspace:*",
"@fern-api/register": "workspace:*",
"@fern-api/task-context": "workspace:*",
"@fern-api/workspace-loader": "workspace:*",
"@types/decompress": "^4.2.7",
"cors": "^2.8.5",
"decompress": "^4.2.1",
"express": "^4.19.2",
"uuid": "^9.0.1"
"tmp-promise": "^3.0.3",
"uuid": "^9.0.1",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@types/cors": "^2.8.13",
"@types/express": "^4.17.20",
"@types/jest": "^29.0.3",
"@types/node": "^18.7.18",
"@types/uuid": "^9.0.8",
"@types/xml2js": "^0.4.14",
"depcheck": "^1.4.6",
"eslint": "^8.56.0",
"jest": "^29.7.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CONSOLE_LOGGER } from "@fern-api/logger";
import { downloadBundle } from "../downloadLocalDocsBundle";

describe("preview", () => {
it("download frontend", async () => {
await downloadBundle({
bucketUrl: "https://dev2-local-preview-bundle2.s3.amazonaws.com/",
logger: CONSOLE_LOGGER,
preferCached: false
});

expect(true).toEqual(true);
}, 60_000);
});
132 changes: 132 additions & 0 deletions packages/cli/docs-preview/src/downloadLocalDocsBundle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils";
import { Logger } from "@fern-api/logger";
import decompress from "decompress";
import { createWriteStream } from "fs";
import { mkdir, readFile, rm, writeFile } from "fs/promises";
import { homedir } from "os";
import path from "path";
import { pipeline } from "stream/promises";
import tmp from "tmp-promise";
import xml2js from "xml2js";

const ETAG_FILENAME = "etag";
const PREVIEW_FOLDER_NAME = "preview";
const BUNDLE_FOLDER_NAME = "bundle";
const LOCAL_STORAGE_FOLDER = process.env.LOCAL_STORAGE_FOLDER ?? ".fern";

export function getLocalStorageFolder(): AbsoluteFilePath {
return join(AbsoluteFilePath.of(homedir()), RelativeFilePath.of(LOCAL_STORAGE_FOLDER));
}

export function getPathToPreviewFolder(): AbsoluteFilePath {
return join(
AbsoluteFilePath.of(homedir()),
RelativeFilePath.of(LOCAL_STORAGE_FOLDER),
RelativeFilePath.of(PREVIEW_FOLDER_NAME)
);
}

export function getPathToBundleFolder(): AbsoluteFilePath {
return join(
AbsoluteFilePath.of(homedir()),
RelativeFilePath.of(LOCAL_STORAGE_FOLDER),
RelativeFilePath.of(PREVIEW_FOLDER_NAME),
RelativeFilePath.of(BUNDLE_FOLDER_NAME)
);
}

export function getPathToEtagFile(): AbsoluteFilePath {
return join(
AbsoluteFilePath.of(homedir()),
RelativeFilePath.of(LOCAL_STORAGE_FOLDER),
RelativeFilePath.of(PREVIEW_FOLDER_NAME),
RelativeFilePath.of(ETAG_FILENAME)
);
}

export declare namespace DownloadLocalBundle {
type Result = SuccesResult | FailureResult;

interface SuccesResult {
type: "success";
}

interface FailureResult {
type: "failure";
}
}

export async function downloadBundle({
bucketUrl,
logger,
preferCached
}: {
bucketUrl: string;
logger: Logger;
preferCached: boolean;
}): Promise<DownloadLocalBundle.Result> {
logger.debug("Setting up docs preview bundle...");
const response = await fetch(bucketUrl);
const body = await response.text();
const parser = new xml2js.Parser();
const parsedResponse = await parser.parseStringPromise(body);
const eTag = parsedResponse?.ListBucketResult?.Contents?.[0]?.ETag?.[0];
const key = parsedResponse?.ListBucketResult?.Contents?.[0]?.Key?.[0];

const eTagFilepath = getPathToEtagFile();
if (preferCached) {
const currentETagExists = await doesPathExist(eTagFilepath);
let currentETag = undefined;
if (currentETagExists) {
logger.debug("Reading existing ETag");
currentETag = (await readFile(eTagFilepath)).toString();
}
if (currentETag != null && currentETag === eTag) {
logger.debug("ETag matches. Using already downloaded bundle");
// The bundle is already downloaded
return {
type: "success"
};
} else {
logger.debug("ETag is different. Downloading latest preview bundle");
}
}

// create tmp directory
const dir = await tmp.dir({ prefix: "fern" });
const absoluteDirectoryToTmpDir = AbsoluteFilePath.of(dir.path);
logger.debug(`Created tmp directory ${absoluteDirectoryToTmpDir}`);

// download docs bundle
const docsBundleZipResponse = await fetch(`${path.join(bucketUrl, key)}`);
const outputZipPath = join(absoluteDirectoryToTmpDir, RelativeFilePath.of("output.zip"));

const contents = docsBundleZipResponse.body;
if (contents == null) {
return {
type: "failure"
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await pipeline(contents as any, createWriteStream(outputZipPath));
logger.debug(`Wrote output.zip to ${outputZipPath}`);

const absolutePathToPreviewFolder = getPathToPreviewFolder();
if (await doesPathExist(absolutePathToPreviewFolder)) {
await rm(absolutePathToPreviewFolder, { recursive: true });
}
await mkdir(absolutePathToPreviewFolder, { recursive: true });
logger.debug(`rm -rf ${absolutePathToPreviewFolder}`);

const absolutePathToBundleFolder = getPathToBundleFolder();
await mkdir(absolutePathToBundleFolder, { recursive: true });
await decompress(outputZipPath, absolutePathToBundleFolder);

// write etag
await writeFile(eTagFilepath, eTag);
logger.debug(`Downloaded bundle to ${absolutePathToBundleFolder}`);

return {
type: "success"
};
}
18 changes: 17 additions & 1 deletion packages/cli/docs-preview/src/runPreviewServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { TaskContext } from "@fern-api/task-context";
import { APIWorkspace, DocsWorkspace } from "@fern-api/workspace-loader";
import cors from "cors";
import express from "express";
import path from "path";
import { downloadBundle, getPathToBundleFolder } from "./downloadLocalDocsBundle";
import { getPreviewDocsDefinition } from "./previewDocs";

export async function runPreviewServer({
Expand All @@ -14,6 +16,14 @@ export async function runPreviewServer({
apiWorkspaces: APIWorkspace[];
context: TaskContext;
}): Promise<void> {
const url = process.env.DOCS_PREVIEW_BUCKET;
if (url == null) {
context.failAndThrow("Failed to connect to the docs preview server. Please contact [email protected]");
return;
}

await downloadBundle({ bucketUrl: url, logger: context.logger, preferCached: true });

const app = express();
app.use(cors());

Expand All @@ -36,7 +46,13 @@ export async function runPreviewServer({
});
app.listen(3000);

context.logger.info("Running server on https://localhost:3000");
app.use("/_next", express.static(path.join(getPathToBundleFolder(), "/_next")));

app.use("*", async (_req, res) => {
return res.sendFile(path.join(getPathToBundleFolder(), "/[[...slug]].html"));
});

context.logger.info("Running server on http://localhost:3000");

// await infiinitely
// eslint-disable-next-line @typescript-eslint/no-empty-function
Expand Down
1 change: 1 addition & 0 deletions packages/cli/docs-preview/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
{ "path": "../../commons/fs-utils" },
{ "path": "../configuration" },
{ "path": "../generation/ir-generator" },
{ "path": "../logger" },
{ "path": "../register" },
{ "path": "../task-context" },
{ "path": "../workspace-loader" }
Expand Down
Loading

0 comments on commit 724f29b

Please sign in to comment.