Compare commits
No commits in common. "b3b5b169e88c2317d90da2af273f4f083be8f973" and "cf6d14abca5a67ce4758f5f660d676eaf34a7e91" have entirely different histories.
b3b5b169e8
...
cf6d14abca
@ -67,11 +67,5 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
||||||
"@typescript-eslint/parser": "^4.26.0",
|
"@typescript-eslint/parser": "^4.26.0",
|
||||||
"eslint": "^7.27.0"
|
"eslint": "^7.27.0"
|
||||||
},
|
|
||||||
"extollo": {
|
|
||||||
"discover": true,
|
|
||||||
"units": {
|
|
||||||
"discover": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import {Dispatchable} from './types'
|
import {Dispatchable} from './types'
|
||||||
import {Awaitable, JSONState} from '../util'
|
import {JSONState} from '../util'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class representing an event that may be fired.
|
* Abstract class representing an event that may be fired.
|
||||||
*/
|
*/
|
||||||
export abstract class Event implements Dispatchable {
|
export abstract class Event implements Dispatchable {
|
||||||
|
abstract dehydrate(): Promise<JSONState>
|
||||||
|
|
||||||
|
abstract rehydrate(state: JSONState): void | Promise<void>
|
||||||
abstract dehydrate(): Awaitable<JSONState>
|
|
||||||
|
|
||||||
abstract rehydrate(state: JSONState): Awaitable<void>
|
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,6 @@ export * from './service/Middlewares'
|
|||||||
|
|
||||||
export * from './support/cache/MemoryCache'
|
export * from './support/cache/MemoryCache'
|
||||||
export * from './support/cache/CacheFactory'
|
export * from './support/cache/CacheFactory'
|
||||||
export * from './support/NodeModules'
|
|
||||||
|
|
||||||
export * from './views/ViewEngine'
|
export * from './views/ViewEngine'
|
||||||
export * from './views/ViewEngineFactory'
|
export * from './views/ViewEngineFactory'
|
||||||
|
@ -48,12 +48,6 @@ export function appPath(...parts: PathLike[]): UniversalPath {
|
|||||||
* The main application container.
|
* The main application container.
|
||||||
*/
|
*/
|
||||||
export class Application extends Container {
|
export class Application extends Container {
|
||||||
public static readonly NODE_MODULES_INJECTION = 'extollo/npm'
|
|
||||||
|
|
||||||
public static get NODE_MODULES_PROVIDER(): string {
|
|
||||||
return process.env.EXTOLLO_NPM || 'pnpm'
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getContainer(): Container {
|
public static getContainer(): Container {
|
||||||
const existing = <Container | undefined> globalRegistry.getGlobal('extollo/injector')
|
const existing = <Container | undefined> globalRegistry.getGlobal('extollo/injector')
|
||||||
if ( !existing ) {
|
if ( !existing ) {
|
||||||
@ -211,7 +205,6 @@ export class Application extends Container {
|
|||||||
this.setupLogging()
|
this.setupLogging()
|
||||||
|
|
||||||
this.registerFactory(new CacheFactory()) // FIXME move this somewhere else?
|
this.registerFactory(new CacheFactory()) // FIXME move this somewhere else?
|
||||||
this.registerSingleton(Application.NODE_MODULES_INJECTION, Application.NODE_MODULES_PROVIDER)
|
|
||||||
|
|
||||||
this.make<Logging>(Logging).debug(`Application root: ${this.baseDir}`)
|
this.make<Logging>(Logging).debug(`Application root: ${this.baseDir}`)
|
||||||
}
|
}
|
||||||
|
@ -73,14 +73,6 @@ export class Routing extends Unit {
|
|||||||
return this.compiledRoutes
|
return this.compiledRoutes
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a UniversalPath to a file served as an asset.
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* this.getAssetPath('images', '123.jpg').toRemote // => http://localhost:8000/assets/images/123.jpg
|
|
||||||
* ```
|
|
||||||
* @param parts
|
|
||||||
*/
|
|
||||||
public getAssetPath(...parts: string[]): UniversalPath {
|
public getAssetPath(...parts: string[]): UniversalPath {
|
||||||
return this.getAssetBase().concat(...parts)
|
return this.getAssetBase().concat(...parts)
|
||||||
}
|
}
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
import * as childProcess from 'child_process'
|
|
||||||
import {UniversalPath} from '../util'
|
|
||||||
import {Inject, Injectable, InjectParam} from '../di'
|
|
||||||
import {Application} from '../lifecycle/Application'
|
|
||||||
import {Logging} from '../service/Logging'
|
|
||||||
import {NodeModule, ExtolloAwareNodeModule} from './types'
|
|
||||||
import {EventBus} from '../event/EventBus'
|
|
||||||
import {PackageDiscovered} from './PackageDiscovered'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper class for discovering and interacting with
|
|
||||||
* NPM-style modules.
|
|
||||||
*/
|
|
||||||
@Injectable()
|
|
||||||
export class NodeModules {
|
|
||||||
@Inject()
|
|
||||||
protected readonly logging!: Logging
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
protected readonly bus!: EventBus
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectParam(Application.NODE_MODULES_INJECTION)
|
|
||||||
protected readonly manager: string,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the NodeModule entry for the base application.
|
|
||||||
*/
|
|
||||||
async app(): Promise<NodeModule> {
|
|
||||||
return new Promise<NodeModule>((res, rej) => {
|
|
||||||
childProcess.exec(`${this.manager} ls --json`, (error, stdout) => {
|
|
||||||
if ( error ) {
|
|
||||||
return rej(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
res(JSON.parse(stdout)[0])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the path to the node_modules folder for the base application.
|
|
||||||
*/
|
|
||||||
async root(): Promise<UniversalPath> {
|
|
||||||
return new Promise<UniversalPath>((res, rej) => {
|
|
||||||
childProcess.exec(`${this.manager} root`, (error, stdout) => {
|
|
||||||
if ( error ) {
|
|
||||||
return rej(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
res(new UniversalPath(stdout.trim()))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterate over packages, recursively, starting with the base application's
|
|
||||||
* package.json and fire PackageDiscovered events for any that have a valid
|
|
||||||
* Extollo discovery entry.
|
|
||||||
*/
|
|
||||||
async discover(): Promise<void> {
|
|
||||||
const root = await this.root()
|
|
||||||
const module = await this.app()
|
|
||||||
return this.discoverRoot(root, module)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively discover child-packages from the node_modules root for the
|
|
||||||
* given module.
|
|
||||||
*
|
|
||||||
* Fires PackageDiscovered events for valid, discovery-enabled packages.
|
|
||||||
*
|
|
||||||
* @param root - the path to node_modules
|
|
||||||
* @param module - the module whose children we are discovering
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
protected async discoverRoot(root: UniversalPath, module: NodeModule): Promise<void> {
|
|
||||||
for ( const key in module.dependencies ) {
|
|
||||||
if ( !Object.prototype.hasOwnProperty.call(module.dependencies, key) ) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logging.verbose(`Auto-discovery considering package: ${key}`)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const packageJson = root.concat(key, 'package.json')
|
|
||||||
this.logging.verbose(`Auto-discovery package path: ${packageJson}`)
|
|
||||||
if ( await packageJson.exists() ) {
|
|
||||||
const packageJsonString: string = await packageJson.read()
|
|
||||||
const packageJsonData: ExtolloAwareNodeModule = JSON.parse(packageJsonString)
|
|
||||||
if ( !packageJsonData?.extollo?.discover ) {
|
|
||||||
this.logging.debug(`Skipping non-discoverable package: ${key}`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logging.info(`Auto-discovering package: ${key}`)
|
|
||||||
await this.bus.dispatch(new PackageDiscovered(packageJsonData, packageJson.clone()))
|
|
||||||
|
|
||||||
const packageNodeModules = packageJson.concat('..', 'node_modules')
|
|
||||||
if ( await packageNodeModules.exists() && packageJsonData?.extollo?.recursiveDependencies?.discover ) {
|
|
||||||
this.logging.debug(`Recursing: ${packageNodeModules}`)
|
|
||||||
await this.discoverRoot(packageNodeModules, packageJsonData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: unknown) {
|
|
||||||
this.logging.error(`Encountered error while discovering package: ${key}`)
|
|
||||||
this.logging.error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
import {Event} from '../event/Event'
|
|
||||||
import {Awaitable, JSONState, UniversalPath} from '../util'
|
|
||||||
import {ExtolloAwareNodeModule} from './types'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event indicating that an NPM package has been discovered
|
|
||||||
* by the framework.
|
|
||||||
*
|
|
||||||
* Application services can listen for this event to register
|
|
||||||
* various discovery logic (e.g. automatically boot units
|
|
||||||
*/
|
|
||||||
export class PackageDiscovered extends Event {
|
|
||||||
constructor(
|
|
||||||
public packageConfig: ExtolloAwareNodeModule,
|
|
||||||
public packageJson: UniversalPath,
|
|
||||||
) {
|
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
dehydrate(): Awaitable<JSONState> {
|
|
||||||
return {
|
|
||||||
packageConfig: this.packageConfig as JSONState,
|
|
||||||
packageJson: this.packageJson.toString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rehydrate(state: JSONState): Awaitable<void> {
|
|
||||||
if ( typeof state === 'object' ) {
|
|
||||||
this.packageConfig = (state.packageConfig as ExtolloAwareNodeModule)
|
|
||||||
this.packageJson = new UniversalPath(String(state.packageJson))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
/**
|
|
||||||
* Partial package.json that may contain a partial Extollo discovery config.
|
|
||||||
*/
|
|
||||||
export interface ExtolloPackageDiscoveryConfig {
|
|
||||||
extollo?: {
|
|
||||||
discover?: boolean,
|
|
||||||
units?: {
|
|
||||||
discover?: boolean,
|
|
||||||
paths?: string[],
|
|
||||||
},
|
|
||||||
recursiveDependencies?: {
|
|
||||||
discover?: boolean,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface that defines a NodeModule dependency.
|
|
||||||
*/
|
|
||||||
export interface NodeDependencySpecEntry {
|
|
||||||
from: string,
|
|
||||||
version: string,
|
|
||||||
resolved?: string,
|
|
||||||
dependencies?: {[key: string]: NodeDependencySpecEntry},
|
|
||||||
devDependencies?: {[key: string]: NodeDependencySpecEntry},
|
|
||||||
unsavedDependencies?: {[key: string]: NodeDependencySpecEntry},
|
|
||||||
optionalDependencies?: {[key: string]: NodeDependencySpecEntry},
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines information and dependencies of an NPM package.
|
|
||||||
*/
|
|
||||||
export interface NodeModule {
|
|
||||||
name?: string,
|
|
||||||
version?: string,
|
|
||||||
dependencies?: {[key: string]: NodeDependencySpecEntry},
|
|
||||||
devDependencies?: {[key: string]: NodeDependencySpecEntry},
|
|
||||||
unsavedDependencies?: {[key: string]: NodeDependencySpecEntry},
|
|
||||||
optionalDependencies?: {[key: string]: NodeDependencySpecEntry},
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type alias for a NodeModule that contains an ExtolloPackageDiscoveryConfig.
|
|
||||||
*/
|
|
||||||
export type ExtolloAwareNodeModule = NodeModule & ExtolloPackageDiscoveryConfig
|
|
@ -2,7 +2,6 @@
|
|||||||
* Type representing a JSON serializable object.
|
* Type representing a JSON serializable object.
|
||||||
*/
|
*/
|
||||||
import {ErrorWithContext} from '../error/ErrorWithContext'
|
import {ErrorWithContext} from '../error/ErrorWithContext'
|
||||||
import {Awaitable} from './types'
|
|
||||||
|
|
||||||
export type JSONState = { [key: string]: string | boolean | number | undefined | JSONState | Array<string | boolean | number | undefined | JSONState> }
|
export type JSONState = { [key: string]: string | boolean | number | undefined | JSONState | Array<string | boolean | number | undefined | JSONState> }
|
||||||
|
|
||||||
@ -31,14 +30,14 @@ export function isJSONState(what: unknown): what is JSONState {
|
|||||||
export interface Rehydratable {
|
export interface Rehydratable {
|
||||||
/**
|
/**
|
||||||
* Dehydrate this class' state and get it.
|
* Dehydrate this class' state and get it.
|
||||||
* @return JSONState|Promise<JSONState>
|
* @return Promise<JSONState>
|
||||||
*/
|
*/
|
||||||
dehydrate(): Awaitable<JSONState>
|
dehydrate(): Promise<JSONState>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rehydrate a state into this class.
|
* Rehydrate a state into this class.
|
||||||
* @param {JSONState} state
|
* @param {JSONState} state
|
||||||
* @return void|Promise<void>
|
* @return void|Promise<void>
|
||||||
*/
|
*/
|
||||||
rehydrate(state: JSONState): Awaitable<void>
|
rehydrate(state: JSONState): void | Promise<void>
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user