UNPKG

@ts-for-gir/lib

Version:

Typescript .d.ts generator from GIR for gjs

340 lines 13 kB
import { parser } from '@gi.ts/parser'; import { readFile } from 'fs/promises'; import { findFilesInDirs, splitModuleName, pascalCase } from './utils/index.js'; import { sanitizeNamespace } from './gir/util.js'; import { Logger } from './logger.js'; import { transformImportName, transformModuleNamespaceName } from './utils/index.js'; import { LibraryVersion } from './library-version.js'; import { GirNSRegistry } from './registry.js'; export class DependencyManager extends GirNSRegistry { config; log; _cache = {}; static instances = {}; constructor(config) { super(); this.config = config; this.log = new Logger(config.verbose, 'DependencyManager'); } /** * Get the DependencyManager singleton instance */ static getInstance(config) { const configKey = config ? JSON.stringify(config) : Object.keys(this.instances)[0]; if (this.instances[configKey]) { return this.instances[configKey]; } if (!config) { throw new Error('config parameter is required to initialize DependencyManager'); } const instance = new DependencyManager(config); this.instances[configKey] = instance; return instance; } parsePackageName(namespaceOrPackageName, version) { let packageName; let namespace; if (version) { namespace = namespaceOrPackageName; packageName = `${namespace}-${version}`; } else { packageName = namespaceOrPackageName; const { namespace: _namespace, version: _version } = splitModuleName(packageName); namespace = _namespace; version = _version; } return { packageName, namespace, version }; } parseArgs(namespaceOrPackageNameOrRepo, version, noOverride) { let packageName; let namespace; let repo = null; if (typeof namespaceOrPackageNameOrRepo === 'string') { // Special case for Gjs if (!noOverride && namespaceOrPackageNameOrRepo === 'Gjs') { return { ...this.getGjs(), repo: null }; } const args = this.parsePackageName(namespaceOrPackageNameOrRepo, version); version = args.version; packageName = args.packageName; namespace = args.namespace; } else { repo = namespaceOrPackageNameOrRepo; const ns = repo.namespace?.[0]; if (!ns) { throw new Error('Invalid GirRepository'); } version = ns.$.version; namespace = ns.$.name; packageName = `${namespace}-${version}`; } return { packageName, namespace, version, repo }; } /** * Get all dependencies in the cache * @returns All dependencies in the cache */ all() { return Object.values(this._cache); } getAllPackageNames() { return Object.keys(this._cache); } /** * Get the core dependencies * @returns */ async core() { return [ await this.get('GObject', '2.0'), await this.get('GLib', '2.0'), await this.get('Gio', '2.0'), await this.get('cairo', '1.0'), ]; } createImportProperties(namespace, packageName, version) { const importPath = this.createImportPath(packageName, namespace, version); const importDef = this.createImportDef(namespace, importPath); const packageJsonImport = this.createPackageJsonImport(importPath); return { importPath, importDef, packageJsonImport, }; } createImportPath(packageName, namespace, version) { if (!this.config.package) { return `gi://${namespace}?version=${version}`; } const importName = transformImportName(packageName); const importPath = `${this.config.npmScope}/${importName}`; return importPath; } createImportDef(namespace, importPath) { return this.config.noNamespace ? `import type * as ${namespace} from '${importPath}'` : `import type ${namespace} from '${importPath}';`; } createPackageJsonImport(importPath) { const depVersion = this.config.workspace ? 'workspace:^' : '*'; return `"${importPath}": "${depVersion}"`; } async parseGir(path) { const girXML = parser.parseGir(await readFile(path, 'utf8')); const repo = girXML.repository[0]; const ns = repo?.namespace?.[0]; const version = ns?.$.version; return { girXML, repo, ns, version }; } async parseGirAndReturnLatestVersion(filesInfo) { const libraryVersions = []; if (filesInfo.length > 1) { this.log.warn(`Multiple paths found for ${filesInfo[0].filename}`); } for (const fileInfo of filesInfo) { if (!fileInfo.exists || !fileInfo.path) { continue; } const { girXML, ns, version } = await this.parseGir(fileInfo.path); if (!version || !ns) { continue; } const libraryVersion = new LibraryVersion(ns?.constant, version); if (filesInfo.length > 1) { this.log.muted(` - ${fileInfo.path} (${libraryVersion.toString()})`); } libraryVersions.push({ libraryVersion, girXML, fileInfo, }); } // Compare all library versions and return the latest version const latestLibraryVersion = libraryVersions.sort((a, b) => a.libraryVersion.compare(b.libraryVersion))[0]; if (!latestLibraryVersion) { this.log.warn('No latest library version found', { libraryVersions, filesInfo, }); return { libraryVersion: new LibraryVersion(), girXML: null, fileInfo: filesInfo[0], }; } if (filesInfo.length > 1) { this.log.muted(`Use latest version ${latestLibraryVersion.libraryVersion.toString()} from ${latestLibraryVersion.fileInfo.path}`); } return latestLibraryVersion; } async get(namespaceOrPackageNameOrRepo, _version, noOverride) { const parsedArgs = this.parseArgs(namespaceOrPackageNameOrRepo, _version, noOverride); const { packageName, repo } = parsedArgs; let { namespace, version } = parsedArgs; namespace = sanitizeNamespace(namespace); if (this._cache[packageName]) { const dep = this._cache[packageName]; return dep; } const filename = `${packageName}.gir`; const filesInfo = await findFilesInDirs(this.config.girDirectories, filename); const { libraryVersion, girXML, fileInfo } = await this.parseGirAndReturnLatestVersion(filesInfo); const ns = girXML?.repository[0]?.namespace?.[0] || repo?.namespace?.[0] || null; // Use the version from the gir file if it exists if (ns?.$.version) { version = ns?.$.version; } if (ns?.$.name) { namespace = ns?.$.name; } const dependency = { ...fileInfo, namespace, packageName, importName: transformImportName(packageName), importNamespace: transformModuleNamespaceName(packageName), version, libraryVersion, girXML, ...this.createImportProperties(namespace, packageName, version), }; // Special case for Cairo // This is a special case for Cairo because Cairo in GJS is provided as a built-in module that doesn't // follow the standard GI repository pattern. // So we need to special case it and redirect to the 'cairo' package. // This changes the typescript import definition to use the internal 'cairo' package instead of the 'cairo-1.0' Gir package. if (!noOverride && namespace === 'cairo' && version === '1.0') { dependency.importDef = this.createImportDef('cairo', 'cairo'); } this._cache[packageName] = dependency; return dependency; } /** * Get all dependencies with the given namespace * @param namespace The namespace of the dependency * @returns All dependencies with the given namespace */ list(namespace) { const packageNames = this.all(); const candidates = packageNames.filter((dep) => { return dep.namespace === namespace && dep.exists; }); return candidates; } /** * Get girModule for dependency * @param girModules * @param packageName */ getModule(girModules, dep) { return girModules.find((m) => m.packageName === dep.packageName && m.namespace === dep.namespace && m.version === dep.version); } /** * Add all dependencies from an array of gir modules * @param girModules */ async addAll(girModules) { for (const girModule of girModules) { await this.get(girModule.namespace, girModule.version || '0.0'); } return this.all(); } /** * Transforms a gir include object array to a dependency object array * @param girIncludes - Array of gir includes * @returns Array of dependencies */ async fromGirIncludes(girIncludes) { const dependencies = []; for (const i of girIncludes) { dependencies.unshift(await this.get(i.$.name, i.$.version || '0.0')); } return dependencies; } /** * Check if multiple dependencies with the given namespace exist in the cache * @param namespace The namespace of the dependency * @returns */ hasConflict(namespace) { const packageNames = this.getAllPackageNames(); const candidates = packageNames.filter((packageName) => { return packageName.startsWith(`${namespace}-`) && this._cache[packageName].namespace === namespace; }); return candidates.length > 1; } /** * get the latest version of the dependency with the given namespace * @param namespace The namespace of the dependency * @returns The latest version of the dependency */ getLatestVersion(namespace) { const candidates = this.list(namespace); const latestVersion = candidates .sort((a, b) => { return a.version.localeCompare(b.version); }) .pop(); return latestVersion; } /** * Check if the given version is the latest version of the dependency * @param namespace The namespace of the dependency * @param version The version of the dependency * @returns */ isLatestVersion(namespace, version) { const latestVersion = this.getLatestVersion(namespace); return latestVersion?.version === version; } /** * Find a dependency by it's namespace from the cache, if multiple versions are found, the latest version is returned * @param namespace The namespace of the dependency * @returns The dependency object or null if not found */ find(namespace) { // Special case for Gjs if (namespace === 'Gjs') { return this.getGjs(); } const packageNames = this.getAllPackageNames(); const candidates = packageNames.filter((packageName) => { return packageName.startsWith(`${namespace}-`) && this._cache[packageName].namespace === namespace; }); if (candidates.length > 1) { this.log.warn(`Found multiple versions of ${namespace}: ${candidates.join(', ')}`); } const latestVersion = candidates.sort().pop(); if (latestVersion && this._cache[latestVersion]) { const dep = this._cache[latestVersion]; return dep; } return null; } getPseudoPackage(packageName, namespace = pascalCase(packageName), version = '2.0') { if (this._cache[packageName + '_pseudo']) { return this._cache[packageName + '_pseudo']; } const dep = { namespace, exists: true, filename: '', path: '', packageName: packageName, importName: transformImportName(packageName), importNamespace: transformModuleNamespaceName(packageName), version, libraryVersion: new LibraryVersion(), girXML: null, ...this.createImportProperties(packageName, packageName, version), }; this._cache[packageName + '_pseudo'] = dep; return dep; } getGjs() { return this.getPseudoPackage('Gjs'); } } //# sourceMappingURL=dependency-manager.js.map