fly-import
Version:
Install and import packages on-the-fly
148 lines • 5.44 kB
JavaScript
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