Skip to content

Commit

Permalink
refactor: code structure
Browse files Browse the repository at this point in the history
  • Loading branch information
zce committed Nov 17, 2023
1 parent 64d2712 commit ed9324a
Show file tree
Hide file tree
Showing 17 changed files with 143 additions and 161 deletions.
15 changes: 0 additions & 15 deletions example/content/categories/index.yml

This file was deleted.

7 changes: 7 additions & 0 deletions example/content/categories/journal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: Journal
slug: journal
cover: journal.jpg
description: serving as a platform for academic exchange and dissemination of knowledge
meta:
title: Journal
description: with a focus on timely updates and research results. It is a reference tool for scholars and professionals in various fields.
3 changes: 3 additions & 0 deletions example/content/categories/photography.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: Travel
slug: travel
description: All travel logs
3 changes: 3 additions & 0 deletions example/content/categories/travel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: Photography
slug: photography
description: All photography logs
6 changes: 3 additions & 3 deletions example/velite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default defineConfig({
},
categories: {
name: 'Category',
pattern: 'categories/index.yml',
pattern: 'categories/*.yml',
fields: s
.object({
name: s.name(),
Expand Down Expand Up @@ -77,8 +77,8 @@ export default defineConfig({
.object({
title: s.title(),
slug: s.slug('post'),
date: s.date(),
updated: s.date().optional(),
date: s.isodate(),
updated: s.isodate().optional(),
cover: s.image().optional(),
description: s.paragraph().optional(),
draft: s.boolean().default(false),
Expand Down
22 changes: 14 additions & 8 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@
[![NPM Version][version-img]][version-url]
[![Code Style][style-img]][style-url]

## TODOs

example
copy-linked-files
--watch
mdx
reference parent

- [ ] excerpt
- [ ] nextjs plugin
- [ ] types generate
- [ ] image command (compress, resize, etc.)
- [ ] docs

## Backups

```typescript
Expand Down Expand Up @@ -84,14 +98,6 @@ export const mdx = ({ gfm = true, removeComments = true, flattenImage = true, fl
}
```

## TODOs

- [ ] excerpt
- [ ] nextjs plugin
- [ ] types generate
- [ ] image command (compress, resize, etc.)
- [ ] docs

## Installation

```shell
Expand Down
132 changes: 67 additions & 65 deletions src/builder.ts → src/build.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
/**
* @file Builder
*/

import { mkdir, rm, watch, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import glob from 'fast-glob'
Expand Down Expand Up @@ -31,6 +27,51 @@ class Builder {
this.result = {}
}

/**
* create builder instance
*/
static async create(options: Options) {
// resolve config
const config = await resolveConfig({ filename: options.config, clean: options.clean, verbose: options.verbose })

// register user loaders
config.loaders.forEach(addLoader)

// init static output config
initOutputConfig(config.output)

// prerequisite
if (config.clean) {
// clean output directories if `--clean` requested
await rm(config.output.data, { recursive: true, force: true })
await rm(config.output.static, { recursive: true, force: true })
config.verbose && console.log('cleaned output directories')
}

return new Builder(config)
}

/**
* output result to dist
*/
async output() {
const { output } = this.config

await mkdir(output.data, { recursive: true })

await Promise.all(
Object.entries(this.result).map(async ([name, data]) => {
if (data == null) return
const json = JSON.stringify(data, null, 2)
await writeFile(join(output.data, name + '.json'), json)
console.log(`wrote ${data.length ?? 1} ${name} to '${join(output.data, name + '.json')}'`)
})
)
}

/**
* build content with config
*/
async build() {
if (this.config == null) throw new Error('config not initialized')
const { root, verbose, schemas, onSuccess } = this.config
Expand All @@ -39,26 +80,22 @@ class Builder {

cache.clear() // clear cache in case of rebuild

const files: File[] = []

const tasks = Object.entries(schemas).map(async ([name, schema]) => {
const filenames = await glob(schema.pattern, { cwd: root, onlyFiles: true, ignore: ['**/_*'] })
verbose && console.log(`found ${filenames.length} files matching '${schema.pattern}'`)

const files = await Promise.all(
filenames.map(async file => {
const doc = await File.create(join(root, file))
await doc.load()
await doc.parse(schema.fields)
return doc
const result = await Promise.all(
filenames.map(async filename => {
const file = await File.create(join(root, filename))
const result = await file.parse(schema.fields)
files.push(file)
return result
})
)

const report = reporter(files, { quiet: true, verbose })
report.length > 0 && console.log(report)

const data = files
.map(f => f.data.result ?? [])
.flat()
.filter(Boolean)
const data = result.flat().filter(Boolean)

if (schema.single) {
if (data.length > 1) {
Expand All @@ -70,37 +107,25 @@ class Builder {
return [name, data] as const
})

const collections = await Promise.all(tasks)

const result = Object.fromEntries<any>(collections)
const entities = await Promise.all(tasks)

// user callback
onSuccess != null && (await onSuccess(result))
// report if any error in parsing
const report = reporter(files, { quiet: true, verbose })
report.length > 0 && console.log(report)

Object.assign(this.result, result)
}
const collections = Object.fromEntries<any>(entities)

/**
* output result to dist
*/
async output() {
const { output } = this.config
// user callback
if (typeof onSuccess === 'function') {
await onSuccess(collections)
}

await mkdir(output.data, { recursive: true })
Object.assign(this.result, collections)

await Promise.all(
Object.entries(this.result).map(async ([name, data]) => {
if (data == null) return
const json = JSON.stringify(data, null, 2)
await writeFile(join(output.data, name + '.json'), json)
console.log(`wrote ${data.length ?? 1} ${name} to '${join(output.data, name + '.json')}'`)
})
)
await this.output()
}

async watch() {
if (!this.config.watch) return

console.log('watching for changes')

const { schemas } = this.config
Expand All @@ -112,38 +137,15 @@ class Builder {
const { filename } = event
if (filename == null) continue
if (!allPatterns.some(pattern => micromatch.isMatch(filename, pattern))) continue
// TODO: rebuild only changed file
// rebuild all
await this.build()
await this.output()
await this.build() // TODO: rebuild only changed file
}
}

static async create(options: Options) {
// resolve config
const config = await resolveConfig({ filename: options.config, clean: options.clean, verbose: options.verbose, watch: options.watch })

// register user loaders
config.loaders.forEach(addLoader)

// init static output config
initOutputConfig(config.output)

// prerequisite
if (config.clean) {
// clean output directories if `--clean` requested
await rm(config.output.data, { recursive: true, force: true })
await rm(config.output.static, { recursive: true, force: true })
config.verbose && console.log('cleaned output directories')
}

return new Builder(config)
}
}

export const build = async (options: Options) => {
const builder = await Builder.create(options)
await builder.build()
await builder.output()
if (!options.watch) return
await builder.watch()
}
6 changes: 1 addition & 5 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
#!/usr/bin/env node

/**
* @file CLI entry point
*/
import cac from 'cac'

import { name, version } from '../package.json'
import { build } from './builder'
import { build } from './build'

const cli = cac(name).version(version).help()

Expand Down
6 changes: 0 additions & 6 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
/**
* @file Load config from user's project
*/

import { access, rm } from 'node:fs/promises'
import { dirname, join, resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
Expand Down Expand Up @@ -75,7 +71,6 @@ const loadConfig = async (filename: string): Promise<UserConfig> => {
interface Options {
filename?: string
clean?: boolean
watch?: boolean
verbose?: boolean
}

Expand Down Expand Up @@ -117,7 +112,6 @@ export const resolveConfig = async (options: Options = {}): Promise<Config> => {
ignoreFileExtensions: userConfig.output?.ignoreFileExtensions ?? []
},
clean: options.clean ?? userConfig.clean ?? false,
watch: options.watch ?? false,
verbose,
schemas: userConfig.schemas,
loaders: userConfig.loaders ?? [],
Expand Down
58 changes: 26 additions & 32 deletions src/file.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,45 @@
/**
* @file Document
*/

import { readFile } from 'node:fs/promises'
import { VFile } from 'vfile'
import { ZodType } from 'zod'

import { resolveLoader } from './loaders'

declare module 'vfile' {
interface DataMap {
original: Record<string, any> | Record<string, any>[]
result: Record<string, any> | Record<string, any>[]
}
}
import type { Collection } from './types'
import type { ZodType } from 'zod'

export class File extends VFile {
/**
* create file instance
* @param path file path
* @returns file instance
*/
static async create(path: string) {
const value = await readFile(path, 'utf8')
return new File({ path, value })
}

/**
* load file content into `this.data.original`
* @returns original data from file
*/
async load(): Promise<void> {
try {
if (this.extname == null) {
throw new Error('can not parse file without extension')
}
const loader = resolveLoader(this.path)
if (loader == null) {
throw new Error(`no loader found for '${this.path}'`)
}
await loader.load(this)
} catch (err: any) {
this.message(err.message)
private async load(): Promise<Collection> {
if (this.extname == null) {
throw new Error('can not parse file without extension')
}
const loader = resolveLoader(this.path)
if (loader == null) {
throw new Error(`no loader found for '${this.path}'`)
}
return loader.load(this)
}

/**
* parse `this.data.original` into `this.data.result` with given fields schema
* parse file content with given fields schema
* @param fields fields schema
* @returns collection data from file, or undefined if parsing failed
*/
async parse(fields: ZodType): Promise<void> {
async parse(fields: ZodType): Promise<Collection | undefined> {
try {
const { original } = this.data
const original = await this.load()

if (original == null || Object.keys(original).length === 0) {
throw new Error('no data parsed from this file')
Expand All @@ -64,14 +63,9 @@ export class File extends VFile {
})
)

this.data.result = processed.length === 1 ? processed[0] : processed
return processed.length === 1 ? processed[0] : processed
} catch (err: any) {
this.message(err.message)
}
}

static async create(path: string) {
const value = await readFile(path, 'utf8')
return new File({ path, value })
}
}
Loading

0 comments on commit ed9324a

Please sign in to comment.