Skip to content

Commit

Permalink
Fix API docs writing
Browse files Browse the repository at this point in the history
  • Loading branch information
thekevinscott committed Sep 15, 2023
1 parent c8a3ce9 commit 1e60941
Show file tree
Hide file tree
Showing 20 changed files with 465 additions and 139 deletions.
2 changes: 1 addition & 1 deletion internals/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"inquirer": "^9.2.10",
"ts-node": "^10.9.1",
"tsc-alias": "^1.8.7",
"typedoc": "^0.25.1"
"typedoc": "^0.24.0"
},
"devDependencies": {
"@commander-js/extra-typings": "^11.0.0",
Expand Down
23 changes: 12 additions & 11 deletions internals/cli/src/commands/write/docs/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { mkdirp } from '@internals/common/fs';
import { writeAPIDocs } from '../../../lib/commands/write/docs/api/index.js';
import { clearOutMarkdownFiles } from '../../../lib/utils/clear-out-markdown-files.js';
import { startWatch } from '../../../lib/cli/start-watch.js';
import { info } from '@internals/common/logger';
import { info, verbose } from '@internals/common/logger';
import { exec, spawn } from 'child_process';

const EXAMPLES_DOCS_DEST = path.resolve(DOCS_DIR, 'docs/documentation/api');

Expand All @@ -15,8 +16,10 @@ interface Opts {
}

const writeAPIDocumentation = async ({ shouldClearMarkdown }: Pick<Opts, 'shouldClearMarkdown'>) => {
info('Writing API documentation');
await mkdirp(EXAMPLES_DOCS_DEST);
if (shouldClearMarkdown) {
verbose(`Clearing out markdown files in ${EXAMPLES_DOCS_DEST}`)
await clearOutMarkdownFiles(EXAMPLES_DOCS_DEST);
}

Expand All @@ -25,20 +28,18 @@ const writeAPIDocumentation = async ({ shouldClearMarkdown }: Pick<Opts, 'should

export default (program: Command) => program.command('api')
.description('Write API documentation')
.action(async ({ watch, ...opts }: Opts) => {
.action(async ({ watch, shouldClearMarkdown }: Opts) => {
if (watch) {
return startWatch(() => {
info('Writing API documentation');
return writeAPIDocumentation(opts);
}, [
path.join(CORE_DIR, `**/*`),
path.join(UPSCALER_DIR, '**/*'),
path.join(CLI_DIR, 'src/lib/write/docs/api/**/*'),
], {
return startWatch(
`pnpm cli write docs api ${shouldClearMarkdown ? '-c' : ''}`,
[
path.join(CORE_DIR, `**/*`),
path.join(UPSCALER_DIR, '**/*'),
], {
ignored: path.join(UPSCALER_DIR, '**/*.generated.ts'),
persistent: true,
});
} else {
writeAPIDocumentation(opts);
writeAPIDocumentation({ shouldClearMarkdown });
}
});
8 changes: 4 additions & 4 deletions internals/cli/src/lib/cli/build-commands-tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ vi.mock('fs-extra', () => {
}
});

vi.mock('foo/index.js', () => ({ default: vi.fn(), }));
vi.mock('foo/guide/index.js', () => ({ default: vi.fn(), }));
vi.mock('foo/guide/file1.ts', () => ({ default: vi.fn(), }));
vi.mock('foo/model/index.js', () => ({ default: vi.fn(), }));
vi.mock('foo/index.js', () => ({ postProcess: vi.fn(), default: vi.fn(), }));
vi.mock('foo/guide/index.js', () => ({ postProcess: vi.fn(), default: vi.fn(), }));
vi.mock('foo/guide/file1.ts', () => ({ postProcess: vi.fn(), default: vi.fn(), }));
vi.mock('foo/model/index.js', () => ({ postProcess: vi.fn(), default: vi.fn(), }));

describe('buildCommandsTree', () => {
it('returns a single node for an empty directory, where that node is the directory', async () => {
Expand Down
89 changes: 80 additions & 9 deletions internals/cli/src/lib/cli/start-watch.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,91 @@
import chokidar from 'chokidar';
import { error } from '@internals/common/logger';
import { error, verbose } from '@internals/common/logger';
import { ChildProcess, spawn } from 'child_process';
import path from 'path';
import { CLI_DIR, ROOT_DIR } from '@internals/common/constants';

const TIMEOUT_LENGTH = 100;
const TIMEOUT_LENGTH = 250;

export const startWatch = (fn: () => Promise<unknown>, ...args: Parameters<typeof chokidar.watch>) => {
const watcher = chokidar.watch(...args);
class SpawnError extends Error {
code: null | number;
constructor(code: null | number) {
super(`Exited with code ${code}`);
this.code = code;
}
}

export const spawnProcessThatInheritsStdioAndPreservesColor = (_cmd: string): [ChildProcess, Promise<void>] => {
let resolve: () => void;
let reject: (err: unknown) => void;
const promise = new Promise<void>((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
const cmd = _cmd.split(' ');
const spawnedProcess = spawn(cmd[0], cmd.slice(1), {
shell: true,
stdio: 'inherit',
});
spawnedProcess.on('close', (code) => {
if (code !== 0) {
reject(new SpawnError(code));
} else {
resolve();
}
});
process.on('exit', () => {
console.log('exiting, kill process', spawnedProcess.pid);
spawnedProcess.kill();
});
return [
spawnedProcess,
promise,
];
};

const d = (now: number) => ((performance.now() - now) / 1000).toFixed(2);

export const startWatch = (
cmd: string,
paths: Parameters<typeof chokidar.watch>[0],
options: Parameters<typeof chokidar.watch>[1],
) => {
const watcher = chokidar.watch([
...paths,
path.join(CLI_DIR, 'src/**/*'),
], options);
let timer: NodeJS.Timeout;
watcher.on('all', async () => {
let last: number;
let iterations = 0;
let spawnedProcess: ChildProcess | undefined;
watcher.on('all', async (event, file, stats) => {
console.clear();
if (spawnedProcess) {
verbose(`>> Killing previous spawned process ${spawnedProcess.pid}`);
spawnedProcess.kill();
}
clearTimeout(timer);
timer = setTimeout(async () => {
try {
await fn();
} catch (err) {
error(err);
verbose(`Running command "${cmd.trim()}" in watch mode, iteration ${iterations++} at ${new Date().toLocaleTimeString()}, ${last ? `last run was ${d(last)}s ago` : 'no last run'}`);
verbose(`>> Running because of event "${event}" on file "${file.split(ROOT_DIR).pop()}"`);
const now = performance.now();
const [child, promise] = spawnProcessThatInheritsStdioAndPreservesColor(cmd);
spawnedProcess = child;
await promise.catch(err => {
if (err instanceof SpawnError) {
// if (err.code !== null) {
// error(err.message);
// }
} else {
error(err.message);
}
});
verbose(`Ran command "${cmd.trim()}" in watch mode, took ${d(now)}s`);
spawnedProcess = undefined;
} finally {
last = performance.now();
}
}, TIMEOUT_LENGTH);
});
};

Original file line number Diff line number Diff line change
@@ -1 +1 @@
<small className="gray">Defined in <a target="_blank" href="<%- url %>"><%- prettyFileName ->:<%- line %></a></small>
<small className="gray">Defined in <a target="_blank" href="<%- url %>"><%- prettyFileName %>:<%- line %></a></small>
2 changes: 1 addition & 1 deletion internals/cli/src/lib/commands/write/docs/api/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const TYPES_TO_EXPAND: Record<string, string[]> = {
};
export const TEMPLATES_DIR = path.resolve(__dirname, '_templates');

const makeNewExternalType = (name: string, _url: string): DeclarationReflection => {
export const makeNewExternalType = (name: string, _url: string): DeclarationReflection => {
const type = new DeclarationReflection(name, ReflectionKind['SomeType']);
type.sources = [];
return type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { CORE_DIR, UPSCALER_DIR } from "@internals/common/constants";
import { CORE_TSCONFIG_PATH, UPSCALER_TSCONFIG_PATH } from "../constants.js";
import { DeclarationReflection } from "typedoc";
import { getDeclarationReflectionsFromPackages } from "./get-declaration-reflections-from-packages.js";
import { PlatformSpecificFileDeclarationReflection, getTypesFromPlatformSpecificUpscalerFiles } from "./get-types-from-platform-specific-upscaler-files.js";
import { getTypesFromPlatformSpecificUpscalerFiles } from "./get-types-from-platform-specific-upscaler-files.js";
import { PlatformSpecificFileDeclarationReflection } from "../types.js";

const DECLARATION_REFLECTION_FILE_DEFINITIONS = [{
tsconfigPath: UPSCALER_TSCONFIG_PATH,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { CORE_DIR, UPSCALER_DIR } from "@internals/common/constants";
import { CORE_SRC_PATH, CORE_TSCONFIG_PATH, UPSCALER_SRC_PATH, UPSCALER_TSCONFIG_PATH } from "../constants.js";
import { getPackageAsTree } from "./get-package-as-tree.js";
import { DeclarationReflection } from "typedoc";
import path from "path";
Expand All @@ -9,24 +7,16 @@ export interface ProjectDefinition {
projectRoot: string;
}

// {
// tsconfigPath: UPSCALER_TSCONFIG_PATH,
// projectRoot: UPSCALER_DIR,
// },
// {
// tsconfigPath: CORE_TSCONFIG_PATH,
// projectRoot: CORE_DIR,
// },
export const getDeclarationReflectionsFromPackages = (projectDefinitions: ProjectDefinition[]): DeclarationReflection[] => [
...projectDefinitions,
].reduce<DeclarationReflection[]>((arr, { tsconfigPath, projectRoot }) => {
const tree = getPackageAsTree(
const { children } = getPackageAsTree(
path.join(projectRoot, 'src'),
tsconfigPath,
projectRoot,
);
if (!tree.children?.length) {
if (children === undefined || children.length === 0) {
throw new Error(`No children were found for ${projectRoot}. Indicates an error in the returned structure from getPackageAsTree`);
}
return arr.concat(tree.children);
return arr.concat(children);
}, []);
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ReflectionKind } from "typedoc";
import { DecRef, Definitions } from "../types.js";
import { Definitions, isPlatformSpecificFileDeclarationReflection } from "../types.js";
import { getAllDeclarationReflections } from "./get-all-declaration-reflections.js";
import { isPlatformSpecificFileDeclarationReflection } from "./get-types-from-platform-specific-upscaler-files.js";
import { } from "./get-types-from-platform-specific-upscaler-files.js";

const KindStringKey: Partial<Record<ReflectionKind, keyof Definitions>> = {
export const KindStringKey: Partial<Record<ReflectionKind, keyof Definitions>> = {
[ReflectionKind.Constructor]: 'constructors',
[ReflectionKind.Method]: 'methods',
[ReflectionKind.Interface]: 'interfaces',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ describe('makeDeclarationReflection', () => {
vi.resetAllMocks();
});
it('gets a declaration reflection', () => {
const decRef = makeDeclarationReflection('foo', ReflectionKind.Function, 'bar' as unknown as SomeType);
const decRef = makeDeclarationReflection('foo', {
type: 'functions',
} as unknown as SomeType);
expect(decRef.name).toEqual('foo');
expect(decRef.kind).toEqual(ReflectionKind.Function);
expect(decRef.type).toEqual('bar');
expect(decRef.type).toEqual({
type: 'functions',
});
})
});

Expand Down Expand Up @@ -70,6 +74,9 @@ describe('getTypesFromPlatformSpecificUpscalerFile', () => {
const typeName = 'typeName';
const child = {
name: typeName,
type: {
type: 'functions',
},
};
vi.mocked(getPackageAsTree).mockImplementation(() => {
return {
Expand All @@ -84,26 +91,6 @@ describe('getTypesFromPlatformSpecificUpscalerFile', () => {
expect(result.browser).toEqual(child);
expect(result.node).toEqual(child);
});

it('throws if there is a mismatch between platforms', () => {
const typeName = 'typeName';
vi.mocked(getPackageAsTree).mockImplementation((srcPath: string) => {
const child = srcPath.includes('browser') ? {
name: typeName,
type: 'browser',
} : {
name: typeName,
type: 'node',
};
return {
children: [child],
} as unknown as ProjectReflection;
});
expect(() => getTypesFromPlatformSpecificUpscalerFile({
fileName: 'fileName',
typeName,
})).toThrow();
});
});

describe('getTypesFromPlatformSpecificFiles', () => {
Expand All @@ -114,7 +101,9 @@ describe('getTypesFromPlatformSpecificFiles', () => {
const typeName = 'typeName';
const child = {
name: typeName,
type: 'sometype',
type: {
type: 'functions',
},
};
vi.mocked(getPackageAsTree).mockImplementation(() => {
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { TFJSLibrary } from "@internals/common/tfjs-library";
import { DeclarationReflection, ReflectionKind, SomeType } from "typedoc";
import { scaffoldUpscaler } from "../../../../../../commands/scaffold/upscaler.js";
import { getPackageAsTree } from "./get-package-as-tree.js";
import { UPSCALER_DIR } from "@internals/common/constants";
import path from "path";

export interface PlatformSpecificFileDeclarationReflection {
declarationReflection: DeclarationReflection;
browser: DeclarationReflection;
node: DeclarationReflection;
}
import { Definitions, PlatformSpecificFileDeclarationReflection } from "../types.js";

export interface PlatformSpecificFileDefinition {
fileName: string;
Expand All @@ -18,11 +12,32 @@ export interface PlatformSpecificFileDefinition {

const tfjsLibraries: TFJSLibrary[] = ['browser', 'node'];

export const isPlatformSpecificFileDeclarationReflection = (
child: DeclarationReflection | PlatformSpecificFileDeclarationReflection
): child is PlatformSpecificFileDeclarationReflection => 'browser' in child;
const reverseKindStringKey: Record<string, ReflectionKind> = {
constructors: ReflectionKind.Constructor,
methods: ReflectionKind.Method,
interfaces: ReflectionKind.Interface,
types: ReflectionKind.TypeAlias,
classes: ReflectionKind.Class,
functions: ReflectionKind.Function,
enums: ReflectionKind.Enum,
};

export const makeDeclarationReflection = (typeName: string, kind: ReflectionKind, type?: SomeType) => {
export const makeDeclarationReflection = (typeName: string, type: SomeType): DeclarationReflection => {
if (type.type === 'union') {
// const childType = type.types?.[0];
// console.log(typeName, type.types);
// if (!childType) {
// throw new Error('No child type for union');
// }
// return makeDeclarationReflection(typeName, childType);
const declarationReflection = new DeclarationReflection(typeName, ReflectionKind.Interface);
declarationReflection.type = type;
return declarationReflection;
}
const kind = reverseKindStringKey[type.type];
if (kind === undefined) {
throw new Error(`Kind is undefined for type ${type.type}`);
}
const declarationReflection = new DeclarationReflection(typeName, kind);
declarationReflection.type = type;
return declarationReflection;
Expand All @@ -49,12 +64,26 @@ export const getPlatformSpecificUpscalerDeclarationReflections = (

export const getTypesFromPlatformSpecificUpscalerFile = ({ fileName, typeName }: PlatformSpecificFileDefinition) => {
const [browser, node] = tfjsLibraries.map(tfjsLibrary => getPlatformSpecificUpscalerDeclarationReflections(tfjsLibrary, { fileName, typeName }));
if (browser.type !== node.type) {
throw new Error('Some mismatch between browser and node types');
if (browser.type?.type !== node.type?.type) {
throw new Error([
'Some mismatch for file name',
fileName,
'and type name',
typeName,
'between browser type:',
`\n\n${JSON.stringify(browser.type)}\n\n`,
'and node type:',
`\n\n${JSON.stringify(node.type)}`,
].join(' '));
}

const type = browser.type;
if (!type) {
throw new Error('No type defined on browser type');
}

return {
declarationReflection: makeDeclarationReflection(typeName, ReflectionKind.Function, browser.type),
declarationReflection: makeDeclarationReflection(typeName, type),
browser,
node,
};
Expand Down
Loading

0 comments on commit 1e60941

Please sign in to comment.