chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:36 +08:00
commit 8e2a6eb840
10194 changed files with 1593658 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
+11
View File
@@ -0,0 +1,11 @@
# documentation-api
This library provides the data necessary to display the documentation.
The [`data`](./src/data) folder contains the version mapping as well as a snapshot of the docs.
## Testing
```
nx test documentation-api
```
@@ -0,0 +1,3 @@
import { baseConfig } from '../../eslint.config.mjs';
export default [...baseConfig];
@@ -0,0 +1,24 @@
/* eslint-disable */
module.exports = {
displayName: 'nx-dev-data-access-documents',
globals: {},
transform: {
'^.+\\.[tj]sx?$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
},
],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/nx-dev/data-access-documents',
preset: '../../jest.preset.js',
moduleNameMapper: {
// Override for nx-dev packages - point to packages directory
'^@nx/devkit$': '<rootDir>/../../packages/devkit/index.ts',
'^@nx/devkit/testing$': '<rootDir>/../../packages/devkit/testing.ts',
'^@nx/devkit/internal-testing-utils$':
'<rootDir>/../../packages/devkit/internal-testing-utils.ts',
'^@nx/devkit/src/(.*)$': '<rootDir>/../../packages/devkit/src/$1',
},
};
@@ -0,0 +1 @@
export * from '../src/node.index';
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@nx/nx-dev-data-access-documents",
"version": "0.0.1",
"type": "commonjs",
"private": true,
"exports": {
".": {
"import": "./src/index.ts",
"default": "./src/index.ts"
},
"./node-only": {
"import": "./src/node.index.ts",
"default": "./src/node.index.ts"
},
"./package.json": "./package.json"
},
"dependencies": {
"@nx/devkit": "workspace:*",
"@nx/nx-dev-models-document": "workspace:*",
"@nx/nx-dev-models-package": "workspace:*",
"@nx/nx-dev-ui-markdoc": "workspace:*"
},
"devDependencies": {}
}
@@ -0,0 +1,8 @@
{
"name": "nx-dev-data-access-documents",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "nx-dev/data-access-documents/src",
"projectType": "library",
"targets": {},
"tags": ["scope:nx-dev", "type:data-access"]
}
@@ -0,0 +1,182 @@
import { readFileSync, accessSync, constants } from 'fs';
import { join, basename, parse, resolve } from 'path';
import { extractFrontmatter } from '@nx/nx-dev-ui-markdoc';
import { sortPosts } from './blog.util';
import { BlogPostDataEntry } from './blog.model';
import { readFile, readdir } from 'fs/promises';
export class BlogApi {
constructor(
private readonly options: {
id: string;
blogRoot: string;
}
) {
if (!options.id) {
throw new Error('id cannot be undefined');
}
if (!options.blogRoot) {
throw new Error('public blog root cannot be undefined');
}
}
async getBlogTags(): Promise<string[]> {
const blogs = await this.getBlogs();
const tags = new Set<string>();
blogs.forEach((blog) => {
blog.tags.forEach((tag) => tags.add(tag));
});
return Array.from(tags);
}
async getBlogs(
filterFn?: (post: BlogPostDataEntry) => boolean
): Promise<BlogPostDataEntry[]> {
return await this.getAllBlogs(filterFn);
}
async getAllBlogs(
filterFn?: (post: BlogPostDataEntry) => boolean
): Promise<BlogPostDataEntry[]> {
const files: string[] = await readdir(this.options.blogRoot);
const authors = JSON.parse(
readFileSync(join(this.options.blogRoot, 'authors.json'), 'utf8')
);
const allPosts: BlogPostDataEntry[] = [];
for (const file of files) {
const filePath = join(this.options.blogRoot, file);
if (!filePath.endsWith('.md')) continue;
const content = await readFile(filePath, 'utf8');
const frontmatter = extractFrontmatter(content);
const slug = this.calculateSlug(filePath, frontmatter);
const { image, type } = this.determineOgImage(frontmatter.cover_image);
const post = {
content,
title: frontmatter.title ?? null,
description: frontmatter.description ?? null,
authors: authors.filter((author) =>
frontmatter.authors?.includes(author.name)
),
eventDate: this.dateFromFileName(file),
date: this.calculateDate(file, frontmatter),
time: frontmatter.time,
status: frontmatter.status,
cover_image: frontmatter.cover_image
? `/documentation${frontmatter.cover_image}` // Match the prefix used by markdown parser
: null,
tags: frontmatter.tags ?? [],
reposts: frontmatter.reposts ?? [],
// Do not default to 'false' so you can 'unpin' a blog post
pinned: frontmatter.pinned ?? null,
ogImage: image,
ogImageType: type,
filePath,
slug,
youtubeUrl: frontmatter.youtubeUrl,
registrationUrl: frontmatter.registrationUrl,
podcastYoutubeId: frontmatter.podcastYoutubeId,
podcastSpotifyId: frontmatter.podcastSpotifyId,
podcastIHeartUrl: frontmatter.podcastIHeartUrl,
podcastAppleUrl: frontmatter.podcastAppleUrl,
podcastAmazonUrl: frontmatter.podcastAmazonUrl,
published: frontmatter.published ?? true,
metrics: frontmatter.metrics,
hideCoverImage: frontmatter.hideCoverImage ?? false,
};
const isDevelopment = process.env.NODE_ENV === 'development';
const shouldIncludePost = !frontmatter.draft || isDevelopment;
if (shouldIncludePost && (!filterFn || filterFn(post))) {
allPosts.push(post);
}
}
return sortPosts(allPosts);
}
// Optimize this so we don't read the FS multiple times
async getBlogPostBySlug(
slug: string | null
): Promise<BlogPostDataEntry | undefined> {
if (!slug) throw new Error(`Could not find blog post with slug: ${slug}`);
return (await this.getBlogs()).find((post) => post.slug === slug);
}
private calculateSlug(filePath: string, frontmatter: any): string {
const baseName = basename(filePath, '.md');
return frontmatter.slug || baseName;
}
private dateFromFileName(filename: string): string {
const timeString = new Date().toISOString().split('T')[1];
const regexp = /^(\d\d\d\d-\d\d-\d\d).+$/;
const match = filename.match(regexp);
if (match) {
return new Date(match[1] + ' ' + timeString).toISOString();
} else {
throw new Error(`Could not parse date from filename: ${filename}`);
}
}
private calculateDate(filename: string, frontmatter: any): string {
const date: Date = new Date();
const timeString = date.toISOString().split('T')[1];
if (frontmatter.date) {
return new Date(
frontmatter.date.toISOString().split('T')[0] + 'T' + timeString
).toISOString();
} else {
return this.dateFromFileName(filename);
}
}
private fileExists(filePath: string): boolean {
try {
accessSync(filePath, constants.F_OK);
return true;
} catch (error) {
return false;
}
}
private determineOgImage(imagePath: string): {
image: string;
type: string;
} {
const allowedExtensions = ['.png', '.webp', '.jpg', '.jpeg'];
const defaultImage = 'https://nx.dev/socials/nx-media.png';
const defaultType = 'png';
if (!imagePath) {
return { image: defaultImage, type: defaultType };
}
const { ext } = parse(imagePath);
if (!allowedExtensions.includes(ext)) {
const foundExt = allowedExtensions.find((allowedExt) => {
const ogImagePath = imagePath.replace(ext, allowedExt);
return this.fileExists(
join(
'public',
'documentation',
resolve(this.options.blogRoot, ogImagePath)
)
);
});
if (!foundExt) {
return { image: defaultImage, type: defaultType };
}
return {
image: join('documentation', imagePath.replace(ext, foundExt)),
type: foundExt.replace('.', ''),
};
}
return {
image: join('documentation', imagePath),
type: ext.replace('.', ''),
};
}
}
@@ -0,0 +1,32 @@
export type BlogPostDataEntry = {
title: string;
content: string;
description: string;
authors: BlogAuthor[];
date: string;
cover_image: string | null;
tags: string[];
reposts: string[];
updated?: string;
pinned?: boolean;
filePath: string;
slug: string;
youtubeUrl?: string;
podcastYoutubeId?: string;
podcastSpotifyId?: string;
podcastAmazonUrl?: string;
podcastAppleUrl?: string;
podcastIHeartUrl?: string;
published?: boolean;
ogImage?: string;
ogImageType?: string;
metrics?: Array<{ value: string; label: string }>;
hideCoverImage?: boolean;
};
export type BlogAuthor = {
name: string;
image: string;
twitter: string;
github: string;
};
@@ -0,0 +1,72 @@
import { sortPosts } from './blog.util';
describe('sortPosts', () => {
test('sort from latest to earliest', () => {
const posts = [
createPost({ date: '2022-01-01', title: 'Post 1', slug: 'post-1' }),
createPost({ date: '2023-01-01', title: 'Post 2', slug: 'post-2' }),
];
const results = sortPosts(posts);
expect(results.map((p) => p.slug)).toEqual(['post-2', 'post-1']);
});
test('latest pinned posts are presented first', () => {
const posts = [
createPost({
date: '2023-01-01',
title: 'Post 1',
slug: 'post-1',
pinned: true,
}),
createPost({ date: '2023-02-01', title: 'Post 2', slug: 'post-2' }),
createPost({
date: '2023-03-01',
title: 'Post 3',
slug: 'post-3',
pinned: true,
}),
createPost({
date: '2023-04-01',
title: 'Post 4',
slug: 'post-4',
pinned: true,
}),
createPost({ date: '2023-05-01', title: 'Post 5', slug: 'post-5' }),
];
const results = sortPosts(posts);
expect(results.map((p) => p.slug)).toEqual([
'post-4',
'post-3',
'post-1',
'post-5',
'post-2',
]);
});
});
function createPost(data: {
date: string;
title: string;
slug: string;
pinned?: boolean;
}) {
return {
content: '',
filePath: `${data.slug}.md`,
slug: data.slug,
date: data.date,
title: data.title,
description: '',
authors: [],
ogImage: '',
ogImageType: '',
cover_image: '',
tags: [],
reposts: [],
pinned: data.pinned ?? undefined,
};
}
@@ -0,0 +1,9 @@
import { BlogPostDataEntry } from './blog.model';
export function sortPosts(posts: BlogPostDataEntry[]): BlogPostDataEntry[] {
return posts.sort((a, b) => {
if (a.pinned === true && b.pinned !== true) return -1;
if (b.pinned === true && a.pinned !== true) return 1;
return new Date(b.date).getTime() - new Date(a.date).getTime();
});
}
@@ -0,0 +1,39 @@
import { readFileSync, readdirSync } from 'fs';
import { join } from 'path';
export interface ChangelogRawContentEntry {
version: string;
content: string;
filePath: string;
}
export class ChangelogApi {
constructor(
private readonly options: {
id: string;
changelogRoot: string;
}
) {
if (!options.id) {
throw new Error('id cannot be undefined');
}
if (!options.changelogRoot) {
throw new Error('public docs root cannot be undefined');
}
}
getChangelogEntries(): ChangelogRawContentEntry[] {
const files = readdirSync(this.options.changelogRoot);
return files.map((file) => {
const filePath = join(this.options.changelogRoot, file);
const content = readFileSync(filePath, 'utf8');
const version = file.replace('.md', '').replace(/_/g, '.');
return {
content,
version,
filePath,
};
});
}
}
@@ -0,0 +1,391 @@
import {
pkgToGeneratedApiDocs,
type DocumentMetadata,
type ProcessedDocument,
type RelatedDocument,
} from '@nx/nx-dev-models-document';
import { type ProcessedPackageMetadata } from '@nx/nx-dev-models-package';
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { type TagsApi } from './tags.api';
import { extractFrontmatter } from '@nx/nx-dev-ui-markdoc';
interface StaticDocumentPaths {
params: { segments: string[] };
}
export class DocumentsApi {
private readonly manifest: Record<string, DocumentMetadata>;
private readonly packagesManifest?: Record<string, ProcessedPackageMetadata>;
constructor(
private readonly options: {
id: string;
manifest: Record<string, DocumentMetadata>;
packagesManifest?: Record<string, ProcessedPackageMetadata>;
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);
if (this.options.packagesManifest) {
this.packagesManifest = structuredClone(this.options.packagesManifest);
}
if (
options.id === 'angular-rspack-documents' ||
options.id === 'angular-rsbuild-documents'
) {
this.manifest = Object.assign(
this.manifest,
this.getAngularRspackPackage()
);
}
}
private getManifestKey(path: string): string {
return '/' + path;
}
// TODO(colum): Remove this once we move angular rspack into main repo (when stable).
getAngularRspackPackage(): Record<string, DocumentMetadata> {
return {
'/nx-api/angular-rspack/documents/create-config': {
id: 'create-config',
name: 'createConfig',
description: 'createConfig for @nx/angular-rspack',
path: '/nx-api/angular-rspack/documents/create-config',
file: 'shared/guides/angular-rspack/api/nx-angular-rspack/create-config',
isExternal: false,
itemList: [],
tags: [],
},
'/nx-api/angular-rspack/documents/create-server': {
id: 'create-server',
name: 'createServer',
description: 'createServer for @nx/angular-rspack',
path: '/nx-api/angular-rspack/documents/create-server',
file: 'shared/guides/angular-rspack/api/nx-angular-rspack/create-server',
isExternal: false,
itemList: [],
tags: [],
},
'/nx-api/angular-rsbuild/documents/create-config': {
id: 'create-config',
name: 'createConfig',
description: 'createConfig for @nx/angular-rsbuild',
path: '/nx-api/angular-rspack/documents/create-config',
file: 'shared/guides/angular-rspack/api/nx-angular-rsbuild/create-config',
isExternal: false,
itemList: [],
tags: [],
},
'/nx-api/angular-rsbuild/documents/create-server': {
id: 'create-server',
name: 'createServer',
description: 'createServer for @nx/angular-rsbuild',
path: '/nx-api/angular-rspack/documents/create-server',
file: 'shared/guides/angular-rspack/api/nx-angular-rsbuild/create-server',
isExternal: false,
itemList: [],
tags: [],
},
};
}
getFilePath(path: string): string {
return join(this.options.publicDocsRoot, path);
}
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[] {
let paths: string[] = [];
// Add regular document paths
if (this.options.prefix) {
paths = Object.keys(this.manifest).map(
(path) => `/${this.options.prefix}` + path
);
} else {
paths = Object.keys(this.manifest);
}
// API Docs
if (this.packagesManifest) {
const packages = Object.values(this.packagesManifest);
packages.forEach((pkg) => {
const apiDocData = pkgToGeneratedApiDocs[pkg.name];
// Packages are not mapped, skip creating URLs for them.
if (!apiDocData) return;
const apiPagePath = apiDocData.pagePath;
paths.push(apiPagePath);
if (apiDocData.includeDocuments) {
paths.push(`${apiPagePath}/documents`);
Object.keys(pkg.documents).forEach((path) => {
paths.push(path);
});
// Legacy devkit API documents
if (pkg.name === 'devkit') {
readdirSync('../../docs/generated/devkit').forEach((fileName) => {
// Private files
if (fileName.startsWith('.')) return;
if (fileName.endsWith('.md')) {
const apiDocPath = `${
pkgToGeneratedApiDocs['devkit'].pagePath
}/documents/${fileName.replace('.md', '')}`;
paths.push(apiDocPath);
} else {
readdirSync('../../docs/generated/devkit/' + fileName).forEach(
(subFileName) => {
const apiDocPath = `${
pkgToGeneratedApiDocs['devkit'].pagePath
}/documents/${fileName}/${subFileName.replace('.md', '')}`;
paths.push(apiDocPath);
}
);
}
});
}
}
paths.push(`${apiPagePath}/executors`);
Object.keys(pkg.executors).forEach((path) => {
paths.push(path);
});
paths.push(`${apiPagePath}/generators`);
Object.keys(pkg.generators).forEach((path) => {
paths.push(path);
});
paths.push(`${apiPagePath}/migrations`);
});
}
return paths;
}
getDocument(path: string[]): ProcessedDocument {
const document: DocumentMetadata | null =
this.manifest[this.getManifestKey(path.join('/'))] || null;
if (!document) {
// Legacy handler for devkit docs
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}.md`), 'utf8'),
description: '',
filePath: this.getFilePath(file),
id: path.at(-1) || '',
name: path.at(-1) || '',
relatedDocuments: {},
tags: [],
};
}
// NEW Legacy handler for devkit docs
if (
path[0] === 'reference' &&
path[1] === 'core-api' &&
path[2] === 'devkit' &&
path[3] === 'documents'
) {
const file = `generated/devkit/${path.slice(4).join('/')}`;
return {
content: readFileSync(this.getFilePath(`${file}.md`), '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}.md`), 'utf8'),
description: document.description,
filePath: this.getFilePath(`${document.file}.md`),
id: document.id,
name: document.name,
mediaImage: document.mediaImage || '',
relatedDocuments: this.getRelatedDocuments(document.tags),
parentDocuments: path.map((segment, index): RelatedDocument => {
const parentPath = path.slice(0, index + 1).join('/');
const parentDocument =
this.manifest[this.getManifestKey(parentPath)] || null;
if (!parentDocument) {
return {
id: segment,
name: '',
description: '',
file: '',
path: '/' + path.slice(0, index + 1).join('/'),
};
}
return {
id: parentDocument.id,
name: parentDocument.name,
description: parentDocument.description,
file: parentDocument.file,
path: parentDocument.path,
};
}),
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 || !document.file;
}
generateDocumentIndexTemplate(document: DocumentMetadata): string {
const cardsTemplate = document.itemList
.map((i) => {
// Try to get description from frontmatter first
let description = i.description ?? '';
// Get the document metadata to find the file path
// i.path might already have a leading slash or might not, so we need to check both
const itemDocument =
this.manifest[i.path] || this.manifest[this.getManifestKey(i.path)];
if (itemDocument && itemDocument.file) {
const filePath = this.getFilePath(`${itemDocument.file}.md`);
if (existsSync(filePath)) {
try {
const content = readFileSync(filePath, 'utf8');
const frontmatter = extractFrontmatter(content);
// Use frontmatter description if available, otherwise fall back to map.json description
description = frontmatter.description || description;
} catch (e) {
// If there's an error reading the file, fall back to the original description
console.warn(`Could not read frontmatter from ${filePath}:`, e);
}
}
}
return {
title: i.name,
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`,
...(!document.itemList.length
? ['No items found.']
: ['{% 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}.md`), 'utf8'),
description: document.description,
filePath: this.getFilePath(`${document.file}.md`),
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,
};
}
}
@@ -0,0 +1,16 @@
import { BlogApi } from './blog.api';
import { PodcastDataEntry } from './podcast.model';
export class PodcastApi {
_blogApi: BlogApi;
constructor(options: { blogApi: BlogApi }) {
this._blogApi = options.blogApi;
}
async getPodcastBlogs(): Promise<PodcastDataEntry[]> {
return await this._blogApi.getBlogs((post) =>
post.tags.map((t) => t.toLowerCase()).includes('podcast')
);
}
}
@@ -0,0 +1,5 @@
import { BlogPostDataEntry } from './blog.model';
export interface PodcastDataEntry extends BlogPostDataEntry {
duration?: string;
}
@@ -0,0 +1,35 @@
import { RelatedDocument } from '@nx/nx-dev-models-document';
export class TagsApi {
private readonly manifest: Record<string, RelatedDocument[]>;
constructor(private readonly tags: Record<string, RelatedDocument[]>) {
if (!tags) {
throw new Error('tags property cannot be undefined');
}
this.manifest = structuredClone(this.tags);
}
getAssociatedItems(tag: string): RelatedDocument[] {
const items: RelatedDocument[] | null = this.manifest[tag] || null;
if (!items) throw new Error(`No associated items found for tag: "${tag}"`);
return items;
}
getAssociatedItemsFromTags(tags: string[]): RelatedDocument[] {
return this.sortAndDeduplicateItems(
tags.map((t) => this.getAssociatedItems(t))
);
}
sortAndDeduplicateItems(tags: RelatedDocument[][]): RelatedDocument[] {
return tags.flat().reduce((acc, item: RelatedDocument) => {
if (acc.findIndex((curr) => curr.file === item.file) === -1) {
acc.push(item);
}
return acc;
}, [] as RelatedDocument[]);
}
}
@@ -0,0 +1,16 @@
import { BlogApi } from './blog.api';
import { WebinarDataEntry } from './webinar.model';
export class WebinarApi {
_blogApi: BlogApi;
constructor(options: { blogApi: BlogApi }) {
this._blogApi = options.blogApi;
}
async getWebinarBlogs(): Promise<WebinarDataEntry[]> {
return await this._blogApi.getBlogs((post) =>
post.tags.map((t) => t.toLowerCase()).includes('webinar')
);
}
}
@@ -0,0 +1,8 @@
import { BlogPostDataEntry } from './blog.model';
export interface WebinarDataEntry extends BlogPostDataEntry {
status?: 'Upcoming' | 'Past - Gated' | 'Past - Ungated';
eventDate?: string;
time?: string;
registrationUrl?: string;
}
@@ -0,0 +1,10 @@
export * from './lib/documents.api';
export * from './lib/changelog.api';
export * from './lib/blog.util';
export * from './lib/blog.api';
export * from './lib/blog.model';
export * from './lib/tags.api';
export * from './lib/podcast.model';
export * from './lib/podcast.api';
export * from './lib/webinar.model';
export * from './lib/webinar.api';
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"strictNullChecks": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
@@ -0,0 +1,29 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"outDir": "dist",
"types": ["node"],
"composite": true,
"declaration": true,
"jsx": "react-jsx"
},
"references": [
{
"path": "../ui-markdoc/tsconfig.lib.json"
},
{
"path": "../models-package/tsconfig.lib.json"
},
{
"path": "../models-document/tsconfig.lib.json"
},
{
"path": "../../packages/devkit/tsconfig.lib.json"
}
],
"include": ["**/*.ts"],
"exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"]
}
@@ -0,0 +1,22 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"outDir": "dist/spec",
"types": ["jest", "node"]
},
"include": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"**/*.d.ts",
"jest.config.ts"
]
}