Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

infra(docs): add docs diff script #1755

Merged
merged 26 commits into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ef2996e
docs: generate module hashes for api docs diff
ST-DDT Jan 18, 2023
7cec095
chore: apply suggestions
ST-DDT Jan 20, 2023
160bc81
Merge branch 'next' into docs/diff-notification
ST-DDT Jan 20, 2023
9a16588
chore: apply suggestions + add shortcut command
ST-DDT Jan 20, 2023
ea0685e
chore: improve console output
ST-DDT Jan 20, 2023
55294a6
chore: use correct test url
ST-DDT Jan 20, 2023
2b87847
Merge branch 'next' into docs/diff-notification
ST-DDT Jan 20, 2023
cca71c8
chore: merge next
ST-DDT Jan 23, 2023
b60bc57
chore: cleanup
ST-DDT Jan 23, 2023
57ff4e6
chore: cleanup
ST-DDT Jan 23, 2023
89bdd53
chore: reduce diff
ST-DDT Jan 23, 2023
9d66dca
Merge branch 'next' into docs/diff-notification
ST-DDT Jan 23, 2023
18489f6
Merge branch 'next' into docs/diff-notification
ST-DDT Jan 24, 2023
9755841
Merge branch 'next' into docs/diff-notification
ST-DDT Jan 24, 2023
258528a
chore: remove debug code
ST-DDT Jan 24, 2023
003a443
chore: cleanup
ST-DDT Jan 24, 2023
b777cae
Merge branch 'next' into docs/diff-notification
ST-DDT Jan 26, 2023
cdd7850
chore: apply review suggestions
ST-DDT Jan 29, 2023
19e670b
chore: merge next
ST-DDT Jan 29, 2023
cacd5e7
chore: apply suggestions
ST-DDT Jan 30, 2023
af9600b
Merge branch 'next' into docs/diff-notification
ST-DDT Jan 30, 2023
a6bd43b
Merge branch 'next' into docs/diff-notification
ST-DDT Jan 30, 2023
55f77b8
Merge branch 'next' into docs/diff-notification
ST-DDT Feb 1, 2023
f52a6dd
Merge branch 'next' into docs/diff-notification
ST-DDT Feb 1, 2023
95e676e
chore: fix lint errors
ST-DDT Feb 1, 2023
3d3ca57
Merge branch 'docs/diff-notification' of github.com:faker-js/faker in…
ST-DDT Feb 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ versions.json
/docs/.vitepress/cache
/docs/.vitepress/dist
/docs/api/typedoc.json
/docs/public/api-diff-index.json
/lib
/locale

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"docs:dev": "run-s docs:prepare docs:dev:run",
"docs:dev:run": "vitepress dev docs",
"docs:serve": "vitepress serve docs --port 5173",
"docs:diff": "tsx ./scripts/diff.ts",
"format": "prettier --write .",
"lint": "eslint --cache --cache-strategy content .",
"ts-check:scripts": "tsc --project tsconfig.check-scripts.json",
Expand Down
8 changes: 6 additions & 2 deletions scripts/apidoc/apiDocsWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
selectApiMethods,
selectApiModules,
} from './typedoc';
import type { PageIndex } from './utils';
import { pathDocsDir, pathOutputDir } from './utils';
import type { DocsApiDiffIndex, PageIndex } from './utils';
import { pathDocsDiffIndexFile, pathDocsDir, pathOutputDir } from './utils';

const pathDocsApiPages = resolve(pathDocsDir, '.vitepress', 'api-pages.ts');
const pathDocsApiSearchIndex = resolve(
Expand Down Expand Up @@ -120,6 +120,10 @@ export function writeApiPagesIndex(pages: PageIndex): void {
writeFileSync(pathDocsApiPages, apiPagesContent);
}

export function writeApiDiffIndex(diffIndex: DocsApiDiffIndex): void {
writeFileSync(pathDocsDiffIndexFile, JSON.stringify(diffIndex));
}

export function writeApiSearchIndex(project: ProjectReflection): void {
const apiIndex: APIGroup[] = [];

Expand Down
96 changes: 96 additions & 0 deletions scripts/apidoc/diff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { DocsApiDiffIndex } from './utils';
import { nameDocsDiffIndexFile, pathDocsDiffIndexFile } from './utils';

/**
* Loads the diff index from the given source url.
*
* @param url The url to load the diff index from.
*/
async function loadRemote(url: string): Promise<DocsApiDiffIndex> {
return fetch(url).then((res) => {
if (!res.ok) {
throw new Error(
`Failed to load remote diff index from ${url}: ${res.statusText}`
);
} else {
return res.json() as Promise<DocsApiDiffIndex>;
}
});
}

/**
* Loads the diff index from the given local path.
*
* @param path The path to load the diff index from. Should start with `file://` for cross platform compatibility.
*/
async function loadLocal(path: string): Promise<DocsApiDiffIndex> {
return import(path).then((imp) => imp.default as DocsApiDiffIndex);
}

/**
* Loads the diff index from the given source.
* If the source starts with `https://` it will be loaded from the remote url.
* Otherwise it will be loaded from the local path.
*
* @param source The source to load the diff index from.
*/
async function load(source: string): Promise<DocsApiDiffIndex> {
if (source.startsWith('https://')) {
return loadRemote(source);
} else {
return loadLocal(source);
}
}

/**
* Returns a set of all keys from the given entries.
*
* @param entries The entries to get the keys from.
*/
function allKeys(...entries: Record<string, unknown>[]): Set<string> {
return new Set(entries.map(Object.keys).flat());
}

/**
* Compares the target (reference) and source (changed) diff index and returns the differences.
* The returned object contains the module names as keys and the method names as values.
* If the module name is `ADDED` or `REMOVED` it means that the module was added or removed in the local diff index.
*
* @param targetDiffIndex The url to the target (reference) diff index. Defaults to the next.fakerjs.dev diff index.
* @param sourceDiffIndex The path to the source (changed) index. Defaults to the local diff index.
*/
export async function diff(
targetDiffIndex = `https://next.fakerjs.dev/${nameDocsDiffIndexFile}`,
sourceDiffIndex = `file://${pathDocsDiffIndexFile}`
): Promise<Record<string, ['ADDED'] | ['REMOVED'] | string[]>> {
const target = await load(targetDiffIndex);
const source = await load(sourceDiffIndex);

const diff: Record<string, string[]> = {};

for (const moduleName of allKeys(target, source)) {
const remoteModule = target[moduleName];
const localModule = source[moduleName];

if (!remoteModule) {
diff[moduleName] = ['ADDED'];
continue;
}

if (!localModule) {
diff[moduleName] = ['REMOVED'];
continue;
}

for (const methodName of allKeys(remoteModule, localModule)) {
const remoteMethod = remoteModule[methodName];
const localMethod = localModule[methodName];

if (remoteMethod !== localMethod) {
(diff[moduleName] ??= []).push(methodName);
}
}
}

return diff;
}
11 changes: 9 additions & 2 deletions scripts/apidoc/generate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { resolve } from 'path';
import { writeApiPagesIndex, writeApiSearchIndex } from './apiDocsWriter';
import {
writeApiDiffIndex,
writeApiPagesIndex,
writeApiSearchIndex,
} from './apiDocsWriter';
import { processModuleMethods } from './moduleMethods';
import { loadProject } from './typedoc';
import { pathOutputDir } from './utils';
Expand All @@ -16,7 +20,10 @@ export async function generate(): Promise<void> {
await app.generateJson(project, pathOutputJson);

const modules = processModuleMethods(project);
writeApiPagesIndex(modules);
writeApiPagesIndex(modules.map(({ text, link }) => ({ text, link })));
writeApiDiffIndex(
modules.reduce((data, { text, diff }) => ({ ...data, [text]: diff }), {})
);

writeApiSearchIndex(project);
}
33 changes: 22 additions & 11 deletions scripts/apidoc/moduleMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ import {
selectApiMethodSignatures,
selectApiModules,
} from './typedoc';
import type { PageIndex } from './utils';
import type { PageAndDiffIndex } from './utils';
import { diffHash } from './utils';

/**
* Analyzes and writes the documentation for modules and their methods such as `faker.animal.cat()`.
*
* @param project The project used to extract the modules.
* @returns The generated pages.
*/
export function processModuleMethods(project: ProjectReflection): PageIndex {
const pages: PageIndex = [];
export function processModuleMethods(
project: ProjectReflection
): PageAndDiffIndex {
const pages: PageAndDiffIndex = [];

// Generate module files
for (const module of selectApiModules(project)) {
Expand All @@ -33,10 +36,11 @@ export function processModuleMethods(project: ProjectReflection): PageIndex {
* @param module The module to process.
* @returns The generated pages.
*/
function processModuleMethod(module: DeclarationReflection): PageIndex {
function processModuleMethod(module: DeclarationReflection): PageAndDiffIndex {
const moduleName = extractModuleName(module);
const moduleFieldName = extractModuleFieldName(module);
console.log(`Processing Module ${moduleName}`);
const comment = toBlock(module.comment);

const methods: Method[] = [];

Expand All @@ -45,22 +49,29 @@ function processModuleMethod(module: DeclarationReflection): PageIndex {
selectApiMethodSignatures(module)
)) {
console.debug(`- ${methodName}`);

methods.push(analyzeSignature(signature, moduleFieldName, methodName));
}

writeApiDocsModulePage(
moduleName,
moduleFieldName,
toBlock(module.comment),
methods
);
writeApiDocsModulePage(moduleName, moduleFieldName, comment, methods);
writeApiDocsData(moduleFieldName, methods);

return [
{
text: moduleName,
link: `/api/${moduleFieldName}.html`,
diff: methods.reduce(
(data, method) => ({
...data,
[method.name]: diffHash(method),
}),
{
moduleHash: diffHash({
name: moduleName,
field: moduleFieldName,
comment,
}),
}
),
},
];
}
39 changes: 39 additions & 0 deletions scripts/apidoc/utils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
import { createHash } from 'node:crypto';
import { resolve } from 'node:path';

// Types

export type Page = { text: string; link: string };
export type PageIndex = Array<Page>;

export type PageAndDiff = Page & {
diff: DocsApiDiff;
};
export type PageAndDiffIndex = Array<PageAndDiff>;

export interface DocsApiDiffIndex {
/**
* The methods in the module by name.
*/
[module: string]: DocsApiDiff;
}

export interface DocsApiDiff {
/**
* The checksum of the entire module.
*/
moduleHash: string;
/**
* The checksum of the method by name.
*/
[method: string]: string;
}

// Paths

const pathRoot = resolve(__dirname, '..', '..');
export const pathDocsDir = resolve(pathRoot, 'docs');
const pathPublicDir = resolve(pathDocsDir, 'public');
export const nameDocsDiffIndexFile = 'api-diff-index.json';
export const pathDocsDiffIndexFile = resolve(
pathPublicDir,
nameDocsDiffIndexFile
);
export const pathOutputDir = resolve(pathDocsDir, 'api');

// Functions
Expand All @@ -22,3 +52,12 @@ export function mapByName<T extends { name: string }, V>(
{}
);
}

/**
* Creates a diff hash for the given object.
*
* @param object The object to create a hash for.
*/
export function diffHash(object: unknown): string {
return createHash('md5').update(JSON.stringify(object)).digest('hex');
}
29 changes: 29 additions & 0 deletions scripts/diff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { existsSync } from 'fs';
import { diff } from './apidoc/diff';
import { pathDocsDiffIndexFile } from './apidoc/utils';

const [target, source] = process.argv.slice(2);

if (!source && !existsSync(pathDocsDiffIndexFile)) {
throw new Error(
`Unable to find local diff index file at: ${pathDocsDiffIndexFile}\n
You can run \`pnpm run generate:api-docs\` to generate it.`
);
}

diff(target, source)
.then((delta) => {
if (Object.keys(delta).length === 0) {
console.log('No documentation changes detected');
return;
}

console.log('Documentation changes detected:');
for (const [module, methods] of Object.entries(delta)) {
console.log(`- ${module}`);
for (const method of methods) {
console.log(` - ${method}`);
}
}
})
.catch(console.error);