Skip to content

Commit

Permalink
(incident 2024-05-05): initialize healthchecks CLI to scan for failed…
Browse files Browse the repository at this point in the history
… docs pages
  • Loading branch information
dsinghvi committed May 5, 2024
1 parent 3f07e43 commit fb2c14c
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 40 deletions.
8 changes: 8 additions & 0 deletions fern/apis/vercel/definition/api.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: vercel
auth: bearer
default-environment: Prod
environments:
Prod: https://api.vercel.com
error-discrimination:
strategy: property
property-name: error
31 changes: 31 additions & 0 deletions fern/apis/vercel/definition/v9/domains.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
types:
ListDomainsResponse:
properties:
domains: list<Domain>

Domain:
properties:
name: string

service:
base-path: /v9/projects/{projectId}/domains
path-parameters:
projectId: string
auth: true
endpoints:
list:
docs: |
See Vercel's docs over here: https://vercel.com/docs/rest-api/endpoints/domains#list-all-the-domains
method: GET
path: /diff
request:
name: ListDomainsRequest
query-parameters:
limit:
type: string
docs: |
The limit of domains to return.
teamId:
type: string
docs: The team id to filter by
response: ListDomainsResponse
13 changes: 13 additions & 0 deletions fern/apis/vercel/generators.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
default-group: sdk
groups:
sdk:
generators:
- name: fernapi/fern-typescript-node-sdk
version: 0.12.8-rc0
output:
location: npm
url: npm.buildwithfern.com
package-name: "@fern-fern/vercel"
config:
skipResponseValidation: true
namespaceExport: Vercel
16 changes: 9 additions & 7 deletions packages/healthchecks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
"types": "lib/index.d.ts",
"sideEffects": false,
"bin": {
"fern-scripts": "./dist/bundle.cjs"
"fern-health": "./dist/cli.cjs"
},
"scripts": {
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"dist:cli": "tsup ./src/cli.ts --format cjs",
"test": "vitest --passWithNoTests --globals --disable-console-intercept",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../.eslintignore",
"lint:eslint:fix": "pnpm lint:eslint --fix",
Expand All @@ -31,21 +32,22 @@
"organize-imports": "organize-imports-cli tsconfig.json",
"depcheck": "depcheck"
},
"dependencies": {
"yargs": "^17.4.1",
"@fern-api/fdr-sdk": "workspace:*"
},
"devDependencies": {
"@fern-api/fdr-sdk": "workspace:*",
"@fern-fern/vercel": "0.0.1",
"@fern-ui/fdr-utils": "workspace:*",
"@types/jest": "^29.5.11",
"@types/node": "^18.7.18",
"@types/yargs": "^17.0.32",
"@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.15",
"depcheck": "^1.4.3",
"esbuild": "^0.15.7",
"eslint": "^8.56.0",
"vitest": "^1.5.0",
"organize-imports-cli": "^0.10.0",
"prettier": "^3.2.4",
"typescript": "4.9.5"
"tsup": "^8.0.2",
"typescript": "4.9.5",
"vitest": "^1.5.0",
"yargs": "^17.4.1"
}
}
24 changes: 19 additions & 5 deletions packages/healthchecks/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* eslint-disable no-console */
import { hideBin } from "yargs/helpers";
import yargs from "yargs/yargs";
import { getAllFernDocsWebsites } from "./getDocsURLs";
import { runRules } from "./rules/runRules";

void yargs(hideBin(process.argv))
Expand All @@ -12,17 +14,29 @@ void yargs(hideBin(process.argv))
argv
.option("url", {
string: true,
default: false,
requred: true,
requred: false,
})
.option("stack", {
string: true,
default: false,
requred: true,
}),
async () => {
// TODO(dsinghvi): actually run rules on specific docs URLs
await runRules({ url: "", stack: "dev" });
async (argv) => {
const urls = argv.url != null ? [argv.url] : await getAllFernDocsWebsites();
let failure = false;
for (const url of urls) {
console.log(`Running rules for ${url}...`);
const results = await runRules({ url });
for (const result of results) {
if (result.success) {
console.log(`:white_check_mark: Rule ${result.name} passed`);
} else {
failure = true;
console.log(`:redx: Rule ${result.name} failed. ${result.message}`);
}
}
}
process.exit(failure ? 1 : 0);
},
)
.demandCommand()
Expand Down
23 changes: 23 additions & 0 deletions packages/healthchecks/src/getDocsURLs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { VercelClient } from "@fern-fern/vercel";

const VERCEL = new VercelClient({
token: process.env.VERCEL_TOKEN ?? "",
});

const CUSTOM_SUBPATHS = [
"https://www.assemblyai.com/docs/api-reference/",
"https://primer.io/docs/api",
"https://astronomer.io/docs/api",
"https://buildwithfern.com/learn",
];

/**
* Returns all the live fern docs urls
*/
export async function getAllFernDocsWebsites(): Promise<string[]> {
const listDomainsResponse = await VERCEL.v9.domains.list("fern-prod", {
limit: "100",
teamId: "team_6FKOM5nw037hv8g2mTk3gaH7",
});
return [...CUSTOM_SUBPATHS, ...listDomainsResponse.domains.map((customDomain) => customDomain.name)];
}
1 change: 1 addition & 0 deletions packages/healthchecks/src/rules/all-pages-load/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AllPagesLoadRule } from "./rule";
52 changes: 52 additions & 0 deletions packages/healthchecks/src/rules/all-pages-load/rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { getAllUrlsFromDocsConfig } from "@fern-ui/fdr-utils";
import { Rule, RuleArgs, RuleResult } from "../runRules";

export class AllPagesLoadRule implements Rule {
name = "all-pages-load";
description = "Check that all URLs are reachable";

public async run({ fdr, url }: RuleArgs): Promise<RuleResult> {
const getDocsForUrlResponse = await fdr.docs.v2.read.getDocsForUrl({
url,
});
if (!getDocsForUrlResponse.ok) {
return {
name: this.name,
success: false,
message: `Failed to load docs for ${url} from FDR`,
};
}
const urls = getAllUrlsFromDocsConfig(
getDocsForUrlResponse.body.baseUrl.domain,
getDocsForUrlResponse.body.baseUrl.basePath,
getDocsForUrlResponse.body.definition.config.navigation,
getDocsForUrlResponse.body.definition.apis,
);

const responses = await Promise.all(
urls.map(async (url) => {
return {
res: await fetch(`https://${url}`, {
redirect: "manual",
}),
url,
};
}),
);
const failedURLs = responses.filter((promise) => promise.res.status >= 400).map((res) => res.url);

if (failedURLs.length > 0) {
return {
name: this.name,
success: false,
message: `The following URLs do not load: ${failedURLs.join(", ")}`,
};
}

return {
name: this.name,
success: true,
message: "All URLs are reachable",
};
}
}
13 changes: 0 additions & 13 deletions packages/healthchecks/src/rules/no-broken-urls/rule.ts

This file was deleted.

10 changes: 7 additions & 3 deletions packages/healthchecks/src/rules/runRules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FdrClient } from "@fern-api/fdr-sdk";
import { AllPagesLoadRule } from "./all-pages-load";

export interface RuleArgs {
url: string;
Expand All @@ -12,21 +13,24 @@ export interface Rule {
}

export interface RuleResult {
name: string;
success: boolean;
message: string;
}

function getAllRules(): Rule[] {
return [];
return [new AllPagesLoadRule()];
}

export async function runRules({ url, stack }: { url: string; stack: "dev" | "prod" }): Promise<RuleResult[]> {
const FDR_CLIENT = new FdrClient({ environment: "https://registry.buildwithfern.com" });

export async function runRules({ url }: { url: string }): Promise<RuleResult[]> {
const rules = getAllRules();
const results = await Promise.all(
rules.map((rule) =>
rule.run({
url,
fdr: stack === "dev" ? new FdrClient({ environment: "" }) : new FdrClient({ environment: "" }),
fdr: FDR_CLIENT,
}),
),
);
Expand Down
Loading

0 comments on commit fb2c14c

Please sign in to comment.