aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/young-rats-sin.md11
-rw-r--r--packages/astro/package.json1
-rw-r--r--packages/astro/src/cli/add/index.ts57
-rw-r--r--pnpm-lock.yaml5
4 files changed, 63 insertions, 11 deletions
diff --git a/.changeset/young-rats-sin.md b/.changeset/young-rats-sin.md
new file mode 100644
index 000000000..0d26ac34a
--- /dev/null
+++ b/.changeset/young-rats-sin.md
@@ -0,0 +1,11 @@
+---
+'astro': patch
+---
+
+Fixes an edge case with `astro add` that could install a prerelease instead of a stable release version.
+
+**Prior to this change**
+`astro add svelte` installs `svelte@5.0.0-next.22`
+
+**After this change**
+`astro add svelte` installs `svelte@4.2.8`
diff --git a/packages/astro/package.json b/packages/astro/package.json
index c7dd43ae3..79992216b 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -200,6 +200,7 @@
"@types/probe-image-size": "^7.2.3",
"@types/prompts": "^2.4.8",
"@types/resolve": "^1.20.5",
+ "@types/semver": "^7.5.2",
"@types/send": "^0.17.4",
"@types/server-destroy": "^1.0.3",
"@types/unist": "^3.0.2",
diff --git a/packages/astro/src/cli/add/index.ts b/packages/astro/src/cli/add/index.ts
index 80c0e10ff..8ddc0e7de 100644
--- a/packages/astro/src/cli/add/index.ts
+++ b/packages/astro/src/cli/add/index.ts
@@ -5,6 +5,7 @@ import { bold, cyan, dim, green, magenta, red, yellow } from 'kleur/colors';
import fsMod, { existsSync, promises as fs } from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
+import maxSatisfying from 'semver/ranges/max-satisfying.js';
import ora from 'ora';
import preferredPM from 'preferred-pm';
import prompts from 'prompts';
@@ -610,15 +611,7 @@ async function getInstallIntegrationsCommand({
logger.debug('add', `package manager: ${JSON.stringify(pm)}`);
if (!pm) return null;
- let dependencies = integrations
- .map<[string, string | null][]>((i) => [[i.packageName, null], ...i.dependencies])
- .flat(1)
- .filter((dep, i, arr) => arr.findIndex((d) => d[0] === dep[0]) === i)
- .map(([name, version]) =>
- version === null ? name : `${name}@${version.split(/\s*\|\|\s*/).pop()}`
- )
- .sort();
-
+ const dependencies = await convertIntegrationsToInstallSpecifiers(integrations);
switch (pm.name) {
case 'npm':
return { pm: 'npm', command: 'install', flags: [], dependencies };
@@ -633,6 +626,35 @@ async function getInstallIntegrationsCommand({
}
}
+async function convertIntegrationsToInstallSpecifiers(
+ integrations: IntegrationInfo[]
+): Promise<string[]> {
+ const ranges: Record<string, string> = {};
+ for (let { packageName, dependencies } of integrations) {
+ ranges[packageName] = '*';
+ for (const [name, range] of dependencies) {
+ ranges[name] = range;
+ }
+ }
+ return Promise.all(
+ Object.entries(ranges).map(([name, range]) => resolveRangeToInstallSpecifier(name, range))
+ );
+}
+
+/**
+ * Resolves package with a given range to a STABLE version
+ * peerDependencies might specify a compatible prerelease,
+ * but `astro add` should only ever install stable releases
+ */
+async function resolveRangeToInstallSpecifier(name: string, range: string): Promise<string> {
+ const versions = await fetchPackageVersions(name);
+ if (versions instanceof Error) return name;
+ // Filter out any prerelease versions
+ const stableVersions = versions.filter(v => !v.includes('-'));
+ const maxStable = maxSatisfying(stableVersions, range);
+ return `${name}@^${maxStable}`;
+}
+
// Allow forwarding of standard `npm install` flags
// See https://docs.npmjs.com/cli/v8/commands/npm-install#description
const INHERITED_FLAGS = new Set<string>([
@@ -725,7 +747,7 @@ async function fetchPackageJson(
scope: string | undefined,
name: string,
tag: string
-): Promise<object | Error> {
+): Promise<Record<string, any> | Error> {
const packageName = `${scope ? `${scope}/` : ''}${name}`;
const registry = await getRegistry();
const res = await fetch(`${registry}/${packageName}/${tag}`);
@@ -739,6 +761,21 @@ async function fetchPackageJson(
}
}
+async function fetchPackageVersions(packageName: string): Promise<string[] | Error> {
+ const registry = await getRegistry();
+ const res = await fetch(`${registry}/${packageName}`, {
+ headers: { accept: 'application/vnd.npm.install-v1+json' },
+ });
+ if (res.status >= 200 && res.status < 300) {
+ return await res.json().then((data) => Object.keys(data.versions));
+ } else if (res.status === 404) {
+ // 404 means the package doesn't exist, so we don't need an error message here
+ return new Error();
+ } else {
+ return new Error(`Failed to fetch ${registry}/${packageName} - GET ${res.status}`);
+ }
+}
+
export async function validateIntegrations(integrations: string[]): Promise<IntegrationInfo[]> {
const spinner = ora('Resolving packages...').start();
try {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9b42c6504..8b1ea077d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -726,6 +726,9 @@ importers:
'@types/resolve':
specifier: ^1.20.5
version: 1.20.5
+ '@types/semver':
+ specifier: ^7.5.2
+ version: 7.5.4
'@types/send':
specifier: ^0.17.4
version: 0.17.4
@@ -5718,7 +5721,7 @@ packages:
'@changesets/write': 0.2.3
'@manypkg/get-packages': 1.1.3
'@types/is-ci': 3.0.3
- '@types/semver': 7.5.4
+ '@types/semver': 7.5.5
ansi-colors: 4.1.3
chalk: 2.4.2
enquirer: 2.4.1