Revert "style: lint"

This reverts commit 98f40d7d0b.
This commit is contained in:
Neko Ayaka
2026-08-26 20:13:10 +08:00
parent cfcfc513ef
commit 146b3da65a
1625 changed files with 75440 additions and 75453 deletions
+8 -8
View File
@@ -1,13 +1,5 @@
import { isAfter, isBefore } from 'date-fns'
export function isBetweenChristmasAndHalfOfJanuary(date: Date) {
const year = date.getFullYear()
const christmas = new Date(year, 11, 20) // December 20
const halfOfJanuary = new Date(year, 0, 10) // January 10 of the next year
return isAfter(date, christmas) || isBefore(date, halfOfJanuary)
}
export function isBetweenHalloweenAndHalfOfNovember(date: Date) {
const year = date.getFullYear()
const halloween = new Date(year, 9, 25) // October 25
@@ -15,3 +7,11 @@ export function isBetweenHalloweenAndHalfOfNovember(date: Date) {
return isAfter(date, halloween) && isBefore(date, halfOfNovember)
}
export function isBetweenChristmasAndHalfOfJanuary(date: Date) {
const year = date.getFullYear()
const christmas = new Date(year, 11, 20) // December 20
const halfOfJanuary = new Date(year, 0, 10) // January 10 of the next year
return isAfter(date, christmas) || isBefore(date, halfOfJanuary)
}
+3 -3
View File
@@ -3,11 +3,11 @@ import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
export function useEditLink() {
const { page, theme } = useData()
const { theme, page } = useData()
const { t } = useI18n()
return computed(() => {
const { pattern = '', text = t('docs.theme.doc.community.edit.title') } = theme.value.editLink || {}
const { text = t('docs.theme.doc.community.edit.title'), pattern = '' } = theme.value.editLink || {}
let url: string
if (typeof pattern === 'function') {
url = pattern(page.value)
@@ -16,6 +16,6 @@ export function useEditLink() {
url = pattern.replace(/:path/g, page.value.filePath)
}
return { text, url }
return { url, text }
})
}
+46 -46
View File
@@ -7,10 +7,6 @@ import { getScrollOffset } from 'vitepress'
import { onMounted, onUpdated } from 'vue'
export interface Header {
/**
* The children of the header
*/
children: Header[]
/**
* The level of the header
*
@@ -18,11 +14,9 @@ export interface Header {
*/
level: number
/**
* Link of the header
*
* Typically using `#${slug}` as the anchor hash
* The title of the header
*/
link: string
title: string
/**
* The slug of the header
*
@@ -30,17 +24,33 @@ export interface Header {
*/
slug: string
/**
* The title of the header
* Link of the header
*
* Typically using `#${slug}` as the anchor hash
*/
title: string
link: string
/**
* The children of the header
*/
children: Header[]
}
// cached list of anchor elements from resolveHeaders
const resolvedHeaders: { element: HTMLHeadElement, link: string }[] = []
export type MenuItem = Omit<Header, 'children' | 'slug'> & {
children?: MenuItem[]
export type MenuItem = Omit<Header, 'slug' | 'children'> & {
element: HTMLHeadElement
children?: MenuItem[]
}
export function resolveTitle(theme: DefaultTheme.Config) {
return (
(typeof theme.outline === 'object'
&& !Array.isArray(theme.outline)
&& theme.outline.label)
|| theme.outlineTitle
|| 'On this page'
)
}
export function getHeaders(range: DefaultTheme.Config['outline']) {
@@ -50,15 +60,35 @@ export function getHeaders(range: DefaultTheme.Config['outline']) {
const level = Number(el.tagName[1])
return {
element: el as HTMLHeadElement,
level,
link: `#${el.id}`,
title: serializeHeader(el),
link: `#${el.id}`,
level,
}
})
return resolveHeaders(headers, range)
}
function serializeHeader(h: Element): string {
let ret = ''
for (const node of Array.from(h.childNodes)) {
if (node.nodeType === 1) {
if (
(node as Element).classList.contains('VPBadge')
|| (node as Element).classList.contains('header-anchor')
|| (node as Element).classList.contains('ignore-header')
) {
continue
}
ret += node.textContent
}
else if (node.nodeType === 3) {
ret += node.textContent
}
}
return ret.trim()
}
export function resolveHeaders(
headers: MenuItem[],
range?: DefaultTheme.Config['outline'],
@@ -110,16 +140,6 @@ export function resolveHeaders(
return ret
}
export function resolveTitle(theme: DefaultTheme.Config) {
return (
(typeof theme.outline === 'object'
&& !Array.isArray(theme.outline)
&& theme.outline.label)
|| theme.outlineTitle
|| 'On this page'
)
}
export function useActiveAnchor(
container: Ref<HTMLElement>,
marker: Ref<HTMLElement>,
@@ -173,7 +193,7 @@ export function useActiveAnchor(
}
// find the last header above the top of viewport
let activeLink: null | string = null
let activeLink: string | null = null
for (const { link, top } of headers) {
if (top > scrollY + getScrollOffset() + 4) {
break
@@ -184,7 +204,7 @@ export function useActiveAnchor(
activateLink(activeLink)
}
function activateLink(hash: null | string) {
function activateLink(hash: string | null) {
if (prevActiveLink) {
prevActiveLink.classList.remove('active')
}
@@ -228,26 +248,6 @@ function getAbsoluteTop(element: HTMLElement): number {
return offsetTop
}
function serializeHeader(h: Element): string {
let ret = ''
for (const node of Array.from(h.childNodes)) {
if (node.nodeType === 1) {
if (
(node as Element).classList.contains('VPBadge')
|| (node as Element).classList.contains('header-anchor')
|| (node as Element).classList.contains('ignore-header')
) {
continue
}
ret += node.textContent
}
else if (node.nodeType === 3) {
ret += node.textContent
}
}
return ret.trim()
}
function throttleAndDebounce(fn: () => void, delay: number): () => void {
let timeoutId: NodeJS.Timeout
let called = false
+30 -30
View File
@@ -11,7 +11,7 @@ import { getFlatSideBarLinks, getSidebar, isActive } from './sidebar'
* - Respects frontmatter overrides and hides when disabled.
*/
export function usePrevNext() {
const { frontmatter, lang, page, theme } = useData()
const { page, theme, frontmatter, lang } = useData()
return computed(() => {
// Blog-specific navigation: ensure next/prev stay within same language blog directory
@@ -53,7 +53,6 @@ export function usePrevNext() {
? undefined
: prevPost
? {
link: withBase(prevPost.url),
text:
(typeof frontmatter.value.prev === 'string'
? frontmatter.value.prev
@@ -61,6 +60,7 @@ export function usePrevNext() {
? frontmatter.value.prev.text
: undefined)
?? prevPost.title,
link: withBase(prevPost.url),
}
: undefined
@@ -68,7 +68,6 @@ export function usePrevNext() {
? undefined
: nextPost
? {
link: withBase(nextPost.url),
text:
(typeof frontmatter.value.next === 'string'
? frontmatter.value.next
@@ -76,15 +75,16 @@ export function usePrevNext() {
? frontmatter.value.next.text
: undefined)
?? nextPost.title,
link: withBase(nextPost.url),
}
: undefined
return {
next: blogNext,
prev: blogPrev,
next: blogNext,
} as {
next?: { link?: string, text?: string }
prev?: { link?: string, text?: string }
prev?: { text?: string, link?: string }
next?: { text?: string, link?: string }
}
}
@@ -110,9 +110,9 @@ export function usePrevNext() {
const isSectionRoot = currentFullUrl.replace(/[?#].*$/, '') === sectionBase
if (isSectionRoot) {
return {
next: undefined,
prev: undefined,
} as { next?: { link?: string, text?: string }, prev?: { link?: string, text?: string } }
next: undefined,
} as { prev?: { text?: string, link?: string }, next?: { text?: string, link?: string } }
}
// Keep navigation within the same docs section and exclude the section root itself
// to avoid showing a "next" link that points back to the section index.
@@ -136,29 +136,9 @@ export function usePrevNext() {
})
return {
next: hideNext || index < 0 || index >= candidates.length - 1
? undefined
: {
link:
(typeof frontmatter.value.next === 'object'
? frontmatter.value.next.link
: undefined) ?? candidates[index + 1]?.link,
text:
(typeof frontmatter.value.next === 'string'
? frontmatter.value.next
: typeof frontmatter.value.next === 'object'
? frontmatter.value.next.text
: undefined)
?? candidates[index + 1]?.docFooterText
?? candidates[index + 1]?.text,
},
prev: hidePrev || index <= 0
? undefined
: {
link:
(typeof frontmatter.value.prev === 'object'
? frontmatter.value.prev.link
: undefined) ?? candidates[index - 1]?.link,
text:
(typeof frontmatter.value.prev === 'string'
? frontmatter.value.prev
@@ -167,10 +147,30 @@ export function usePrevNext() {
: undefined)
?? candidates[index - 1]?.docFooterText
?? candidates[index - 1]?.text,
link:
(typeof frontmatter.value.prev === 'object'
? frontmatter.value.prev.link
: undefined) ?? candidates[index - 1]?.link,
},
next: hideNext || index < 0 || index >= candidates.length - 1
? undefined
: {
text:
(typeof frontmatter.value.next === 'string'
? frontmatter.value.next
: typeof frontmatter.value.next === 'object'
? frontmatter.value.next.text
: undefined)
?? candidates[index + 1]?.docFooterText
?? candidates[index + 1]?.text,
link:
(typeof frontmatter.value.next === 'object'
? frontmatter.value.next.link
: undefined) ?? candidates[index + 1]?.link,
},
} as {
next?: { link?: string, text?: string }
prev?: { link?: string, text?: string }
prev?: { text?: string, link?: string }
next?: { text?: string, link?: string }
}
})
}
+90 -90
View File
@@ -17,10 +17,10 @@ import {
export interface SidebarControl {
collapsed: Ref<boolean>
collapsible: ComputedRef<boolean>
isLink: ComputedRef<boolean>
isActiveLink: Ref<boolean>
hasActiveLink: ComputedRef<boolean>
hasChildren: ComputedRef<boolean>
isActiveLink: Ref<boolean>
isLink: ComputedRef<boolean>
toggle: () => void
}
@@ -55,7 +55,7 @@ export function useCloseSidebarOnEscape(
export function useSidebarControl(
item: ComputedRef<DefaultTheme.SidebarItem>,
): SidebarControl {
const { hash, page } = useData()
const { page, hash } = useData()
const collapsed = ref(false)
@@ -106,10 +106,10 @@ export function useSidebarControl(
return {
collapsed,
collapsible,
isLink,
isActiveLink,
hasActiveLink,
hasChildren,
isActiveLink,
isLink,
toggle,
}
}
@@ -121,35 +121,70 @@ const HASH_RE = /#.*$/
const HASH_OR_QUERY_RE = /[?#].*$/
const INDEX_OR_EXT_RE = /(?:(^|\/)index)?\.(?:md|html)$/
// From https://github.com/vuejs/vitepress/blob/fa81e89643523170047ca2c9a690f4d7adf4ffdc/src/client/theme-default/support/sidebar.ts
export interface SidebarLink {
docFooterText?: string
link: string
text: string
}
export function getFlatSideBarLinks(sidebar: SidebarItem[]): SidebarLink[] {
const links: SidebarLink[] = []
function recursivelyExtractLinks(items: SidebarItem[]) {
for (const item of items) {
if (item.text && item.link) {
links.push({
docFooterText: item.docFooterText,
link: item.link,
text: item.text,
})
}
if (item.items) {
recursivelyExtractLinks(item.items)
}
}
export function isActive(
currentPath: string,
matchPath?: string,
asRegex: boolean = false,
): boolean {
if (matchPath === undefined) {
return false
}
recursivelyExtractLinks(sidebar)
if (currentPath.startsWith('/')) {
currentPath = normalize(`${currentPath}`)
}
else {
currentPath = normalize(`/${currentPath}`)
}
return links
if (asRegex) {
return new RegExp(matchPath).test(currentPath)
}
if (normalize(matchPath) !== currentPath) {
return false
}
const hashMatch = matchPath.match(HASH_RE)
if (hashMatch) {
return (inBrowser ? location.hash : '') === hashMatch[0]
}
return true
}
function normalize(path: string): string {
return decodeURI(path)
.replace(HASH_OR_QUERY_RE, '')
.replace(INDEX_OR_EXT_RE, '$1')
}
// From https://github.com/vuejs/vitepress/blob/97f9469b6d4eb7ba9de9a1111986581d1f704ec3/src/client/theme-default/support/sidebar.ts
function containsActiveLink(
path: string,
items: any | any[],
): boolean {
if (Array.isArray(items)) {
return items.some(item => containsActiveLink(path, item))
}
return isActive(path, items.link)
? true
: items.items
? containsActiveLink(path, items.items)
: false
}
// From https://github.com/vuejs/vitepress/blob/fa81e89643523170047ca2c9a690f4d7adf4ffdc/src/client/theme-default/support/sidebar.ts
export interface SidebarLink {
text: string
link: string
docFooterText?: string
}
function ensureStartingSlash(path: string): string {
return path.startsWith('/') ? path : `/${path}`
}
/**
@@ -210,6 +245,30 @@ export function getSidebarGroups(sidebar: SidebarItem[]): SidebarItem[] {
return groups
}
export function getFlatSideBarLinks(sidebar: SidebarItem[]): SidebarLink[] {
const links: SidebarLink[] = []
function recursivelyExtractLinks(items: SidebarItem[]) {
for (const item of items) {
if (item.text && item.link) {
links.push({
text: item.text,
link: item.link,
docFooterText: item.docFooterText,
})
}
if (item.items) {
recursivelyExtractLinks(item.items)
}
}
}
recursivelyExtractLinks(sidebar)
return links
}
/**
* Check if the given sidebar item contains any active link.
*/
@@ -228,39 +287,6 @@ export function hasActiveLink(
: false
}
export function isActive(
currentPath: string,
matchPath?: string,
asRegex: boolean = false,
): boolean {
if (matchPath === undefined) {
return false
}
if (currentPath.startsWith('/')) {
currentPath = normalize(`${currentPath}`)
}
else {
currentPath = normalize(`/${currentPath}`)
}
if (asRegex) {
return new RegExp(matchPath).test(currentPath)
}
if (normalize(matchPath) !== currentPath) {
return false
}
const hashMatch = matchPath.match(HASH_RE)
if (hashMatch) {
return (inBrowser ? location.hash : '') === hashMatch[0]
}
return true
}
function addBase(items: SidebarItem[], _base?: string): SidebarItem[] {
return Array.from(items, (_item) => {
const item = { ..._item }
@@ -272,29 +298,3 @@ function addBase(items: SidebarItem[], _base?: string): SidebarItem[] {
return item
})
}
// From https://github.com/vuejs/vitepress/blob/97f9469b6d4eb7ba9de9a1111986581d1f704ec3/src/client/theme-default/support/sidebar.ts
function containsActiveLink(
path: string,
items: any | any[],
): boolean {
if (Array.isArray(items)) {
return items.some(item => containsActiveLink(path, item))
}
return isActive(path, items.link)
? true
: items.items
? containsActiveLink(path, items.items)
: false
}
function ensureStartingSlash(path: string): string {
return path.startsWith('/') ? path : `/${path}`
}
function normalize(path: string): string {
return decodeURI(path)
.replace(HASH_OR_QUERY_RE, '')
.replace(INDEX_OR_EXT_RE, '$1')
}
+2 -2
View File
@@ -26,7 +26,7 @@ export function themeColorFromPropertyOf(colorFromClass: string, property: strin
* Reading VitePress' `isDark` also avoids stray `useDark()` instances that
* force the theme back to the system preference.
*/
export function themeColorFromValue(value: string | { dark: string, light: string }): () => Promise<string> {
export function themeColorFromValue(value: string | { light: string, dark: string }): () => Promise<string> {
const { isDark } = useData()
return async () => {
if (typeof value === 'string') {
@@ -36,7 +36,7 @@ export function themeColorFromValue(value: string | { dark: string, light: strin
}
}
export function useThemeColor(colorFrom: () => Promise<string> | string) {
export function useThemeColor(colorFrom: () => string | Promise<string>) {
async function updateThemeColor() {
if (!('document' in globalThis) || globalThis.document == null)
return
+787 -787
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -3,16 +3,16 @@ import type { DefaultTheme } from 'vitepress'
import contributorNames from './contributor-names.json'
export interface Contributor {
avatar: string
name: string
avatar: string
}
export interface CoreTeam extends DefaultTheme.TeamMember {
discord?: string
// required to download avatars from GitHub
github: string
mastodon?: string
twitter?: string
mastodon?: string
discord?: string
youtube?: string
}
@@ -24,7 +24,7 @@ function getAvatarUrl(name: string) {
export const contributors = (contributorNames as string[]).reduce((acc, name) => {
contributorsAvatars[name] = getAvatarUrl(name)
acc.push({ avatar: contributorsAvatars[name], name })
acc.push({ name, avatar: contributorsAvatars[name] })
return acc
}, [] as Contributor[])
function createLinks(tm: CoreTeam): CoreTeam {
+36 -36
View File
@@ -1,33 +1,33 @@
import { defineLoader } from 'vitepress'
export interface NightlyBuild {
conclusion: string
created_at: string
head_commit_message: string
head_sha: string
html_url: string
id: number
export interface Release {
name: string
status: string
updated_at: string
workflow_name: string
tag_name: string
html_url: string
published_at: string
prerelease: boolean
draft: boolean
body: string
}
export interface Release {
body: string
draft: boolean
html_url: string
export interface NightlyBuild {
id: number
name: string
prerelease: boolean
published_at: string
tag_name: string
html_url: string
created_at: string
updated_at: string
status: string
conclusion: string
workflow_name: string
head_sha: string
head_commit_message: string
}
export interface ReleasesData {
stable: Release[]
prerelease: Release[]
nightly: NightlyBuild[]
nightlyUrl: string
prerelease: Release[]
stable: Release[]
}
declare const data: ReleasesData
@@ -91,17 +91,17 @@ export default defineLoader({
if (actionsResponse.ok) {
const actionsData = await actionsResponse.json()
nightlyBuilds = actionsData.workflow_runs?.map((run: {
conclusion: string
id: number
name: string
head_sha: string
html_url: string
created_at: string
updated_at: string
status: string
conclusion: string
head_commit?: {
message: string
}
head_sha: string
html_url: string
id: number
name: string
status: string
updated_at: string
}) => {
const shortSha = run.head_sha.substring(0, 7)
// Get first line of commit message
@@ -109,16 +109,16 @@ export default defineLoader({
const firstLine = commitMessage.split('\n')[0]
return {
conclusion: run.conclusion,
created_at: run.created_at,
head_commit_message: commitMessage,
head_sha: shortSha,
html_url: run.html_url,
id: run.id,
name: firstLine,
status: run.status,
html_url: run.html_url,
created_at: run.created_at,
updated_at: run.updated_at,
status: run.status,
conclusion: run.conclusion,
workflow_name: run.name,
head_sha: shortSha,
head_commit_message: commitMessage,
}
}) || []
}
@@ -128,20 +128,20 @@ export default defineLoader({
}
return {
stable,
prerelease,
nightly: nightlyBuilds,
nightlyUrl,
prerelease,
stable,
}
}
catch (error) {
console.error('Failed to fetch releases:', error)
// Return empty data if fetch fails
return {
stable: [],
prerelease: [],
nightly: [],
nightlyUrl,
prerelease: [],
stable: [],
}
}
},
@@ -7,12 +7,12 @@ import { formatDate } from '../utils/utils'
const config: SiteConfig = (globalThis as any).VITEPRESS_CONFIG
interface Document {
date: ReturnType<typeof formatDate>
frontmatter?: Record<string, any>
lang: string
title: string
url: string
urlWithoutLang: string
lang: string
date: ReturnType<typeof formatDate>
frontmatter?: Record<string, any>
}
declare const data: Document[]
@@ -21,7 +21,7 @@ export { data }
export default createContentLoader('**/*.md', {
transform(raw): Document[] {
return raw
.map(({ frontmatter, url }) => {
.map(({ url, frontmatter }) => {
const foundLanguage = Object.values(config.userConfig.locales!).find((locale) => {
let normalizedLanguagePrefix = locale.lang || 'en'
if (!normalizedLanguagePrefix.startsWith('/')) {
@@ -32,12 +32,12 @@ export default createContentLoader('**/*.md', {
})
return {
date: formatDate(frontmatter.date),
frontmatter,
lang: foundLanguage?.lang || 'en',
title: frontmatter.title,
url,
urlWithoutLang: url.replace(`/${foundLanguage?.lang || 'en'}`, ''),
date: formatDate(frontmatter.date),
lang: foundLanguage?.lang || 'en',
frontmatter,
}
})
.sort((a, b) => b.date.time - a.date.time)
+19 -19
View File
@@ -3,26 +3,26 @@ import { webcrypto } from 'node:crypto'
import { createContentLoader } from 'vitepress'
export interface Author {
avatar?: string
avatarFallback: string
role: string
kind: 'person' | 'team'
displayName: string
githubEmail?: string
githubUsername?: string
githubEmail?: string
kind: 'person' | 'team'
role: string
avatar?: string
avatarFallback: string
}
interface MarkdownAuthor {
avatar?: string
githubEmail?: string
githubUsername?: string
kind?: 'person' | 'team'
name?: string
role?: string
kind?: 'person' | 'team'
avatar?: string
githubUsername?: string
githubEmail?: string
}
/**
@@ -42,7 +42,7 @@ async function digestStringAsSHA256(message: string) {
return hashHex
}
async function newAvatarForAuthor(mappedAuthor?: null | { displayName?: string, githubUsername?: string, overrideAvatar?: string }, email?: null | string): Promise<string> {
async function newAvatarForAuthor(mappedAuthor?: { overrideAvatar?: string, githubUsername?: string, displayName?: string } | null, email?: string | null): Promise<string> {
if (mappedAuthor) {
if (mappedAuthor.overrideAvatar)
return mappedAuthor.overrideAvatar
@@ -54,10 +54,10 @@ async function newAvatarForAuthor(mappedAuthor?: null | { displayName?: string,
}
export default createContentLoader('**/*.md', {
async transform(raw): Promise<Array<{ authors: Author[], url: string }>> {
async transform(raw): Promise<Array<{ url: string, authors: Author[] }>> {
return (await Promise.all(
raw
.map(async ({ frontmatter, url }) => {
.map(async ({ url, frontmatter }) => {
const authors: MarkdownAuthor[] = frontmatter.authors
if (!authors || !Array.isArray(authors)) {
return
@@ -67,22 +67,22 @@ export default createContentLoader('**/*.md', {
const displayName = author.name || author.githubUsername || author.githubEmail || 'Unknown Author'
return {
avatar: author.avatar || await newAvatarForAuthor({ displayName, githubUsername: author.githubUsername }, author.githubEmail),
avatarFallback: `https://gravatar.com/avatar/${await digestStringAsSHA256(displayName)}?d=retro`,
role: author.role || 'Contributor',
kind: author.kind || 'person',
displayName,
githubEmail: author.githubEmail,
githubUsername: author.githubUsername,
githubEmail: author.githubEmail,
kind: author.kind || 'person',
role: author.role || 'Contributor',
avatar: author.avatar || await newAvatarForAuthor({ githubUsername: author.githubUsername, displayName }, author.githubEmail),
avatarFallback: `https://gravatar.com/avatar/${await digestStringAsSHA256(displayName)}?d=retro`,
}
}))
return {
authors: authorsTransformed,
url,
authors: authorsTransformed,
}
}),
)).filter(item => item != null)
+16 -16
View File
@@ -14,13 +14,13 @@ const config: SiteConfig = (globalThis as any).VITEPRESS_CONFIG
const base = config.userConfig.base || env.BASE_URL || '/'
interface Post {
date: ReturnType<typeof formatDate>
excerpt: string | undefined
frontmatter?: Record<string, any>
lang: string
title: string
url: string
urlWithoutLang: string
lang: string
date: ReturnType<typeof formatDate>
excerpt: string | undefined
frontmatter?: Record<string, any>
}
declare const data: Post[]
@@ -77,12 +77,12 @@ function withDirname(url?: string, cwd?: string) {
}
export default createContentLoader('**/blog/**/*.md', {
excerpt: true,
includeSrc: true,
render: true,
excerpt: true,
async transform(raw): Promise<Post[]> {
return (await Promise.all(raw
.map(async ({ excerpt, frontmatter, url }) => {
.map(async ({ url, frontmatter, excerpt }) => {
const foundLanguage = Object.values(config.userConfig.locales!).find((locale) => {
let normalizedLanguagePrefix = locale.lang || 'en'
if (!normalizedLanguagePrefix.startsWith('/')) {
@@ -117,19 +117,19 @@ export default createContentLoader('**/blog/**/*.md', {
const previewCoverDark = withBase(await fileToUrl(withDirname(fromAtAssets(frontmatter['preview-cover']?.dark), cwdFromUrl(url))), base)
const res = {
date: formatDate(frontmatter.date),
excerpt,
frontmatter: {
...frontmatter,
'preview-cover': {
dark: previewCoverDark,
light: previewCoverLight,
},
},
lang: foundLanguage?.lang || 'en',
title: frontmatter.title,
url,
urlWithoutLang: url.replace(`/${foundLanguage?.lang || 'en'}`, ''),
excerpt,
date: formatDate(frontmatter.date),
lang: foundLanguage?.lang || 'en',
frontmatter: {
...frontmatter,
'preview-cover': {
light: previewCoverLight,
dark: previewCoverDark,
},
},
}
return res
+10 -10
View File
@@ -7,25 +7,25 @@ import { formatDate } from '../utils/utils'
const config: SiteConfig = (globalThis as any).VITEPRESS_CONFIG
interface ChronicleEntry {
date: ReturnType<typeof formatDate>
excerpt: string | undefined
frontmatter?: Record<string, any>
lang: string
title: string
url: string
urlWithoutLang: string
lang: string
date: ReturnType<typeof formatDate>
excerpt: string | undefined
frontmatter?: Record<string, any>
}
declare const data: ChronicleEntry[]
export { data }
export default createContentLoader('**/chronicles/**/*.md', {
excerpt: true,
includeSrc: true,
render: true,
excerpt: true,
transform(raw): ChronicleEntry[] {
return raw
.map(({ excerpt, frontmatter, url }) => {
.map(({ url, frontmatter, excerpt }) => {
const foundLanguage = Object.values(config.userConfig.locales!).find((locale) => {
let normalizedLanguagePrefix = locale.lang || 'en'
if (!normalizedLanguagePrefix.startsWith('/')) {
@@ -36,13 +36,13 @@ export default createContentLoader('**/chronicles/**/*.md', {
})
return {
date: formatDate(frontmatter.date),
excerpt,
frontmatter,
lang: foundLanguage?.lang || 'en',
title: frontmatter.title,
url,
urlWithoutLang: url.replace(`/${foundLanguage?.lang || 'en'}`, ''),
excerpt,
date: formatDate(frontmatter.date),
lang: foundLanguage?.lang || 'en',
frontmatter,
}
})
.sort((a, b) => b.date.time - a.date.time)
@@ -9,102 +9,6 @@ import matter from 'gray-matter'
import { glob } from 'tinyglobby'
interface VitePressConfig extends ResolvedConfig {
vitepress: SiteConfig
}
export function frontmatterAssets(): Plugin {
let resolvedConfig: undefined | VitePressConfig
const mAssetAbsoluteUrlMetadata = new Map<string, { builtUrl?: string, hash?: string, url: string }>()
const mapAssetBuiltUrlAssetAbsoluteUrl = new Map<string, string>()
async function fileToUrl(file: string) {
if (!file) {
return {
url: file,
}
}
const parsed = parse(file)
const hash = createHash('sha256')
.update(await readFile(file))
.digest('hex')
.slice(0, 8)
return {
hash,
url: `/assets/${parsed.name}.${hash}${parsed.ext}`,
}
}
return {
async configResolved(config) {
resolvedConfig = config as VitePressConfig
const markdownFiles = await glob('**/*.md', { absolute: true, cwd: resolvedConfig?.vitepress.srcDir || '', ignore: ['**/node_modules/**'] })
for (const file of markdownFiles) {
const res = (await readFile(file))
const { data } = matter(res.toString('utf-8'))
if (Object.keys(data).length === 0) {
continue
}
recursivelyFindAtAssets(data, (matched) => {
const assetPath = fromAtAssets(matched)
let absoluteAssetPath: string
if (assetPath.startsWith('/')) {
absoluteAssetPath = join(resolvedConfig?.vitepress.srcDir || '', assetPath)
}
else {
absoluteAssetPath = join(dirname(file), assetPath)
}
mAssetAbsoluteUrlMetadata.set(absoluteAssetPath, { builtUrl: file, hash: '', url: file })
return undefined
})
}
for (const [key, value] of mAssetAbsoluteUrlMetadata) {
const { hash, url } = await fileToUrl(key)
mapAssetBuiltUrlAssetAbsoluteUrl.set(url, key)
mAssetAbsoluteUrlMetadata.set(key, { builtUrl: url, hash, url: value.url })
}
},
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
const requesting = withoutBase(req.url, resolvedConfig?.base)
if (!requesting || !mapAssetBuiltUrlAssetAbsoluteUrl.has(requesting)) {
return next()
}
const filePath = mapAssetBuiltUrlAssetAbsoluteUrl.get(requesting)
if (!filePath) {
return next()
}
const ext = parse(filePath).ext.slice(1)
const fileContent = await readFile(filePath)
res.writeHead(200, {
'Cache-Control': 'public, max-age=31536000, immutable',
'Content-Length': fileContent.length,
'Content-Type': ext === 'svg' ? 'image/svg+xml' : `image/${ext}`,
})
res.end(fileContent)
res.end()
})
},
enforce: 'pre',
name: '@proj-airi/docs:vite-plugin-frontmatter-assets',
async writeBundle() {
for (const [builtUrl, absoluteUrl] of mapAssetBuiltUrlAssetAbsoluteUrl.entries()) {
const content = await this.fs.readFile(absoluteUrl)
await this.fs.writeFile(join(resolvedConfig!.vitepress.outDir!, builtUrl), content)
}
},
}
}
function fromAtAssets(url: string): string {
const reg = /^@assets\((?:'(\S+)'|"(\S+)"|(\S+))\)$/
if (reg.test(url)) {
@@ -124,6 +28,10 @@ function fromAtAssets(url: string): string {
return url
}
interface VitePressConfig extends ResolvedConfig {
vitepress: SiteConfig
}
function recursivelyFindAtAssets(propertyMaybeObjectOrScalar: unknown, fn: (value: string) => string | undefined) {
if (typeof propertyMaybeObjectOrScalar === 'string') {
// eslint-disable-next-line regexp/no-unused-capturing-group
@@ -181,3 +89,95 @@ function withoutBase(url?: string, base?: string): string | undefined {
return url
}
export function frontmatterAssets(): Plugin {
let resolvedConfig: VitePressConfig | undefined
const mAssetAbsoluteUrlMetadata = new Map<string, { url: string, builtUrl?: string, hash?: string }>()
const mapAssetBuiltUrlAssetAbsoluteUrl = new Map<string, string>()
async function fileToUrl(file: string) {
if (!file) {
return {
url: file,
}
}
const parsed = parse(file)
const hash = createHash('sha256')
.update(await readFile(file))
.digest('hex')
.slice(0, 8)
return {
hash,
url: `/assets/${parsed.name}.${hash}${parsed.ext}`,
}
}
return {
name: '@proj-airi/docs:vite-plugin-frontmatter-assets',
enforce: 'pre',
async configResolved(config) {
resolvedConfig = config as VitePressConfig
const markdownFiles = await glob('**/*.md', { ignore: ['**/node_modules/**'], cwd: resolvedConfig?.vitepress.srcDir || '', absolute: true })
for (const file of markdownFiles) {
const res = (await readFile(file))
const { data } = matter(res.toString('utf-8'))
if (Object.keys(data).length === 0) {
continue
}
recursivelyFindAtAssets(data, (matched) => {
const assetPath = fromAtAssets(matched)
let absoluteAssetPath: string
if (assetPath.startsWith('/')) {
absoluteAssetPath = join(resolvedConfig?.vitepress.srcDir || '', assetPath)
}
else {
absoluteAssetPath = join(dirname(file), assetPath)
}
mAssetAbsoluteUrlMetadata.set(absoluteAssetPath, { url: file, builtUrl: file, hash: '' })
return undefined
})
}
for (const [key, value] of mAssetAbsoluteUrlMetadata) {
const { url, hash } = await fileToUrl(key)
mapAssetBuiltUrlAssetAbsoluteUrl.set(url, key)
mAssetAbsoluteUrlMetadata.set(key, { url: value.url, builtUrl: url, hash })
}
},
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
const requesting = withoutBase(req.url, resolvedConfig?.base)
if (!requesting || !mapAssetBuiltUrlAssetAbsoluteUrl.has(requesting)) {
return next()
}
const filePath = mapAssetBuiltUrlAssetAbsoluteUrl.get(requesting)
if (!filePath) {
return next()
}
const ext = parse(filePath).ext.slice(1)
const fileContent = await readFile(filePath)
res.writeHead(200, {
'Content-Type': ext === 'svg' ? 'image/svg+xml' : `image/${ext}`,
'Content-Length': fileContent.length,
'Cache-Control': 'public, max-age=31536000, immutable',
})
res.end(fileContent)
res.end()
})
},
async writeBundle() {
for (const [builtUrl, absoluteUrl] of mapAssetBuiltUrlAssetAbsoluteUrl.entries()) {
const content = await this.fs.readFile(absoluteUrl)
await this.fs.writeFile(join(resolvedConfig!.vitepress.outDir!, builtUrl), content)
}
},
}
}
+10 -10
View File
@@ -1,11 +1,21 @@
import type { DefaultTheme } from 'vitepress'
interface ExtraThemeConfig {
homepage: HomePageConfig
}
interface HomePageConfig {
buttons: ButtonItem[]
}
export interface ButtonItem extends Link {
primary?: boolean
}
export interface Link {
text?: string
link?: string
/**
* VitePress intercepts `<a>` tag clicks for SPA navigation, which can cause routing errors for external links.<br/>
* Adding a `target` attribute allows the browser to handle the navigation natively, avoiding this problem.
@@ -15,16 +25,6 @@ export interface Link {
* https://stackoverflow.com/questions/79348337/redirect-main-title-link-in-vitepress-to-my-personal-website/79386388#79386388
*/
target?: string
text?: string
}
export type ThemeConfig = DefaultTheme.Config & ExtraThemeConfig
interface ExtraThemeConfig {
homepage: HomePageConfig
}
interface HomePageConfig {
buttons: ButtonItem[]
}
+2 -2
View File
@@ -23,20 +23,20 @@ import '@fontsource/dm-serif-display/index.css'
import '@fontsource-variable/comfortaa/index.css'
export default {
Layout,
enhanceApp({ app, siteData }) {
if (!import.meta.env.SSR && import.meta.env.PROD) {
import('../modules/posthog')
}
const i18n = createI18n({
fallbackLocale: 'en',
legacy: false,
locale: siteData.value.lang || 'en',
fallbackLocale: 'en',
messages,
})
app.use(i18n)
app.component('ThemedVideo', ThemedVideo)
},
Layout,
} satisfies Theme
+9 -9
View File
@@ -2,21 +2,13 @@
// eslint-disable-next-line ts/ban-ts-comment
// @ts-nocheck
export class LRUCache {
cache
max
cache
constructor(max = 10) {
this.max = max
this.cache = new Map()
}
clear() {
this.cache.clear()
}
first() {
return this.cache.keys().next().value
}
get(key) {
const item = this.cache.get(key)
if (item !== undefined) {
@@ -36,4 +28,12 @@ export class LRUCache {
this.cache.delete(this.first())
this.cache.set(key, val)
}
first() {
return this.cache.keys().next().value
}
clear() {
this.cache.clear()
}
}
+6 -6
View File
@@ -1,16 +1,16 @@
export function formatDate(raw: string): {
string: string
time: number
string: string
} {
const date = new Date(raw)
date.setUTCHours(12)
return {
string: date.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
}),
time: +date,
string: date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
}),
}
}
@@ -148,9 +148,9 @@ And connect the `pgvector.rs` instance with Drizzle:
```typescript
export const chatMessagesTable = pgTable('chat_messages', {
id: uuid().primaryKey().defaultRandom(),
content: text().notNull().default(''),
content_vector_1024: vector({ dimensions: 1024 }),
id: uuid().primaryKey().defaultRandom(),
}, table => [
index('chat_messages_content_vector_1024_index').using('hnsw', table.content_vector_1024.op('vector_cosine_ops')),
])
@@ -260,11 +260,11 @@ import { index, pgTable, serial, text, vector } from 'drizzle-orm/pg-core'
export const demoTable = pgTable(
'demo',
{
description: text('description').notNull().default(''),
embedding: vector('embedding', { dimensions: 1536 }),
id: uuid().primaryKey().defaultRandom(),
title: text('title').notNull().default(''),
description: text('description').notNull().default(''),
url: text('url').notNull().default(''),
embedding: vector('embedding', { dimensions: 1536 }),
},
table => [
index('embeddingIndex').using('hnsw', table.embedding.op('vector_cosine_ops')),
@@ -301,14 +301,14 @@ integrations here:
let similarity: SQL<number>
switch (env.EMBEDDING_DIMENSION) {
case '768':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_768, embedding.embedding)}))`
case '1536':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
break
case '1024':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1024, embedding.embedding)}))`
break
case '1536':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
case '768':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_768, embedding.embedding)}))`
break
default:
throw new Error(`Unsupported embedding dimension: ${env.EMBEDDING_DIMENSION}`)
@@ -317,8 +317,8 @@ switch (env.EMBEDDING_DIMENSION) {
// Get top messages with similarity above threshold
const relevantMessages = await db
.select({
content: chatMessagesTable.content,
id: chatMessagesTable.id,
content: chatMessagesTable.content,
similarity: sql`${similarity} AS "similarity"`,
})
.from(chatMessagesTable)
@@ -52,11 +52,11 @@ import { invoke } from '@Tauri-apps/api/core'
export const mcp = [
{
name: 'list_tools',
description: 'List all tools',
execute: async () => {
return await invoke('list_tools')
},
name: 'list_tools'
}
}
]
```
@@ -157,7 +157,7 @@ Then on the JavaScript side, we can simply pass an object:
```javascript
import { invoke } from '@Tauri-apps/api/core'
invoke('call_tool', { args: { duration: 500, x1: 100, x2: 200, y1: 100, y2: 200 }, name: 'input_swipe' })
invoke('call_tool', { name: 'input_swipe', args: { x1: 100, y1: 100, x2: 200, y2: 200, duration: 500 } })
```
Super convenient!
@@ -114,9 +114,9 @@ services:
```typescript
export const chatMessagesTable = pgTable('chat_messages', {
id: uuid().primaryKey().defaultRandom(),
content: text().notNull().default(''),
content_vector_1024: vector({ dimensions: 1024 }),
id: uuid().primaryKey().defaultRandom(),
}, table => [
index('chat_messages_content_vector_1024_index').using('hnsw', table.content_vector_1024.op('vector_cosine_ops')),
])
@@ -213,11 +213,11 @@ import { index, pgTable, serial, text, vector } from 'drizzle-orm/pg-core'
export const demoTable = pgTable(
'demo',
{
description: text('description').notNull().default(''),
embedding: vector('embedding', { dimensions: 1536 }),
id: uuid().primaryKey().defaultRandom(),
title: text('title').notNull().default(''),
description: text('description').notNull().default(''),
url: text('url').notNull().default(''),
embedding: vector('embedding', { dimensions: 1536 }),
},
table => [
index('embeddingIndex').using('hnsw', table.embedding.op('vector_cosine_ops')),
@@ -252,14 +252,14 @@ CREATE INDEX "embeddingIndex" ON "demo" USING hnsw ("embedding" vector_cosine_op
let similarity: SQL<number>
switch (env.EMBEDDING_DIMENSION) {
case '768':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_768, embedding.embedding)}))`
case '1536':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
break
case '1024':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1024, embedding.embedding)}))`
break
case '1536':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
case '768':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_768, embedding.embedding)}))`
break
default:
throw new Error(`Unsupported embedding dimension: ${env.EMBEDDING_DIMENSION}`)
@@ -268,8 +268,8 @@ switch (env.EMBEDDING_DIMENSION) {
// 類似度が閾値を超える上位メッセージを取得
const relevantMessages = await db
.select({
content: chatMessagesTable.content,
id: chatMessagesTable.id,
content: chatMessagesTable.content,
similarity: sql`${similarity} AS "similarity"`,
})
.from(chatMessagesTable)
@@ -52,11 +52,11 @@ import { invoke } from '@Tauri-apps/api/core'
export const mcp = [
{
name: 'list_tools',
description: 'List all tools',
execute: async () => {
return await invoke('list_tools')
},
name: 'list_tools'
}
}
]
```
@@ -157,7 +157,7 @@ JavaScript 側では、単にオブジェクトを渡すだけです:
```javascript
import { invoke } from '@Tauri-apps/api/core'
invoke('call_tool', { args: { duration: 500, x1: 100, x2: 200, y1: 100, y2: 200 }, name: 'input_swipe' })
invoke('call_tool', { name: 'input_swipe', args: { x1: 100, y1: 100, x2: 200, y2: 200, duration: 500 } })
```
超便利!
@@ -143,9 +143,9 @@ Drizzle로 `pgvector.rs` 인스턴스에 연결하면:
```typescript
export const chatMessagesTable = pgTable('chat_messages', {
id: uuid().primaryKey().defaultRandom(),
content: text().notNull().default(''),
content_vector_1024: vector({ dimensions: 1024 }),
id: uuid().primaryKey().defaultRandom(),
}, table => [
index('chat_messages_content_vector_1024_index').using('hnsw', table.content_vector_1024.op('vector_cosine_ops')),
])
@@ -249,11 +249,11 @@ import { index, pgTable, serial, text, vector } from 'drizzle-orm/pg-core'
export const demoTable = pgTable(
'demo',
{
description: text('description').notNull().default(''),
embedding: vector('embedding', { dimensions: 1536 }),
id: uuid().primaryKey().defaultRandom(),
title: text('title').notNull().default(''),
description: text('description').notNull().default(''),
url: text('url').notNull().default(''),
embedding: vector('embedding', { dimensions: 1536 }),
},
table => [
index('embeddingIndex').using('hnsw', table.embedding.op('vector_cosine_ops')),
@@ -288,14 +288,14 @@ CREATE INDEX "embeddingIndex" ON "demo" USING hnsw ("embedding" vector_cosine_op
let similarity: SQL<number>
switch (env.EMBEDDING_DIMENSION) {
case '768':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_768, embedding.embedding)}))`
case '1536':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
break
case '1024':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1024, embedding.embedding)}))`
break
case '1536':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
case '768':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_768, embedding.embedding)}))`
break
default:
throw new Error(`Unsupported embedding dimension: ${env.EMBEDDING_DIMENSION}`)
@@ -304,8 +304,8 @@ switch (env.EMBEDDING_DIMENSION) {
// 임계값 이상의 유사도를 가진 상위 메시지를 가져온다
const relevantMessages = await db
.select({
content: chatMessagesTable.content,
id: chatMessagesTable.id,
content: chatMessagesTable.content,
similarity: sql`${similarity} AS "similarity"`,
})
.from(chatMessagesTable)
@@ -54,11 +54,11 @@ import { invoke } from '@Tauri-apps/api/core'
export const mcp = [
{
name: 'list_tools',
description: 'List all tools',
execute: async () => {
return await invoke('list_tools')
},
name: 'list_tools'
}
}
]
```
@@ -159,7 +159,7 @@ async fn call_tool(state: State<'_, Mutex<Option<McpClient>>>, name: String, arg
```javascript
import { invoke } from '@Tauri-apps/api/core'
invoke('call_tool', { args: { duration: 500, x1: 100, x2: 200, y1: 100, y2: 200 }, name: 'input_swipe' })
invoke('call_tool', { name: 'input_swipe', args: { x1: 100, y1: 100, x2: 200, y2: 200, duration: 500 } })
```
정말 편리하네요!
@@ -114,9 +114,9 @@ services:
```typescript
export const chatMessagesTable = pgTable('chat_messages', {
id: uuid().primaryKey().defaultRandom(),
content: text().notNull().default(''),
content_vector_1024: vector({ dimensions: 1024 }),
id: uuid().primaryKey().defaultRandom(),
}, table => [
index('chat_messages_content_vector_1024_index').using('hnsw', table.content_vector_1024.op('vector_cosine_ops')),
])
@@ -213,11 +213,11 @@ import { index, pgTable, serial, text, vector } from 'drizzle-orm/pg-core'
export const demoTable = pgTable(
'demo',
{
description: text('description').notNull().default(''),
embedding: vector('embedding', { dimensions: 1536 }),
id: uuid().primaryKey().defaultRandom(),
title: text('title').notNull().default(''),
description: text('description').notNull().default(''),
url: text('url').notNull().default(''),
embedding: vector('embedding', { dimensions: 1536 }),
},
table => [
index('embeddingIndex').using('hnsw', table.embedding.op('vector_cosine_ops')),
@@ -252,14 +252,14 @@ CREATE INDEX "embeddingIndex" ON "demo" USING hnsw ("embedding" vector_cosine_op
let similarity: SQL<number>
switch (env.EMBEDDING_DIMENSION) {
case '768':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_768, embedding.embedding)}))`
case '1536':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
break
case '1024':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1024, embedding.embedding)}))`
break
case '1536':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
case '768':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_768, embedding.embedding)}))`
break
default:
throw new Error(`Unsupported embedding dimension: ${env.EMBEDDING_DIMENSION}`)
@@ -268,8 +268,8 @@ switch (env.EMBEDDING_DIMENSION) {
// Get top messages with similarity above threshold
const relevantMessages = await db
.select({
content: chatMessagesTable.content,
id: chatMessagesTable.id,
content: chatMessagesTable.content,
similarity: sql`${similarity} AS "similarity"`,
})
.from(chatMessagesTable)
@@ -52,11 +52,11 @@ import { invoke } from '@Tauri-apps/api/core'
export const mcp = [
{
name: 'list_tools',
description: 'List all tools',
execute: async () => {
return await invoke('list_tools')
},
name: 'list_tools'
}
}
]
```
@@ -157,7 +157,7 @@ async fn call_tool(state: State<'_, Mutex<Option<McpClient>>>, name: String, arg
```javascript
import { invoke } from '@Tauri-apps/api/core'
invoke('call_tool', { args: { duration: 500, x1: 100, x2: 200, y1: 100, y2: 200 }, name: 'input_swipe' })
invoke('call_tool', { name: 'input_swipe', args: { x1: 100, y1: 100, x2: 200, y2: 200, duration: 500 } })
```
超方便!
+11 -11
View File
@@ -7,17 +7,6 @@ import { Transformer } from '@napi-rs/image'
const SOURCE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp']
async function main() {
const files = process.argv.slice(2).map(f => path.resolve(process.cwd(), f))
if (files.length === 0) {
console.error('No files provided.')
process.exit(1)
}
await Promise.all(files.map(f => stat(f)))
await Promise.allSettled(files.map(file => transform(file)))
}
async function transform(filePath: string): Promise<any> {
if ((await stat(filePath)).isDirectory()) {
return await Promise.allSettled(
@@ -38,4 +27,15 @@ async function transform(filePath: string): Promise<any> {
console.info(`${filePath} -> ${dist}`)
}
async function main() {
const files = process.argv.slice(2).map(f => path.resolve(process.cwd(), f))
if (files.length === 0) {
console.error('No files provided.')
process.exit(1)
}
await Promise.all(files.map(f => stat(f)))
await Promise.allSettled(files.map(file => transform(file)))
}
main()