UNPKG

studiocms

Version:

Astro Native CMS for AstroDB. Built from the ground up by the Astro community.

128 lines (127 loc) 4.52 kB
import { exec } from "@withstudiocms/cli-kit/utils"; import { detect as _detect } from "package-manager-detector"; import maxSatisfying from "semver/ranges/max-satisfying.js"; import { Effect, genLogger } from "../../effect.js"; const eDetect = Effect.tryPromise(() => _detect()); const eExec = (command, args) => Effect.tryPromise(() => exec(command, args)); let _registry; const getRegistry = genLogger("studiocms/cli/add/npm-utils.getRegistry")(function* () { if (_registry) return _registry; const fallback = "https://registry.npmjs.org"; const packageManager = (yield* eDetect)?.name || "npm"; try { const { stdout } = yield* eExec(packageManager, ["config", "get", "registry"]); _registry = stdout.trim()?.replace(/\/$/, "") || fallback; try { const url = new URL(_registry); if (!url.host || !["http:", "https:"].includes(url.protocol)) _registry = fallback; } catch { _registry = fallback; } } catch { _registry = fallback; } return _registry; }); const fetchPackageVersions = (packageName) => genLogger("studiocms/cli/add/npm-utils.fetchPackageVersions")(function* () { const registry = yield* getRegistry; try { const res = yield* Effect.tryPromise( () => fetch(`${registry}/${packageName}`, { headers: { accept: "application/vnd.npm.install-v1+json" } }) ); if (res.status >= 200 && res.status < 300) { return yield* Effect.tryPromise( () => res.json().then((data) => Object.keys(data.versions)) ); } if (res.status === 404) { return new Error(); } return new Error(`Failed to fetch ${registry}/${packageName} - GET ${res.status}`); } catch (error) { return new Error( `Network error while fetching ${registry}/${packageName}: ${error.message}` ); } }); const resolveRangeToInstallSpecifier = (name, range) => genLogger("studiocms/cli/add/npm-utils.resolveRangeToInstallSpecifier")(function* () { const versions = yield* fetchPackageVersions(name); if (versions instanceof Error || !Array.isArray(versions)) return name; const stableVersions = versions.filter((v) => !v.includes("-")); const maxStable = maxSatisfying(stableVersions, range) ?? maxSatisfying(versions, range); if (!maxStable) return name; return `${name}@^${maxStable}`; }); const convertPluginsToInstallSpecifiers = (plugins) => genLogger("studiocms/cli/add/npm-utils.convertPluginsToInstallSpecifiers")(function* () { const ranges = {}; for (const { dependencies } of plugins) { for (const [name, range] of dependencies) { ranges[name] = range; } } return yield* Effect.all( Object.entries(ranges).map(([name, range]) => resolveRangeToInstallSpecifier(name, range)) ); }); const fetchPackageJson = (scope, name, tag) => genLogger("studiocms/cli/add/npm-utils.fetchPackageJson")(function* () { const packageName = `${scope ? `${scope}/` : ""}${name}`; const registry = yield* getRegistry; try { const res = yield* Effect.tryPromise(() => fetch(`${registry}/${packageName}/${tag}`)); if (res.status >= 200 && res.status < 300) { return yield* Effect.tryPromise(() => res.json()); } if (res.status === 404) { return new Error(); } return new Error(`Failed to fetch ${registry}/${packageName}/${tag} - GET ${res.status}`); } catch (error) { return new Error( `Network error while fetching ${registry}/${packageName}/${tag}: ${error.message}` ); } }); const parseNpmName = (spec) => Effect.try(() => { if (!spec || spec[0] === "." || spec[0] === "/") return void 0; let scope; let name = ""; const parts = spec.split("/"); if (parts[0][0] === "@") { scope = parts[0]; parts.shift(); } name = parts.shift() || ""; const subpath = parts.length ? `./${parts.join("/")}` : void 0; return { scope, name, subpath }; }); const parsePluginName = (spec) => genLogger("studiocms/cli/add/npm-utils.parsePluginName")(function* () { const result = yield* parseNpmName(spec); if (!result) return; let { scope, name } = result; let tag = "latest"; if (scope) { name = name.replace(`${scope}/`, ""); } if (name.includes("@")) { const tagged = name.split("@"); name = tagged[0]; tag = tagged[1]; } if (!name) return void 0; return { scope, name, tag }; }); export { convertPluginsToInstallSpecifiers, fetchPackageJson, fetchPackageVersions, getRegistry, parseNpmName, parsePluginName, resolveRangeToInstallSpecifier };