summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Ben Holmes <hey@bholmes.dev> 2022-07-08 16:55:33 -0400
committerGravatar GitHub <noreply@github.com> 2022-07-08 16:55:33 -0400
commitb012ee55b107dea0730286263b27d83e530fad5d (patch)
treef818b78891786deb0c4e7a035f4fcdb75ce4e9c3
parent6258cd1c3a45a56385b2dc24f0408eb751365743 (diff)
downloadastro-b012ee55b107dea0730286263b27d83e530fad5d.tar.gz
astro-b012ee55b107dea0730286263b27d83e530fad5d.tar.zst
astro-b012ee55b107dea0730286263b27d83e530fad5d.zip
[astro add] Support adapters and third party packages (#3854)
* feat: support adapters and third part integrations by keywords * refactor: add keywords to all official integrations * docs: add adapter ex to astro add help * nit: clarify astro add usage * nit: highlight link * fix: use process.exit(1) on error * chore: changeset * nit: bold integration name * fix: log install instructions for adapters instead * nit: change to logAdapterConfigInstructions * Revert "fix: log install instructions for adapters instead" This reverts commit 1a459f152bc7b7991db289999f7393e5be64ea3e. * feat: add hardcoded adapter export map * refactor: inline adapter config log
-rw-r--r--.changeset/lucky-bottles-wait.md23
-rw-r--r--packages/astro/src/core/add/index.ts217
-rw-r--r--packages/integrations/cloudflare/package.json1
-rw-r--r--packages/integrations/deno/package.json1
-rw-r--r--packages/integrations/image/package.json1
-rw-r--r--packages/integrations/lit/package.json1
-rw-r--r--packages/integrations/mdx/package.json1
-rw-r--r--packages/integrations/netlify/package.json1
-rw-r--r--packages/integrations/node/package.json1
-rw-r--r--packages/integrations/partytown/package.json1
-rw-r--r--packages/integrations/preact/package.json1
-rw-r--r--packages/integrations/prefetch/package.json1
-rw-r--r--packages/integrations/react/package.json1
-rw-r--r--packages/integrations/sitemap/package.json1
-rw-r--r--packages/integrations/solid/package.json1
-rw-r--r--packages/integrations/svelte/package.json1
-rw-r--r--packages/integrations/tailwind/package.json1
-rw-r--r--packages/integrations/turbolinks/package.json1
-rw-r--r--packages/integrations/vercel/package.json1
-rw-r--r--packages/integrations/vue/package.json1
20 files changed, 221 insertions, 37 deletions
diff --git a/.changeset/lucky-bottles-wait.md b/.changeset/lucky-bottles-wait.md
new file mode 100644
index 000000000..271c28ed4
--- /dev/null
+++ b/.changeset/lucky-bottles-wait.md
@@ -0,0 +1,23 @@
+---
+'astro': patch
+'@astrojs/cloudflare': patch
+'@astrojs/deno': patch
+'@astrojs/image': patch
+'@astrojs/lit': patch
+'@astrojs/mdx': patch
+'@astrojs/netlify': patch
+'@astrojs/node': patch
+'@astrojs/partytown': patch
+'@astrojs/preact': patch
+'@astrojs/prefetch': patch
+'@astrojs/react': patch
+'@astrojs/sitemap': patch
+'@astrojs/solid-js': patch
+'@astrojs/svelte': patch
+'@astrojs/tailwind': patch
+'@astrojs/turbolinks': patch
+'@astrojs/vercel': patch
+'@astrojs/vue': patch
+---
+
+[astro add] Support adapters and third party packages
diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts
index 5a1fb9cf6..dde670653 100644
--- a/packages/astro/src/core/add/index.ts
+++ b/packages/astro/src/core/add/index.ts
@@ -3,7 +3,7 @@ import boxen from 'boxen';
import { diffWords } from 'diff';
import { execa } from 'execa';
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 path from 'path';
import preferredPM from 'preferred-pm';
@@ -32,6 +32,7 @@ export interface IntegrationInfo {
id: string;
packageName: string;
dependencies: [name: string, version: string][];
+ type: 'integration' | 'adapter';
}
const ALIASES = new Map([
['solid', 'solid-js'],
@@ -47,11 +48,19 @@ module.exports = {
plugins: [],
}\n`;
+const OFFICIAL_ADAPTER_TO_IMPORT_MAP: Record<string, string> = {
+ 'netlify': '@astrojs/netlify/functions',
+ 'vercel': '@astrojs/vercel/serverless',
+ 'cloudflare': '@astrojs/cloudflare',
+ 'node': '@astrojs/node',
+ 'deno': '@astrojs/deno',
+}
+
export default async function add(names: string[], { cwd, flags, logging, telemetry }: AddOptions) {
if (flags.help || names.length === 0) {
printHelp({
commandName: 'astro add',
- usage: '[...integrations]',
+ usage: '[...integrations] [...adapters]',
tables: {
Flags: [
['--yes', 'Accept all prompts.'],
@@ -70,6 +79,11 @@ export default async function add(names: string[], { cwd, flags, logging, teleme
['partytown', 'astro add partytown'],
['sitemap', 'astro add sitemap'],
],
+ 'Example: Add an Adapter': [
+ ['netlify', 'astro add netlify'],
+ ['vercel', 'astro add vercel'],
+ ['deno', 'astro add deno'],
+ ],
},
description: `Check out the full integration catalog: ${cyan(
'https://astro.build/integrations'
@@ -120,7 +134,20 @@ export default async function add(names: string[], { cwd, flags, logging, teleme
debug('add', 'Astro config ensured `defineConfig`');
for (const integration of integrations) {
- await addIntegration(ast, integration);
+ if (isAdapter(integration)) {
+ const officialExportName = OFFICIAL_ADAPTER_TO_IMPORT_MAP[integration.id];
+ if (officialExportName) {
+ await setAdapter(ast, integration, officialExportName);
+ } else {
+ info(
+ logging,
+ null,
+ `\n ${magenta(`Check our deployment docs for ${bold(integration.packageName)} to update your "adapter" config.`)}`
+ );
+ }
+ } else {
+ await addIntegration(ast, integration);
+ }
debug('add', `Astro config added integration ${integration.id}`);
}
} catch (err) {
@@ -133,7 +160,13 @@ export default async function add(names: string[], { cwd, flags, logging, teleme
if (ast) {
try {
- configResult = await updateAstroConfig({ configURL, ast, flags, logging });
+ configResult = await updateAstroConfig({
+ configURL,
+ ast,
+ flags,
+ logging,
+ logAdapterInstructions: integrations.some(isAdapter),
+ });
} catch (err) {
debug('add', 'Error updating astro config', err);
throw createPrettyError(err as Error);
@@ -231,6 +264,10 @@ export default async function add(names: string[], { cwd, flags, logging, teleme
}
}
+function isAdapter(integration: IntegrationInfo): integration is IntegrationInfo & { type: 'adapter' } {
+ return integration.type === 'adapter';
+}
+
async function parseAstroConfig(configURL: URL): Promise<t.File> {
const source = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' });
const result = parse(source);
@@ -314,6 +351,50 @@ async function addIntegration(ast: t.File, integration: IntegrationInfo) {
});
}
+async function setAdapter(ast: t.File, adapter: IntegrationInfo, exportName: string) {
+ const adapterId = t.identifier(toIdent(adapter.id));
+
+ ensureImport(
+ ast,
+ t.importDeclaration(
+ [t.importDefaultSpecifier(adapterId)],
+ t.stringLiteral(exportName)
+ )
+ );
+
+ 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 {
none,
updated,
@@ -326,11 +407,13 @@ async function updateAstroConfig({
ast,
flags,
logging,
+ logAdapterInstructions,
}: {
configURL: URL;
ast: t.File;
flags: yargs.Arguments;
logging: LogOptions;
+ logAdapterInstructions: boolean;
}): Promise<UpdateResult> {
const input = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' });
let output = await generate(ast);
@@ -378,6 +461,14 @@ async function updateAstroConfig({
`\n ${magenta('Astro will make the following changes to your config file:')}\n${message}`
);
+ if (logAdapterInstructions) {
+ info(
+ logging,
+ null,
+ magenta(` For complete deployment options, visit\n ${bold('https://docs.astro.build/en/guides/deploy/')}\n`)
+ );
+ }
+
if (await askToContinue({ flags })) {
await fs.writeFile(fileURLToPath(configURL), output, { encoding: 'utf-8' });
debug('add', `Updated astro config`);
@@ -479,46 +570,98 @@ async function tryToInstallIntegrations({
}
}
-export async function validateIntegrations(integrations: string[]): Promise<IntegrationInfo[]> {
- const spinner = ora('Resolving integrations...').start();
- const integrationEntries = await Promise.all(
- integrations.map(async (integration): Promise<IntegrationInfo> => {
- const parsed = parseIntegrationName(integration);
- if (!parsed) {
- spinner.fail();
- throw new Error(`${integration} does not appear to be a valid package name!`);
- }
+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();
+ }
+}
- let { scope = '', name, tag } = parsed;
- // Allow third-party integrations starting with `astro-` namespace
- if (!name.startsWith('astro-')) {
- scope = `astrojs`;
- }
- const packageName = `${scope ? `@${scope}/` : ''}${name}`;
+export async function validateIntegrations(integrations: string[]): Promise<IntegrationInfo[]> {
+ const spinner = ora('Resolving packages...').start();
+ try {
+ const integrationEntries = await Promise.all(
+ integrations.map(async (integration): Promise<IntegrationInfo> => {
+ const parsed = parseIntegrationName(integration);
+ if (!parsed) {
+ throw new Error(`${bold(integration)} does not appear to be a valid package name!`);
+ }
- const result = await fetch(`https://registry.npmjs.org/${packageName}/${tag}`).then((res) => {
- if (res.status === 404) {
- spinner.fail();
- throw new Error(`Unable to fetch ${packageName}. Does this package exist?`);
+ let { scope, name, tag } = parsed;
+ let pkgJson = null;
+ let pkgType: 'first-party' | 'third-party' = 'first-party';
+
+ if (!scope) {
+ const firstPartyPkgCheck = await fetchPackageJson('astrojs', name, tag);
+ if (firstPartyPkgCheck instanceof Error) {
+ spinner.warn(yellow(`${bold(integration)} is not an official Astro package. Use at your own risk!`));
+ const response = await prompts({
+ type: 'confirm',
+ name: 'askToContinue',
+ message: 'Continue?',
+ initial: true,
+ });
+ if (!response.askToContinue) {
+ throw new Error(`No problem! Find our official integrations at ${cyan('https://astro.build/integrations')}`);
+ }
+ spinner.start('Resolving with third party packages...');
+ pkgType = 'third-party';
+ } else {
+ pkgJson = firstPartyPkgCheck as any;
+ }
}
- return res.json();
- });
+ 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'] = [
+ [pkgJson['name'], `^${pkgJson['version']}`],
+ ];
- let dependencies: IntegrationInfo['dependencies'] = [
- [result['name'], `^${result['version']}`],
- ];
+ if (pkgJson['peerDependencies']) {
+ for (const peer in pkgJson['peerDependencies']) {
+ dependencies.push([peer, pkgJson['peerDependencies'][peer]]);
+ }
+ }
- if (result['peerDependencies']) {
- for (const peer in result['peerDependencies']) {
- dependencies.push([peer, result['peerDependencies'][peer]]);
+ 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 ${cyan('https://astro.build/integrations')}`
+ );
}
- }
- return { id: integration, packageName, dependencies };
- })
- );
- spinner.succeed();
- return integrationEntries;
+ return { id: integration, packageName, dependencies, type: integrationType };
+ })
+ );
+ spinner.succeed();
+ return integrationEntries;
+ } catch (e) {
+ if (e instanceof Error) {
+ spinner.fail(e.message);
+ process.exit(1);
+ } else {
+ throw e;
+ }
+ }
}
function parseIntegrationName(spec: string) {
diff --git a/packages/integrations/cloudflare/package.json b/packages/integrations/cloudflare/package.json
index 54856276a..cd4a637b7 100644
--- a/packages/integrations/cloudflare/package.json
+++ b/packages/integrations/cloudflare/package.json
@@ -11,6 +11,7 @@
"url": "https://github.com/withastro/astro.git",
"directory": "packages/integrations/cloudflare"
},
+ "keywords": ["astro-adapter"],
"bugs": "https://github.com/withastro/astro/issues",
"homepage": "https://astro.build",
"exports": {
diff --git a/packages/integrations/deno/package.json b/packages/integrations/deno/package.json
index b5d6f06ea..02ed83554 100644
--- a/packages/integrations/deno/package.json
+++ b/packages/integrations/deno/package.json
@@ -11,6 +11,7 @@
"url": "https://github.com/withastro/astro.git",
"directory": "packages/integrations/deno"
},
+ "keywords": ["astro-adapter"],
"bugs": "https://github.com/withastro/astro/issues",
"homepage": "https://astro.build",
"exports": {
diff --git a/packages/integrations/image/package.json b/packages/integrations/image/package.json
index 1c3bec67e..5aeb3bb17 100644
--- a/packages/integrations/image/package.json
+++ b/packages/integrations/image/package.json
@@ -12,6 +12,7 @@
"directory": "packages/integrations/image"
},
"keywords": [
+ "astro-integration",
"astro-component",
"withastro",
"image"
diff --git a/packages/integrations/lit/package.json b/packages/integrations/lit/package.json
index 12beab245..bfda1e1af 100644
--- a/packages/integrations/lit/package.json
+++ b/packages/integrations/lit/package.json
@@ -12,6 +12,7 @@
"directory": "packages/integrations/lit"
},
"keywords": [
+ "astro-integration",
"astro-component",
"renderer",
"lit"
diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json
index 285690337..1f76f4718 100644
--- a/packages/integrations/mdx/package.json
+++ b/packages/integrations/mdx/package.json
@@ -12,6 +12,7 @@
"directory": "packages/integrations/mdx"
},
"keywords": [
+ "astro-integration",
"astro-component",
"renderer",
"mdx"
diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json
index 2e2c15432..5eee7a595 100644
--- a/packages/integrations/netlify/package.json
+++ b/packages/integrations/netlify/package.json
@@ -11,6 +11,7 @@
"url": "https://github.com/withastro/astro.git",
"directory": "packages/integrations/netlify"
},
+ "keywords": ["astro-adapter"],
"bugs": "https://github.com/withastro/astro/issues",
"homepage": "https://astro.build",
"exports": {
diff --git a/packages/integrations/node/package.json b/packages/integrations/node/package.json
index e4a37cee3..de42e7d16 100644
--- a/packages/integrations/node/package.json
+++ b/packages/integrations/node/package.json
@@ -11,6 +11,7 @@
"url": "https://github.com/withastro/astro.git",
"directory": "packages/integrations/node"
},
+ "keywords": ["astro-adapter"],
"bugs": "https://github.com/withastro/astro/issues",
"homepage": "https://astro.build",
"exports": {
diff --git a/packages/integrations/partytown/package.json b/packages/integrations/partytown/package.json
index 2833e3d61..e775e30ca 100644
--- a/packages/integrations/partytown/package.json
+++ b/packages/integrations/partytown/package.json
@@ -12,6 +12,7 @@
"directory": "packages/integrations/partytown"
},
"keywords": [
+ "astro-integration",
"astro-component",
"analytics",
"performance"
diff --git a/packages/integrations/preact/package.json b/packages/integrations/preact/package.json
index 426a48444..36b04827b 100644
--- a/packages/integrations/preact/package.json
+++ b/packages/integrations/preact/package.json
@@ -12,6 +12,7 @@
"directory": "packages/integrations/preact"
},
"keywords": [
+ "astro-integration",
"astro-component",
"renderer",
"preact"
diff --git a/packages/integrations/prefetch/package.json b/packages/integrations/prefetch/package.json
index 3b07034eb..4ce1db137 100644
--- a/packages/integrations/prefetch/package.json
+++ b/packages/integrations/prefetch/package.json
@@ -11,6 +11,7 @@
"url": "https://github.com/withastro/astro.git",
"directory": "packages/astro-prefetch"
},
+ "keywords": ["astro-integration"],
"bugs": "https://github.com/withastro/astro/issues",
"homepage": "https://astro.build",
"exports": {
diff --git a/packages/integrations/react/package.json b/packages/integrations/react/package.json
index 0bc3937bf..dae26dcd6 100644
--- a/packages/integrations/react/package.json
+++ b/packages/integrations/react/package.json
@@ -12,6 +12,7 @@
"directory": "packages/integrations/react"
},
"keywords": [
+ "astro-integration",
"astro-component",
"renderer",
"react"
diff --git a/packages/integrations/sitemap/package.json b/packages/integrations/sitemap/package.json
index ac98fa05c..8aa96f33d 100644
--- a/packages/integrations/sitemap/package.json
+++ b/packages/integrations/sitemap/package.json
@@ -12,6 +12,7 @@
"directory": "packages/integrations/sitemap"
},
"keywords": [
+ "astro-integration",
"astro-component",
"seo",
"sitemap"
diff --git a/packages/integrations/solid/package.json b/packages/integrations/solid/package.json
index a3e347659..036ce728a 100644
--- a/packages/integrations/solid/package.json
+++ b/packages/integrations/solid/package.json
@@ -12,6 +12,7 @@
"directory": "packages/integrations/solid"
},
"keywords": [
+ "astro-integration",
"astro-component",
"renderer",
"solid"
diff --git a/packages/integrations/svelte/package.json b/packages/integrations/svelte/package.json
index 53ce95147..755fbc8d3 100644
--- a/packages/integrations/svelte/package.json
+++ b/packages/integrations/svelte/package.json
@@ -12,6 +12,7 @@
"directory": "packages/integrations/svelte"
},
"keywords": [
+ "astro-integration",
"astro-component",
"renderer",
"svelte"
diff --git a/packages/integrations/tailwind/package.json b/packages/integrations/tailwind/package.json
index 0b543d9ea..3b8d88909 100644
--- a/packages/integrations/tailwind/package.json
+++ b/packages/integrations/tailwind/package.json
@@ -12,6 +12,7 @@
"directory": "packages/integrations/tailwind"
},
"keywords": [
+ "astro-integration",
"astro-component"
],
"bugs": "https://github.com/withastro/astro/issues",
diff --git a/packages/integrations/turbolinks/package.json b/packages/integrations/turbolinks/package.json
index c3b54f1e8..5b7310fca 100644
--- a/packages/integrations/turbolinks/package.json
+++ b/packages/integrations/turbolinks/package.json
@@ -12,6 +12,7 @@
"directory": "packages/integrations/turbolinks"
},
"keywords": [
+ "astro-integration",
"astro-component",
"performance"
],
diff --git a/packages/integrations/vercel/package.json b/packages/integrations/vercel/package.json
index 3f09baf01..055f08b8d 100644
--- a/packages/integrations/vercel/package.json
+++ b/packages/integrations/vercel/package.json
@@ -10,6 +10,7 @@
"url": "https://github.com/withastro/astro.git",
"directory": "packages/integrations/vercel"
},
+ "keywords": ["astro-adapter"],
"bugs": "https://github.com/withastro/astro/issues",
"homepage": "https://astro.build",
"exports": {
diff --git a/packages/integrations/vue/package.json b/packages/integrations/vue/package.json
index a6b2989fe..03d7b7b2f 100644
--- a/packages/integrations/vue/package.json
+++ b/packages/integrations/vue/package.json
@@ -12,6 +12,7 @@
"directory": "packages/integrations/vue"
},
"keywords": [
+ "astro-integration",
"astro-component",
"renderer",
"vue"