studiocms
Version:
Astro Native CMS for AstroDB. Built from the ground up by the Astro community.
128 lines (127 loc) • 4.52 kB
JavaScript
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
};