aliaset
Version:
twind monorepo
210 lines (178 loc) • 5.63 kB
text/typescript
import * as path from 'node:path'
import fg, { type Options as FastGlobOptions } from 'fast-glob'
import { findUpSync, pathExistsSync } from 'find-up'
import { createIndex } from '$lib/search'
import { readFile } from './read-cache'
import { sectionize } from './transform'
export interface Page {
path: string
href: string
section: string | undefined
label: string | undefined
title: string | undefined
description: string | undefined
excerpt: string | undefined
package: string | undefined
playground: boolean | string | undefined
example: boolean | string | undefined
file: string
/** include in nav */
nav: boolean
editLink: boolean
prev?: string | undefined
next: string | undefined
}
export interface ReadOptions extends FastGlobOptions {
slug?: string
root?: string
pattern?: string | string[]
}
export async function read({
slug = '/',
cwd = process.cwd(),
root = findUpSync(
(directory) =>
['.git', 'pnpm-workspace.yaml', 'lerna.json'].some((file) =>
pathExistsSync(path.join(directory, file)),
)
? directory
: undefined,
{ cwd, type: 'directory' },
) || cwd,
pattern = '**/*.md',
ignore = ['**/README.md'],
caseSensitiveMatch = false,
...globOptions
}: ReadOptions = {}) {
const pages = new Map<string | undefined, Page>()
const index = createIndex()
const store: {
href: string
section: string
label: string
category: string
title: string
content: string
}[] = []
await Promise.all(
(
await fg(pattern, { ...globOptions, cwd, ignore, absolute: true })
).map(async (filename) => {
const href = toHref(cwd, filename, slug)
const { data: frontmatter, content } = await readFile(filename)
if (frontmatter.hidden) return
if (frontmatter.draft && import.meta.env.PROD) return
const {
section = path.relative(cwd, filename).split('/')[0],
package: packageName,
label,
title = packageName || label || path.relative(cwd, filename).replace(/\.md$/, ''),
description,
excerpt,
playground,
example,
nav = true,
editLink = true,
next,
} = frontmatter
for (const block of (await sectionize(
[`# ${title}`, description || excerpt || '', content].join('\n\n'),
)) || []) {
const id =
store.push({
href: block.rank === 1 ? href : `${href}#${block.anchor}`,
section,
label: label || title,
category: section === 'Packages' ? (packageName ? 'packages' : 'api') : 'guides',
title: block.title,
content: block.content,
}) - 1
index.add({
id,
title: block.title,
content: block.content,
})
}
pages.set(href, {
path: filename,
href,
section,
label: label || title,
title,
description,
excerpt: excerpt || description,
package: packageName,
playground,
example,
file: path.relative(root, filename),
editLink,
nav,
next: next && toHref(cwd, path.resolve(path.dirname(filename), next), slug),
})
}),
)
// we can not really know when the export is finished
// flexsearch uses setTimeout internally - just wait for a moment
const exported = await new Promise<[key: string, data: string][]>((resolve) => {
const exported: [key: string, data: string][] = []
let doneRef = setTimeout(resolve, 50, exported)
index.export((key: string, data: string) => {
// you need to store both the key and the data!
// e.g. use the key for the filename and save your data
exported.push([key, data])
clearTimeout(doneRef)
doneRef = setTimeout(resolve, 250, exported)
})
})
// console.debug(index.info())
// console.time('suggest:t')
// console.debug(...index.search('t', 100, { suggest: true, enrich: true }))
// console.timeEnd('suggest:t')
// console.time('suggest:tx')
// console.debug(...index.search('tx', 100, { enrich: true }))
// console.timeEnd('suggest:tx')
// update prev link
pages.forEach((value, href) => {
const next = pages.get(value.next)
if (next) {
next.prev = href
}
})
let startHref: string | undefined = undefined
for (const [href, entry] of pages) {
if (entry.next && !entry.prev) {
startHref = href
break
}
}
if (!startHref) startHref = pages.keys().next().value
const seen = new Set<Page>()
const sections = new Map<string | undefined, string[]>([[undefined, []]])
for (let current = pages.get(startHref); current; current = pages.get(current.next)) {
seen.add(current)
if (current.nav === false) continue
let section = sections.get(current.section)
if (!section) {
sections.set(current.section, (section = []))
}
section.push(current.href)
}
// collect those that are not linked
for (const current of pages.values()) {
if (seen.has(current)) continue
if (current.nav === false) continue
let section = sections.get(current.section)
if (!section) {
sections.set(current.section, (section = []))
}
section.push(current.href)
section.sort()
}
return { startHref, pages, sections, search: { store, data: exported } }
}
function toHref(cwd: string, file: string, slug = '/') {
return slug + path.relative(cwd, omitExtension(file)).replace(/\/index$/, '')
}
function omitExtension(file: string) {
return file.replace(/\.[^.]+$/, '')
}