-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(config): incremental sync packages
- Loading branch information
Showing
15 changed files
with
217 additions
and
153 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,88 +1,117 @@ | ||
/// <reference types="@types/node" /> | ||
|
||
import { defineProperty, Dict, pick } from 'cosmokit' | ||
import { dirname } from 'path' | ||
import { readdir, readFile } from 'fs/promises' | ||
import { Awaitable, defineProperty, Dict, pick } from 'cosmokit' | ||
import { dirname } from 'node:path' | ||
import { createRequire } from 'node:module' | ||
import { readdir, readFile } from 'node:fs/promises' | ||
import { PackageJson, SearchObject, SearchResult } from './types' | ||
import { conclude } from './utils' | ||
|
||
export interface LocalScanner extends SearchResult {} | ||
interface LocalObject extends Pick<SearchObject, 'shortname' | 'workspace' | 'manifest'> { | ||
package: Pick<PackageJson, 'name' | 'version' | 'peerDependencies' | 'peerDependenciesMeta'> | ||
} | ||
|
||
export class LocalScanner { | ||
private cache: Dict<Promise<SearchObject>> | ||
private task: Promise<SearchObject[]> | ||
export interface LocalScanner extends SearchResult<LocalObject>, LocalScanner.Options {} | ||
|
||
constructor(public baseDir: string) { | ||
defineProperty(this, 'cache', {}) | ||
export namespace LocalScanner { | ||
export interface Options { | ||
onFailure?(reason: any, name: string): void | ||
onSuccess?(object: LocalObject): Awaitable<void> | ||
} | ||
} | ||
|
||
onError(reason: any, name: string) {} | ||
function clear(object: Dict) { | ||
for (const key of Object.keys(object)) { | ||
delete object[key] | ||
} | ||
} | ||
|
||
export class LocalScanner { | ||
public cache: Dict<LocalObject> = Object.create(null) | ||
|
||
private subTasks!: Dict<Promise<LocalObject | undefined>> | ||
private mainTask?: Promise<LocalObject[]> | ||
private require!: NodeRequire | ||
|
||
constructor(public baseDir: string, options: LocalScanner.Options = {}) { | ||
defineProperty(this, 'require', createRequire(baseDir + '/package.json')) | ||
Object.assign(this, options) | ||
} | ||
|
||
async _collect() { | ||
this.cache = {} | ||
clear(this.cache) | ||
clear(this.subTasks) | ||
let root = this.baseDir | ||
const tasks: Promise<void>[] = [] | ||
const directoryTasks: Promise<void>[] = [] | ||
while (1) { | ||
tasks.push(this.loadDirectory(root)) | ||
directoryTasks.push(this.loadDirectory(root)) | ||
const parent = dirname(root) | ||
if (root === parent) break | ||
root = parent | ||
} | ||
await Promise.all(tasks) | ||
return Promise.all(Object.values(this.cache)) | ||
await Promise.all(directoryTasks) | ||
await Promise.allSettled(Object.values(this.subTasks)) | ||
return Object.values(this.cache) | ||
} | ||
|
||
async collect(forced = false) { | ||
if (forced) delete this.task | ||
this.objects = await (this.task ||= this._collect()) | ||
if (forced) delete this.mainTask | ||
this.objects = await (this.mainTask ||= this._collect()) | ||
} | ||
|
||
private async loadDirectory(baseDir: string) { | ||
const base = baseDir + '/node_modules' | ||
const files = await readdir(base).catch(() => []) | ||
for (const name of files) { | ||
if (name.startsWith('cordis-plugin-')) { | ||
this.cache[name] ||= this.loadPackage(name) | ||
this.loadPackage(name) | ||
} else if (name.startsWith('@')) { | ||
const base2 = base + '/' + name | ||
const files = await readdir(base2).catch(() => []) | ||
for (const name2 of files) { | ||
if (name === '@cordisjs' && name2.startsWith('plugin-') || name2.startsWith('cordis-plugin-')) { | ||
this.cache[name + '/' + name2] ||= this.loadPackage(name + '/' + name2) | ||
this.loadPackage(name + '/' + name2) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
private async loadPackage(name: string) { | ||
async loadPackage(name: string) { | ||
return this.subTasks[name] ||= this._loadPackage(name) | ||
} | ||
|
||
private async _loadPackage(name: string) { | ||
try { | ||
return await this.parsePackage(name) | ||
const [meta, workspace] = await this.loadManifest(name) | ||
const object: LocalObject = { | ||
workspace, | ||
manifest: conclude(meta), | ||
shortname: meta.name.replace(/(cordis-|^@cordisjs\/)plugin-/, ''), | ||
package: pick(meta, [ | ||
'name', | ||
'version', | ||
'peerDependencies', | ||
'peerDependenciesMeta', | ||
]), | ||
} | ||
this.cache[name] = object | ||
await this.onSuccess?.(object) | ||
return object | ||
} catch (error) { | ||
this.onError(error, name) | ||
this.onFailure?.(error, name) | ||
} | ||
} | ||
|
||
private async loadManifest(name: string) { | ||
const filename = require.resolve(name + '/package.json') | ||
const filename = this.require.resolve(name + '/package.json') | ||
const meta: PackageJson = JSON.parse(await readFile(filename, 'utf8')) | ||
meta.peerDependencies ||= {} | ||
meta.peerDependenciesMeta ||= {} | ||
return [meta, !filename.includes('node_modules')] as const | ||
} | ||
|
||
protected async parsePackage(name: string) { | ||
const [data, workspace] = await this.loadManifest(name) | ||
return { | ||
workspace, | ||
manifest: conclude(data), | ||
shortname: data.name.replace(/(cordis-|^@cordisjs\/)plugin-/, ''), | ||
package: pick(data, [ | ||
'name', | ||
'version', | ||
'peerDependencies', | ||
'peerDependenciesMeta', | ||
]), | ||
} as SearchObject | ||
toJSON(): SearchResult<LocalObject> { | ||
return pick(this, ['total', 'time', 'objects']) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.