2023-09-22 14:24:35 -04:00

198 lines
5.5 KiB
TypeScript

import {
DocumentMetadata,
ProcessedDocument,
RelatedDocument,
} from '@nx/nx-dev/models-document';
import { readFileSync } from 'fs';
import { join } from 'path';
import { TagsApi } from './tags.api';
interface StaticDocumentPaths {
params: { segments: string[] };
}
export class DocumentsApi {
private readonly manifest: Record<string, DocumentMetadata>;
constructor(
private readonly options: {
id: string;
manifest: Record<string, DocumentMetadata>;
prefix: string;
publicDocsRoot: string;
tagsApi: TagsApi;
}
) {
if (!options.id) {
throw new Error('id cannot be undefined');
}
if (!options.prefix) {
options.prefix = '';
}
if (!options.publicDocsRoot) {
throw new Error('public docs root cannot be undefined');
}
if (!options.manifest) {
throw new Error('public document sources cannot be undefined');
}
this.manifest = structuredClone(this.options.manifest);
}
private getManifestKey(path: string): string {
return '/' + path;
}
getFilePath(path: string): string {
return join(this.options.publicDocsRoot, `${path}.md`);
}
getParamsStaticDocumentPaths(): StaticDocumentPaths[] {
return Object.keys(this.manifest).map((path) => ({
params: {
segments: !!this.options.prefix
? [this.options.prefix].concat(path.split('/').filter(Boolean).flat())
: path.split('/').filter(Boolean).flat(),
},
}));
}
getSlugsStaticDocumentPaths(): string[] {
if (this.options.prefix)
return Object.keys(this.manifest).map(
(path) => `/${this.options.prefix}` + path
);
return Object.keys(this.manifest);
}
getDocument(path: string[]): ProcessedDocument {
const document: DocumentMetadata | null =
this.manifest[this.getManifestKey(path.join('/'))] || null;
if (!document) {
if (
path[0] === 'nx-api' &&
path[1] === 'devkit' &&
path[2] === 'documents'
) {
const file = `generated/devkit/${path.slice(3).join('/')}`;
return {
content: readFileSync(this.getFilePath(file), 'utf8'),
description: '',
filePath: this.getFilePath(file),
id: path.at(-1) || '',
name: path.at(-1) || '',
relatedDocuments: {},
tags: [],
};
}
throw new Error(
`Document not found in manifest with: "${path.join('/')}"`
);
}
if (this.isDocumentIndex(document)) return this.getDocumentIndex(path);
return {
content: readFileSync(this.getFilePath(document.file), 'utf8'),
description: document.description,
filePath: this.getFilePath(document.file),
id: document.id,
name: document.name,
relatedDocuments: this.getRelatedDocuments(document.tags),
tags: document.tags,
};
}
getRelatedDocuments(tags: string[]): Record<string, RelatedDocument[]> {
const relatedDocuments = {};
tags.forEach(
(tag) =>
(relatedDocuments[tag] = this.options.tagsApi.getAssociatedItems(tag))
);
return relatedDocuments;
}
isDocumentIndex(document: DocumentMetadata): boolean {
return !!document.itemList.length;
}
generateDocumentIndexTemplate(document: DocumentMetadata): string {
const cardsTemplate = document.itemList
.map((i) => ({
title: i.name,
description: i.description ?? '',
url: i.path,
}))
.map(
(card) =>
`{% card title="${card.title}" description="${
card.description
}" url="${[this.options.prefix, card.url]
.filter(Boolean)
.join('/')}" /%}\n`
)
.join('');
return [
`# ${document.name}\n\n ${document.description ?? ''}\n\n`,
'{% cards %}\n',
cardsTemplate,
'{% /cards %}\n\n',
].join('');
}
getDocumentIndex(path: string[]): ProcessedDocument {
const document: DocumentMetadata | null =
this.manifest[this.getManifestKey(path.join('/'))] || null;
if (!document)
throw new Error(
`Document not found in manifest with: "${path.join('/')}"`
);
if (!!document.file)
return {
content: readFileSync(this.getFilePath(document.file), 'utf8'),
description: document.description,
filePath: this.getFilePath(document.file),
id: document.id,
name: document.name,
relatedDocuments: this.getRelatedDocuments(document.tags),
tags: document.tags,
};
return {
content: this.generateDocumentIndexTemplate(document),
description: document.description,
filePath: '',
id: document.id,
name: document.name,
relatedDocuments: this.getRelatedDocuments(document.tags),
tags: document.tags,
};
}
generateRootDocumentIndex(options: {
name: string;
description: string;
}): ProcessedDocument {
const document = {
id: 'root',
name: options.name,
description: options.description,
file: '',
path: '',
isExternal: false,
itemList: Object.keys(this.manifest)
.filter((k) => k.split('/').length < 4) // Getting only top categories
.map((k) => this.manifest[k]),
tags: [],
};
return {
content: this.generateDocumentIndexTemplate(document),
description: document.description,
filePath: '',
id: document.id,
name: document.name,
relatedDocuments: this.getRelatedDocuments(document.tags),
tags: document.tags,
};
}
}