Skip to content

Commit

Permalink
feat(registry): check ecosystem for remote scanner
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed May 17, 2024
1 parent 2178f5c commit 88bfaea
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 95 deletions.
2 changes: 1 addition & 1 deletion packages/registry/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './local'
export * from './remote'
export * from './types'
export * from './utils'
export * from './manifest'
16 changes: 8 additions & 8 deletions packages/registry/src/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { dirname } from 'node:path'
import { createRequire } from 'node:module'
import { Dirent } from 'node:fs'
import { readdir, readFile } from 'node:fs/promises'
import { DependencyKey, Ecosystem, PackageJson, SearchObject, SearchResult } from './types'
import { conclude } from './utils'
import { DependencyKey, PackageJson, SearchObject, SearchResult } from './types'
import { Ecosystem, Manifest } from './manifest'

const LocalKey = ['name', 'version', 'peerDependencies', 'peerDependenciesMeta'] as const
type LocalKeys = typeof LocalKey[number]
Expand Down Expand Up @@ -83,11 +83,11 @@ export class LocalScanner {

// check for candidates
this.ecosystems.push({
name: 'cordis',
property: 'cordis',
inject: [],
pattern: ['cordis-plugin-*', '@cordisjs/plugin-*'],
keywords: ['cordis', 'plugin'],
peerDependencies: { cordis: '*' },
inject: [],
})
while (this.ecosystems.length) {
const ecosystem = this.ecosystems.shift()!
Expand Down Expand Up @@ -125,12 +125,12 @@ export class LocalScanner {
return results.flat(1).filter((x): x is string => !!x)
}

private loadEcosystem(eco: Ecosystem) {
private loadEcosystem(ecosystem: Ecosystem) {
for (const [name, { meta, workspace }] of Object.entries(this.candidates)) {
const shortname = Ecosystem.check(eco, meta)
const shortname = Ecosystem.check(ecosystem, meta)
if (!shortname) continue
delete this.candidates[name]
const manifest = conclude(meta, eco.property)
const manifest = Manifest.conclude(meta, ecosystem.property)
this.pkgTasks[name] ||= this.loadPackage(name, {
shortname,
workspace,
Expand All @@ -139,11 +139,11 @@ export class LocalScanner {
})
if (!manifest.ecosystem) continue
this.ecosystems.push({
name,
property: manifest.ecosystem.property || 'cordis',
inject: manifest.service?.implements || [],
pattern: manifest.ecosystem.pattern || [`${name}-plugin-`],
keywords: manifest.ecosystem.keywords || [name, 'plugin'],
peerDependencies: manifest.ecosystem.peerDependencies || { [name]: '*' },
})
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Dict } from 'cosmokit'
import { Manifest, PackageJson } from './types'
import { PackageJson } from './types'

interface Ensure<T> {
(value: any): T | undefined
Expand Down Expand Up @@ -36,6 +36,32 @@ export namespace Ensure {
export const string = primitive<string>('string')
}

export interface Ecosystem {
name: string
property: string
inject: string[]
pattern: string[]
keywords: string[]
}

export namespace Ecosystem {
export function check(ecosystem: Ecosystem, meta: PackageJson) {
if (!meta.peerDependencies?.[ecosystem.name]) return
for (const pattern of ecosystem.pattern) {
const regexp = new RegExp('^' + pattern.replace('*', '.*') + '$')
let prefix = '', name = meta.name
if (!pattern.startsWith('@')) {
prefix = /^@.+\//.exec(meta.name)?.[0] || ''
name = name.slice(prefix.length)
}
if (!regexp.test(name)) continue
const index = pattern.indexOf('*')
return prefix + name.slice(index)
}
if (ecosystem.property in meta) return meta.name
}
}

function concludeExport(base?: Manifest.Export | null, description?: string) {
// undefined values are dropped during serialization
if (typeof base !== 'object') return undefined!
Expand Down Expand Up @@ -63,8 +89,31 @@ function concludeExport(base?: Manifest.Export | null, description?: string) {
return result
}

export function conclude(meta: PackageJson, prop = 'cordis'): Manifest {
return {
export interface Manifest extends Manifest.Export {
hidden?: boolean
preview?: boolean
insecure?: boolean
category?: string
public?: string[]
exports?: Dict<Manifest.Export | null>
ecosystem?: Partial<Ecosystem>
}

export namespace Manifest {
export interface Export {
browser?: boolean
description?: string | Dict<string>
service?: Manifest.Service
resources?: Dict
}

export interface Service {
required?: string[]
optional?: string[]
implements?: string[]
}

export const conclude = (meta: PackageJson, prop = 'cordis'): Manifest => ({
...concludeExport(meta[prop], meta.description),
hidden: Ensure.boolean(meta[prop]?.hidden),
preview: Ensure.boolean(meta[prop]?.preview),
Expand All @@ -76,8 +125,7 @@ export function conclude(meta: PackageJson, prop = 'cordis'): Manifest {
inject: Ensure.array(ecosystem.inject),
pattern: Ensure.array(ecosystem.pattern),
keywords: Ensure.array(ecosystem.keywords),
peerDependencies: Ensure.dict(ecosystem.peerDependencies),
})),
exports: Ensure.dict(meta[prop]?.exports, concludeExport),
}
})
}
44 changes: 23 additions & 21 deletions packages/registry/src/remote.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Awaitable, Dict, Time } from 'cosmokit'
import { Ecosystem, Registry, RemotePackage, SearchObject, SearchResult } from './types'
import { conclude } from './utils'
import { Registry, RemotePackage, SearchObject, SearchResult } from './types'
import { Ecosystem, Manifest } from './manifest'
import { compare } from 'semver'
import pMap from 'p-map'

Expand Down Expand Up @@ -47,11 +47,11 @@ export class RemoteScanner {

constructor(public options: RemoteScanner.Options) {
this.ecosystems.cordis = {
name: 'cordis',
property: 'cordis',
inject: [],
pattern: ['cordis-plugin-*', '@cordisjs/plugin-*'],
keywords: ['cordis', 'plugin'],
peerDependencies: { cordis: '*' },
}
}

Expand All @@ -66,43 +66,46 @@ export class RemoteScanner {
return await response.json() as T
}

private async search(name: string, offset: number, config: RemoteScanner.CollectOptions = {}) {
private async search(eco: string, offset: number, config: RemoteScanner.CollectOptions = {}) {
const { step = 250, timeout = Time.second * 30 } = config
const { keywords } = this.ecosystems[name]
const { keywords } = this.ecosystems[eco]
const result = await this.request<SearchResult>(`/-/v1/search?text=${keywords.join('+')}&size=${step}&from=${offset}`, { timeout })
this.version = result.version
for (const object of result.objects) {
this.cache[name][object.package.name] = object
this.cache[eco][object.package.name] = object
}
return result.total
}

public async collect(name: string, config: RemoteScanner.CollectOptions = {}) {
public async collect(eco: string, config: RemoteScanner.CollectOptions = {}) {
const { step = 250, margin = 25 } = config
this.cache[name] = Object.create(null)
this.cache[eco] = Object.create(null)
this.time = new Date().toUTCString()
const total = await this.search(name, 0, config)
const total = await this.search(eco, 0, config)
for (let offset = Object.values(this.cache).length; offset < total; offset += step - margin) {
await this.search(name, offset - margin, config)
await this.search(eco, offset - margin, config)
}
this.objects = Object.values(this.cache[name])
this.objects = Object.values(this.cache[eco])
this.total = this.objects.length
}

public async process(object: SearchObject, options: RemoteScanner.AnalyzeConfig) {
const { name } = object.package
const registry = await this.request<Registry>(`/${name}`)
public async process(eco: string, object: SearchObject, options: RemoteScanner.AnalyzeConfig) {
const registry = await this.request<Registry>(`/${object.package.name}`)
const compatible = Object.values(registry.versions).sort((a, b) => compare(b.version, a.version))

await options?.onRegistry?.(registry, compatible)
const versions = compatible.filter(item => !item.deprecated)
if (!versions.length) return

const latest = registry.versions[versions[0].version]
const manifest = conclude(latest)
const shortname = Ecosystem.check(this.ecosystems[eco], latest)
if (!shortname) return

const manifest = Manifest.conclude(latest, this.ecosystems[eco].property)
const times = compatible.map(item => registry.time[item.version]).sort()

object.shortname = name.replace(/(cordis-|^@cordisjs\/)plugin-/, '')
object.ecosystem = eco
object.shortname = shortname
object.manifest = manifest
object.insecure = manifest.insecure
object.category = manifest.category
Expand All @@ -113,25 +116,24 @@ export class RemoteScanner {
return versions
}

public async analyze(config: RemoteScanner.AnalyzeConfig) {
public async analyze(eco: string, config: RemoteScanner.AnalyzeConfig) {
const { concurrency = 5, before, onSuccess, onFailure, onSkipped, after } = config

const result = await pMap(this.objects, async (object) => {
if (object.ignored) return
before?.(object)
const { name } = object.package
try {
const versions = await this.process(object, config)
const versions = await this.process(eco, object, config)
if (versions) {
await onSuccess?.(object, versions)
return versions
} else {
object.ignored = true
await onSkipped?.(name)
await onSkipped?.(object.package.name)
}
} catch (error) {
object.ignored = true
await onFailure?.(name, error)
await onFailure?.(object.package.name, error)
} finally {
after?.(object)
}
Expand Down
61 changes: 1 addition & 60 deletions packages/registry/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Dict } from 'cosmokit'
import { Manifest } from './manifest'

export interface User {
name?: string
Expand Down Expand Up @@ -43,66 +44,6 @@ export namespace PackageJson {
}
}

export interface IconSvg {
type: 'svg'
viewBox: string
pathData: string
}

export interface Manifest extends Manifest.Export {
icon?: IconSvg
hidden?: boolean
preview?: boolean
insecure?: boolean
category?: string
public?: string[]
exports?: Dict<Manifest.Export | null>
ecosystem?: Partial<Ecosystem>
}

export namespace Manifest {
export interface Export {
browser?: boolean
description?: string | Dict<string>
service?: Manifest.Service
resources?: Dict
}

export interface Service {
required?: string[]
optional?: string[]
implements?: string[]
}
}

export interface Ecosystem {
property: string
inject: string[]
pattern: string[]
keywords: string[]
peerDependencies: Dict<string>
}

export namespace Ecosystem {
export function check(eco: Ecosystem, meta: PackageJson) {
for (const peer in eco.peerDependencies) {
if (!meta.peerDependencies?.[peer]) return
}
for (const pattern of eco.pattern) {
const regexp = new RegExp('^' + pattern.replace('*', '.*') + '$')
let prefix = '', name = meta.name
if (!pattern.startsWith('@')) {
prefix = /^@.+\//.exec(meta.name)?.[0] || ''
name = name.slice(prefix.length)
}
if (!regexp.test(name)) continue
const index = pattern.indexOf('*')
return prefix + name.slice(index)
}
if (eco.property in meta) return meta.name
}
}

export interface RemotePackage extends PackageJson {
deprecated?: string
author?: User
Expand Down

0 comments on commit 88bfaea

Please sign in to comment.