UNPKG

fly-import

Version:

Install and import packages on-the-fly

148 lines 5.44 kB
import path, { join } from 'node:path'; import { createRequire } from 'node:module'; import { pathToFileURL } from 'node:url'; import envPaths from 'env-paths'; import Arborist from '@npmcli/arborist'; import registryUrl from 'registry-url'; import registryAuthToken from 'registry-auth-token'; // eslint-disable-next-line @typescript-eslint/naming-convention const { cache: DEFAULT_REPOSITORY_PATH } = envPaths('fly-import'); const defaultConfig = { repositoryPath: DEFAULT_REPOSITORY_PATH }; /** * @private */ export class FlyRepository { arboristConfig; _arborist; _repositoryPath; nodeModulesPath; _require; constructor(config) { this.repositoryPath = config.repositoryPath; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.arboristConfig = config.arboristConfig; } get #arborist() { if (!this._arborist) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const registry = this.arboristConfig?.registry ?? registryUrl(); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument this._arborist = new Arborist({ global: true, path: this.repositoryPath, token: registry ? registryAuthToken(registry) : undefined, registry, ...this.arboristConfig, }); } return this._arborist; } get #tree() { if (!this.#arborist.actualTree) { throw new Error('Repository has not been initialized'); } return this.#arborist.actualTree; } get #require() { this._require ??= createRequire(pathToFileURL(join(this.nodeModulesPath)).href); return this._require; } /** * Repository absolute path (npm --prefix). */ get repositoryPath() { return this._repositoryPath; } set repositoryPath(repositoryPath) { this._repositoryPath = path.resolve(repositoryPath); this.nodeModulesPath = path.join(this._repositoryPath, 'node_modules'); this._require = undefined; this._arborist = undefined; } async load() { return this.#arborist.loadActual(); } async install(spec) { const specs = Array.isArray(spec) ? spec : [spec]; await this.#arborist.reify({ add: specs }); const installed = this.findSpecs(specs); return Array.isArray(spec) ? installed : installed[0]; } async import(spec) { return this.findSpecs([spec])[0].import(); } async resolve(realpath) { // Node's import.meta.resolve is experimental and not enabled return pathToFileURL(this.#require.resolve(realpath)).href; } findSpecs(specs) { const edgesOut = new Map(); for (const edgeOut of this.#tree.edgesOut) { // Type is not correct. const edge = edgeOut[1]; if (edge.spec === '*' && specs.includes(edge.name)) { edgesOut.set(edge.name, edge.name); } else { edgesOut.set(`${edge.name}@${edge.spec}`, edge.name); } } return specs.map(spec => { const child = edgesOut.get(spec); const node = this.#tree.children.get(child); if (node) { const { realpath } = node; return { name: node.name, path: node.path, realpath, pkgid: node.pkgid, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment version: node.version, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment packageName: node.packageName, import: async () => import(await this.resolve(realpath)), }; } return { pkgid: spec, async import() { throw new Error(`Could not find installed spec ${spec}`); }, }; }); } } let defaultRepository = new FlyRepository(defaultConfig); export const resetConfig = () => { Object.assign(defaultConfig, { repositoryPath: DEFAULT_REPOSITORY_PATH, arboristConfig: undefined }); defaultRepository = new FlyRepository(defaultConfig); }; export const defineConfig = (config) => { if (config.repositoryPath) { defaultConfig.repositoryPath = config.repositoryPath; } if (config.arboristConfig) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment defaultConfig.arboristConfig = config.arboristConfig; } defaultRepository = new FlyRepository(defaultConfig); }; export const getConfig = () => ({ ...defaultConfig }); export const getDefaultRepository = () => defaultRepository; export const flyInstall = async (specifier, options) => { let repo = defaultRepository; if (options) { repo = new FlyRepository({ ...defaultConfig, ...options }); } return repo.install(specifier); }; export const flyImport = async (specifier, options) => { let repo = defaultRepository; if (options) { repo = new FlyRepository({ ...defaultConfig, ...options }); } await repo.install(specifier); return repo.import(specifier); }; //# sourceMappingURL=fly-import.js.map