UNPKG

@homer0/package-info

Version:

Gets the content of the project's package.json

127 lines (124 loc) 4.13 kB
import * as fsSync from 'fs'; import * as fsPromises from 'fs/promises'; import type { IPackageJson } from 'package-json-type'; import { pathUtils, type PathUtils } from '@homer0/path-utils'; import { deferred, type DeferredPromise } from '@homer0/deferred'; import { providerCreator, injectHelper } from '@homer0/jimple'; /** * The dictionary of dependencies that need to be injected in {@link PackageInfo}. */ type PackageInfoInjectOptions = { /** * The service that creates the path relative to the project root. */ pathUtils: PathUtils; }; /** * The inject helper to resolve the dependencies. */ const deps = injectHelper<PackageInfoInjectOptions>(); /** * The options for the service constructor. */ export type PackageInfoOptions = { /** * A dictionary with the dependency injections for the service. If one or more are not * provided, the service will create new instances. */ inject?: Partial<PackageInfoInjectOptions>; }; /** * A small service that reads the contents of the implementation's package.json file. */ export class PackageInfo { /** * A deferred promise that resolves when the package.json file is read. It will be * `undefined` if the file hasn't been read yet, or the contents are already saved in * the service. */ protected defer?: DeferredPromise<IPackageJson>; /** * This property will store the contents of the file once it is read. */ protected contents?: IPackageJson; /** * The absolute path to the package.json file. */ protected filepath: string; constructor({ inject = {} }: PackageInfoOptions = {}) { const usePathUtils = deps.get(inject, 'pathUtils', () => pathUtils()); this.filepath = usePathUtils.join('package.json'); } /** * Gets the contents of the implementation's package.json file. */ async get(): Promise<Readonly<IPackageJson>> { if (this.contents) return this.contents; if (this.defer) return this.defer.promise; this.defer = deferred<IPackageJson>(); const packageJson = await fsPromises.readFile(this.filepath, 'utf8'); return this.updateContents(packageJson); } /** * Synchronously gets the contents of the implementation's package.json file. */ getSync(): Readonly<IPackageJson> { if (this.contents) return this.contents; const packageJson = fsSync.readFileSync(this.filepath, 'utf8'); return this.updateContents(packageJson); } /** * This is a helper that takes care of updating the property with the file contents, and * if the deferred promise exists, resolve it and delete it. * * @param contents The contents of the package.json file. */ protected updateContents(contents: string): IPackageJson { this.contents = JSON.parse(contents) as IPackageJson; if (this.defer) { this.defer.resolve(this.contents); this.defer = undefined; } return this.contents; } } /** * Shorthand for `new PackageInfo()`. * * @param args The same parameters as the {@link PackageInfo} constructor. * @returns A new instance of {@link PackageInfo}. */ export const packageInfo = ( ...args: ConstructorParameters<typeof PackageInfo> ): PackageInfo => new PackageInfo(...args); /** * The options for the {@link PackageInfo} Jimple's provider creator. */ export type PackageInfoProviderOptions = { /** * The name that will be used to register the service. * * @default 'packageInfo' */ serviceName?: string; /** * A dictionary with the name of the services to inject. If one or more are not * provided, the service will create new instances. */ services?: { [key in keyof PackageInfoInjectOptions]?: string; }; }; /** * A provider creator to register {@link PackageInfo} in a Jimple container. */ export const packageInfoProvider = providerCreator( ({ serviceName = 'packageInfo', ...rest }: PackageInfoProviderOptions = {}) => (container) => { container.set(serviceName, () => { const { services = {} } = rest; const inject = deps.resolve(['pathUtils'], container, services); return new PackageInfo({ inject }); }); }, );