|
| 1 | +import packageJson from "@/../package.json" with { type: "json" }; |
| 2 | + |
| 3 | +import { existsSync, readdirSync, readFileSync } from "node:fs"; |
| 4 | +import { dirname, join } from "node:path"; |
| 5 | +import { fileURLToPath } from "node:url"; |
| 6 | + |
| 7 | +import type { EnsApiVersionInfo } from "@ensnode/ensnode-sdk"; |
| 8 | + |
| 9 | +/** |
| 10 | + * Get ENS API version |
| 11 | + */ |
| 12 | +function getEnsApiVersion(): string { |
| 13 | + return packageJson.version; |
| 14 | +} |
| 15 | + |
| 16 | +/** |
| 17 | + * Get NPM package version. |
| 18 | + * |
| 19 | + * Note: |
| 20 | + * Since we use PNPM's `catalog:` references, reading directly from |
| 21 | + * the `package.json` file would give us `catalog:` values, and not resolved |
| 22 | + * version values. We need the latter, so we implement our own version |
| 23 | + * resolution method. |
| 24 | + * |
| 25 | + * @throws If the package version cannot be found in any |
| 26 | + * `node_modules` up to the workspace root. |
| 27 | + */ |
| 28 | +function getPackageVersion(packageName: string): string { |
| 29 | + // Start from current file's directory |
| 30 | + const currentFile = fileURLToPath(import.meta.url); |
| 31 | + let searchDir = dirname(currentFile); |
| 32 | + |
| 33 | + while (true) { |
| 34 | + const workspaceFile = join(searchDir, "pnpm-workspace.yaml"); |
| 35 | + const isWorkspaceRoot = existsSync(workspaceFile); |
| 36 | + |
| 37 | + // Check for node_modules in current directory |
| 38 | + const nodeModulesPath = join(searchDir, "node_modules", packageName, "package.json"); |
| 39 | + if (existsSync(nodeModulesPath)) { |
| 40 | + const packageJson = JSON.parse(readFileSync(nodeModulesPath, "utf8")); |
| 41 | + return packageJson.version; |
| 42 | + } |
| 43 | + |
| 44 | + // Check PNPM's .pnpm virtual store |
| 45 | + const pnpmDir = join(searchDir, "node_modules", ".pnpm"); |
| 46 | + if (existsSync(pnpmDir)) { |
| 47 | + const version = getPackageVersionFromPnpmStore(pnpmDir, packageName); |
| 48 | + if (version) return version; |
| 49 | + } |
| 50 | + |
| 51 | + // If we're at workspace root and still haven't found it, stop searching |
| 52 | + if (isWorkspaceRoot) { |
| 53 | + throw new Error(`Package ${packageName} not found in any node_modules up to workspace root`); |
| 54 | + } |
| 55 | + |
| 56 | + // Move up one directory |
| 57 | + const parentDir = dirname(searchDir); |
| 58 | + |
| 59 | + // Prevent infinite loop if we reach filesystem root |
| 60 | + if (parentDir === searchDir) { |
| 61 | + throw new Error(`Package ${packageName} not found and no workspace root detected`); |
| 62 | + } |
| 63 | + |
| 64 | + searchDir = parentDir; |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +/** |
| 69 | + * Get package version from PNPM virtual store. |
| 70 | + * |
| 71 | + * PNPM stores packages in its virtual store that |
| 72 | + * can be located at, for example, `./node_modules/.pnpm` path. |
| 73 | + * |
| 74 | + * This function is used in a fallback method by {@link getPackageVersion} to |
| 75 | + * get package version by package name in case it was not found |
| 76 | + * directly in `./node_modules` directory. |
| 77 | + */ |
| 78 | +function getPackageVersionFromPnpmStore(pnpmDir: string, packageName: string): string | null { |
| 79 | + try { |
| 80 | + const entries = readdirSync(pnpmDir); |
| 81 | + |
| 82 | + // Convert package name to PNPM's format: @scope/name -> @scope+name |
| 83 | + const normalizedName = packageName.replace("/", "+"); |
| 84 | + |
| 85 | + // Find entries that match the package name |
| 86 | + // They will be in format: packagename@version or @scope+packagename@version |
| 87 | + for (const entry of entries) { |
| 88 | + if (entry.startsWith(`${normalizedName}@`)) { |
| 89 | + const pkgPath = join(pnpmDir, entry, "node_modules", packageName, "package.json"); |
| 90 | + if (existsSync(pkgPath)) { |
| 91 | + const packageJson = JSON.parse(readFileSync(pkgPath, "utf8")); |
| 92 | + return packageJson.version; |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + } catch (_error) { |
| 97 | + // Ignore errors in this helper |
| 98 | + } |
| 99 | + |
| 100 | + return null; |
| 101 | +} |
| 102 | + |
| 103 | +export const ensApiVersionInfo = { |
| 104 | + ensApi: getEnsApiVersion(), |
| 105 | + ensNormalize: getPackageVersion("@adraffy/ens-normalize"), |
| 106 | +} as const satisfies EnsApiVersionInfo; |
0 commit comments