0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-17 22:44:24 -05:00

feat: support adapters and third part integrations by keywords

This commit is contained in:
bholmesdev 2022-07-07 17:20:41 -04:00
parent 568960f175
commit 749aa11fb0

View file

@ -3,7 +3,7 @@ import boxen from 'boxen';
import { diffWords } from 'diff'; import { diffWords } from 'diff';
import { execa } from 'execa'; import { execa } from 'execa';
import { existsSync, promises as fs } from 'fs'; import { existsSync, promises as fs } from 'fs';
import { bold, cyan, dim, green, magenta } from 'kleur/colors'; import { bold, cyan, dim, green, magenta, yellow } from 'kleur/colors';
import ora from 'ora'; import ora from 'ora';
import path from 'path'; import path from 'path';
import preferredPM from 'preferred-pm'; import preferredPM from 'preferred-pm';
@ -32,6 +32,7 @@ export interface IntegrationInfo {
id: string; id: string;
packageName: string; packageName: string;
dependencies: [name: string, version: string][]; dependencies: [name: string, version: string][];
type: 'integration' | 'adapter';
} }
const ALIASES = new Map([ const ALIASES = new Map([
['solid', 'solid-js'], ['solid', 'solid-js'],
@ -120,7 +121,11 @@ export default async function add(names: string[], { cwd, flags, logging, teleme
debug('add', 'Astro config ensured `defineConfig`'); debug('add', 'Astro config ensured `defineConfig`');
for (const integration of integrations) { for (const integration of integrations) {
if (integration.type === 'adapter') {
await setAdapter(ast, integration);
} else {
await addIntegration(ast, integration); await addIntegration(ast, integration);
}
debug('add', `Astro config added integration ${integration.id}`); debug('add', `Astro config added integration ${integration.id}`);
} }
} catch (err) { } catch (err) {
@ -314,6 +319,50 @@ async function addIntegration(ast: t.File, integration: IntegrationInfo) {
}); });
} }
async function setAdapter(ast: t.File, adapter: IntegrationInfo) {
const adapterId = t.identifier(toIdent(adapter.id));
ensureImport(
ast,
t.importDeclaration(
[t.importDefaultSpecifier(adapterId)],
t.stringLiteral(adapter.packageName)
)
);
visit(ast, {
// eslint-disable-next-line @typescript-eslint/no-shadow
ExportDefaultDeclaration(path) {
if (!t.isCallExpression(path.node.declaration)) return;
const configObject = path.node.declaration.arguments[0];
if (!t.isObjectExpression(configObject)) return;
let adapterProp = configObject.properties.find((prop) => {
if (prop.type !== 'ObjectProperty') return false;
if (prop.key.type === 'Identifier') {
if (prop.key.name === 'adapter') return true;
}
if (prop.key.type === 'StringLiteral') {
if (prop.key.value === 'adapter') return true;
}
return false;
}) as t.ObjectProperty | undefined;
const adapterCall = t.callExpression(adapterId, []);
if (!adapterProp) {
configObject.properties.push(
t.objectProperty(t.identifier('adapter'), adapterCall)
);
return;
}
adapterProp.value = adapterCall;
},
});
}
const enum UpdateResult { const enum UpdateResult {
none, none,
updated, updated,
@ -479,46 +528,98 @@ async function tryToInstallIntegrations({
} }
} }
async function fetchPackageJson(scope: string | undefined, name: string, tag: string): Promise<object | Error> {
const packageName = `${scope ? `@${scope}/` : ''}${name}`;
const res = await fetch(`https://registry.npmjs.org/${packageName}/${tag}`)
if (res.status === 404) {
return new Error();
} else {
return await res.json();
}
}
export async function validateIntegrations(integrations: string[]): Promise<IntegrationInfo[]> { export async function validateIntegrations(integrations: string[]): Promise<IntegrationInfo[]> {
const spinner = ora('Resolving integrations...').start(); const spinner = ora('Resolving packages...').start();
try {
const integrationEntries = await Promise.all( const integrationEntries = await Promise.all(
integrations.map(async (integration): Promise<IntegrationInfo> => { integrations.map(async (integration): Promise<IntegrationInfo> => {
const parsed = parseIntegrationName(integration); const parsed = parseIntegrationName(integration);
if (!parsed) { if (!parsed) {
spinner.fail();
throw new Error(`${integration} does not appear to be a valid package name!`); throw new Error(`${integration} does not appear to be a valid package name!`);
} }
let { scope = '', name, tag } = parsed; let { scope, name, tag } = parsed;
// Allow third-party integrations starting with `astro-` namespace let pkgJson = null;
if (!name.startsWith('astro-')) { let pkgType: 'first-party' | 'third-party' = 'first-party';
scope = `astrojs`;
}
const packageName = `${scope ? `@${scope}/` : ''}${name}`;
const result = await fetch(`https://registry.npmjs.org/${packageName}/${tag}`).then((res) => { if (!scope) {
if (res.status === 404) { const firstPartyPkgCheck = await fetchPackageJson('astrojs', name, tag);
spinner.fail(); if (firstPartyPkgCheck instanceof Error) {
throw new Error(`Unable to fetch ${packageName}. Does this package exist?`); spinner.warn(yellow(`${bold(integration)} is not an official Astro package. Use at your own risk!`));
} const response = await prompts({
return res.json(); type: 'confirm',
name: 'askToContinue',
message: 'Continue?',
initial: true,
}); });
if (!response.askToContinue) {
throw new Error('No problem! Find our official integrations at https://astro.build/integrations');
}
spinner.start('Resolving with third party packages...');
pkgType = 'third-party';
} else {
pkgJson = firstPartyPkgCheck as any;
}
}
if (pkgType === 'third-party') {
const thirdPartyPkgCheck = await fetchPackageJson(scope, name, tag);
if (thirdPartyPkgCheck instanceof Error) {
throw new Error(
`Unable to fetch ${bold(integration)}. Does the package exist?`,
);
} else {
pkgJson = thirdPartyPkgCheck as any;
}
}
const resolvedScope = pkgType === 'first-party' ? 'astrojs' : scope;
const packageName = `${resolvedScope ? `@${resolvedScope}/` : ''}${name}`;
let dependencies: IntegrationInfo['dependencies'] = [ let dependencies: IntegrationInfo['dependencies'] = [
[result['name'], `^${result['version']}`], [pkgJson['name'], `^${pkgJson['version']}`],
]; ];
if (result['peerDependencies']) { if (pkgJson['peerDependencies']) {
for (const peer in result['peerDependencies']) { for (const peer in pkgJson['peerDependencies']) {
dependencies.push([peer, result['peerDependencies'][peer]]); dependencies.push([peer, pkgJson['peerDependencies'][peer]]);
} }
} }
return { id: integration, packageName, dependencies }; let integrationType: IntegrationInfo['type'];
const keywords = Array.isArray(pkgJson['keywords']) ? pkgJson['keywords'] : [];
if (keywords.includes('astro-integration')) {
integrationType = 'integration';
} else if (keywords.includes('astro-adapter')) {
integrationType = 'adapter';
} else {
throw new Error(
`${bold(packageName)} doesn't appear to be an integration or an adapter. Find our official integrations at https://astro.build/integrations`
);
}
return { id: integration, packageName, dependencies, type: integrationType };
}) })
); );
spinner.succeed(); spinner.succeed();
return integrationEntries; return integrationEntries;
} catch (e) {
if (e instanceof Error) {
spinner.fail(e.message);
process.exit(0);
} else {
throw e;
}
}
} }
function parseIntegrationName(spec: string) { function parseIntegrationName(spec: string) {