Skip to content

Commit 5f8cbb5

Browse files
committed
feat(loader): base import loader
1 parent c7eeecb commit 5f8cbb5

File tree

6 files changed

+130
-132
lines changed

6 files changed

+130
-132
lines changed

packages/loader/src/file.ts

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
import { access, constants, readFile, writeFile } from 'node:fs/promises'
2-
import { pathToFileURL } from 'node:url'
1+
import { Context } from '@cordisjs/core'
2+
import { dirname, extname, resolve } from 'node:path'
3+
import { access, constants, readdir, readFile, stat, writeFile } from 'node:fs/promises'
4+
import { fileURLToPath, pathToFileURL } from 'node:url'
35
import * as yaml from 'js-yaml'
46
import { Entry } from './entry.ts'
7+
import { EntryGroup } from './group.ts'
58
import { Loader } from './shared.ts'
69

7-
export class FileLoader<T extends Loader = Loader> {
10+
export class LoaderFile {
811
public url: string
912
public suspend = false
1013
public mutable = false
1114

1215
private _writeTask?: NodeJS.Timeout
1316

14-
constructor(public loader: T, public name: string, public type?: string) {
17+
constructor(public ctx: Context, public name: string, public type?: string) {
1518
this.url = pathToFileURL(name).href
1619
}
1720

@@ -48,7 +51,7 @@ export class FileLoader<T extends Loader = Loader> {
4851
}
4952

5053
write(config: Entry.Options[]) {
51-
this.loader.ctx.emit('config')
54+
this.ctx.emit('config')
5255
clearTimeout(this._writeTask)
5356
this._writeTask = setTimeout(() => {
5457
this._writeTask = undefined
@@ -57,8 +60,8 @@ export class FileLoader<T extends Loader = Loader> {
5760
}
5861

5962
async import(name: string) {
60-
if (this.loader.internal) {
61-
return this.loader.internal.import(name, this.url, {})
63+
if (this.ctx.loader.internal) {
64+
return this.ctx.loader.internal.import(name, this.url, {})
6265
} else {
6366
return import(name)
6467
}
@@ -69,7 +72,7 @@ export class FileLoader<T extends Loader = Loader> {
6972
}
7073
}
7174

72-
export namespace FileLoader {
75+
export namespace LoaderFile {
7376
export const writable = {
7477
'.json': 'application/json',
7578
'.yaml': 'application/yaml',
@@ -85,3 +88,99 @@ export namespace FileLoader {
8588
}
8689
}
8790
}
91+
92+
export class BaseLoader extends EntryGroup {
93+
public file!: LoaderFile
94+
95+
constructor(public ctx: Context) {
96+
super(ctx)
97+
ctx.on('ready', () => this.start())
98+
}
99+
100+
async start() {
101+
await this.refresh()
102+
await this.file.checkAccess()
103+
}
104+
105+
async refresh() {
106+
this._update(await this.file.read())
107+
}
108+
109+
stop() {
110+
this.file?.dispose()
111+
return super.stop()
112+
}
113+
114+
write() {
115+
return this.file!.write(this.data)
116+
}
117+
118+
async init(baseDir: string, options: Loader.Config) {
119+
if (options.filename) {
120+
const filename = resolve(baseDir, options.filename)
121+
const stats = await stat(filename)
122+
if (stats.isFile()) {
123+
baseDir = dirname(filename)
124+
const ext = extname(filename)
125+
const type = LoaderFile.writable[ext]
126+
if (!LoaderFile.supported.has(ext)) {
127+
throw new Error(`extension "${ext}" not supported`)
128+
}
129+
this.file = new LoaderFile(this.ctx, filename, type)
130+
} else {
131+
baseDir = filename
132+
await this.findConfig(baseDir, options)
133+
}
134+
} else {
135+
await this.findConfig(baseDir, options)
136+
}
137+
this.ctx.provide('baseDir', baseDir, true)
138+
}
139+
140+
private async findConfig(baseDir: string, options: Loader.Config) {
141+
const { name, initial } = options
142+
const dirents = await readdir(baseDir, { withFileTypes: true })
143+
for (const extension of LoaderFile.supported) {
144+
const dirent = dirents.find(dirent => dirent.name === name + extension)
145+
if (!dirent) continue
146+
if (!dirent.isFile()) {
147+
throw new Error(`config file "${dirent.name}" is not a file`)
148+
}
149+
const type = LoaderFile.writable[extension]
150+
const filename = resolve(baseDir, name + extension)
151+
this.file = new LoaderFile(this.ctx, filename, type)
152+
return
153+
}
154+
if (initial) {
155+
const type = LoaderFile.writable['.yml']
156+
const filename = resolve(baseDir, name + '.yml')
157+
this.file = new LoaderFile(this.ctx, filename, type)
158+
return this.file.write(initial as any)
159+
}
160+
throw new Error('config file not found')
161+
}
162+
}
163+
164+
export namespace Import {
165+
export interface Config {
166+
url: string
167+
// disabled?: boolean
168+
}
169+
}
170+
171+
export class Import extends BaseLoader {
172+
constructor(ctx: Context, public config: Import.Config) {
173+
super(ctx)
174+
}
175+
176+
async start() {
177+
const { url } = this.config
178+
const filename = fileURLToPath(new URL(url, this.ctx.loader.file.url))
179+
const ext = extname(filename)
180+
if (!LoaderFile.supported.has(ext)) {
181+
throw new Error(`extension "${ext}" not supported`)
182+
}
183+
this.file = new LoaderFile(this.ctx, filename, LoaderFile.writable[ext])
184+
await super.start()
185+
}
186+
}

packages/loader/src/group.ts

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { Context } from '@cordisjs/core'
2-
import { FileLoader } from './file.ts'
32
import { Entry } from './entry.ts'
4-
import { fileURLToPath } from 'node:url'
5-
import { extname } from 'node:path'
63

74
export class EntryGroup {
85
public data: Entry.Options[] = []
@@ -52,11 +49,7 @@ export class EntryGroup {
5249
}
5350

5451
write() {
55-
if (this.ctx.scope.entry) {
56-
return this.ctx.scope.entry!.parent.write()
57-
} else {
58-
return this.ctx.loader.file.write(this.ctx.loader.data)
59-
}
52+
return this.ctx.scope.entry!.parent.write()
6053
}
6154

6255
stop() {
@@ -94,55 +87,3 @@ export function defineGroup(config?: Entry.Options[], options: GroupOptions = {}
9487
}
9588

9689
export const group = defineGroup()
97-
98-
export class BaseImportLoader extends EntryGroup {
99-
public file!: FileLoader
100-
101-
constructor(public ctx: Context) {
102-
super(ctx)
103-
ctx.on('ready', () => this.start())
104-
}
105-
106-
async start() {
107-
await this.refresh()
108-
await this.file.checkAccess()
109-
}
110-
111-
async refresh() {
112-
this._update(await this.file.read())
113-
}
114-
115-
stop() {
116-
this.file?.dispose()
117-
return super.stop()
118-
}
119-
120-
write() {
121-
return this.file!.write(this.data)
122-
}
123-
}
124-
125-
export namespace Import {
126-
export interface Config {
127-
url: string
128-
// disabled?: boolean
129-
}
130-
}
131-
132-
export class Import extends BaseImportLoader {
133-
constructor(ctx: Context, public config: Import.Config) {
134-
super(ctx)
135-
}
136-
137-
async start() {
138-
const { url } = this.config
139-
const filename = fileURLToPath(new URL(url, this.ctx.loader.file.url))
140-
const ext = extname(filename)
141-
if (!FileLoader.supported.has(ext)) {
142-
throw new Error(`extension "${ext}" not supported`)
143-
}
144-
this.file = new FileLoader(this.ctx.loader, filename, FileLoader.writable[ext])
145-
this._update(await this.file.read())
146-
await this.file.checkAccess()
147-
}
148-
}

packages/loader/src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ const oldEnv = { ...process.env }
1111
class NodeLoader extends Loader {
1212
static readonly exitCode = 51
1313

14-
async start() {
14+
async init(baseDir: string, options: Loader.Config) {
15+
await super.init(baseDir, options)
16+
1517
// restore process.env
1618
for (const key in process.env) {
1719
if (key in oldEnv) {
@@ -26,7 +28,7 @@ class NodeLoader extends Loader {
2628
const envFiles = ['.env', '.env.local']
2729
for (const filename of envFiles) {
2830
try {
29-
const raw = await fs.readFile(path.resolve(this.baseDir, filename), 'utf8')
31+
const raw = await fs.readFile(path.resolve(this.ctx.baseDir, filename), 'utf8')
3032
Object.assign(override, dotenv.parse(raw))
3133
} catch {}
3234
}
@@ -35,8 +37,6 @@ class NodeLoader extends Loader {
3537
for (const key in override) {
3638
process.env[key] = override[key]
3739
}
38-
39-
return await super.start()
4040
}
4141

4242
exit(code = NodeLoader.exitCode) {

packages/loader/src/shared.ts

Lines changed: 4 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import { Context, EffectScope } from '@cordisjs/core'
22
import { Dict, isNullable, valueMap } from 'cosmokit'
3-
import { readdir, stat } from 'node:fs/promises'
43
import { ModuleLoader } from './internal.ts'
54
import { interpolate } from './utils.ts'
65
import { Entry } from './entry.ts'
7-
import { BaseImportLoader } from './group.ts'
8-
import { FileLoader } from './file.ts'
9-
import * as path from 'node:path'
6+
import { BaseLoader } from './file.ts'
107

118
export * from './entry.ts'
129
export * from './file.ts'
@@ -21,6 +18,7 @@ declare module '@cordisjs/core' {
2118
}
2219

2320
interface Context {
21+
baseDir: string
2422
loader: Loader
2523
}
2624

@@ -38,20 +36,18 @@ declare module '@cordisjs/core' {
3836
export namespace Loader {
3937
export interface Config {
4038
name: string
41-
immutable?: boolean
4239
initial?: Omit<Entry.Options, 'id'>[]
4340
filename?: string
4441
}
4542
}
4643

47-
export abstract class Loader extends BaseImportLoader {
44+
export abstract class Loader extends BaseLoader {
4845
// TODO auto inject optional when provided?
4946
static inject = {
5047
optional: ['loader'],
5148
}
5249

5350
// process
54-
public baseDir = process.cwd()
5551
public envData = process.env.CORDIS_SHARED
5652
? JSON.parse(process.env.CORDIS_SHARED)
5753
: { startTime: Date.now() }
@@ -106,55 +102,13 @@ export abstract class Loader extends BaseImportLoader {
106102
}
107103

108104
async start() {
109-
if (this.config.filename) {
110-
const filename = path.resolve(this.baseDir, this.config.filename)
111-
const stats = await stat(filename)
112-
if (stats.isFile()) {
113-
this.baseDir = path.dirname(filename)
114-
const extname = path.extname(filename)
115-
const type = FileLoader.writable[extname]
116-
if (!FileLoader.supported.has(extname)) {
117-
throw new Error(`extension "${extname}" not supported`)
118-
}
119-
this.file = new FileLoader(this, filename, type)
120-
} else {
121-
this.baseDir = filename
122-
await this.findConfig()
123-
}
124-
} else {
125-
await this.findConfig()
126-
}
127-
this.ctx.provide('baseDir', this.baseDir, true)
128-
105+
await this.init(process.cwd(), this.config)
129106
await super.start()
130107
while (this.tasks.size) {
131108
await Promise.all(this.tasks)
132109
}
133110
}
134111

135-
private async findConfig() {
136-
const { name, initial } = this.config
137-
const dirents = await readdir(this.baseDir, { withFileTypes: true })
138-
for (const extension of FileLoader.supported) {
139-
const dirent = dirents.find(dirent => dirent.name === name + extension)
140-
if (!dirent) continue
141-
if (!dirent.isFile()) {
142-
throw new Error(`config file "${dirent.name}" is not a file`)
143-
}
144-
const type = FileLoader.writable[extension]
145-
const filename = path.resolve(this.baseDir, name + extension)
146-
this.file = new FileLoader(this, filename, type)
147-
return
148-
}
149-
if (initial) {
150-
const type = FileLoader.writable['.yml']
151-
const filename = path.resolve(this.baseDir, name + '.yml')
152-
this.file = new FileLoader(this, filename, type)
153-
return this.file.write(initial as any)
154-
}
155-
throw new Error('config file not found')
156-
}
157-
158112
interpolate(source: any) {
159113
if (typeof source === 'string') {
160114
return interpolate(source, this.params, /\$\{\{(.+?)\}\}/g)

packages/loader/tests/index.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { expect } from 'chai'
22
import { Context } from '@cordisjs/core'
33
import MockLoader from './utils'
44

5-
describe('loader: basic', () => {
5+
describe('loader: basic support', () => {
66
const root = new Context()
77
root.plugin(MockLoader)
88
const loader = root.loader as MockLoader
@@ -32,7 +32,7 @@ describe('loader: basic', () => {
3232
disabled: true,
3333
}],
3434
}])
35-
await loader.start()
35+
await loader.refresh()
3636

3737
loader.expectEnable(foo, {})
3838
loader.expectEnable(bar, { a: 1 })
@@ -52,7 +52,7 @@ describe('loader: basic', () => {
5252
id: '4',
5353
name: 'qux',
5454
}])
55-
await loader.start()
55+
await loader.refresh()
5656

5757
loader.expectEnable(foo, {})
5858
loader.expectDisable(bar)

0 commit comments

Comments
 (0)