Last active
November 11, 2023 05:17
-
-
Save en9inerd/2b7b45fee3910ca11df10e0c3b3fd702 to your computer and use it in GitHub Desktop.
Dynamic module discovery using json5 and glob
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
import { access, readFile } from 'fs/promises'; | |
import { join, resolve, dirname } from 'path'; | |
import { TSConfig } from './types'; | |
import { sync as globSync } from 'glob'; | |
import { parse as parseJSON } from 'json5'; | |
import { DiscoveryError } from './exceptions'; | |
async function getRootAppDir(): Promise<string> { | |
const appDir = process.env.PWD; | |
if (appDir) { | |
return appDir; | |
} | |
let dir = __dirname; | |
if (dir.includes('node_modules')) { | |
dir = dir.split('node_modules')[0]; | |
} | |
let dirFound = false; | |
while (true) { | |
try { | |
await access(join(dir, 'package.json')); | |
dirFound = true; | |
break; | |
} catch (error) { | |
const parentDir = dirname(dir); | |
if (parentDir === dir) { | |
break; | |
} | |
dir = parentDir; | |
} | |
} | |
if (dirFound) { | |
return dir; | |
} | |
throw new DiscoveryError('Cannot find root app directory'); | |
} | |
async function getTSConfig(): Promise<TSConfig | void> { | |
const tsConfigPath = join(await getRootAppDir(), 'tsconfig.json'); | |
try { | |
const tsConfig = await readFile(tsConfigPath, 'utf8'); | |
return parseJSON(tsConfig); | |
} catch (err: unknown) { | |
if ((<{ code: string }>err).code === 'ENOENT') { | |
return; | |
} | |
throw err; | |
} | |
} | |
async function discover<T>(pattern: string, instantiate = false, validator?: CallableFunction): Promise<T[]> { | |
const outDir = resolve( | |
await getRootAppDir(), | |
(await getTSConfig())?.compilerOptions?.outDir || '' | |
); | |
const files = globSync(pattern, { | |
cwd: outDir, | |
absolute: true, | |
nodir: true, | |
ignore: '**/node_modules/**' | |
}); | |
const instancesOrClasses: T[] = files.map((filePath) => { | |
// eslint-disable-next-line @typescript-eslint/no-var-requires | |
const module = require(filePath); | |
if (Object.keys(module).length !== 1) { | |
throw new DiscoveryError(`Command module '${filePath}' must have only one named export or export default`); | |
} | |
return instantiate ? new module[Object.keys(module)[0]]() : module[Object.keys(module)[0]]; | |
}); | |
if (validator) validator(instancesOrClasses); | |
return instancesOrClasses; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment