mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
Ensure astro add
only installs stable versions (#9387)
* fix(add): update peerDependency resolution logic to exclude prereleases * chore: add changeset
This commit is contained in:
parent
ea09182599
commit
a7c75b3339
4 changed files with 63 additions and 11 deletions
11
.changeset/young-rats-sin.md
Normal file
11
.changeset/young-rats-sin.md
Normal file
|
@ -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`
|
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue